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): |
class BaseGraph(object): |
||||||
"""Graphs commons""" |
"""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): |
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() |
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 |
from lxml.html import open_in_browser |
||||||
self.draw(*args, **kwargs) |
self._draw() |
||||||
open_in_browser(self.svg.root, encoding='utf-8') |
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 import Serie, Margin, Label |
||||||
from pygal.svg import Svg |
from pygal.util import round_to_scale |
||||||
from pygal.util import round_to_int, round_to_float |
|
||||||
from pygal.base import BaseGraph |
from pygal.base import BaseGraph |
||||||
|
|
||||||
|
|
||||||
class Line(BaseGraph): |
class Line(BaseGraph): |
||||||
"""Line graph""" |
"""Line graph""" |
||||||
|
|
||||||
def __init__(self, width, height, scale=1, style=None): |
def _draw(self): |
||||||
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): |
|
||||||
vals = [val for serie in self.series for val in serie.values] |
vals = [val for serie in self.series for val in serie.values] |
||||||
if not vals: |
ymin, ymax = min(vals), max(vals) |
||||||
return |
|
||||||
self.validate() |
|
||||||
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) |
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 |
||||||
margin = Margin(*(4 * [10])) |
y_pos = self._y_pos(ymin, ymax) if not self.y_labels else map( |
||||||
ymin, ymax = min(vals), max(vals) |
int, self.y_labels) |
||||||
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 |
|
||||||
|
|
||||||
# Actual drawing |
x_labels = self.x_labels and zip(self.x_labels, x_pos) |
||||||
self.svg.set_view(margin, ymin, ymax) |
y_labels = zip(map(str, y_pos), y_pos) |
||||||
self.svg.graph(margin) |
|
||||||
if self.x_labels: |
self._compute_margin(x_labels, y_labels) |
||||||
self.svg.x_axis(x_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.y_axis(y_labels) |
||||||
self.svg.legend(margin, series_labels) |
self.svg.legend([serie.title for serie in self.series]) |
||||||
self.svg.title(margin, self.title) |
self.svg.title() |
||||||
|
|
||||||
for serie_index, serie in enumerate(self.series): |
for serie_index, serie in enumerate(self.series): |
||||||
serie_node = self.svg.serie(serie_index) |
self.svg.line( |
||||||
self.svg.line(serie_node, [ |
self.svg.serie(serie_index), [ |
||||||
(x_pos[i], v) |
(x_pos[i], v) |
||||||
for i, v in enumerate(serie.values)]) |
for i, v in enumerate(serie.values)]) |
||||||
|
Loading…
Reference in new issue