diff --git a/demo/moulinrouge/__init__.py b/demo/moulinrouge/__init__.py index b61787e..fff61cb 100644 --- a/demo/moulinrouge/__init__.py +++ b/demo/moulinrouge/__init__.py @@ -83,7 +83,7 @@ def create_app(): random_value((-max, min)[random.randrange(0, 2)], max)) for i in range(data)] else: - values = [random_value((-max, min)[random.randrange(0, 2)], + values = [random_value((-max, min)[random.randrange(1, 2)], max) for i in range(data)] g.add(random_label(), values) return g.render_response() @@ -94,8 +94,10 @@ def create_app(): svgs = [url_for('all_svg', type=type, style=style, fill=fill) for style in styles for fill in (False, True) - for type in ('Bar', 'Line', 'XY', 'Pie', 'StackedBar', - 'HorizontalBar', 'HorizontalStackedBar', 'Radar')] + for type in ('Bar', 'Line', 'XY', 'StackedBar', + 'StackedLine', 'HorizontalBar', + 'HorizontalStackedBar', + 'Pie', 'Radar')] return render_template('svgs.jinja2', svgs=svgs, width=width, diff --git a/demo/simple_test.py b/demo/simple_test.py index be2d2da..f514eb4 100755 --- a/demo/simple_test.py +++ b/demo/simple_test.py @@ -19,7 +19,7 @@ # along with pygal. If not, see . from pygal import ( Line, Bar, XY, Pie, Radar, StackedBar, Config, - HorizontalBar, HorizontalStackedBar) + StackedLine, HorizontalBar, HorizontalStackedBar) from pygal.style import NeonStyle from math import cos, sin @@ -72,16 +72,25 @@ hstackedbar.add('--->', rng3) with open('out-horizontalstackedbar.svg', 'w') as f: f.write(hstackedbar.render()) -line = Line(Config(y_scale=.0005)) +line = Line(Config(y_scale=.0005, fill=True, style=NeonStyle)) rng = range(-30, 31, 5) -line.add('test1', [1000 + cos(x / 10.) for x in rng]) -line.add('test2', [1000 + sin(x / 10.) for x in rng]) -line.add('test3', [1000 + cos(x / 10.) - sin(x / 10.) for x in rng]) +line.add('test1', [cos(x / 10.) for x in rng]) +line.add('test2', [sin(x / 10.) for x in rng]) +line.add('test3', [cos(x / 10.) - sin(x / 10.) for x in rng]) line.x_labels = map(str, rng) line.title = "Line test" with open('out-line.svg', 'w') as f: f.write(line.render()) +stackedline = StackedLine(Config(y_scale=.0005, fill=True, style=NeonStyle)) +stackedline.add('test1', [1, 3, 2, 18, 2, 13, 8]) +stackedline.add('test2', [4, 1, 0, 1, 3, 12, 3]) +stackedline.add('test3', [9, 3, 2, 10, 8, 2, 3]) +stackedline.x_labels = map(str, ['a', 'b', 'c', 'd', 'e', 'f', 'g']) +stackedline.title = "Stackedline test" +with open('out-stackedline.svg', 'w') as f: + f.write(stackedline.render()) + xy = XY(Config(x_scale=1)) xy.add('test1', [(1981, 1), (2004, 2), (2003, 10), (2012, 8), (1999, -4)]) xy.add('test2', [(1988, -1), (1986, 12), (2007, 7), (2010, 4), (1999, 2)]) diff --git a/pygal/__init__.py b/pygal/__init__.py index 1bb8cba..3b389b9 100644 --- a/pygal/__init__.py +++ b/pygal/__init__.py @@ -24,6 +24,7 @@ from pygal.graph.horizontal import HorizontalBar from pygal.graph.stackedbar import StackedBar from pygal.graph.horizontal import HorizontalStackedBar from pygal.graph.line import Line +from pygal.graph.stackedline import StackedLine from pygal.graph.xy import XY from pygal.graph.pie import Pie from pygal.graph.radar import Radar diff --git a/pygal/graph/base.py b/pygal/graph/base.py index aed77ab..162279e 100644 --- a/pygal/graph/base.py +++ b/pygal/graph/base.py @@ -88,6 +88,10 @@ class BaseGraph(object): def _values(self): return [val for serie in self.series for val in serie.values] + @cached_property + def _len(self): + return len(self.series[0].values) + def _draw(self): self._compute() self._compute_margin() diff --git a/pygal/graph/line.py b/pygal/graph/line.py index 77ed8b8..f71f16d 100644 --- a/pygal/graph/line.py +++ b/pygal/graph/line.py @@ -38,6 +38,11 @@ class Line(Graph): self.svg.node(dot, 'circle', cx=x, cy=y, r=2.5) self.svg.node(dot, 'text', x=x, y=y ).text = self._get_value(values, i) + if self.fill: + zero = self.view.y(min(max(0, self._box.ymin), self._box.ymax)) + view_values = ([(view_values[0][0], zero)] + + view_values + + [(view_values[-1][0], zero)]) self.svg.line( serie_node, view_values, class_='line', close=self._line_close) @@ -49,9 +54,8 @@ class Line(Graph): self._box.ymin = min(self._values) self._box.ymax = max(self._values) - x_step = len(self.series[0].values) - self._x_pos = [x / float(x_step - 1) for x in range(x_step) - ] if x_step != 1 else [.5] # Center if only one value + self._x_pos = [x / float(self._len - 1) for x in range(self._len) + ] if self._len != 1 else [.5] # Center if only one value self._y_pos = self._pos(self._box.ymin, self._box.ymax, self.y_scale ) if not self.y_labels else map(int, self.y_labels) diff --git a/pygal/graph/radar.py b/pygal/graph/radar.py index e04ddfa..ab44155 100644 --- a/pygal/graph/radar.py +++ b/pygal/graph/radar.py @@ -19,7 +19,7 @@ from pygal.graph.line import Line from pygal.view import PolarView from pygal.util import deg -from math import cos, sin, pi +from math import cos, pi class Radar(Line): diff --git a/pygal/graph/stackedline.py b/pygal/graph/stackedline.py new file mode 100644 index 0000000..2d4a620 --- /dev/null +++ b/pygal/graph/stackedline.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# This file is part of pygal +# +# A python svg graph plotting library +# Copyright © 2012 Kozea +# +# This library is free software: you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) any +# later version. +# +# This library is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with pygal. If not, see . +from pygal.graph.line import Line + + +class StackedLine(Line): + """Stacked Line graph""" + + @property + def _values(self): + sums = map(sum, zip(*[serie.values for serie in self.series])) + return sums + super(StackedLine, self)._values + + def _plot(self): + accumulation = map(sum, zip(*[serie.values for serie in self.series])) + for serie in self.series: + self.line( + self._serie(serie.index), [ + (self._x_pos[i], v) + for i, v in enumerate(accumulation)]) + accumulation = map(sum, zip(accumulation, + [-v for v in serie.values]))