From 72ef8ea11b8a3ddf53235ed0a4a95a077d77c87c Mon Sep 17 00:00:00 2001 From: Florian Mounier Date: Fri, 25 Jan 2013 17:15:19 +0100 Subject: [PATCH] Fix the rest as much as I can with secondary --- demo/moulinrouge/tests.py | 12 +++++--- pygal/graph/bar.py | 30 +++++++++++++++++-- pygal/graph/graph.py | 6 ++-- pygal/graph/radar.py | 2 +- pygal/graph/stackedbar.py | 54 +++++++++++++++++++++++++++++----- pygal/graph/verticalpyramid.py | 41 ++++++++++++++++++++++---- pygal/test/__init__.py | 5 ++-- 7 files changed, 124 insertions(+), 26 deletions(-) diff --git a/demo/moulinrouge/tests.py b/demo/moulinrouge/tests.py index 8af8f10..1698551 100644 --- a/demo/moulinrouge/tests.py +++ b/demo/moulinrouge/tests.py @@ -184,12 +184,16 @@ def get_test_routes(app): @app.route('/test/secondary/') def test_secondary_for(chart): chart = CHARTS_BY_NAME[chart](fill=True) + chart.title = 'LOL ' * 23 + chart.x_labels = 'abc' chart.x_label_rotation = 25 chart.y_label_rotation = 50 - chart.add('1', [30, 20, 25]) - chart.add(10 * '1b', [4000000, 5, 6], secondary=True) - chart.add(10 * '2b', [3, 0, 12], secondary=True) - chart.add('2', [8, 21, 5]) + chart.add('1', [30, 20, -2]) + chart.add(10 * '1b', [-4, 50, 6], secondary=True) + chart.add(10 * '2b', [3, 30, -1], secondary=True) + chart.add('2', [8, 21, -0]) + chart.add('3', [1, 2, 3]) + chart.add('3b', [-1, 2, -3], secondary=True) return chart.render_response() @app.route('/test/secondary_xy') diff --git a/pygal/graph/bar.py b/pygal/graph/bar.py index 5c34e19..6f45c15 100644 --- a/pygal/graph/bar.py +++ b/pygal/graph/bar.py @@ -36,7 +36,7 @@ class Bar(Graph): self._x_ranges = None super(Bar, self).__init__(*args, **kwargs) - def _bar(self, parent, x, y, index, i, zero, shift=True): + def _bar(self, parent, x, y, index, i, zero, shift=True, secondary=False): width = (self.view.x(1) - self.view.x(0)) / self._len x, y = self.view((x, y)) series_margin = width * self._series_margin @@ -79,7 +79,7 @@ class Bar(Graph): val = self._format(serie.values[i]) x_center, y_center = self._bar( - bar, x, y, index, i, self.zero) + bar, x, y, index, i, self.zero, secondary=rescale) self._tooltip_data( bar, val, x_center, y_center, classes="centered") self._static_value(serie_node, val, x_center, y_center) @@ -104,6 +104,32 @@ class Bar(Graph): (i + .5) / self._len for i in range(self._len)]) self._y_labels = zip(map(self._format, y_pos), y_pos) + def _compute_secondary(self): + if self.secondary_series: + y_pos = zip(*self._y_labels)[1] + ymin = self._secondary_min + ymax = self._secondary_max + + min_0_ratio = (self.zero - self._box.ymin) / self._box.height + max_0_ratio = (self._box.ymax - self.zero) / self._box.height + + new_ymax = (self.zero - ymin) * (1 / min_0_ratio - 1) + new_ymin = -(ymax - self.zero) * (1 / max_0_ratio - 1) + if ymax > self._box.ymax: + ymin = new_ymin + else: + ymax = new_ymax + + left_range = abs(self._box.ymax - self._box.ymin) + right_range = abs(ymax - ymin) + self._scale = left_range / right_range + self._scale_diff = self._box.ymin + self._scale_min_2nd = ymin + self._y_2nd_labels = [ + (self._format(self._box.xmin + y * right_range / left_range), + y) + for y in y_pos] + def _plot(self): for index, serie in enumerate(self.series): self.bar(self._serie(index), serie, index) diff --git a/pygal/graph/graph.py b/pygal/graph/graph.py index 28ed0a6..ee20cb2 100644 --- a/pygal/graph/graph.py +++ b/pygal/graph/graph.py @@ -411,10 +411,8 @@ class Graph(BaseGraph): def _compute_secondary(self): # secondary y axis support - y_pos = zip(*self._y_labels)[1] - - # secondary y axis support - if self.secondary_series: + if self.secondary_series and self._y_labels: + y_pos = zip(*self._y_labels)[1] if self.include_x_axis: ymin = min(self._secondary_min, 0) ymax = max(self._secondary_max, 0) diff --git a/pygal/graph/radar.py b/pygal/graph/radar.py index 54b6e63..6444b50 100644 --- a/pygal/graph/radar.py +++ b/pygal/graph/radar.py @@ -117,7 +117,7 @@ class Radar(Line): def _compute(self): delta = 2 * pi / self._len if self._len else 0 x_pos = [.5 * pi + i * delta for i in range(self._len + 1)] - for serie in self.series: + for serie in self.all_series: serie.points = [ (v, x_pos[i]) for i, v in enumerate(serie.values)] diff --git a/pygal/graph/stackedbar.py b/pygal/graph/stackedbar.py index fe98148..f570bf0 100644 --- a/pygal/graph/stackedbar.py +++ b/pygal/graph/stackedbar.py @@ -23,7 +23,7 @@ Stacked Bar chart from __future__ import division from pygal.graph.bar import Bar -from pygal.util import compute_scale +from pygal.util import compute_scale, swap, ident from pygal.adapters import none_to_zero @@ -32,8 +32,9 @@ class StackedBar(Bar): _adapters = [none_to_zero] - def _get_separated_values(self): - transposed = zip(*[serie.values for serie in self.series]) + def _get_separated_values(self, secondary=False): + series = self.secondary_series if secondary else self.series + transposed = zip(*[serie.values for serie in series]) positive_vals = [sum([ val for val in vals if val is not None and val >= self.zero]) @@ -77,13 +78,50 @@ class StackedBar(Bar): self.negative_cumulation = [0] * self._len self.positive_cumulation = [0] * self._len - def _bar(self, parent, x, y, index, i, zero, shift=True): - cumulation = (self.negative_cumulation if y < self.zero else - self.positive_cumulation) + if self.secondary_series: + positive_vals, negative_vals = self._get_separated_values(True) + self.secondary_negative_cumulation = [0] * self._len + self.secondary_positive_cumulation = [0] * self._len + + # In case of pyramids + sum_ = lambda x: sum(x) if isinstance(x, tuple) else x + self._secondary_min = negative_vals and min( + sum_(min(negative_vals)), self.zero) + self._secondary_max = positive_vals and max( + sum_(max(positive_vals)), self.zero) + + def _bar(self, parent, x, y, index, i, zero, shift=False, secondary=False): + if secondary: + cumulation = (self.secondary_negative_cumulation + if y < self.zero else + self.secondary_positive_cumulation) + else: + cumulation = (self.negative_cumulation + if y < self.zero else + self.positive_cumulation) zero = cumulation[i] cumulation[i] = zero + y if zero == 0: zero = self.zero y -= self.zero - return super(StackedBar, self)._bar( - parent, x, zero + y, index, i, zero, False) + y += zero + + width = (self.view.x(1) - self.view.x(0)) / self._len + x, y = self.view((x, y)) + series_margin = width * self._series_margin + x += series_margin + width -= 2 * series_margin + if self.secondary_series: + width /= 2 + x += int(secondary) * width + serie_margin = width * self._serie_margin + x += serie_margin + width -= 2 * serie_margin + height = self.view.y(zero) - y + r = self.rounded_bars * 1 if self.rounded_bars else 0 + self.svg.transposable_node( + parent, 'rect', + x=x, y=y, rx=r, ry=r, width=width, height=height, + class_='rect reactive tooltip-trigger') + transpose = swap if self.horizontal else ident + return transpose((x + width / 2, y + height / 2)) diff --git a/pygal/graph/verticalpyramid.py b/pygal/graph/verticalpyramid.py index 4403ff9..f0763ac 100644 --- a/pygal/graph/verticalpyramid.py +++ b/pygal/graph/verticalpyramid.py @@ -35,12 +35,13 @@ class VerticalPyramid(StackedBar): def _format(self, value): return super(VerticalPyramid, self)._format(abs(value)) - def _get_separated_values(self): + def _get_separated_values(self, secondary=False): + series = self.secondary_series if secondary else self.series positive_vals = zip(*[serie.safe_values - for index, serie in enumerate(self.series) + for index, serie in enumerate(series) if index % 2]) negative_vals = zip(*[serie.safe_values - for index, serie in enumerate(self.series) + for index, serie in enumerate(series) if not index % 2]) return positive_vals, negative_vals @@ -50,8 +51,38 @@ class VerticalPyramid(StackedBar): self._box.ymax = max(max(positive_sum), max(negative_sum)) self._box.ymin = - self._box.ymax - def _bar(self, parent, x, y, index, i, zero, shift=True): + def _compute_secondary(self): + # Need refactoring + if self.secondary_series: + y_pos = zip(*self._y_labels)[1] + positive_vals, negative_vals = self._get_separated_values(True) + positive_sum = map(sum, positive_vals) or [self.zero] + negative_sum = map(sum, negative_vals) or [self.zero] + ymax = max(max(positive_sum), max(negative_sum)) + ymin = -ymax + + min_0_ratio = (self.zero - self._box.ymin) / self._box.height + max_0_ratio = (self._box.ymax - self.zero) / self._box.height + + new_ymax = (self.zero - ymin) * (1 / min_0_ratio - 1) + new_ymin = -(ymax - self.zero) * (1 / max_0_ratio - 1) + if ymax > self._box.ymax: + ymin = new_ymin + else: + ymax = new_ymax + + left_range = abs(self._box.ymax - self._box.ymin) + right_range = abs(ymax - ymin) + self._scale = left_range / right_range + self._scale_diff = self._box.ymin + self._scale_min_2nd = ymin + self._y_2nd_labels = [ + (self._format(self._box.xmin + y * right_range / left_range), + y) + for y in y_pos] + + def _bar(self, parent, x, y, index, i, zero, shift=True, secondary=False): if index % 2: y = -y return super(VerticalPyramid, self)._bar( - parent, x, y, index, i, zero, False) + parent, x, y, index, i, zero, False, secondary) diff --git a/pygal/test/__init__.py b/pygal/test/__init__.py index ac7e4f2..556573e 100644 --- a/pygal/test/__init__.py +++ b/pygal/test/__init__.py @@ -42,8 +42,9 @@ def pytest_generate_tests(metafunc): ]) -def make_data(chart, datas): +def make_data(chart, datas, secondary=False): for data in datas: chart.add(data[0], - data[1] if chart.__class__ == pygal.XY else cut(data[1])) + data[1] if chart.__class__ == pygal.XY else cut(data[1]), + secondary=secondary) return chart