Browse Source

Adapt #20, fix tests (x_labels at None), fix pep8. Make it work with stacked lines

pull/26/merge
Florian Mounier 12 years ago
parent
commit
87ece43c99
  1. 6
      demo/moulinrouge.py
  2. 9
      demo/moulinrouge/tests.py
  3. 15
      pygal/graph/base.py
  4. 35
      pygal/graph/graph.py
  5. 49
      pygal/graph/line.py
  6. 17
      pygal/graph/stackedline.py

6
demo/moulinrouge.py

@ -50,5 +50,11 @@ else:
wsreload.monkey_patch_http_server({'url': url}, callback=log)
app.logger.debug('HTTPServer monkey patched for url %s' % url)
try:
import wdb
except ImportError:
pass
else:
app.wsgi_app = wdb.Wdb(app.wsgi_app, start_disabled=True)
app.run(debug=True, threaded=True, host='0.0.0.0', port=21112)

9
demo/moulinrouge/tests.py

@ -181,6 +181,15 @@ def get_test_routes(app):
bar.add('2', [4, 5, 6])
return bar.render_response()
@app.route('/test/secondary/<chart>')
def test_secondary_for(chart):
chart = CHARTS_BY_NAME[chart]()
chart.add(10 * '1', [30, 20, 25])
chart.add(10 * '1b', [4, 5, 6], secondary=True)
chart.add(10 * '2b', [3, 0, 12], secondary=True)
chart.add(10 * '2', [8, 21, 5])
return chart.render_response()
@app.route('/test/stacked')
def test_stacked():
stacked = StackedBar()

15
pygal/graph/base.py

@ -28,6 +28,7 @@ from pygal.util import (
from pygal.svg import Svg
from pygal.util import cached_property, reverse_text_len
from math import sin, cos, sqrt
from itertools import chain
class BaseGraph(object):
@ -79,6 +80,10 @@ class BaseGraph(object):
title = title[i:].strip()
self.title.append(title)
@property
def all_series(self):
return chain(self.series, self.secondary_series)
@property
def _format(self):
"""Return the value formatter for this graph"""
@ -115,7 +120,7 @@ class BaseGraph(object):
self.margin.bottom += 10 + h_max * round(
sqrt(self._order) - 1) * 1.5 + h_max
else:
self.margin.right += 10 + w + self.legend_box_size
self.margin.right += 10 + w + self.legend_box_size
if self.title:
h, _ = get_text_box(self.title[0], self.title_font_size)
@ -143,7 +148,7 @@ class BaseGraph(object):
def _legends(self):
"""Getter for series title"""
return [serie.title for serie in self.series]
@cached_property
def _secondary_legends(self):
"""Getter for series title on secondary y axis"""
@ -168,7 +173,9 @@ class BaseGraph(object):
@cached_property
def _len(self):
"""Getter for the maximum series size"""
return max([len(serie.values) for serie in self.series + self.secondary_series] or [0])
return max([
len(serie.values)
for serie in self.all_series] or [0])
@cached_property
def _secondary_min(self):
@ -197,7 +204,7 @@ class BaseGraph(object):
@cached_property
def _order(self):
"""Getter for the maximum series value"""
return len(self.series + self.secondary_series)
return len(self.all_series)
def _draw(self):
"""Draw all the things"""

35
pygal/graph/graph.py

@ -25,9 +25,10 @@ from __future__ import division
from pygal.interpolate import interpolation
from pygal.graph.base import BaseGraph
from pygal.view import View, LogView, XYLogView
from pygal.util import is_major, truncate, reverse_text_len, get_texts_box, cut, rad
from pygal.util import (
is_major, truncate, reverse_text_len, get_texts_box, cut, rad)
from math import isnan, pi, sqrt, ceil, cos
from itertools import repeat, izip, chain, count
from itertools import repeat, izip, chain
class Graph(BaseGraph):
@ -142,8 +143,7 @@ class Graph(BaseGraph):
d='M%f %f v%f' % (x, 0, self.view.height),
class_='%s%sline' % (
'major ' if major else '',
'guide ' if position != 0 and not last_guide
else ''))
'guide ' if position != 0 and not last_guide else ''))
y += .5 * self.label_font_size + 5
text = self.svg.node(
guides, 'text',
@ -197,24 +197,25 @@ class Graph(BaseGraph):
self.y_label_rotation, x, y)
if self._y_2nd_labels:
secondary_ax = self.svg.node(self.nodes['plot'], class_="axis y2")
secondary_ax = self.svg.node(
self.nodes['plot'], class_="axis y2")
for label, position in self._y_2nd_labels:
major = is_major(position)
# it is needed, to have the same structure as primary axis
guides = self.svg.node(secondary_ax, class_='guides')
x = self.view.width + 5
y = self.view.y(position)
text = self.svg.node(guides, 'text',
x = x,
y = y + .35 * self.label_font_size,
class_ = 'major' if major else ''
text = self.svg.node(
guides, 'text',
x=x,
y=y + .35 * self.label_font_size,
class_='major' if major else ''
)
text.text = label
if self.y_label_rotation:
text.attrib['transform'] = "rotate(%d %f %f)" % (
self.y_label_rotation, x, y)
def _legend(self):
"""Make the legend box"""
if not self.show_legend:
@ -242,25 +243,23 @@ class Graph(BaseGraph):
self.nodes['graph'], class_='legends',
transform='translate(%d, %d)' % (x, y))
h = max(self.legend_box_size, self.legend_font_size)
x_step = self.view.width / cols
if self.legend_at_bottom:
# if legends at the bottom, we dont split the windows
counter = count()
# gen structure - (i, (j, (l, tf)))
# i - global serie number - used for coloring and identification
# j - position within current legend box
# l - label
# tf - whether it is secondary label
gen = enumerate(enumerate(chain(
izip(self._legends, repeat(False)),
izip(self._secondary_legends, repeat(True)))))
secondary_legends = legends # svg node is the same
izip(self._legends, repeat(False)),
izip(self._secondary_legends, repeat(True)))))
secondary_legends = legends # svg node is the same
else:
gen = enumerate(chain(
enumerate(izip(self._legends, repeat(False))),
enumerate(izip(self._secondary_legends, repeat(True)))))
enumerate(izip(self._legends, repeat(False))),
enumerate(izip(self._secondary_legends, repeat(True)))))
# draw secondary axis on right
x = self.margin.left + self.view.width + 10
@ -383,7 +382,7 @@ class Graph(BaseGraph):
return self._format(values[i][1])
def _points(self, x_pos):
for serie in self.series + self.secondary_series:
for serie in self.all_series:
serie.points = [
(x_pos[i], v)
for i, v in enumerate(serie.values)]

49
pygal/graph/line.py

@ -41,6 +41,15 @@ class Line(Graph):
if self.interpolate else serie.points)
if val[1] is not None and (not self.logarithmic or val[1] > 0)]
@cached_property
def _secondary_values(self):
return [
val[1]
for serie in self.secondary_series
for val in (serie.interpolated
if self.interpolate else serie.points)
if val[1] is not None and (not self.logarithmic or val[1] > 0)]
def _fill(self, values):
"""Add extra values to fill the line"""
zero = self.view.y(min(max(self.zero, self._box.ymin), self._box.ymax))
@ -51,7 +60,9 @@ class Line(Graph):
def line(self, serie_node, serie, rescale=False):
"""Draw the line serie"""
if rescale and self.secondary_series:
points = list ((x, self._scale_diff+(y - self._scale_min_2nd) * self._scale) for x, y in serie.points)
points = [
(x, self._scale_diff + (y - self._scale_min_2nd) * self._scale)
for x, y in serie.points]
else:
points = serie.points
view_values = map(self.view, points)
@ -75,11 +86,11 @@ class Line(Graph):
val = self._get_value(serie.points, i)
self.svg.node(dots, 'circle', cx=x, cy=y, r=self.dots_size,
class_='dot reactive tooltip-trigger')
self._tooltip_data(dots,
"%s: %s" % (self.x_labels[i], val) if self.x_labels and
self.x_labels_num_limit
else val,
x, y)
self._tooltip_data(
dots, "%s: %s" % (
self.x_labels[i], val)
if self.x_labels and self.x_labels_num_limit
else val, x, y)
self._static_value(
serie_node, val,
x + self.value_font_size,
@ -102,16 +113,19 @@ class Line(Graph):
self._points(x_pos)
x_labels = zip(self.x_labels, x_pos)
if self.x_labels_num_limit and len(x_labels)>self.x_labels_num_limit:
step = (len(x_labels)-1)/(self.x_labels_num_limit-1)
x_labels = list(x_labels[int(i*step)] for i in range(self.x_labels_num_limit))
self._x_labels = self.x_labels and x_labels
# Y Label
if self.x_labels:
x_labels = zip(self.x_labels, x_pos)
if (self.x_labels_num_limit and
len(x_labels) > self.x_labels_num_limit):
step = (len(x_labels) - 1) / (self.x_labels_num_limit - 1)
x_labels = [x_labels[int(i * step)]
for i in range(self.x_labels_num_limit)]
self._x_labels = x_labels
else:
self._x_labels = None
if self.include_x_axis:
# Y Label
self._box.ymin = min(self._min, 0)
self._box.ymax = max(self._max, 0)
else:
@ -132,10 +146,11 @@ class Line(Graph):
ymin = self._secondary_min
ymax = self._secondary_max
steps = len(y_pos)
left_range = abs(y_pos[-1] - y_pos[0])
left_range = abs(y_pos[-1] - y_pos[0])
right_range = abs(ymax - ymin)
scale = right_range / (steps-1)
self._y_2nd_labels = list((self._format(ymin+i*scale), pos) for i, pos in enumerate(y_pos))
scale = right_range / (steps - 1)
self._y_2nd_labels = [(self._format(ymin + i * scale), pos)
for i, pos in enumerate(y_pos)]
min_2nd = float(self._y_2nd_labels[0][0])
self._scale = left_range / right_range

17
pygal/graph/stackedline.py

@ -43,11 +43,12 @@ class StackedLine(Line):
return new_values
def _points(self, x_pos):
accumulation = [0] * self._len
for serie in self.series:
accumulation = map(sum, zip(accumulation, serie.values))
serie.points = [
(x_pos[i], v)
for i, v in enumerate(accumulation)]
if self.interpolate:
serie.interpolated = self._interpolate(accumulation, x_pos)
for series_group in (self.series, self.secondary_series):
accumulation = [0] * self._len
for serie in series_group:
accumulation = map(sum, zip(accumulation, serie.values))
serie.points = [
(x_pos[i], v)
for i, v in enumerate(accumulation)]
if self.interpolate:
serie.interpolated = self._interpolate(accumulation, x_pos)

Loading…
Cancel
Save