mirror of https://github.com/Kozea/pygal.git
Florian Mounier
13 years ago
13 changed files with 172 additions and 204 deletions
@ -1,13 +1,63 @@
|
||||
from pygal import Serie, Label, Margin |
||||
from pygal.util import round_to_scale |
||||
from pygal.svg import Svg |
||||
from pygal.config import Config |
||||
|
||||
|
||||
class BaseGraph(object): |
||||
"""Graphs commons""" |
||||
|
||||
def __init__(self, config=None): |
||||
self.config = config or Config() |
||||
self.svg = Svg(self) |
||||
self.series = [] |
||||
self.margin = Margin(*([10] * 4)) |
||||
|
||||
def __getattr__(self, attr): |
||||
if attr in dir(self.config): |
||||
return object.__getattribute__(self.config, attr) |
||||
return object.__getattribute__(self, attr) |
||||
|
||||
def _y_pos(self, ymin, ymax): |
||||
step = (ymax - ymin) / float(self.max_scale_step) |
||||
position = ymin |
||||
if not step: |
||||
return [position] |
||||
positions = set() |
||||
while position < (ymax + step): |
||||
positions.add(round_to_scale(position, self.scale)) |
||||
position += step |
||||
return positions |
||||
|
||||
def _compute_margin(self, x_labels, y_labels): |
||||
self.margin.left += 10 + max( |
||||
map(len, [l for l, _ in y_labels]) |
||||
) * 0.6 * self.label_font_size |
||||
if x_labels: |
||||
self.margin.bottom += 10 + self.label_font_size |
||||
self.margin.right += 20 + max( |
||||
map(len, [serie.title for serie in self.series]) |
||||
) * 0.6 * self.label_font_size |
||||
self.margin.top += 10 + self.label_font_size |
||||
|
||||
def add(self, title, values): |
||||
self.series.append(Serie(title, values, len(self.series))) |
||||
|
||||
def render(self): |
||||
self.draw() |
||||
if len(self.series) == 0 or sum( |
||||
map(len, map(lambda s: s.values, self.series))) == 0: |
||||
return |
||||
self.validate() |
||||
self._draw() |
||||
return self.svg.render() |
||||
|
||||
def _in_browser(self, *args, **kwargs): |
||||
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(*args, **kwargs) |
||||
self._draw() |
||||
open_in_browser(self.svg.root, encoding='utf-8') |
||||
|
@ -0,0 +1,14 @@
|
||||
from pygal.style import DefaultStyle |
||||
|
||||
|
||||
class Config(object): |
||||
width = 800 |
||||
height = 600 |
||||
scale = 1 |
||||
max_scale_step = 20 |
||||
base_css = None |
||||
style = DefaultStyle |
||||
label_font_size = 12 |
||||
x_labels = None |
||||
y_labels = None |
||||
title = None |
@ -1,80 +1,33 @@
|
||||
from pygal import Serie, Margin, Label |
||||
from pygal.svg import Svg |
||||
from pygal.util import round_to_int, round_to_float |
||||
from pygal.util import round_to_scale |
||||
from pygal.base import BaseGraph |
||||
|
||||
|
||||
class Line(BaseGraph): |
||||
"""Line graph""" |
||||
|
||||
def __init__(self, width, height, scale=1, style=None): |
||||
self.width = width |
||||
self.height = height |
||||
self.svg = Svg(width, height, style=style) |
||||
self.label_font_size = 12 |
||||
self.series = [] |
||||
self.scale = scale |
||||
self.x_labels = self.y_labels = self.title = None |
||||
rnd = round_to_float if self.scale < 1 else round_to_int |
||||
self.round = lambda x: rnd(x, self.scale) |
||||
|
||||
def add(self, title, values): |
||||
self.series.append(Serie(title, values)) |
||||
|
||||
def _label(self, number): |
||||
return Label(*self.round(number)) |
||||
|
||||
def _y_labels(self, ymin, ymax): |
||||
step = (ymax - ymin) / 20. |
||||
|
||||
if not step: |
||||
return [self._label(ymin)] |
||||
label = ymin |
||||
labels = set() |
||||
while label < (ymax + step): |
||||
labels.add(self._label(label)) |
||||
label += step |
||||
return labels |
||||
|
||||
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 draw(self): |
||||
def _draw(self): |
||||
vals = [val for serie in self.series for val in serie.values] |
||||
if not vals: |
||||
return |
||||
self.validate() |
||||
ymin, 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) |
||||
] if x_step != 1 else [.5] # Center if only one value |
||||
margin = Margin(*(4 * [10])) |
||||
ymin, ymax = min(vals), max(vals) |
||||
if self.x_labels: |
||||
x_labels = [Label(label, x_pos[i]) |
||||
for i, label in enumerate(self.x_labels)] |
||||
y_labels = self.y_labels or self._y_labels(ymin, ymax) |
||||
series_labels = [serie.title for serie in self.series] |
||||
margin.left += 10 + max( |
||||
map(len, [l.label for l in y_labels])) * 0.6 * self.label_font_size |
||||
if self.x_labels: |
||||
margin.bottom += 10 + self.label_font_size |
||||
margin.right += 20 + max( |
||||
map(len, series_labels)) * 0.6 * self.label_font_size |
||||
margin.top += 10 + self.label_font_size |
||||
y_pos = self._y_pos(ymin, ymax) if not self.y_labels else map( |
||||
int, self.y_labels) |
||||
|
||||
# Actual drawing |
||||
self.svg.set_view(margin, ymin, ymax) |
||||
self.svg.graph(margin) |
||||
if self.x_labels: |
||||
self.svg.x_axis(x_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(margin, series_labels) |
||||
self.svg.title(margin, self.title) |
||||
self.svg.legend([serie.title for serie in self.series]) |
||||
self.svg.title() |
||||
|
||||
for serie_index, serie in enumerate(self.series): |
||||
serie_node = self.svg.serie(serie_index) |
||||
self.svg.line(serie_node, [ |
||||
self.svg.line( |
||||
self.svg.serie(serie_index), [ |
||||
(x_pos[i], v) |
||||
for i, v in enumerate(serie.values)]) |
||||
|
Loading…
Reference in new issue