mirror of https://github.com/Kozea/pygal.git
Florian Mounier
13 years ago
16 changed files with 358 additions and 282 deletions
@ -0,0 +1,128 @@ |
|||||||
|
from pygal.graph.base import BaseGraph |
||||||
|
from pygal.view import View |
||||||
|
|
||||||
|
|
||||||
|
class Graph(BaseGraph): |
||||||
|
"""Graph super class containing generic common functions""" |
||||||
|
|
||||||
|
def _decorate(self): |
||||||
|
self._set_view() |
||||||
|
self._make_graph() |
||||||
|
self._x_axis() |
||||||
|
self._y_axis() |
||||||
|
self._legend() |
||||||
|
self._title() |
||||||
|
|
||||||
|
def _set_view(self): |
||||||
|
self.view = View( |
||||||
|
self.width - self.margin.x, |
||||||
|
self.height - self.margin.y, |
||||||
|
self._box) |
||||||
|
|
||||||
|
def _make_graph(self): |
||||||
|
self.graph_node = self.svg.node( |
||||||
|
class_='graph %s' % self.__class__.__name__) |
||||||
|
self.svg.node(self.graph_node, 'rect', |
||||||
|
class_='background', |
||||||
|
x=0, y=0, |
||||||
|
width=self.width, |
||||||
|
height=self.height) |
||||||
|
self.plot = self.svg.node( |
||||||
|
self.graph_node, class_="plot", |
||||||
|
transform="translate(%d, %d)" % ( |
||||||
|
self.margin.left, self.margin.top)) |
||||||
|
self.svg.node(self.plot, 'rect', |
||||||
|
class_='background', |
||||||
|
x=0, y=0, |
||||||
|
width=self.view.width, |
||||||
|
height=self.view.height) |
||||||
|
|
||||||
|
def _x_axis(self): |
||||||
|
if not self._x_labels: |
||||||
|
return |
||||||
|
|
||||||
|
axis = self.svg.node(self.plot, class_="axis x") |
||||||
|
|
||||||
|
if 0 not in [label[1] for label in self._x_labels]: |
||||||
|
self.svg.node(axis, 'path', |
||||||
|
d='M%f %f v%f' % (0, 0, self.view.height), |
||||||
|
class_='line') |
||||||
|
for label, position in self._x_labels: |
||||||
|
guides = self.svg.node(axis, class_='guides') |
||||||
|
x = self.view.x(position) |
||||||
|
y = self.view.height + 5 |
||||||
|
self.svg.node(guides, 'path', |
||||||
|
d='M%f %f v%f' % (x, 0, self.view.height), |
||||||
|
class_='%sline' % ( |
||||||
|
'guide ' if position != 0 else '')) |
||||||
|
text = self.svg.node(guides, 'text', |
||||||
|
x=x, |
||||||
|
y=y + .5 * self.label_font_size + 5 |
||||||
|
) |
||||||
|
text.text = label |
||||||
|
if self.x_label_rotation: |
||||||
|
text.attrib['transform'] = "rotate(%d %f %f)" % ( |
||||||
|
self.x_label_rotation, x, y) |
||||||
|
|
||||||
|
def _y_axis(self): |
||||||
|
if not self._y_labels: |
||||||
|
return |
||||||
|
|
||||||
|
axis = self.svg.node(self.plot, class_="axis y") |
||||||
|
|
||||||
|
if 0 not in [label[1] for label in self._y_labels]: |
||||||
|
self.svg.node(axis, 'path', |
||||||
|
d='M%f %f h%f' % (0, self.view.height, self.view.width), |
||||||
|
class_='line') |
||||||
|
for label, position in self._y_labels: |
||||||
|
guides = self.svg.node(axis, class_='guides') |
||||||
|
x = -5 |
||||||
|
y = self.view.y(position) |
||||||
|
self.svg.node(guides, 'path', |
||||||
|
d='M%f %f h%f' % (0, y, self.view.width), |
||||||
|
class_='%sline' % ( |
||||||
|
'guide ' if position != 0 else '')) |
||||||
|
text = self.svg.node(guides, 'text', |
||||||
|
x=x, |
||||||
|
y=y + .35 * self.label_font_size |
||||||
|
) |
||||||
|
text.text = label |
||||||
|
if self.y_label_rotation: |
||||||
|
text.attrib['transform'] = "rotate(%d %f %f)" % ( |
||||||
|
self.y_label_rotation, x, y) |
||||||
|
|
||||||
|
def _legend(self): |
||||||
|
if not self.show_legend: |
||||||
|
return |
||||||
|
legends = self.svg.node( |
||||||
|
self.graph_node, class_='legends', |
||||||
|
transform='translate(%d, %d)' % ( |
||||||
|
self.margin.left + self.view.width + 10, |
||||||
|
self.margin.top + 10)) |
||||||
|
for i, title in enumerate(self._legends): |
||||||
|
legend = self.svg.node(legends, class_='legend') |
||||||
|
self.svg.node(legend, 'rect', |
||||||
|
x=0, |
||||||
|
y=1.5 * i * self.legend_box_size, |
||||||
|
width=self.legend_box_size, |
||||||
|
height=self.legend_box_size, |
||||||
|
class_="color-%d" % i, |
||||||
|
).text = title |
||||||
|
# Serious magical numbers here |
||||||
|
self.svg.node(legend, 'text', |
||||||
|
x=self.legend_box_size + 5, |
||||||
|
y=1.5 * i * self.legend_box_size |
||||||
|
+ .5 * self.legend_box_size |
||||||
|
+ .3 * self.legend_font_size |
||||||
|
).text = title |
||||||
|
|
||||||
|
def _title(self): |
||||||
|
if self.title: |
||||||
|
self.svg.node(self.graph_node, 'text', class_='title', |
||||||
|
x=self.margin.left + self.view.width / 2, |
||||||
|
y=self.title_font_size + 10 |
||||||
|
).text = self.title |
||||||
|
|
||||||
|
def _serie(self, serie): |
||||||
|
return self.svg.node( |
||||||
|
self.plot, class_='series serie-%d color-%d' % (serie, serie)) |
@ -1,27 +1,81 @@ |
|||||||
from pygal.graph.base import BaseGraph |
from pygal.graph.line import Line |
||||||
from math import pi |
from pygal.view import PolarView |
||||||
|
from pygal.util import deg |
||||||
|
from math import cos, sin, pi |
||||||
|
|
||||||
|
|
||||||
class Radar(BaseGraph): |
class Radar(Line): |
||||||
"""Kiviat graph""" |
"""Kiviat graph""" |
||||||
|
|
||||||
|
def _set_view(self): |
||||||
|
self.view = PolarView( |
||||||
|
self.width - self.margin.x, |
||||||
|
self.height - self.margin.y, |
||||||
|
self._box) |
||||||
|
|
||||||
|
def _x_axis(self): |
||||||
|
if not self._x_labels: |
||||||
|
return |
||||||
|
|
||||||
|
axis = self.svg.node(self.plot, class_="axis x web") |
||||||
|
format = lambda x: '%f %f' % x |
||||||
|
center = self.view((0, 0)) |
||||||
|
r = self._rmax |
||||||
|
for label, theta in self._x_labels: |
||||||
|
guides = self.svg.node(axis, class_='guides') |
||||||
|
end = self.view((r, theta)) |
||||||
|
self.svg.node(guides, 'path', |
||||||
|
d='M%s L%s' % (format(center), format(end)), |
||||||
|
class_='line') |
||||||
|
r_txt = (1 - self._box.__class__._margin) * self._box.ymax |
||||||
|
pos_text = self.view((r_txt, theta)) |
||||||
|
text = self.svg.node(guides, 'text', |
||||||
|
x=pos_text[0], |
||||||
|
y=pos_text[1] |
||||||
|
) |
||||||
|
text.text = label |
||||||
|
angle = - theta + pi / 2. |
||||||
|
if cos(angle) < 0: |
||||||
|
angle -= pi |
||||||
|
text.attrib['transform'] = 'rotate(%f %s)' % ( |
||||||
|
deg(angle), format(pos_text)) |
||||||
|
|
||||||
|
def _y_axis(self): |
||||||
|
if not self._y_labels: |
||||||
|
return |
||||||
|
|
||||||
|
axis = self.svg.node(self.plot, class_="axis y web") |
||||||
|
|
||||||
|
for label, r in reversed(self._y_labels): |
||||||
|
guides = self.svg.node(axis, class_='guides') |
||||||
|
self.svg.line( |
||||||
|
guides, [self.view((r, theta)) for theta in self._x_pos], |
||||||
|
close=True, |
||||||
|
class_='guide line') |
||||||
|
x, y = self.view((r, self._x_pos[0])) |
||||||
|
self.svg.node(guides, 'text', |
||||||
|
x=x - 5, |
||||||
|
y=y |
||||||
|
).text = label |
||||||
|
|
||||||
def _compute(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] |
||||||
self._box.ymax = 2 * max(vals) |
self._box._margin *= 2 |
||||||
self._box.ymin = - self._box.ymax |
self._box.xmin = self._box.ymin = 0 |
||||||
self._box.xmin = self._box.ymin |
self._box.xmax = self._box.ymax = self._rmax = max(vals) |
||||||
self._box.xmax = self._box.ymax |
|
||||||
|
|
||||||
delta = 2 * pi / float(len(self.x_labels)) |
|
||||||
x_step = len(self.series[0].values) |
x_step = len(self.series[0].values) |
||||||
|
delta = 2 * pi / float(len(self.x_labels)) |
||||||
self._x_pos = [.5 * pi - i * delta for i in range(x_step)] |
self._x_pos = [.5 * pi - i * delta for i in range(x_step)] |
||||||
self._y_pos = self._pos(self._box.ymin, self._box.ymax, self.y_scale |
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) |
) 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._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) |
self._y_labels = zip(map(str, self._y_pos), self._y_pos) |
||||||
|
self._box.xmin = self._box.ymin = - self._box.ymax |
||||||
|
|
||||||
def _plot(self): |
def _plot(self): |
||||||
for serie in self.series: |
for serie in self.series: |
||||||
serie_node = self.svg.serie(serie.index) |
serie_node = self._serie(serie.index) |
||||||
# self.svg.web(serie_node, serie, |
self.line(serie_node, [ |
||||||
# [val / float(self._rmax) for val in serie.values]) |
(v, self._x_pos[i]) |
||||||
|
for i, v in enumerate(serie.values)]) |
||||||
|
Loading…
Reference in new issue