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) wsreload.monkey_patch_http_server({'url': url}, callback=log)
app.logger.debug('HTTPServer monkey patched for url %s' % url) 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) 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]) bar.add('2', [4, 5, 6])
return bar.render_response() 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') @app.route('/test/stacked')
def test_stacked(): def test_stacked():
stacked = StackedBar() stacked = StackedBar()

15
pygal/graph/base.py

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

35
pygal/graph/graph.py

@ -25,9 +25,10 @@ from __future__ import division
from pygal.interpolate import interpolation from pygal.interpolate import interpolation
from pygal.graph.base import BaseGraph from pygal.graph.base import BaseGraph
from pygal.view import View, LogView, XYLogView 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 math import isnan, pi, sqrt, ceil, cos
from itertools import repeat, izip, chain, count from itertools import repeat, izip, chain
class Graph(BaseGraph): class Graph(BaseGraph):
@ -142,8 +143,7 @@ class Graph(BaseGraph):
d='M%f %f v%f' % (x, 0, self.view.height), d='M%f %f v%f' % (x, 0, self.view.height),
class_='%s%sline' % ( class_='%s%sline' % (
'major ' if major else '', 'major ' if major else '',
'guide ' if position != 0 and not last_guide 'guide ' if position != 0 and not last_guide else ''))
else ''))
y += .5 * self.label_font_size + 5 y += .5 * self.label_font_size + 5
text = self.svg.node( text = self.svg.node(
guides, 'text', guides, 'text',
@ -197,24 +197,25 @@ class Graph(BaseGraph):
self.y_label_rotation, x, y) self.y_label_rotation, x, y)
if self._y_2nd_labels: 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: for label, position in self._y_2nd_labels:
major = is_major(position) major = is_major(position)
# it is needed, to have the same structure as primary axis # it is needed, to have the same structure as primary axis
guides = self.svg.node(secondary_ax, class_='guides') guides = self.svg.node(secondary_ax, class_='guides')
x = self.view.width + 5 x = self.view.width + 5
y = self.view.y(position) y = self.view.y(position)
text = self.svg.node(guides, 'text', text = self.svg.node(
x = x, guides, 'text',
y = y + .35 * self.label_font_size, x=x,
class_ = 'major' if major else '' y=y + .35 * self.label_font_size,
class_='major' if major else ''
) )
text.text = label text.text = label
if self.y_label_rotation: if self.y_label_rotation:
text.attrib['transform'] = "rotate(%d %f %f)" % ( text.attrib['transform'] = "rotate(%d %f %f)" % (
self.y_label_rotation, x, y) self.y_label_rotation, x, y)
def _legend(self): def _legend(self):
"""Make the legend box""" """Make the legend box"""
if not self.show_legend: if not self.show_legend:
@ -242,25 +243,23 @@ class Graph(BaseGraph):
self.nodes['graph'], class_='legends', self.nodes['graph'], class_='legends',
transform='translate(%d, %d)' % (x, y)) transform='translate(%d, %d)' % (x, y))
h = max(self.legend_box_size, self.legend_font_size) h = max(self.legend_box_size, self.legend_font_size)
x_step = self.view.width / cols x_step = self.view.width / cols
if self.legend_at_bottom: if self.legend_at_bottom:
# if legends at the bottom, we dont split the windows # if legends at the bottom, we dont split the windows
counter = count()
# gen structure - (i, (j, (l, tf))) # gen structure - (i, (j, (l, tf)))
# i - global serie number - used for coloring and identification # i - global serie number - used for coloring and identification
# j - position within current legend box # j - position within current legend box
# l - label # l - label
# tf - whether it is secondary label # tf - whether it is secondary label
gen = enumerate(enumerate(chain( gen = enumerate(enumerate(chain(
izip(self._legends, repeat(False)), izip(self._legends, repeat(False)),
izip(self._secondary_legends, repeat(True))))) izip(self._secondary_legends, repeat(True)))))
secondary_legends = legends # svg node is the same secondary_legends = legends # svg node is the same
else: else:
gen = enumerate(chain( gen = enumerate(chain(
enumerate(izip(self._legends, repeat(False))), enumerate(izip(self._legends, repeat(False))),
enumerate(izip(self._secondary_legends, repeat(True))))) enumerate(izip(self._secondary_legends, repeat(True)))))
# draw secondary axis on right # draw secondary axis on right
x = self.margin.left + self.view.width + 10 x = self.margin.left + self.view.width + 10
@ -383,7 +382,7 @@ class Graph(BaseGraph):
return self._format(values[i][1]) return self._format(values[i][1])
def _points(self, x_pos): def _points(self, x_pos):
for serie in self.series + self.secondary_series: for serie in self.all_series:
serie.points = [ serie.points = [
(x_pos[i], v) (x_pos[i], v)
for i, v in enumerate(serie.values)] 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 self.interpolate else serie.points)
if val[1] is not None and (not self.logarithmic or val[1] > 0)] 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): def _fill(self, values):
"""Add extra values to fill the line""" """Add extra values to fill the line"""
zero = self.view.y(min(max(self.zero, self._box.ymin), self._box.ymax)) 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): def line(self, serie_node, serie, rescale=False):
"""Draw the line serie""" """Draw the line serie"""
if rescale and self.secondary_series: 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: else:
points = serie.points points = serie.points
view_values = map(self.view, points) view_values = map(self.view, points)
@ -75,11 +86,11 @@ class Line(Graph):
val = self._get_value(serie.points, i) val = self._get_value(serie.points, i)
self.svg.node(dots, 'circle', cx=x, cy=y, r=self.dots_size, self.svg.node(dots, 'circle', cx=x, cy=y, r=self.dots_size,
class_='dot reactive tooltip-trigger') class_='dot reactive tooltip-trigger')
self._tooltip_data(dots, self._tooltip_data(
"%s: %s" % (self.x_labels[i], val) if self.x_labels and dots, "%s: %s" % (
self.x_labels_num_limit self.x_labels[i], val)
else val, if self.x_labels and self.x_labels_num_limit
x, y) else val, x, y)
self._static_value( self._static_value(
serie_node, val, serie_node, val,
x + self.value_font_size, x + self.value_font_size,
@ -102,16 +113,19 @@ class Line(Graph):
self._points(x_pos) self._points(x_pos)
x_labels = zip(self.x_labels, x_pos) 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: if (self.x_labels_num_limit and
step = (len(x_labels)-1)/(self.x_labels_num_limit-1) len(x_labels) > self.x_labels_num_limit):
x_labels = list(x_labels[int(i*step)] for i in range(self.x_labels_num_limit)) step = (len(x_labels) - 1) / (self.x_labels_num_limit - 1)
x_labels = [x_labels[int(i * step)]
self._x_labels = self.x_labels and x_labels for i in range(self.x_labels_num_limit)]
# Y Label self._x_labels = x_labels
else:
self._x_labels = None
if self.include_x_axis: if self.include_x_axis:
# Y Label
self._box.ymin = min(self._min, 0) self._box.ymin = min(self._min, 0)
self._box.ymax = max(self._max, 0) self._box.ymax = max(self._max, 0)
else: else:
@ -132,10 +146,11 @@ class Line(Graph):
ymin = self._secondary_min ymin = self._secondary_min
ymax = self._secondary_max ymax = self._secondary_max
steps = len(y_pos) 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) right_range = abs(ymax - ymin)
scale = right_range / (steps-1) scale = right_range / (steps - 1)
self._y_2nd_labels = list((self._format(ymin+i*scale), pos) for i, pos in enumerate(y_pos)) 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]) min_2nd = float(self._y_2nd_labels[0][0])
self._scale = left_range / right_range self._scale = left_range / right_range

17
pygal/graph/stackedline.py

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

Loading…
Cancel
Save