diff --git a/demo/moulinrouge/__init__.py b/demo/moulinrouge/__init__.py index 9af3746..377e72a 100644 --- a/demo/moulinrouge/__init__.py +++ b/demo/moulinrouge/__init__.py @@ -98,7 +98,11 @@ def create_app(): @app.route("/svg///") def svg(type, series, config): - graph = getattr(pygal, type)(pickle.loads(b64decode(str(config)))) + module = '.'.join(type.split('.')[:-1]) + name = type.split('.')[-1] + from importlib import import_module + graph = getattr(import_module(module), name)( + pickle.loads(b64decode(str(config)))) for title, values in pickle.loads(b64decode(str(series))): graph.add(title, values) return graph.render_response() @@ -196,10 +200,13 @@ def create_app(): config.human_readable = True config.interpolate = interpolate config.style = style - config.x_labels = [random_label() for i in range(data)] svgs = [] for chart in pygal.CHARTS: - type = chart.__name__ + type = '.'.join((chart.__module__, chart.__name__)) + if chart._dual: + config.x_labels = None + else: + config.x_labels = [random_label() for i in range(data)] svgs.append({'type': type, 'series': xy_series if type == 'XY' else other_series, 'config': b64encode(pickle.dumps(config))}) diff --git a/pygal/_compat.py b/pygal/_compat.py index 0424f73..2dc9e08 100644 --- a/pygal/_compat.py +++ b/pygal/_compat.py @@ -16,6 +16,10 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . +""" +Various hacks for transparent python 2 / python 3 support +""" + import sys from collections import Iterable import time diff --git a/pygal/adapters.py b/pygal/adapters.py index d36d04a..95ef55a 100644 --- a/pygal/adapters.py +++ b/pygal/adapters.py @@ -24,6 +24,7 @@ from decimal import Decimal def positive(x): + """Return zero if value is negative""" if x is None: return if x < 0: @@ -32,16 +33,21 @@ def positive(x): def not_zero(x): + """Return None if value is zero""" if x == 0: return return x def none_to_zero(x): - return x or 0 + """Return 0 if value is None""" + if x is None: + return 0 + return x def decimal_to_float(x): + """Cast Decimal values to float""" if isinstance(x, Decimal): return float(x) return x diff --git a/pygal/colors.py b/pygal/colors.py index a609e23..fa47e37 100644 --- a/pygal/colors.py +++ b/pygal/colors.py @@ -150,6 +150,7 @@ _clamp = lambda x: max(0, min(100, x)) def _adjust(hsl, attribute, percent): + """Internal adjust function""" hsl = list(hsl) if attribute > 0: hsl[attribute] = _clamp(hsl[attribute] + percent) diff --git a/pygal/css/style.css b/pygal/css/style.css index ca71cd1..e8c0ec7 100644 --- a/pygal/css/style.css +++ b/pygal/css/style.css @@ -97,6 +97,7 @@ {{ id }}.reactive.active, {{ id }}.active .reactive { fill-opacity: {{ style.opacity_hover }}; + stroke-width: 4; } {{ id }}.series text { diff --git a/pygal/etree.py b/pygal/etree.py index 9d542a5..aa5a46f 100644 --- a/pygal/etree.py +++ b/pygal/etree.py @@ -16,10 +16,17 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . +""" +Wrapper for seemless lxml.etree / xml.etree usage +depending on whether lxml is installed or not. +""" + import os class Etree(object): + """Etree wrapper using lxml.etree or standard xml.etree""" + def __init__(self): from xml.etree import ElementTree as _py_etree self._py_etree = _py_etree @@ -42,10 +49,12 @@ class Etree(object): return object.__getattribute__(self, attr) def to_lxml(self): + """Force lxml.etree to be used""" self._etree = self._lxml_etree self.lxml = True def to_etree(self): + """Force xml.etree to be used""" self._etree = self._py_etree self.lxml = False diff --git a/pygal/interpolate.py b/pygal/interpolate.py index 363fdea..975a6aa 100644 --- a/pygal/interpolate.py +++ b/pygal/interpolate.py @@ -17,7 +17,11 @@ # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . """ -Interpolation +Interpolation functions + +These functions takes two lists of points x and y and +returns an iterator over the interpolation between all these points +with `precision` interpolated points between each of them """ from __future__ import division @@ -25,6 +29,11 @@ from math import sin def quadratic_interpolate(x, y, precision=250, **kwargs): + """ + Interpolate x, y using a quadratic algorithm + https://en.wikipedia.org/wiki/Spline_(mathematics) + """ + n = len(x) - 1 delta_x = [x2 - x1 for x1, x2 in zip(x, x[1:])] delta_y = [y2 - y1 for y1, y2 in zip(y, y[1:])] @@ -51,6 +60,10 @@ def quadratic_interpolate(x, y, precision=250, **kwargs): def cubic_interpolate(x, y, precision=250, **kwargs): + """ + Interpolate x, y using a cubic algorithm + https://en.wikipedia.org/wiki/Spline_interpolation + """ n = len(x) - 1 # Spline equation is a + bx + cx² + dx³ # ie: Spline part i equation is a[i] + b[i]x + c[i]x² + d[i]x³ @@ -91,6 +104,25 @@ def cubic_interpolate(x, y, precision=250, **kwargs): def hermite_interpolate(x, y, precision=250, type='cardinal', c=None, b=None, t=None): + """ + Interpolate x, y using the hermite method. + See https://en.wikipedia.org/wiki/Cubic_Hermite_spline + + This interpolation is configurable and contain 4 subtypes: + * Catmull Rom + * Finite Difference + * Cardinal + * Kochanek Bartels + + The cardinal subtype is customizable with a parameter: + * c: tension (0, 1) + + This last type is also customizable using 3 parameters: + * c: continuity (-1, 1) + * b: bias (-1, 1) + * t: tension (-1, 1) + + """ n = len(x) - 1 m = [1] * (n + 1) w = [1] * (n + 1) @@ -148,6 +180,10 @@ def hermite_interpolate(x, y, precision=250, def lagrange_interpolate(x, y, precision=250, **kwargs): + """ + Interpolate x, y using Lagrange polynomials + https://en.wikipedia.org/wiki/Lagrange_polynomial + """ n = len(x) - 1 delta_x = [x2 - x1 for x1, x2 in zip(x, x[1:])] for i in range(n + 1): @@ -170,7 +206,10 @@ def lagrange_interpolate(x, y, precision=250, **kwargs): def trigonometric_interpolate(x, y, precision=250, **kwargs): - """As per http://en.wikipedia.org/wiki/Trigonometric_interpolation""" + """ + Interpolate x, y using trigonometric + As per http://en.wikipedia.org/wiki/Trigonometric_interpolation + """ n = len(x) - 1 delta_x = [x2 - x1 for x1, x2 in zip(x, x[1:])] for i in range(n + 1): @@ -191,12 +230,6 @@ def trigonometric_interpolate(x, y, precision=250, **kwargs): s += y[k] * p yield X, s -""" -These functions takes two lists of points x and y and -returns an iterator over the interpolation between all these points -with `precision` interpolated points between each of them - -""" INTERPOLATIONS = { 'quadratic': quadratic_interpolate, 'cubic': cubic_interpolate, @@ -213,4 +246,18 @@ if __name__ == '__main__': xy.add('normal', points) xy.add('quadratic', quadratic_interpolate(*zip(*points))) xy.add('cubic', cubic_interpolate(*zip(*points))) + xy.add('lagrange', lagrange_interpolate(*zip(*points))) + xy.add('trigonometric', trigonometric_interpolate(*zip(*points))) + xy.add('hermite catmul_rom', hermite_interpolate( + *zip(*points), type='catmul_rom')) + xy.add('hermite finite_difference', hermite_interpolate( + *zip(*points), type='finite_difference')) + xy.add('hermite cardinal -.5', hermite_interpolate( + *zip(*points), type='cardinal', c=-.5)) + xy.add('hermite cardinal .5', hermite_interpolate( + *zip(*points), type='cardinal', c=.5)) + xy.add('hermite kochanek_bartels .5 .75 -.25', hermite_interpolate( + *zip(*points), type='kochanek_bartels', c=.5, b=.75, t=-.25)) + xy.add('hermite kochanek_bartels .25 -.75 .5', hermite_interpolate( + *zip(*points), type='kochanek_bartels', c=.25, b=-.75, t=.5)) xy.render_in_browser()