Browse Source

Prepare data in util function with composed adapters

pull/8/head
Florian Mounier 13 years ago
parent
commit
b780de9773
  1. 8
      demo/moulinrouge/tests.py
  2. 13
      pygal/ghost.py
  3. 17
      pygal/graph/__init__.py
  4. 19
      pygal/graph/bar.py
  5. 39
      pygal/graph/base.py
  6. 24
      pygal/graph/dot.py
  7. 24
      pygal/graph/funnel.py
  8. 19
      pygal/graph/gauge.py
  9. 2
      pygal/graph/graph.py
  10. 27
      pygal/graph/line.py
  11. 12
      pygal/graph/pie.py
  12. 50
      pygal/graph/pyramid.py
  13. 4
      pygal/graph/radar.py
  14. 26
      pygal/graph/stackedbar.py
  15. 8
      pygal/graph/stackedline.py
  16. 9
      pygal/graph/xy.py
  17. 33
      pygal/serie.py
  18. 6
      pygal/test/test_graph.py
  19. 81
      pygal/util.py

8
demo/moulinrouge/tests.py

@ -45,6 +45,12 @@ def get_test_routes(app):
bar.zero = 1
return bar.render_response()
@app.route('/test/none')
def test_bar_none():
bar = Bar()
bar.add('Lol', [2, None, 12])
return bar.render_response()
@app.route('/test/gauge')
def test_gauge():
gauge = Gauge()
@ -54,7 +60,7 @@ def get_test_routes(app):
gauge.add('No', [99, -99])
return gauge.render_response()
@app.route('/test/gauge')
@app.route('/test/pyramid')
def test_pyramid():
pyramid = Pyramid()

13
pygal/ghost.py

@ -26,8 +26,8 @@ It is used to delegate rendering to real objects but keeping config in place
import io
import sys
from pygal.config import Config
from pygal.serie import Serie
from pygal.graph import CHARTS_NAMES
from pygal.util import prepare_values
REAL_CHARTS = {}
@ -54,21 +54,24 @@ class Ghost(object):
config(**kwargs)
self.config = config
self.series = []
self.raw_series = []
def add(self, title, values):
"""Add a serie to this graph"""
self.series.append(
Serie(title, values, len(self.series), self.cls.__value__))
self.raw_series.append((title, values))
def _check(self):
if self.config.logarithmic and self.config.zero == 0:
self.config.zero = 1
def make_series(self):
return prepare_values(self.raw_series, self.config, self.cls)
def make_instance(self):
self.config(**self.__dict__)
self._check()
self._last__inst = self.cls(self.config, self.series)
series = self.make_series()
self._last__inst = self.cls(self.config, series)
return self._last__inst
# Rendering

17
pygal/graph/__init__.py

@ -22,17 +22,18 @@ Graph modules
"""
CHARTS_NAMES = [
'Line',
'StackedLine',
'XY',
'Bar',
'Dot',
'Funnel',
'Gauge',
'HorizontalBar',
'StackedBar',
'HorizontalStackedBar',
'Line',
'Pie',
'Pyramid',
'Radar',
'StackedBar',
'StackedLine',
'XY'
'Funnel',
'Pyramid',
'VerticalPyramid',
'Dot',
'Gauge'
]

19
pygal/graph/bar.py

@ -33,7 +33,7 @@ class Bar(Graph):
self._x_ranges = None
super(Bar, self).__init__(*args, **kwargs)
def bar(self, serie_node, serie, values, stack_vals=None):
def bar(self, serie_node, serie, values, index, stack_vals=None):
"""Draw a bar graph for a serie"""
# value here is a list of tuple range of tuple coord
@ -60,7 +60,7 @@ class Bar(Graph):
# (x,y) (X,Y)
#
# x and y are left range coords and X, Y right ones
metadata = serie.metadata[i]
metadata = serie.metadata.get(i)
val = self._format(values[i][1][1])
if self.horizontal:
x, y, X, Y = Y, X, y, x
@ -71,11 +71,11 @@ class Bar(Graph):
height = self.view.x(self.zero) - y
else:
height = self.view.y(self.zero) - y
if stack_vals == None:
bar_width = inner_width / len(self.series)
if stack_vals is None:
bar_width = inner_width / self._order
bar_padding = .1 * bar_width
bar_inner_width = bar_width - 2 * bar_padding
offset = serie.index * bar_width + bar_padding
offset = index * bar_width + bar_padding
shift = 0
else:
offset = 0
@ -117,7 +117,8 @@ class Bar(Graph):
def _compute(self):
self._box.ymin = min(self._min, self.zero)
self._box.ymax = max(self._max, self.zero)
x_pos = [x / self._len for x in range(self._len + 1)
x_pos = [
x / self._len for x in range(self._len + 1)
] if self._len > 1 else [0, 1] # Center if only one value
y_pos = compute_scale(
self._box.ymin, self._box.ymax, self.logarithmic, self.order_min
@ -129,8 +130,8 @@ class Bar(Graph):
self._y_labels = zip(map(self._format, y_pos), y_pos)
def _plot(self):
for serie in self.series:
serie_node = self._serie(serie.index)
for index, serie in enumerate(self.series):
serie_node = self._serie(index)
self.bar(serie_node, serie, [
tuple((self._x_ranges[i][j], v) for j in range(2))
for i, v in enumerate(serie.values)])
for i, v in enumerate(serie.values)], index)

39
pygal/graph/base.py

@ -23,7 +23,6 @@ Base for pygal charts
from __future__ import division
from pygal.view import Margin, Box
from pygal.serie import Value
from pygal.util import (
get_text_box, get_texts_box, cut, rad, humanize, truncate)
from pygal.svg import Svg
@ -34,7 +33,7 @@ from math import sin, cos, sqrt
class BaseGraph(object):
"""Graphs commons"""
__value__ = Value
_adapters = []
def __init__(self, config, series):
"""Init the graph"""
@ -76,7 +75,7 @@ class BaseGraph(object):
if self.legend_at_bottom:
h_max = max(h, self.legend_box_size)
self.margin.bottom += 10 + h_max * round(
sqrt(len(self.series)) - 1) * 1.5 + h_max
sqrt(self._order) - 1) * 1.5 + h_max
else:
self.margin.right += 10 + w + self.legend_box_size
@ -130,9 +129,18 @@ class BaseGraph(object):
"""Getter for the maximum series value"""
return (self.range and self.range[1]) or max(self._values)
@cached_property
def _cumul_max(self):
"""Getter for the maximum sum of series value"""
return max(map(sum, cut(self.series, 'safe_values')))
@cached_property
def _order(self):
"""Getter for the maximum series value"""
return len(self.series)
def _draw(self):
"""Draw all the things"""
self._prepare_data()
self._compute()
self._compute_margin()
self._decorate()
@ -140,34 +148,15 @@ class BaseGraph(object):
def _has_data(self):
"""Check if there is any data"""
if len(self.series) == 0:
if self._order == 0:
return False
if sum(map(len, map(lambda s: s.values, self.series))) == 0:
return False
return True
def _prepare_data(self):
"""Remove aberrant values"""
if self.logarithmic:
for serie in self.series:
for metadata in serie.metadata:
if metadata.value <= 0:
metadata.value = None
def _uniformize_data(self):
"""Make all series to max len"""
for serie in self.series:
if len(serie.values) < self._len:
diff = self._len - len(serie.values)
serie.metadata += diff * [self.__value__(0)]
for metadata in serie.metadata:
if metadata.value is None:
metadata.value = 0
def _render(self):
"""Make the graph internally"""
if self._has_data():
if self.series and self._has_data():
self._draw()
self.svg.pre_render(False)
else:

24
pygal/graph/dot.py

@ -22,15 +22,15 @@ Dot chart
"""
from __future__ import division
from pygal.util import decorate, cut
from pygal.serie import PositiveValue
from pygal.util import decorate, cut, safe_enumerate
from pygal.adapters import positive
from pygal.graph.graph import Graph
class Dot(Graph):
"""Dot graph"""
__value__ = PositiveValue
_adapters = [positive]
def _axes(self):
"""Draw axes"""
@ -40,15 +40,15 @@ class Dot(Graph):
def dot(self, serie_node, serie, r_max):
"""Draw a dot line"""
view_values = map(self.view, serie.points)
for i, (x, y) in enumerate(view_values):
value = serie.values[i]
for i, value in safe_enumerate(serie.values):
x, y = view_values[i]
size = r_max * value
value = self._format(value)
metadata = serie.metadata[i]
metadata = serie.metadata.get(i)
dots = decorate(
self.svg,
self.svg.node(serie_node['plot'], class_="dots"),
metadata)
self.svg,
self.svg.node(serie_node['plot'], class_="dots"),
metadata)
self.svg.node(dots, 'circle', cx=x, cy=y, r=size,
class_='dot reactive tooltip-trigger')
@ -57,7 +57,7 @@ class Dot(Graph):
def _compute(self):
x_len = self._len
y_len = len(self.series)
y_len = self._order
self._box.xmax = x_len
self._box.ymax = y_len
@ -76,5 +76,5 @@ class Dot(Graph):
r_max = min(
self.view.x(1) - self.view.x(0),
self.view.y(0) - self.view.y(1)) / (2 * (self._max or 1) * 1.05)
for serie in self.series:
self.dot(self._serie(serie.index), serie, r_max)
for index, serie in enumerate(self.series):
self.dot(self._serie(index), serie, r_max)

24
pygal/graph/funnel.py

@ -23,24 +23,24 @@ Funnel chart
from __future__ import division
from pygal.util import decorate, cut, compute_scale
from pygal.serie import PositiveValue
from pygal.adapters import positive, none_to_zero
from pygal.graph.graph import Graph
class Funnel(Graph):
"""Funnel graph"""
__value__ = PositiveValue
_adapters = [positive, none_to_zero]
def _format(self, value):
return super(Funnel, self)._format(abs(value))
def funnel(self, serie_node, serie):
def funnel(self, serie_node, serie, index):
"""Draw a dot line"""
fmt = lambda x: '%f %f' % x
for i, poly in enumerate(serie.points):
metadata = serie.metadata[i]
metadata = serie.metadata.get(i)
value = self._format(serie.values[i])
funnels = decorate(
@ -54,19 +54,19 @@ class Funnel(Graph):
class_='funnel reactive tooltip-trigger')
x, y = self.view((
self._x_labels[serie.index][1], # Poly center from label
self._x_labels[index][1], # Poly center from label
sum([point[1] for point in poly]) / len(poly)))
self._tooltip_data(funnels, value, x, y, classes='centered')
self._static_value(serie_node, value, x, y)
def _compute(self):
xlen = len(self.series)
x_pos = [(x + 1) / xlen for x in range(xlen)
] if xlen != 1 else [.5] # Center if only one value
x_pos = [
(x + 1) / self._order for x in range(self._order)
] if self._order != 1 else [.5] # Center if only one value
previous = [[0, 0] for i in range(self._len)]
for i, serie in enumerate(self.series):
y_height = - sum(serie.values) / 2
y_height = - sum(serie.safe_values) / 2
all_x_pos = [0] + x_pos
serie.points = []
for j, value in enumerate(serie.values):
@ -88,10 +88,10 @@ class Funnel(Graph):
) if not self.y_labels else map(float, self.y_labels)
self._x_labels = zip(cut(self.series, 'title'),
map(lambda x: x - 1 / (2 * xlen), x_pos))
map(lambda x: x - 1 / (2 * self._order), x_pos))
self._y_labels = zip(map(self._format, y_pos), y_pos)
def _plot(self):
for serie in self.series:
for index, serie in enumerate(self.series):
self.funnel(
self._serie(serie.index), serie)
self._serie(index), serie, index)

19
pygal/graph/gauge.py

@ -55,18 +55,19 @@ class Gauge(Graph):
theta = self.arc_pos(value)
fmt = lambda x: '%f %f' % x
value = self._format(serie.values[i])
metadata = serie.metadata[i]
metadata = serie.metadata.get(i)
gauges = decorate(
self.svg,
self.svg.node(serie_node['plot'], class_="dots"),
metadata)
self.svg.node(gauges, 'polygon', points=' '.join([
fmt(self.view((0, 0))),
fmt(self.view((.75, theta + thickness))),
fmt(self.view((.8, theta))),
fmt(self.view((.75, theta - thickness)))]),
class_='line reactive tooltip-trigger')
self.svg.node(
gauges, 'polygon', points=' '.join([
fmt(self.view((0, 0))),
fmt(self.view((.75, theta + thickness))),
fmt(self.view((.8, theta))),
fmt(self.view((.75, theta - thickness)))]),
class_='line reactive tooltip-trigger')
x, y = self.view((.75, theta))
self._tooltip_data(gauges, value, x, y)
@ -119,6 +120,6 @@ class Gauge(Graph):
self._x_labels = zip(map(self._format, x_pos), x_pos)
def _plot(self):
for serie in self.series:
for index, serie in enumerate(self.series):
self.needle(
self._serie(serie.index), serie)
self._serie(index), serie)

2
pygal/graph/graph.py

@ -187,7 +187,7 @@ class Graph(BaseGraph):
x = self.margin.left + 10
y = (self.margin.top + self.view.height +
self._x_labels_height + 10)
cols = ceil(sqrt(len(self.series)))
cols = ceil(sqrt(self._order))
if not truncation:
available_space = self.view.width / cols - (

27
pygal/graph/line.py

@ -39,15 +39,17 @@ class Line(Graph):
@cached_property
def _values(self):
if self.interpolate:
return [val[1]
for serie in self.series
for val in serie.interpolated
if val[1] != None and (not self.logarithmic or val[1] > 0)]
return [
val[1]
for serie in self.series
for val in serie.interpolated
if val[1] is not None and (not self.logarithmic or val[1] > 0)]
else:
return [val[1]
for serie in self.series
for val in serie.points
if val[1] != None and (not self.logarithmic or val[1] > 0)]
return [
val[1]
for serie in self.series
for val in 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"""
@ -64,7 +66,7 @@ class Line(Graph):
if None in (x, y):
continue
metadata = serie.metadata[i]
metadata = serie.metadata.get(i)
classes = []
if x > self.view.width / 2:
classes.append('left')
@ -95,7 +97,8 @@ class Line(Graph):
class_='line reactive' + (' nofill' if not self.fill else ''))
def _compute(self):
x_pos = [x / (self._len - 1) for x in range(self._len)
x_pos = [
x / (self._len - 1) for x in range(self._len)
] if self._len != 1 else [.5] # Center if only one value
for serie in self.series:
@ -121,5 +124,5 @@ class Line(Graph):
self._y_labels = zip(map(self._format, y_pos), y_pos)
def _plot(self):
for serie in self.series:
self.line(self._serie(serie.index), serie)
for index, serie in enumerate(self.series):
self.line(self._serie(index), serie)

12
pygal/graph/pie.py

@ -22,20 +22,20 @@ Pie chart
"""
from __future__ import division
from pygal.serie import PositiveValue
from pygal.util import decorate
from pygal.graph.graph import Graph
from pygal.adapters import positive, none_to_zero
from math import pi
class Pie(Graph):
"""Pie graph"""
__value__ = PositiveValue
_adapters = [positive, none_to_zero]
def slice(self, serie_node, start_angle, serie, total):
"""Make a serie slice"""
dual = self._len > 1 and not len(self.series) == 1
dual = self._len > 1 and not self._order == 1
slices = self.svg.node(serie_node['plot'], class_="slices")
serie_angle = 0
@ -49,7 +49,7 @@ class Pie(Graph):
angle = 2 * pi * perc
serie_angle += angle
val = '{0:.2%}'.format(perc)
metadata = serie.metadata[i]
metadata = serie.metadata.get(i)
slice_ = decorate(
self.svg,
self.svg.node(slices, class_="slice"),
@ -81,7 +81,7 @@ class Pie(Graph):
if total == 0:
return
current_angle = 0
for serie in self.series:
for index, serie in enumerate(self.series):
angle = self.slice(
self._serie(serie.index), current_angle, serie, total)
self._serie(index), current_angle, serie, total)
current_angle += angle

50
pygal/graph/pyramid.py

@ -21,56 +21,8 @@ Pyramid chart
"""
from __future__ import division
from pygal.util import decorate, cut, compute_scale
from pygal.serie import PositiveValue
from pygal.graph.bar import Bar
from pygal.graph.horizontal import HorizontalGraph
class VerticalPyramid(Bar):
"""Pyramid graph"""
__value__ = PositiveValue
def _format(self, value):
return super(VerticalPyramid, self)._format(abs(value))
def _compute(self):
positive_vals = zip(*[serie.values for serie in self.series
if serie.index % 2])
negative_vals = zip(*[serie.values for serie in self.series
if not serie.index % 2])
positive_sum = map(sum, positive_vals) or [0]
negative_sum = map(sum, negative_vals)
self._box.ymax = max(max(positive_sum), max(negative_sum))
self._box.ymin = - self._box.ymax
x_pos = [x / self._len
for x in range(self._len + 1)
] if self._len > 1 else [0, 1] # Center if only one value
y_pos = compute_scale(
self._box.ymin, self._box.ymax, self.logarithmic, self.order_min
) if not self.y_labels else map(float, self.y_labels)
self._x_ranges = zip(x_pos, x_pos[1:])
self._x_labels = self.x_labels and zip(self.x_labels, [
sum(x_range) / 2 for x_range in self._x_ranges])
self._y_labels = zip(map(self._format, y_pos), y_pos)
def _plot(self):
stack_vals = [[0, 0] for i in range(self._len)]
for serie in self.series:
serie_node = self._serie(serie.index)
stack_vals = self.bar(
serie_node, serie, [
tuple(
(self._x_ranges[i][j],
v * (-1 if serie.index % 2 else 1)) for j in range(2))
for i, v in enumerate(serie.values)],
stack_vals)
from pygal.graph.verticalpyramid import VerticalPyramid
class Pyramid(HorizontalGraph, VerticalPyramid):

4
pygal/graph/radar.py

@ -23,7 +23,7 @@ Radar chart
from __future__ import division
from pygal.graph.line import Line
from pygal.serie import PositiveValue
from pygal.adapters import positive
from pygal.view import PolarView
from pygal.util import deg, cached_property, compute_scale
from math import cos, pi
@ -32,7 +32,7 @@ from math import cos, pi
class Radar(Line):
"""Kiviat graph"""
__value__ = PositiveValue
_adapters = [positive]
def __init__(self, *args, **kwargs):
self.x_pos = None

26
pygal/graph/stackedbar.py

@ -31,19 +31,19 @@ class StackedBar(Bar):
def _compute(self):
transposed = zip(*[serie.values for serie in self.series])
positive_vals = [sum([val
if val != None and val > 0 else 0 for val in vals])
for vals in transposed]
negative_vals = [sum([val
if val != None and val < 0 else 0 for val in vals])
for vals in transposed]
positive_vals = [
sum([val if val is not None and val > 0 else 0 for val in vals])
for vals in transposed]
negative_vals = [
sum([val if val is not None and val < 0 else 0 for val in vals])
for vals in transposed]
self._box.ymin, self._box.ymax = (
min(min(negative_vals), 0), max(max(positive_vals), 0))
x_pos = [x / self._len
for x in range(self._len + 1)
] if self._len > 1 else [0, 1] # Center if only one value
x_pos = [
x / self._len for x in range(self._len + 1)
] if self._len > 1 else [0, 1] # Center if only one value
y_pos = compute_scale(
self._box.ymin, self._box.ymax, self.logarithmic, self.order_min
) if not self.y_labels else map(float, self.y_labels)
@ -55,10 +55,10 @@ class StackedBar(Bar):
def _plot(self):
stack_vals = [[0, 0] for i in range(self._len)]
for serie in self.series:
serie_node = self._serie(serie.index)
for index, serie in enumerate(self.series):
serie_node = self._serie(index)
stack_vals = self.bar(
serie_node, serie, [
tuple((self._x_ranges[i][j], v) for j in range(2))
for i, v in enumerate(serie.values)],
tuple((self._x_ranges[i][j], v) for j in range(2))
for i, v in enumerate(serie.values)], index,
stack_vals)

8
pygal/graph/stackedline.py

@ -22,12 +22,14 @@ Stacked Line chart
"""
from pygal.graph.line import Line
from itertools import izip_longest
from pygal.adapters import none_to_zero
class StackedLine(Line):
"""Stacked Line graph"""
_adapters = [none_to_zero]
def __init__(self, *args, **kwargs):
self._previous_line = None
super(StackedLine, self).__init__(*args, **kwargs)
@ -41,8 +43,8 @@ class StackedLine(Line):
return new_values
def _compute(self):
self._uniformize_data()
x_pos = [x / float(self._len - 1) for x in range(self._len)
x_pos = [
x / float(self._len - 1) for x in range(self._len)
] if self._len != 1 else [.5] # Center if only one value
accumulation = [0] * self._len
for serie in self.series:

9
pygal/graph/xy.py

@ -33,19 +33,14 @@ class XY(Line):
return 'x=%s, y=%s' % tuple(map(self._format, values[i]))
def _compute(self):
for serie in self.series:
for metadata in serie.metadata:
if not hasattr(metadata.value, '__iter__'):
metadata.value = (metadata.value, self.zero)
xvals = [val[0]
for serie in self.series
for val in serie.values
if val[0] != None]
if val[0] is not None]
yvals = [val[1]
for serie in self.series
for val in serie.values
if val[1] != None]
if val[1] is not None]
xmin = min(xvals)
xmax = max(xvals)
rng = (xmax - xmin)

33
pygal/serie.py

@ -20,38 +20,19 @@
Little helpers for series
"""
from pygal.util import cut
from pygal.util import cached_property
class Serie(object):
"""Serie containing title, values and the graph serie index"""
def __init__(self, title, values, index, value_class):
def __init__(self, title, values, metadata=None):
self.title = title
if isinstance(values, dict) or not hasattr(values, '__iter__'):
values = [values]
self.metadata = map(value_class, values)
self.index = index
self.values = values
self.metadata = metadata or {}
@property
def values(self):
return cut(self.metadata, 'value')
class Value(object):
"""Value container"""
def __init__(self, value):
if not isinstance(value, dict):
value = {'value': value}
self.__dict__.update(value)
class PositiveValue(Value):
"""Positive or zero value container"""
def __init__(self, value):
super(PositiveValue, self).__init__(value)
self.value = max(self.value or 0, 0)
@cached_property
def safe_values(self):
return filter(lambda x: x is not None, self.values)
class Label(object):

6
pygal/test/test_graph.py

@ -62,12 +62,6 @@ def test_render_to_png(Chart, datas):
os.remove(file_name)
def test_simple_value(Chart):
chart = Chart()
chart.add('uni-value', 1)
assert chart.render()
def test_metadata(Chart):
chart = Chart()
v = range(7)

81
pygal/util.py

@ -20,12 +20,12 @@
Various utils
"""
from __future__ import division
import re
from decimal import Decimal
from math import floor, pi, log, log10, ceil
from itertools import cycle
import re
from pygal.adapters import not_zero, positive
ORDERS = u"yzafpnµm kMGTPEZY"
@ -65,7 +65,8 @@ def round_to_int(number, precision):
def round_to_float(number, precision):
"""Round a float to a precision"""
rounded = Decimal(str(floor((number + precision / 2) // precision))
rounded = Decimal(
str(floor((number + precision / 2) // precision))
) * Decimal(str(precision))
return float(rounded)
@ -146,8 +147,9 @@ def compute_logarithmic_scale(min_, max_):
return positions
def compute_scale(min_, max_, logarithmic=False, order_min=None,
min_scale=4, max_scale=20):
def compute_scale(
min_, max_, logarithmic=False, order_min=None,
min_scale=4, max_scale=20):
"""Compute an optimal scale between min and max"""
if min_ == 0 and max_ == 0:
return [0]
@ -204,18 +206,18 @@ def get_texts_box(texts, fs):
def decorate(svg, node, metadata):
"""Add metedata next to a node"""
if hasattr(metadata, 'xlink'):
xlink = metadata.xlink
if not metadata:
return node
xlink = metadata.get('xlink')
if xlink:
if not isinstance(xlink, dict):
xlink = {'href': xlink, 'target': '_blank'}
node = svg.node(node, 'a', **xlink)
for key in dir(metadata):
if key not in ('value') and not key.startswith('_'):
value = getattr(metadata, key)
if key == 'xlink' and isinstance(value, dict):
value = value.get('href', value)
if value:
svg.node(node, 'desc', class_=key).text = str(value)
for key, value in metadata.items():
if key == 'xlink' and isinstance(value, dict):
value = value.get('href', value)
if value:
svg.node(node, 'desc', class_=key).text = str(value)
return node
@ -278,3 +280,54 @@ def minify_css(css):
css = re.sub(r';}', r'}', css)
css = re.sub(r'}//-->', r'}\n//-->', css)
return css.strip()
def compose(f, g):
"""Chain functions"""
return lambda *args, **kwargs: f(g(*args, **kwargs))
def safe_enumerate(iterable):
for i, v in enumerate(iterable):
if v is not None:
yield i, v
from pygal.serie import Serie
def prepare_values(raw, config, cls):
"""Prepare the values to start with sane values"""
if not raw:
return
adapters = list(cls._adapters) or [lambda x:x]
if config.logarithmic:
for fun in not_zero, positive:
if fun in adapters:
adapters.remove(fun)
adapters = [not_zero, positive] + adapters
adapter = reduce(compose, adapters)
series = []
width = max([len(values) for _, values in raw])
for title, raw_values in raw:
metadata = {}
values = []
for index, raw_value in enumerate(
raw_values + (
(width - len(raw_values)) * [None] # aligning values
if len(raw_values) < width else [])):
if isinstance(raw_value, dict):
value = dict(raw_value).pop('value')
metadata[index] = raw_value
else:
value = raw_value
if cls.__name__ == 'XY':
if not hasattr(value, '__iter__'):
value = (value, config.zero)
value = map(adapter, value)
else:
value = adapter(value)
values.append(value)
series.append(Serie(title, values, metadata))
return series

Loading…
Cancel
Save