Skip to content

Commit

Permalink
Fix some logarithmic combination
Browse files Browse the repository at this point in the history
  • Loading branch information
paradoxxxzero committed Sep 28, 2012
1 parent cbe70dd commit c7c4e21
Show file tree
Hide file tree
Showing 9 changed files with 131 additions and 51 deletions.
21 changes: 20 additions & 1 deletion demo/moulinrouge/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,28 @@ def test_interpolate_for(chart):
graph.add('2', [7, -4, 10, None, 8, 3, 1])
return graph.render_response()

@app.route('/test/logarithmic/<chart>')
def test_logarithmic_for(chart):
graph = CHARTS_BY_NAME[chart](logarithmic=True)
if graph.__class__.__name__ == 'XY':
graph.add('xy', [
(.1, .234), (10, 243), (.001, 2), (1000000, 1231)])
else:
graph.add('1', [.1, 10, .001, 1000000])
graph.add('2', [.234, 243, 2, 2981379, 1231])
return graph.render_response()

@app.route('/test/zero_at_34/<chart>')
@app.route('/test/zero_at_<int:zero>/<chart>')
def test_zero_at_34_for(chart, zero=34):
graph = CHARTS_BY_NAME[chart](fill=True, zero=zero)
graph.add('1', [100, 34, 12, 43, -48])
graph.add('2', [73, -14, 10, None, -58, 32, 91])
return graph.render_response()

@app.route('/test/negative/<chart>')
def test_negative_for(chart):
graph = CHARTS_BY_NAME[chart](interpolate='cubic')
graph = CHARTS_BY_NAME[chart]()
graph.add('1', [10, 0, -10])
return graph.render_response()

Expand Down
5 changes: 0 additions & 5 deletions pygal/ghost.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,16 +62,11 @@ def add(self, title, values):
values = [values]
self.raw_series.append((title, values))

def _check(self):
if self.config.logarithmic and self.config.zero == 0:
self.config.zero = 1

def make_series(self):
return prepare_values(self.raw_series, self.config, self.cls)

def make_instance(self):
self.config(**self.__dict__)
self._check()
series = self.make_series()
self._last__inst = self.cls(self.config, series)
return self._last__inst
Expand Down
2 changes: 2 additions & 0 deletions pygal/graph/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ def __init__(self, config, series):
self.margin = Margin(*([20] * 4))
self._box = Box()
self.view = None
if self.logarithmic and self.zero == 0:
self.zero = self._min

if self.series and self._has_data():
self._draw()
Expand Down
4 changes: 2 additions & 2 deletions pygal/graph/funnel.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def _compute(self):
(x + 1) / self._order for x in range(self._order)
] if self._order != 1 else [.5] # Center if only one value

previous = [[0, 0] for i in range(self._len)]
previous = [[self.zero, self.zero] for i in range(self._len)]
for i, serie in enumerate(self.series):
y_height = - sum(serie.safe_values) / 2
all_x_pos = [0] + x_pos
Expand All @@ -79,7 +79,7 @@ def _compute(self):
poly.append((all_x_pos[i + 1], previous[j][0]))
serie.points.append(poly)

val_max = max(map(sum, cut(self.series, 'values')))
val_max = max(list(map(sum, cut(self.series, 'values'))) + [self.zero])
self._box.ymin = -val_max
self._box.ymax = val_max

Expand Down
81 changes: 49 additions & 32 deletions pygal/graph/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@
from __future__ import division
from pygal.interpolate import interpolation
from pygal.graph.base import BaseGraph
from pygal.view import View, LogView
from pygal.view import View, XLogView, YLogView, XYLogView
from pygal.util import is_major, truncate, reverse_text_len
from math import isnan, pi, sqrt, floor, ceil
from math import isnan, pi, sqrt, ceil


class Graph(BaseGraph):
Expand All @@ -47,7 +47,17 @@ def _axes(self):

def _set_view(self):
"""Assign a view to current graph"""
self.view = (LogView if self.logarithmic else View)(
if self.logarithmic:
if self.__class__.__name__ == 'XY':
view_class = XYLogView
elif self.horizontal:
view_class = XLogView
else:
view_class = YLogView
else:
view_class = View

self.view = view_class(
self.width - self.margin.x,
self.height - self.margin.y,
self._box)
Expand All @@ -57,21 +67,21 @@ def _make_graph(self):
self.nodes['graph'] = self.svg.node(
class_='graph %s-graph %s' % (
self.__class__.__name__.lower(),
'horizontal' if self.horizontal else 'vertical'))
'horizontal' if self.horizontal else 'vertical'))
self.svg.node(self.nodes['graph'], 'rect',
class_='background',
x=0, y=0,
width=self.width,
height=self.height)
class_='background',
x=0, y=0,
width=self.width,
height=self.height)
self.nodes['plot'] = self.svg.node(
self.nodes['graph'], class_="plot",
transform="translate(%d, %d)" % (
self.margin.left, self.margin.top))
self.svg.node(self.nodes['plot'], 'rect',
class_='background',
x=0, y=0,
width=self.view.width,
height=self.view.height)
class_='background',
x=0, y=0,
width=self.view.width,
height=self.view.height)
self.nodes['overlay'] = self.svg.node(
self.nodes['graph'], class_="plot overlay",
transform="translate(%d, %d)" % (
Expand Down Expand Up @@ -120,8 +130,8 @@ def _x_axis(self, draw_axes=True):

if 0 not in [label[1] for label in self._x_labels] and draw_axes:
self.svg.node(axis, 'path',
d='M%f %f v%f' % (0, 0, self.view.height),
class_='line')
d='M%f %f v%f' % (0, 0, self.view.height),
class_='line')
for label, position in self._x_labels:
guides = self.svg.node(axis, class_='guides')
x = self.view.x(position)
Expand All @@ -131,10 +141,11 @@ def _x_axis(self, draw_axes=True):
guides, 'path',
d='M%f %f v%f' % (x, 0, self.view.height),
class_='%sline' % (
'guide ' if position != 0 else ''))
text = self.svg.node(guides, 'text',
x=x,
y=y + .5 * self.label_font_size + 5
'guide ' if position != 0 else ''))
text = self.svg.node(
guides, 'text',
x=x,
y=y + .5 * self.label_font_size + 5
)
text.text = truncate(label, truncation)
if text.text != label:
Expand All @@ -151,9 +162,11 @@ def _y_axis(self, draw_axes=True):
axis = self.svg.node(self.nodes['plot'], class_="axis y")

if 0 not in [label[1] for label in self._y_labels] and draw_axes:
self.svg.node(axis, 'path',
d='M%f %f h%f' % (0, self.view.height, self.view.width),
class_='line')
self.svg.node(
axis, 'path',
d='M%f %f h%f' % (0, self.view.height, self.view.width),
class_='line'
)
for label, position in self._y_labels:
major = is_major(position)
guides = self.svg.node(axis, class_='%sguides' % (
Expand All @@ -168,10 +181,11 @@ def _y_axis(self, draw_axes=True):
class_='%s%sline' % (
'major ' if major else '',
'guide ' if position != 0 else ''))
text = self.svg.node(guides, 'text',
x=x,
y=y + .35 * self.label_font_size,
class_='major' if major else ''
text = self.svg.node(
guides, 'text',
x=x,
y=y + .35 * self.label_font_size,
class_='major' if major else ''
)
text.text = label
if self.y_label_rotation:
Expand Down Expand Up @@ -240,11 +254,13 @@ def _legend(self):
def _title(self):
"""Make the title"""
if self.title:
self.svg.node(self.nodes['graph'], 'text', class_='title',
x=self.margin.left + self.view.width / 2,
y=self.title_font_size + 10
).text = truncate(self.title, int(reverse_text_len(
self.width, self.title_font_size)))
self.svg.node(
self.nodes['graph'], 'text', class_='title',
x=self.margin.left + self.view.width / 2,
y=self.title_font_size + 10
).text = truncate(
self.title, int(reverse_text_len(
self.width, self.title_font_size)))

def _serie(self, serie):
"""Make serie node"""
Expand All @@ -259,8 +275,9 @@ def _serie(self, serie):
self.nodes['text_overlay'],
class_='series serie-%d color-%d' % (serie, serie % 16)))

def _interpolate(self, ys, xs,
polar=False, xy=False, xy_xmin=None, xy_rng=None):
def _interpolate(
self, ys, xs,
polar=False, xy=False, xy_xmin=None, xy_rng=None):
"""Make the interpolation"""
interpolate = interpolation(
xs, ys, kind=self.interpolate)
Expand Down
14 changes: 9 additions & 5 deletions pygal/graph/stackedbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,19 @@ class StackedBar(Bar):

def _compute(self):
transposed = zip(*[serie.values for serie in self.series])
positive_vals = [
sum([val if val is not None and val > 0 else 0 for val in vals])
positive_vals = [sum([
val if val is not None and val > self.zero else self.zero
for val in vals]) - self.zero
for vals in transposed]
negative_vals = [
sum([val if val is not None and val < 0 else 0 for val in vals])
negative_vals = [sum([
val - self.zero
if val is not None and val < self.zero else self.zero
for val in vals]) + self.zero
for vals in transposed]

self._box.ymin, self._box.ymax = (
min(min(negative_vals), 0), max(max(positive_vals), 0))
min(min(negative_vals), self.zero),
max(max(positive_vals), self.zero))

x_pos = [
x / self._len for x in range(self._len + 1)
Expand Down
1 change: 1 addition & 0 deletions pygal/graph/stackedline.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,6 @@ def _points(self, x_pos):
serie.points = [
(x_pos[i], v)
for i, v in enumerate(accumulation)]
print serie.points
if self.interpolate:
serie.interpolated = self._interpolate(accumulation, x_pos)
6 changes: 4 additions & 2 deletions pygal/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,9 @@ def minify_css(css):

def compose(f, g):
"""Chain functions"""
return lambda *args, **kwargs: f(g(*args, **kwargs))
fun = lambda *args, **kwargs: f(g(*args, **kwargs))
fun.__name__ = "%s o %s" % (f.__name__, g.__name__)
return fun


def safe_enumerate(iterable):
Expand All @@ -298,7 +300,7 @@ def prepare_values(raw, config, cls):
for fun in not_zero, positive:
if fun in adapters:
adapters.remove(fun)
adapters = [not_zero, positive] + adapters
adapters = adapters + [positive, not_zero]
adapter = reduce(compose, adapters) if not config.strict else ident
series = []
width = max([len(values) for _, values in raw] +
Expand Down
48 changes: 44 additions & 4 deletions pygal/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,13 @@ def __init__(self, width, height, box):

def x(self, x):
"""Project x"""
if x == None:
if x is None:
return None
return self.width * (x - self.box.xmin) / self.box.width

def y(self, y):
"""Project y"""
if y == None:
if y is None:
return None
return (self.height - self.height *
(y - self.box.ymin) / self.box.height)
Expand All @@ -124,7 +124,7 @@ def __call__(self, rhotheta):
(rho * cos(theta), rho * sin(theta)))


class LogView(View):
class YLogView(View):
"""Logarithmic projection """
# Do not want to call the parent here
# pylint: disable-msg=W0231
Expand All @@ -141,8 +141,48 @@ def __init__(self, width, height, box):
# pylint: enable-msg=W0231
def y(self, y):
"""Project y"""
if y == None or y <= 0:
if y is None or y <= 0 or self.log10_ymax - self.log10_ymin == 0:
return None
return (self.height - self.height *
(log10(y) - self.log10_ymin)
/ (self.log10_ymax - self.log10_ymin))


class XLogView(View):
"""Logarithmic projection """
# Do not want to call the parent here
# pylint: disable-msg=W0231
def __init__(self, width, height, box):
self.width = width
self.height = height
self.box = box
self.xmin = self.box.xmin
self.xmax = self.box.xmax
self.log10_xmax = log10(self.box.xmax)
self.log10_xmin = log10(self.box.xmin)
self.box.fix(False)

# pylint: enable-msg=W0231
def x(self, x):
"""Project x"""
if x is None or x <= 0 or self.log10_xmax - self.log10_xmin == 0:
return None
return (self.width - self.width *
(log10(x) - self.log10_xmin)
/ (self.log10_xmax - self.log10_xmin))


class XYLogView(XLogView, YLogView):
def __init__(self, width, height, box):
self.width = width
self.height = height
self.box = box
self.xmin = self.box.xmin
self.xmax = self.box.xmax
self.ymin = self.box.ymin
self.ymax = self.box.ymax
self.log10_ymax = log10(self.box.ymax)
self.log10_ymin = log10(self.box.ymin)
self.log10_xmax = log10(self.box.xmax)
self.log10_xmin = log10(self.box.xmin)
self.box.fix(False)

0 comments on commit c7c4e21

Please sign in to comment.