diff --git a/pygal/bar.py b/pygal/bar.py index 0df6ac7..1bd3592 100644 --- a/pygal/bar.py +++ b/pygal/bar.py @@ -4,30 +4,23 @@ from pygal.base import BaseGraph class Bar(BaseGraph): """Bar graph""" - def _draw(self): + def _compute(self): vals = [val for serie in self.series for val in serie.values] - ymin, ymax = min(min(vals), 0), max(max(vals), 0) + self._box.ymin, self._box.ymax = min(min(vals), 0), max(max(vals), 0) x_step = len(self.series[0].values) x_pos = [x / float(x_step) for x in range(x_step + 1) ] if x_step > 1 else [0, 1] # Center if only one value - y_pos = self._pos( - ymin, ymax, self.y_scale) if not self.y_labels else map( - int, self.y_labels) - x_ranges = zip(x_pos, x_pos[1:]) + y_pos = self._pos(self._box.ymin, self._box.ymax, self.y_scale + ) if not self.y_labels else map(int, self.y_labels) - x_labels = self.x_labels and zip(self.x_labels, [ - sum(x_range) / 2. for x_range in x_ranges]) - y_labels = zip(map(str, y_pos), y_pos) - self._compute_margin(x_labels, y_labels) - self.svg.set_view(ymin, ymax) - self.svg.make_graph() - self.svg.x_axis(x_labels) - self.svg.y_axis(y_labels) - self.svg.legend([serie.title for serie in self.series]) - self.svg.title() + self._x_ranges = zip(x_pos, x_pos[1:]) + self._x_labels = self.x_labels and zip(self.x_labels, [ + sum(x_range) / 2. for x_range in self._x_ranges]) + self._y_labels = zip(map(str, y_pos), y_pos) + def _plot(self): for serie in self.series: serie_node = self.svg.serie(serie.index) self.svg.bar(serie_node, serie, [ - tuple((x_ranges[i][j], v) for j in range(2)) + tuple((self._x_ranges[i][j], v) for j in range(2)) for i, v in enumerate(serie.values)]) diff --git a/pygal/base.py b/pygal/base.py index 812a5a1..0b229e2 100644 --- a/pygal/base.py +++ b/pygal/base.py @@ -1,5 +1,5 @@ from pygal.serie import Serie -from pygal.view import Margin +from pygal.view import Margin, Box from pygal.util import round_to_scale, cut, rad from pygal.svg import Svg from pygal.config import Config @@ -14,6 +14,8 @@ class BaseGraph(object): self.svg = Svg(self) self.series = [] self.margin = Margin(*([20] * 4)) + self._x_labels = self._y_labels = None + self._box = Box() def __getattr__(self, attr): if attr in dir(self.config): @@ -51,7 +53,10 @@ class BaseGraph(object): max_len = max(map(len, texts)) return (fs, self._text_len(max_len, fs)) - def _compute_margin(self, x_labels=None, y_labels=None): + def _compute(self): + """Initial computations to draw the graph""" + + def _compute_margin(self): if self.show_legend: h, w = self._get_texts_box( cut(self.series, 'title'), self.legend_font_size) @@ -61,19 +66,39 @@ class BaseGraph(object): h, w = self._get_text_box(self.title, self.title_font_size) self.margin.top += 10 + h - if x_labels: - h, w = self._get_texts_box(cut(x_labels), self.label_font_size) + if self._x_labels: + h, w = self._get_texts_box( + cut(self._x_labels), self.label_font_size) self.margin.bottom += 10 + max( w * sin(rad(self.x_label_rotation)), h) if self.x_label_rotation: self.margin.right = max( .5 * w * cos(rad(self.x_label_rotation)), self.margin.right) - if y_labels: - h, w = self._get_texts_box(cut(y_labels), self.label_font_size) + if self._y_labels: + h, w = self._get_texts_box( + cut(self._y_labels), self.label_font_size) self.margin.left += 10 + max( w * cos(rad(self.y_label_rotation)), h) + @property + def _legends(self): + return [serie.title for serie in self.series] + + def _decorate(self): + self.svg.set_view() + self.svg.make_graph() + self.svg.x_axis() + self.svg.y_axis() + self.svg.legend() + self.svg.title() + + def _draw(self): + self._compute() + self._compute_margin() + self._decorate() + self._plot() + def add(self, title, values): self.series.append(Serie(title, values, len(self.series))) diff --git a/pygal/line.py b/pygal/line.py index eed1a3f..31fe46f 100644 --- a/pygal/line.py +++ b/pygal/line.py @@ -4,29 +4,21 @@ from pygal.base import BaseGraph class Line(BaseGraph): """Line graph""" - def _draw(self): + def _compute(self): vals = [val for serie in self.series for val in serie.values] - ymin, ymax = min(vals), max(vals) + self._box.ymin, self._box.ymax = min(vals), max(vals) x_step = len(self.series[0].values) - x_pos = [x / float(x_step - 1) for x in range(x_step) + 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 - y_pos = self._pos( - ymin, ymax, self.y_scale) if not self.y_labels else map( - int, self.y_labels) + 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) - x_labels = self.x_labels and zip(self.x_labels, x_pos) - y_labels = zip(map(str, y_pos), y_pos) - - self._compute_margin(x_labels, y_labels) - self.svg.set_view(ymin, ymax) - self.svg.make_graph() - self.svg.x_axis(x_labels) - self.svg.y_axis(y_labels) - self.svg.legend([serie.title for serie in self.series]) - self.svg.title() + self._x_labels = self.x_labels and zip(self.x_labels, self._x_pos) + self._y_labels = zip(map(str, self._y_pos), self._y_pos) + def _plot(self): for serie in self.series: self.svg.line( self.svg.serie(serie.index), [ - (x_pos[i], v) + (self._x_pos[i], v) for i, v in enumerate(serie.values)]) diff --git a/pygal/pie.py b/pygal/pie.py index 769c449..f438a11 100644 --- a/pygal/pie.py +++ b/pygal/pie.py @@ -9,12 +9,7 @@ class Pie(BaseGraph): def add(self, title, value): self.series.append(Serie(title, [value], len(self.series))) - def _draw(self): - self._compute_margin() - self.svg.set_view() - self.svg.make_graph() - self.svg.legend([serie.title for serie in self.series]) - self.svg.title() + def _plot(self): total = float(sum(serie.values[0] for serie in self.series)) current_angle = 0 for serie in self.series: diff --git a/pygal/stackedbar.py b/pygal/stackedbar.py index 100214b..54d7001 100644 --- a/pygal/stackedbar.py +++ b/pygal/stackedbar.py @@ -4,38 +4,34 @@ from pygal.base import BaseGraph class StackedBar(BaseGraph): """Stacked Bar graph""" - def _draw(self): + def _compute(self): transposed = zip(*[serie.values for serie in self.series]) positive_vals = [sum([val if val > 0 else 0 for val in vals]) for vals in transposed] negative_vals = [sum([val if val < 0 else 0 for val in vals]) for vals in transposed] - ymin, ymax = min(min(negative_vals), 0), max(max(positive_vals), 0) - length = len(self.series[0].values) - x_pos = [x / float(length) for x in range(length + 1) - ] if length > 1 else [0, 1] # Center if only one value - y_pos = self._pos( - ymin, ymax, self.y_scale) if not self.y_labels else map( - int, self.y_labels) - x_ranges = zip(x_pos, x_pos[1:]) - x_labels = self.x_labels and zip(self.x_labels, [ - sum(x_range) / 2 for x_range in x_ranges]) - y_labels = zip(map(str, y_pos), y_pos) + self._box.ymin, self._box.ymax = ( + min(min(negative_vals), 0), max(max(positive_vals), 0)) + self._length = len(self.series[0].values) - self._compute_margin(x_labels, y_labels) - self.svg.set_view(ymin, ymax) - self.svg.make_graph() - self.svg.x_axis(x_labels) - self.svg.y_axis(y_labels) - self.svg.legend([serie.title for serie in self.series]) - self.svg.title() + x_pos = [x / float(self._length) + for x in range(self._length + 1) + ] if self._length > 1 else [0, 1] # Center if only one value + y_pos = self._pos(self._box.ymin, self._box.ymax, self.y_scale + ) if not self.y_labels else map(int, self.y_labels) + self._x_ranges = zip(x_pos, x_pos[1:]) - stack_vals = [[0, 0] for i in range(length)] + self._x_labels = self.x_labels and zip(self.x_labels, [ + sum(x_range) / 2 for x_range in self._x_ranges]) + self._y_labels = zip(map(str, y_pos), y_pos) + + def _plot(self): + stack_vals = [[0, 0] for i in range(self._length)] for serie in self.series: serie_node = self.svg.serie(serie.index) stack_vals = self.svg.bar( serie_node, serie, [ - tuple((x_ranges[i][j], v) for j in range(2)) + tuple((self._x_ranges[i][j], v) for j in range(2)) for i, v in enumerate(serie.values)], stack_vals) diff --git a/pygal/svg.py b/pygal/svg.py index 651a030..0a56828 100644 --- a/pygal/svg.py +++ b/pygal/svg.py @@ -53,11 +53,11 @@ class Svg(object): return etree.SubElement(parent, tag, attrib) - def set_view(self, ymin=0, ymax=1, xmin=0, xmax=1): + def set_view(self): self.view = View( self.graph.width - self.graph.margin.x, self.graph.height - self.graph.margin.y, - xmin, xmax, ymin, ymax) + self.graph._box) def make_graph(self): self.graph_node = self.node( @@ -77,17 +77,17 @@ class Svg(object): width=self.view.width, height=self.view.height) - def x_axis(self, labels): - if not labels: + def x_axis(self): + if not self.graph._x_labels: return axis = self.node(self.plot, class_="axis x") - if 0 not in [label[1] for label in labels]: + if 0 not in [label[1] for label in self.graph._x_labels]: self.node(axis, 'path', d='M%f %f v%f' % (0, 0, self.view.height), class_='line') - for label, position in labels: + for label, position in self.graph._x_labels: guides = self.node(axis, class_='guides') x = self.view.x(position) y = self.view.height + 5 @@ -103,17 +103,17 @@ class Svg(object): self.graph.x_label_rotation, x, y) text.text = label - def y_axis(self, labels): - if not labels: + def y_axis(self): + if not self.graph._y_labels: return axis = self.node(self.plot, class_="axis y") - if 0 not in [label[1] for label in labels]: + if 0 not in [label[1] for label in self.graph._y_labels]: self.node(axis, 'path', d='M%f %f h%f' % (0, self.view.height, self.view.width), class_='line') - for label, position in labels: + for label, position in self.graph._y_labels: guides = self.node(axis, class_='guides') x = -5 y = self.view.y(position) @@ -129,7 +129,7 @@ class Svg(object): self.graph.y_label_rotation, x, y) text.text = label - def legend(self, titles): + def legend(self): if not self.graph.show_legend: return legends = self.node( @@ -137,7 +137,7 @@ class Svg(object): transform='translate(%d, %d)' % ( self.graph.margin.left + self.view.width + 10, self.graph.margin.top + 10)) - for i, title in enumerate(titles): + for i, title in enumerate(self.graph._legends): legend = self.node(legends, class_='legend') self.node(legend, 'rect', x=0, diff --git a/pygal/view.py b/pygal/view.py index dde11d0..20689df 100644 --- a/pygal/view.py +++ b/pygal/view.py @@ -15,29 +15,38 @@ class Margin(object): class Box(object): - def __init__(self, x, y, width, height): - self.x = x - self.y = y - self.width = width - self.height = height + def __init__(self): + self.xmin = self.ymin = 0 + self.xmax = self.ymax = 1 + + @property + def width(self): + return self.xmax - self.xmin + + @property + def height(self): + return self.ymax - self.ymin + + def fix(self): + if not self.width: + self.xmax = self.xmin + 1 + if not self.height: + self.ymin -= .5 + self.ymax = self.ymin + 1 class View(object): - def __init__(self, width, height, xmin, xmax, ymin, ymax): + def __init__(self, width, height, box): self.width = width self.height = height - xrng = (xmax - xmin) or 1 - yrng = (ymax - ymin) or 1 - if (ymax - ymin) == 0: - ymin -= .5 - self.box = Box(xmin, ymin, xrng, yrng) + self.box = box def x(self, x): - return self.width * (x - self.box.x) / float(self.box.width) + return self.width * (x - self.box.xmin) / float(self.box.width) def y(self, y): return (self.height - self.height * - (y - self.box.y) / float(self.box.height)) + (y - self.box.ymin) / float(self.box.height)) def __call__(self, xy): x, y = xy diff --git a/pygal/xy.py b/pygal/xy.py index 2b8a7d0..9f271e5 100644 --- a/pygal/xy.py +++ b/pygal/xy.py @@ -4,28 +4,21 @@ from pygal.base import BaseGraph class XY(BaseGraph): """XY Line graph""" - def _draw(self): + def _compute(self): for serie in self.series: serie.values = sorted(serie.values, key=lambda x: x[0]) xvals = [val[0] for serie in self.series for val in serie.values] yvals = [val[1] for serie in self.series for val in serie.values] - xmin, xmax = min(xvals), max(xvals) - ymin, ymax = min(yvals), max(yvals) + self._box.xmin, self._box.xmax = min(xvals), max(xvals) + self._box.ymin, self._box.ymax = min(yvals), max(yvals) - x_pos = self._pos(xmin, xmax, self.x_scale) - y_pos = self._pos(ymin, ymax, self.y_scale) + x_pos = self._pos(self._box.xmin, self._box.xmax, self.x_scale) + y_pos = self._pos(self._box.ymin, self._box.ymax, self.y_scale) - x_labels = zip(map(str, x_pos), x_pos) - y_labels = zip(map(str, y_pos), y_pos) - - self._compute_margin(x_labels, y_labels) - self.svg.set_view(ymin, ymax, xmin, xmax) - self.svg.make_graph() - self.svg.x_axis(x_labels) - self.svg.y_axis(y_labels) - self.svg.legend([serie.title for serie in self.series]) - self.svg.title() + self._x_labels = zip(map(str, x_pos), x_pos) + self._y_labels = zip(map(str, y_pos), y_pos) + def _plot(self): for serie in self.series: self.svg.line( self.svg.serie(serie.index), serie.values, True) diff --git a/relauncher b/relauncher index fb56a84..1ee37f2 100755 --- a/relauncher +++ b/relauncher @@ -1,5 +1,5 @@ #!/bin/zsh livereload& -reload ./out.py& +reload ./out.py **/*.py& python -m SimpleHTTPServer 1515& chromium http://localhost:1515/&