Browse Source

Support different series array sizes

pull/8/head
Florian Mounier 13 years ago
parent
commit
b004099770
  1. 40
      demo/simple_test.py
  2. 2
      pygal/__init__.py
  3. 4
      pygal/graph/bar.py
  4. 21
      pygal/graph/base.py
  5. 21
      pygal/graph/line.py
  6. 1
      pygal/graph/radar.py
  7. 6
      pygal/graph/stackedbar.py
  8. 13
      pygal/graph/stackedline.py
  9. 12
      pygal/graph/xy.py
  10. 11
      pygal/interpolate.py
  11. 4
      pygal/style.py
  12. 9
      pygal/svg.py
  13. 8
      pygal/view.py
  14. 3
      setup.py

40
demo/simple_test.py

@ -27,16 +27,19 @@ bar = Bar()
rng = [-6, -19, 0, -1, 2]
bar.add('test1', rng)
bar.add('test2', map(abs, rng))
bar.add('inc', [None, 1, None, 2])
bar.x_labels = map(str, rng)
bar.title = "Bar test"
bar.fill = True
bar.render_to_file('out-bar.svg')
hbar = HorizontalBar()
rng = [18, 9, 7, 3, 1, 0, -5]
rng = [18, 9, 7, 3, 1, None, -5]
hbar.add('test1', rng)
rng2 = [16, 14, 10, 9, 7, 3, -1]
hbar.add('test2', rng2)
rng3 = [123, None, None, 4, None, 6]
hbar.add('test3', rng3)
hbar.x_labels = map(
lambda x: '%s / %s' % x, zip(map(str, rng), map(str, rng2)))
hbar.title = "Horizontal Bar test"
@ -58,6 +61,7 @@ stackedbar = StackedBar(config)
stackedbar.add('@@@@@@@', rng)
stackedbar.add('++++++', rng2)
stackedbar.add('--->', rng3)
stackedbar.add('None', [None, 42, 42])
stackedbar.render_to_file('out-stackedbar.svg')
config.title = "Horizontal Stacked Bar test"
@ -68,32 +72,39 @@ hstackedbar.add('--->', rng3)
hstackedbar.render_to_file('out-horizontalstackedbar.svg')
line = Line(Config(y_scale=.0005, style=NeonStyle,
zero=1, interpolate='cubic',
zero=1,
human_readable=True, logarithmic=True))
# rng = range(-30, 31, 1)
# line.add('test1', [1000 ** cos(x / 10.) for x in rng])
# line.add('test2', [1000 ** sin(x / 10.) for x in rng])
# line.add('test3', [1000 ** (cos(x / 10.) - sin(x / 10.)) for x in rng])
rng = range(1, 2000, 50)
line.add('x', rng)
line.add('10^x', map(lambda x: 10 ** (x / 333.), rng))
line.add('10^10^x', map(lambda x: ((x / 333.) ** (x / 333.)), rng))
rng = range(1, 2000, 25)
# line.add('x', rng)
# line.add('x', rng)
# line.add('10^10^x', map(lambda x: ((x / 333.) ** (x / 333.)), rng))
# line.add('None', [None, None, None, 12, 31, 11, None, None, 12, 14])
line.add('2', [None, None, 2, 4, 8, None, 14, 10, None])
line.add('1', [1, 5, 3, 4, 6, 12, 13, 7, 2])
line.x_labels = map(str, rng)
line.title = "Line test"
line.interpolate = "cubic"
line.interpolation_precision = 200
line.render_to_file('out-line.svg')
stackedline = StackedLine(Config(y_scale=.0005, fill=True))
stackedline.add('test1', [1, 3, 2, 18, 2, 13, 8])
stackedline.add('test2', [4, 1, 10, 1, 3, 12, 3])
stackedline.add('test3', [9, 3, 2, 10, 8, 2, 3])
stackedline.add('test1', [1, 3, 2, None, 2, 13, 2, 5, 8])
stackedline.add('test2', [4, 1, 1, 3, 12, 3])
stackedline.add('test3', [9, 3, 2, 10, 8, 2])
stackedline.x_labels = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
stackedline.title = "Stackedline test"
stackedline.interpolate = "cubic"
stackedline.render_to_file('out-stackedline.svg')
xy = XY(Config(x_scale=1, fill=True, style=NeonStyle, interpolate='cubic'))
xy.add('test1', [(1981, 1), (2004, 2), (2003, 10), (2012, 8), (1999, -4)])
xy.add('test2', [(1988, -1), (1986, 12), (2007, 7), (2010, 4), (1999, 2)])
xy.add('test1', [(1981, 1), (1999, -4), (2001, 2), (2003, 10), (2012, 8)])
xy.add('test2', [(1988, -1), (1986, 12), (2007, 7), (2010, 4)])
xy.add('test2', [(None, None), (None, 12), (2007, None), (2002.3, 12)])
# xy.add('test2', [(1980, 0), (1985, 2), (1995, -2), (2005, 4), (2020, -4)])
# (2005, 6), (2010, -6), (2015, 3), (2020, -3), (2025, 0)])
xy.title = "XY test"
@ -101,10 +112,11 @@ xy.render_to_file('out-xy.svg')
pie = Pie(Config(style=NeonStyle))
pie.add('test', [11, 8, 21])
pie.add('test2', [29, 21, 9])
pie.add('test2', [29, None, 9])
pie.add('test3', [24, 10, 32])
pie.add('test4', [20, 18, 9])
pie.add('test5', [17, 5, 10])
pie.add('test6', [None, None, 10])
pie.title = "Pie test"
pie.render_to_file('out-pie.svg')
@ -115,8 +127,8 @@ config.x_labels = (
'black', 'red', 'blue', 'yellow', 'orange', 'green', 'white')
config.interpolate = 'nearest'
radar = Radar(config)
radar.add('test', [1, 4, 1, 5, 7, 2, 5])
radar.add('test2', [10, 2, 7, 5, 1, 9, 4])
radar.add('test', [1, 4, 1, 5, None, 2, 5])
radar.add('test2', [10, 2, 0, 5, 1, 9, 4])
radar.title = "Radar test"
radar.render_to_file('out-radar.svg')

2
pygal/__init__.py

@ -16,7 +16,7 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with pygal. If not, see <http://www.gnu.org/licenses/>.
__version__ = '0.9.13-dev'
__version__ = '0.9.15'
from collections import namedtuple
from pygal.graph.bar import Bar

4
pygal/graph/bar.py

@ -31,11 +31,13 @@ class Bar(Graph):
"""Project range"""
t, T = rng
fun = swap if self._horizontal else ident
return (self.view(fun(t)), self.view(fun(T)))
return self.view(fun(t)), self.view(fun(T))
bars = self.svg.node(serie_node['plot'], class_="bars")
view_values = map(view, values)
for i, ((x, y), (X, Y)) in enumerate(view_values):
if None in (x, y):
continue
# x and y are left range coords and X, Y right ones
val = self.format(values[i][1][1])
if self._horizontal:

21
pygal/graph/base.py

@ -49,12 +49,13 @@ class BaseGraph(object):
step = float(10 ** order)
while (max_ - min_) / step > max_scale:
step *= 2.
positions = set()
positions = []
position = round_to_scale(min_, step)
while position < (max_ + step):
rounded = round_to_scale(position, scale)
if min_ <= rounded <= max_:
positions.add(rounded)
if rounded not in positions:
positions.append(rounded)
position += step
if len(positions) < 2:
return [min_, max_]
@ -104,11 +105,14 @@ class BaseGraph(object):
@cached_property
def _values(self):
return [val for serie in self.series for val in serie.values]
return [val
for serie in self.series
for val in serie.values
if val != None]
@cached_property
def _len(self):
return len(self.series[0].values)
return max([len(serie.values) for serie in self.series])
def _draw(self):
self._compute()
@ -127,18 +131,9 @@ class BaseGraph(object):
serie.values = [serie.values]
if sum(map(len, map(lambda s: s.values, self.series))) == 0:
return self.svg.render(no_data=True)
self.validate()
self._draw()
return self.svg.render()
def validate(self):
if self.x_labels:
assert len(self.series[0].values) == len(self.x_labels), (
'Labels and series must have the same length')
for serie in self.series:
assert len(self.series[0].values) == len(serie.values), (
'All series must have the same length')
def _in_browser(self):
from lxml.html import open_in_browser
self._draw()

21
pygal/graph/line.py

@ -19,6 +19,7 @@
from pygal.graph.graph import Graph
from pygal.util import cached_property
from pygal.interpolate import interpolation
from math import isnan
class Line(Graph):
@ -30,11 +31,15 @@ class Line(Graph):
@cached_property
def _values(self):
if self.interpolate:
return [val[1] for serie in self.series
for val in serie.interpolated]
return [val[1]
for serie in self.series
for val in serie.interpolated
if val[1] != None]
else:
return [val[1] for serie in self.series
for val in serie.points]
return [val[1]
for serie in self.series
for val in serie.points
if val[1] != None]
def _fill(self, values):
zero = self.view.y(min(max(self.zero, self._box.ymin), self._box.ymax))
@ -46,6 +51,8 @@ class Line(Graph):
view_values = map(self.view, serie.points)
if self.show_dots:
for i, (x, y) in enumerate(view_values):
if None in (x, y):
continue
dots = self.svg.node(serie_node['overlay'], class_="dots")
val = self._get_value(serie.points, i)
self.svg.node(dots, 'circle', cx=x, cy=y, r=2.5,
@ -81,8 +88,10 @@ class Line(Graph):
interpolate = interpolation(
self._x_pos, serie.values, kind=self.interpolate)
p = float(self.interpolation_precision)
serie.interpolated = [(x / p, float(interpolate(x / p)))
for x in range(int(p + 1))]
serie.interpolated = [
(x / p, float(interpolate(x / p)))
for x in range(int(p + 1))
if not isnan(float(interpolate(x / p)))]
if self.include_x_axis:
self._box.ymin = min(min(self._values), 0)
self._box.ymax = max(max(self._values), 0)

1
pygal/graph/radar.py

@ -97,6 +97,7 @@ class Radar(Line):
for serie in self.series:
vals = list(serie.values)
vals.append(vals[0])
vals = [val if val != None else 0 for val in vals]
serie.points = [
(v, self._x_pos[i])
for i, v in enumerate(vals)]

6
pygal/graph/stackedbar.py

@ -24,9 +24,11 @@ class StackedBar(Bar):
def _compute(self):
transposed = zip(*[serie.values for serie in self.series])
positive_vals = [sum([val if val > 0 else 0 for val in vals])
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 < 0 else 0 for val in vals])
negative_vals = [sum([val
if val != None and val < 0 else 0 for val in vals])
for vals in transposed]
self._box.ymin, self._box.ymax = (

13
pygal/graph/stackedline.py

@ -18,6 +18,8 @@
# along with pygal. If not, see <http://www.gnu.org/licenses/>.
from pygal.graph.line import Line
from pygal.interpolate import interpolation
from math import isnan
from itertools import izip_longest
class StackedLine(Line):
@ -36,7 +38,10 @@ class StackedLine(Line):
] if self._len != 1 else [.5] # Center if only one value
accumulation = [0] * self._len
for serie in self.series:
accumulation = map(sum, zip(accumulation, serie.values))
accumulation = map(sum, izip_longest(
accumulation, [val
if val != None else 0
for val in serie.values], fillvalue=0))
serie.points = [
(self._x_pos[i], v)
for i, v in enumerate(accumulation)]
@ -44,6 +49,8 @@ class StackedLine(Line):
interpolate = interpolation(
self._x_pos, accumulation, kind=self.interpolate)
p = float(self.interpolation_precision)
serie.interpolated = [(x / p, float(interpolate(x / p)))
for x in range(int(p + 1))]
serie.interpolated = [
(x / p, float(interpolate(x / p)))
for x in range(int(p + 1))
if not isnan(float(interpolate(x / p)))]
return super(StackedLine, self)._compute()

12
pygal/graph/xy.py

@ -27,12 +27,18 @@ class XY(Line):
return str(values[i])
def _compute(self):
xvals = [val[0] for serie in self.series for val in serie.values]
yvals = [val[1] for serie in self.series for val in serie.values]
xvals = [val[0]
for serie in self.series
for val in serie.values
if val[0] != None]
yvals = [val[1]
for serie in self.series
for val in serie.values
if val[1] != None]
xmin = min(xvals)
for serie in self.series:
serie.points = sorted(serie.values, key=lambda x: x[0])
serie.points = serie.values
if self.interpolate:
vals = zip(*serie.points)
interpolate = interpolation(

11
pygal/interpolate.py

@ -27,13 +27,18 @@ except:
def interpolation(x, y, kind):
assert scipy != None, 'You must have scipy installed to use interpolation'
order = None
if len(y) < len(x):
x = x[:len(y)]
x, y = zip(*filter(lambda t: None not in t, zip(x, y)))
if len(x) < 2:
return ident
if isinstance(kind, int):
order = kind
elif kind in ['zero', 'slinear', 'quadratic', 'cubic']:
elif kind in ['zero', 'slinear', 'quadratic', 'cubic', 'univariate']:
order = {'nearest': 0, 'zero': 0, 'slinear': 1,
'quadratic': 2, 'cubic': 3}[kind]
'quadratic': 2, 'cubic': 3, 'univariate': 3}[kind]
if order and len(x) <= order:
kind = len(x) - 1
if kind == 'krogh':
@ -42,4 +47,4 @@ def interpolation(x, y, kind):
return interpolate.BarycentricInterpolator(x, y)
elif kind == 'univariate':
return interpolate.InterpolatedUnivariateSpline(x, y)
return interpolate.interp1d(x, y, kind=kind)
return interpolate.interp1d(x, y, kind=kind, bounds_error=False)

4
pygal/style.py

@ -85,7 +85,7 @@ DarkSolarizedStyle = Style(
background='#073642',
plot_background='#002b36',
foreground='#839496',
foreground_light='#93a1a1',
foreground_light='#fdf6e3',
foreground_dark='#657b83',
opacity='.66',
opacity_hover='.9',
@ -96,7 +96,7 @@ LightSolarizedStyle = Style(
background='#fdf6e3',
plot_background='#eee8d5',
foreground='#657b83',
foreground_light='#586e75',
foreground_light='#073642',
foreground_dark='#073642',
opacity='.6',
opacity_hover='.9',

9
pygal/svg.py

@ -86,8 +86,13 @@ class Svg(object):
def line(self, node, coords, close=False, **kwargs):
root = 'M%s L%s Z' if close else 'M%s L%s'
origin = self.format(coords[0])
line = ' '.join(map(self.format, coords[1:]))
origin_index = 0
while None in coords[origin_index]:
origin_index += 1
origin = self.format(coords[origin_index])
line = ' '.join([self.format(c)
for c in coords[origin_index + 1:]
if None not in c])
self.node(node, 'path',
d=root % (origin, line), **kwargs)

8
pygal/view.py

@ -76,9 +76,13 @@ class View(object):
self.box.fix()
def x(self, x):
if x == None:
return None
return self.width * (x - self.box.xmin) / float(self.box.width)
def y(self, y):
if y == None:
return None
return (self.height - self.height *
(y - self.box.ymin) / float(self.box.height))
@ -89,6 +93,8 @@ class View(object):
class PolarView(View):
def __call__(self, rhotheta):
if None in rhotheta:
return None, None
rho, theta = rhotheta
rho = max(rho, 0)
return super(PolarView, self).__call__(
@ -107,6 +113,8 @@ class LogView(View):
self.box.fix()
def y(self, y):
if y == None:
return None
return (self.height - self.height *
(log10(y) - self.log10_ymin)
/ float(self.log10_ymax - self.log10_ymin))

3
setup.py

@ -18,11 +18,10 @@
# You should have received a copy of the GNU Lesser General Public License
# along with pygal. If not, see <http://www.gnu.org/licenses/>.
from setuptools import setup, find_packages
import pygal
setup(
name="pygal",
version=pygal.__version__.replace('-dev', ''),
version='0.9.15',
description="A python svg graph plotting library",
author="Kozea",
author_email="florian.mounier@kozea.fr",

Loading…
Cancel
Save