Browse Source

Rewrite of drawing pattern

pull/8/head
Florian Mounier 13 years ago
parent
commit
b964163f82
  1. 27
      pygal/bar.py
  2. 37
      pygal/base.py
  3. 26
      pygal/line.py
  4. 7
      pygal/pie.py
  5. 38
      pygal/stackedbar.py
  6. 24
      pygal/svg.py
  7. 35
      pygal/view.py
  8. 23
      pygal/xy.py
  9. 2
      relauncher

27
pygal/bar.py

@ -4,30 +4,23 @@ from pygal.base import BaseGraph
class Bar(BaseGraph): class Bar(BaseGraph):
"""Bar graph""" """Bar graph"""
def _draw(self): def _compute(self):
vals = [val for serie in self.series for val in serie.values] 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_step = len(self.series[0].values)
x_pos = [x / float(x_step) for x in range(x_step + 1) 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 ] if x_step > 1 else [0, 1] # Center if only one value
y_pos = self._pos( y_pos = self._pos(self._box.ymin, self._box.ymax, self.y_scale
ymin, ymax, self.y_scale) if not self.y_labels else map( ) if not self.y_labels else map(int, self.y_labels)
int, self.y_labels)
x_ranges = zip(x_pos, x_pos[1:])
x_labels = self.x_labels and zip(self.x_labels, [ self._x_ranges = zip(x_pos, x_pos[1:])
sum(x_range) / 2. for x_range in x_ranges]) self._x_labels = self.x_labels and zip(self.x_labels, [
y_labels = zip(map(str, y_pos), y_pos) sum(x_range) / 2. for x_range in self._x_ranges])
self._compute_margin(x_labels, y_labels) self._y_labels = zip(map(str, y_pos), y_pos)
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()
def _plot(self):
for serie in self.series: for serie in self.series:
serie_node = self.svg.serie(serie.index) serie_node = self.svg.serie(serie.index)
self.svg.bar(serie_node, serie, [ 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)]) for i, v in enumerate(serie.values)])

37
pygal/base.py

@ -1,5 +1,5 @@
from pygal.serie import Serie 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.util import round_to_scale, cut, rad
from pygal.svg import Svg from pygal.svg import Svg
from pygal.config import Config from pygal.config import Config
@ -14,6 +14,8 @@ class BaseGraph(object):
self.svg = Svg(self) self.svg = Svg(self)
self.series = [] self.series = []
self.margin = Margin(*([20] * 4)) self.margin = Margin(*([20] * 4))
self._x_labels = self._y_labels = None
self._box = Box()
def __getattr__(self, attr): def __getattr__(self, attr):
if attr in dir(self.config): if attr in dir(self.config):
@ -51,7 +53,10 @@ class BaseGraph(object):
max_len = max(map(len, texts)) max_len = max(map(len, texts))
return (fs, self._text_len(max_len, fs)) 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: if self.show_legend:
h, w = self._get_texts_box( h, w = self._get_texts_box(
cut(self.series, 'title'), self.legend_font_size) 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) h, w = self._get_text_box(self.title, self.title_font_size)
self.margin.top += 10 + h self.margin.top += 10 + h
if x_labels: if self._x_labels:
h, w = self._get_texts_box(cut(x_labels), self.label_font_size) h, w = self._get_texts_box(
cut(self._x_labels), self.label_font_size)
self.margin.bottom += 10 + max( self.margin.bottom += 10 + max(
w * sin(rad(self.x_label_rotation)), h) w * sin(rad(self.x_label_rotation)), h)
if self.x_label_rotation: if self.x_label_rotation:
self.margin.right = max( self.margin.right = max(
.5 * w * cos(rad(self.x_label_rotation)), .5 * w * cos(rad(self.x_label_rotation)),
self.margin.right) self.margin.right)
if y_labels: if self._y_labels:
h, w = self._get_texts_box(cut(y_labels), self.label_font_size) h, w = self._get_texts_box(
cut(self._y_labels), self.label_font_size)
self.margin.left += 10 + max( self.margin.left += 10 + max(
w * cos(rad(self.y_label_rotation)), h) 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): def add(self, title, values):
self.series.append(Serie(title, values, len(self.series))) self.series.append(Serie(title, values, len(self.series)))

26
pygal/line.py

@ -4,29 +4,21 @@ from pygal.base import BaseGraph
class Line(BaseGraph): class Line(BaseGraph):
"""Line graph""" """Line graph"""
def _draw(self): def _compute(self):
vals = [val for serie in self.series for val in serie.values] 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_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 ] if x_step != 1 else [.5] # Center if only one value
y_pos = self._pos( self._y_pos = self._pos(self._box.ymin, self._box.ymax, self.y_scale
ymin, ymax, self.y_scale) if not self.y_labels else map( ) if not self.y_labels else map(int, self.y_labels)
int, self.y_labels)
x_labels = self.x_labels and zip(self.x_labels, x_pos) self._x_labels = self.x_labels and zip(self.x_labels, self._x_pos)
y_labels = zip(map(str, y_pos), y_pos) self._y_labels = zip(map(str, self._y_pos), self._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()
def _plot(self):
for serie in self.series: for serie in self.series:
self.svg.line( self.svg.line(
self.svg.serie(serie.index), [ self.svg.serie(serie.index), [
(x_pos[i], v) (self._x_pos[i], v)
for i, v in enumerate(serie.values)]) for i, v in enumerate(serie.values)])

7
pygal/pie.py

@ -9,12 +9,7 @@ class Pie(BaseGraph):
def add(self, title, value): def add(self, title, value):
self.series.append(Serie(title, [value], len(self.series))) self.series.append(Serie(title, [value], len(self.series)))
def _draw(self): def _plot(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()
total = float(sum(serie.values[0] for serie in self.series)) total = float(sum(serie.values[0] for serie in self.series))
current_angle = 0 current_angle = 0
for serie in self.series: for serie in self.series:

38
pygal/stackedbar.py

@ -4,38 +4,34 @@ from pygal.base import BaseGraph
class StackedBar(BaseGraph): class StackedBar(BaseGraph):
"""Stacked Bar graph""" """Stacked Bar graph"""
def _draw(self): def _compute(self):
transposed = zip(*[serie.values for serie in self.series]) transposed = zip(*[serie.values for serie in self.series])
positive_vals = [sum([val if val > 0 else 0 for val in vals]) positive_vals = [sum([val if val > 0 else 0 for val in vals])
for vals in transposed] for vals in transposed]
negative_vals = [sum([val if val < 0 else 0 for val in vals]) negative_vals = [sum([val if val < 0 else 0 for val in vals])
for vals in transposed] 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, [ self._box.ymin, self._box.ymax = (
sum(x_range) / 2 for x_range in x_ranges]) min(min(negative_vals), 0), max(max(positive_vals), 0))
y_labels = zip(map(str, y_pos), y_pos) self._length = len(self.series[0].values)
self._compute_margin(x_labels, y_labels) x_pos = [x / float(self._length)
self.svg.set_view(ymin, ymax) for x in range(self._length + 1)
self.svg.make_graph() ] if self._length > 1 else [0, 1] # Center if only one value
self.svg.x_axis(x_labels) y_pos = self._pos(self._box.ymin, self._box.ymax, self.y_scale
self.svg.y_axis(y_labels) ) if not self.y_labels else map(int, self.y_labels)
self.svg.legend([serie.title for serie in self.series]) self._x_ranges = zip(x_pos, x_pos[1:])
self.svg.title()
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: for serie in self.series:
serie_node = self.svg.serie(serie.index) serie_node = self.svg.serie(serie.index)
stack_vals = self.svg.bar( stack_vals = self.svg.bar(
serie_node, serie, [ 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)], for i, v in enumerate(serie.values)],
stack_vals) stack_vals)

24
pygal/svg.py

@ -53,11 +53,11 @@ class Svg(object):
return etree.SubElement(parent, tag, attrib) 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.view = View(
self.graph.width - self.graph.margin.x, self.graph.width - self.graph.margin.x,
self.graph.height - self.graph.margin.y, self.graph.height - self.graph.margin.y,
xmin, xmax, ymin, ymax) self.graph._box)
def make_graph(self): def make_graph(self):
self.graph_node = self.node( self.graph_node = self.node(
@ -77,17 +77,17 @@ class Svg(object):
width=self.view.width, width=self.view.width,
height=self.view.height) height=self.view.height)
def x_axis(self, labels): def x_axis(self):
if not labels: if not self.graph._x_labels:
return return
axis = self.node(self.plot, class_="axis x") 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', self.node(axis, 'path',
d='M%f %f v%f' % (0, 0, self.view.height), d='M%f %f v%f' % (0, 0, self.view.height),
class_='line') class_='line')
for label, position in labels: for label, position in self.graph._x_labels:
guides = self.node(axis, class_='guides') guides = self.node(axis, class_='guides')
x = self.view.x(position) x = self.view.x(position)
y = self.view.height + 5 y = self.view.height + 5
@ -103,17 +103,17 @@ class Svg(object):
self.graph.x_label_rotation, x, y) self.graph.x_label_rotation, x, y)
text.text = label text.text = label
def y_axis(self, labels): def y_axis(self):
if not labels: if not self.graph._y_labels:
return return
axis = self.node(self.plot, class_="axis y") 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', self.node(axis, 'path',
d='M%f %f h%f' % (0, self.view.height, self.view.width), d='M%f %f h%f' % (0, self.view.height, self.view.width),
class_='line') class_='line')
for label, position in labels: for label, position in self.graph._y_labels:
guides = self.node(axis, class_='guides') guides = self.node(axis, class_='guides')
x = -5 x = -5
y = self.view.y(position) y = self.view.y(position)
@ -129,7 +129,7 @@ class Svg(object):
self.graph.y_label_rotation, x, y) self.graph.y_label_rotation, x, y)
text.text = label text.text = label
def legend(self, titles): def legend(self):
if not self.graph.show_legend: if not self.graph.show_legend:
return return
legends = self.node( legends = self.node(
@ -137,7 +137,7 @@ class Svg(object):
transform='translate(%d, %d)' % ( transform='translate(%d, %d)' % (
self.graph.margin.left + self.view.width + 10, self.graph.margin.left + self.view.width + 10,
self.graph.margin.top + 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') legend = self.node(legends, class_='legend')
self.node(legend, 'rect', self.node(legend, 'rect',
x=0, x=0,

35
pygal/view.py

@ -15,29 +15,38 @@ class Margin(object):
class Box(object): class Box(object):
def __init__(self, x, y, width, height): def __init__(self):
self.x = x self.xmin = self.ymin = 0
self.y = y self.xmax = self.ymax = 1
self.width = width
self.height = height @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): class View(object):
def __init__(self, width, height, xmin, xmax, ymin, ymax): def __init__(self, width, height, box):
self.width = width self.width = width
self.height = height self.height = height
xrng = (xmax - xmin) or 1 self.box = box
yrng = (ymax - ymin) or 1
if (ymax - ymin) == 0:
ymin -= .5
self.box = Box(xmin, ymin, xrng, yrng)
def x(self, x): 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): def y(self, y):
return (self.height - self.height * 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): def __call__(self, xy):
x, y = xy x, y = xy

23
pygal/xy.py

@ -4,28 +4,21 @@ from pygal.base import BaseGraph
class XY(BaseGraph): class XY(BaseGraph):
"""XY Line graph""" """XY Line graph"""
def _draw(self): def _compute(self):
for serie in self.series: for serie in self.series:
serie.values = sorted(serie.values, key=lambda x: x[0]) serie.values = sorted(serie.values, key=lambda x: x[0])
xvals = [val[0] for serie in self.series for val in serie.values] 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] yvals = [val[1] for serie in self.series for val in serie.values]
xmin, xmax = min(xvals), max(xvals) self._box.xmin, self._box.xmax = min(xvals), max(xvals)
ymin, ymax = min(yvals), max(yvals) self._box.ymin, self._box.ymax = min(yvals), max(yvals)
x_pos = self._pos(xmin, xmax, self.x_scale) x_pos = self._pos(self._box.xmin, self._box.xmax, self.x_scale)
y_pos = self._pos(ymin, ymax, self.y_scale) y_pos = self._pos(self._box.ymin, self._box.ymax, self.y_scale)
x_labels = zip(map(str, x_pos), x_pos) self._x_labels = zip(map(str, x_pos), x_pos)
y_labels = zip(map(str, y_pos), y_pos) self._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()
def _plot(self):
for serie in self.series: for serie in self.series:
self.svg.line( self.svg.line(
self.svg.serie(serie.index), serie.values, True) self.svg.serie(serie.index), serie.values, True)

2
relauncher

@ -1,5 +1,5 @@
#!/bin/zsh #!/bin/zsh
livereload& livereload&
reload ./out.py& reload ./out.py **/*.py&
python -m SimpleHTTPServer 1515& python -m SimpleHTTPServer 1515&
chromium http://localhost:1515/& chromium http://localhost:1515/&

Loading…
Cancel
Save