Browse Source

And the graph

pull/8/head
Florian Mounier 13 years ago
parent
commit
5833458a32
  1. 0
      pygal/graph/__init__.py
  2. 26
      pygal/graph/bar.py
  3. 143
      pygal/graph/base.py
  4. 24
      pygal/graph/line.py
  5. 22
      pygal/graph/pie.py
  6. 37
      pygal/graph/stackedbar.py
  7. 24
      pygal/graph/xy.py

0
pygal/graph/__init__.py

26
pygal/graph/bar.py

@ -0,0 +1,26 @@
from pygal.graph.base import BaseGraph
class Bar(BaseGraph):
"""Bar graph"""
def _compute(self):
vals = [val for serie in self.series for val in serie.values]
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(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:])
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((self._x_ranges[i][j], v) for j in range(2))
for i, v in enumerate(serie.values)])

143
pygal/graph/base.py

@ -0,0 +1,143 @@
from pygal.serie import Serie
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
from math import log10, sin, cos, pi
class BaseGraph(object):
"""Graphs commons"""
def __init__(self, config=None):
self.config = config or Config()
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):
return object.__getattribute__(self.config, attr)
return object.__getattribute__(self, attr)
def _pos(self, min_, max_, scale):
order = round(log10(max(abs(min_), abs(max_)))) - 1
while (max_ - min_) / float(10 ** order) < 4:
order -= 1
step = float(10 ** order)
while (max_ - min_) / step > 20:
step *= 2.
positions = set()
if self.x_start_at_zero:
position = 0
else:
position = round_to_scale(min_, step)
while position < (max_ + step):
rounded = round_to_scale(position, scale)
if min_ <= rounded <= max_:
positions.add(rounded)
position += step
if not positions:
return [min_]
return positions
def _text_len(self, lenght, fs):
return lenght * 0.6 * fs
def _get_text_box(self, text, fs):
return (fs, self._text_len(len(text), fs))
def _get_texts_box(self, texts, fs):
max_len = max(map(len, texts))
return (fs, self._text_len(max_len, fs))
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)
self.margin.right += 10 + w + self.legend_box_size
if self.title:
h, w = self._get_text_box(self.title, self.title_font_size)
self.margin.top += 10 + h
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 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)))
def render(self):
if len(self.series) == 0 or sum(
map(len, map(lambda s: s.values, self.series))) == 0:
return "No data"
try:
self.validate()
self._draw()
return self.svg.render()
except Exception:
from traceback import format_exc
error_svg = (
'<?xml version="1.0" standalone="no"?>'
'<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" '
'"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">'
'<svg xmlns="http://www.w3.org/2000/svg" version="1.1">')
trace = (format_exc()
.replace('&', '&amp;')
.replace('<', '&lt;')
.replace('>', '&gt;'))
for i, line in enumerate(trace.split('\n')):
error_svg += '<text y="%d">%s</text>' % (
(i + 1) * 25, line)
error_svg += '</svg>'
return error_svg
def validate(self):
if self.x_labels:
assert len(self.series[0].values) == len(self.x_labels)
for serie in self.series:
assert len(self.series[0].values) == len(serie.values)
def _in_browser(self):
from lxml.html import open_in_browser
self._draw()
open_in_browser(self.svg.root, encoding='utf-8')
def render_response(self):
from flask import Response
return Response(self.render(), mimetype='image/svg+xml')

24
pygal/graph/line.py

@ -0,0 +1,24 @@
from pygal.graph.base import BaseGraph
class Line(BaseGraph):
"""Line graph"""
def _compute(self):
vals = [val for serie in self.series for val in serie.values]
self._box.ymin, self._box.ymax = min(vals), max(vals)
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._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_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), [
(self._x_pos[i], v)
for i, v in enumerate(serie.values)])

22
pygal/graph/pie.py

@ -0,0 +1,22 @@
from pygal.serie import Serie
from pygal.graph.base import BaseGraph
from math import pi
class Pie(BaseGraph):
"""Pie graph"""
def add(self, title, value):
self.series.append(Serie(title, [value], len(self.series)))
def _plot(self):
total = float(sum(serie.values[0] for serie in self.series))
current_angle = 0
for serie in self.series:
val = serie.values[0]
angle = 2 * pi * val / total
self.svg.slice(
self.svg.serie(serie.index),
current_angle,
angle, val / total)
current_angle += angle

37
pygal/graph/stackedbar.py

@ -0,0 +1,37 @@
from pygal.graph.base import BaseGraph
class StackedBar(BaseGraph):
"""Stacked Bar graph"""
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]
self._box.ymin, self._box.ymax = (
min(min(negative_vals), 0), max(max(positive_vals), 0))
self._length = len(self.series[0].values)
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:])
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((self._x_ranges[i][j], v) for j in range(2))
for i, v in enumerate(serie.values)],
stack_vals)

24
pygal/graph/xy.py

@ -0,0 +1,24 @@
from pygal.graph.base import BaseGraph
class XY(BaseGraph):
"""XY Line graph"""
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]
self._box.xmin, self._box.xmax = min(xvals), max(xvals)
self._box.ymin, self._box.ymax = min(yvals), max(yvals)
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)
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)
Loading…
Cancel
Save