Browse Source

Drop scipy dependecy + add cubic interpolation

pull/38/head
Florian Mounier 12 years ago
parent
commit
db8363b477
  1. 5
      demo/moulinrouge.py
  2. 2
      pygal/config.py
  3. 2
      pygal/graph/base.py
  4. 3
      pygal/graph/datey.py
  5. 35
      pygal/graph/graph.py
  6. 18
      pygal/graph/radar.py
  7. 2
      pygal/graph/stackedline.py
  8. 3
      pygal/graph/xy.py
  9. 73
      pygal/interpolate.py
  10. 21
      pygal/test/test_config.py

5
demo/moulinrouge.py

@ -51,10 +51,11 @@ else:
app.logger.debug('HTTPServer monkey patched for url %s' % url) app.logger.debug('HTTPServer monkey patched for url %s' % url)
try: try:
import wdb from wdb.ext import WdbMiddleware, add_w_builtin
except ImportError: except ImportError:
pass pass
else: else:
app.wsgi_app = wdb.Wdb(app.wsgi_app, start_disabled=True) add_w_builtin()
app.wsgi_app = WdbMiddleware(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)

2
pygal/config.py

@ -180,7 +180,7 @@ class Config(object):
False, bool, "Value", "Display values in logarithmic scale") False, bool, "Value", "Display values in logarithmic scale")
interpolate = Key( interpolate = Key(
None, str, "Value", "Interpolation, this requires scipy module", None, str, "Value", "Interpolation. ",
"May be any of 'linear', 'nearest', 'zero', 'slinear', 'quadratic," "May be any of 'linear', 'nearest', 'zero', 'slinear', 'quadratic,"
"'cubic', 'krogh', 'barycentric', 'univariate'," "'cubic', 'krogh', 'barycentric', 'univariate',"
"or an integer specifying the order" "or an integer specifying the order"

2
pygal/graph/base.py

@ -216,7 +216,7 @@ class BaseGraph(object):
@cached_property @cached_property
def _order(self): def _order(self):
"""Getter for the maximum series value""" """Getter for the number of series"""
return len(self.all_series) return len(self.all_series)
def _draw(self): def _draw(self):

3
pygal/graph/datey.py

@ -95,8 +95,7 @@ class DateY(XY):
vals = list(zip(*sorted( vals = list(zip(*sorted(
[t for t in serie.points if None not in t], [t for t in serie.points if None not in t],
key=lambda x: x[0]))) key=lambda x: x[0])))
serie.interpolated = self._interpolate( serie.interpolated = self._interpolate(vals[0], vals[1])
vals[1], vals[0], xy=True, xy_xmin=xmin, xy_rng=rng)
if self.interpolate and rng: if self.interpolate and rng:
xvals = [val[0] xvals = [val[0]

35
pygal/graph/graph.py

@ -22,7 +22,7 @@ Commmon graphing functions
""" """
from __future__ import division from __future__ import division
from pygal.interpolate import interpolation from pygal.interpolate import cubic_interpolate
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 ( from pygal.util import (
@ -375,29 +375,16 @@ class Graph(BaseGraph):
self.nodes['text_overlay'], self.nodes['text_overlay'],
class_='series serie-%d color-%d' % (serie, serie % 16))) class_='series serie-%d color-%d' % (serie, serie % 16)))
def _interpolate( def _interpolate(self, xs, ys):
self, ys, xs,
polar=False, xy=False, xy_xmin=None, xy_rng=None):
"""Make the interpolation""" """Make the interpolation"""
interpolate = interpolation( x = []
xs, ys, kind=self.interpolate) y = []
p = self.interpolation_precision for i in range(len(ys)):
xmin = min(xs) if ys[i] is not None:
xmax = max(xs) x.append(xs[i])
interpolateds = [] y.append(ys[i])
for i in range(int(p + 1)):
x = i / p return list(cubic_interpolate(x, y, self.interpolation_precision))
if polar:
x = .5 * pi + 2 * pi * x
elif xy:
x = xy_xmin + xy_rng * x
interpolated = float(interpolate(x))
if not isnan(interpolated) and xmin <= x <= xmax:
coord = (x, interpolated)
if polar:
coord = tuple(reversed(coord))
interpolateds.append(coord)
return interpolateds
def _tooltip_data(self, node, value, x, y, classes=None): def _tooltip_data(self, node, value, x, y, classes=None):
self.svg.node(node, 'desc', class_="value").text = value self.svg.node(node, 'desc', class_="value").text = value
@ -433,7 +420,7 @@ class Graph(BaseGraph):
(x_pos[i], v) (x_pos[i], v)
for i, v in enumerate(serie.values)] for i, v in enumerate(serie.values)]
if serie.points and self.interpolate: if serie.points and self.interpolate:
serie.interpolated = self._interpolate(serie.values, x_pos) serie.interpolated = self._interpolate(x_pos, serie.values)
else: else:
serie.interpolated = [] serie.interpolated = []

18
pygal/graph/radar.py

@ -143,17 +143,15 @@ class Radar(Line):
(v, x_pos[i]) (v, x_pos[i])
for i, v in enumerate(serie.values)] for i, v in enumerate(serie.values)]
if self.interpolate: if self.interpolate:
extend = 2
extended_x_pos = ( extended_x_pos = (
[.5 * pi + i * delta for i in range(-extend, 0)] + [.5 * pi - delta] + x_pos)
x_pos + extended_vals = (serie.values[-1:] +
[.5 * pi + i * delta for i in range( serie.values)
self._len + 1, self._len + 1 + extend)]) serie.interpolated = list(
extended_vals = (serie.values[-extend:] + map(tuple,
serie.values + map(reversed,
serie.values[:extend]) self._interpolate(
serie.interpolated = self._interpolate( extended_x_pos, extended_vals))))
extended_vals, extended_x_pos, polar=True)
# x labels space # x labels space
self._box.margin *= 2 self._box.margin *= 2

2
pygal/graph/stackedline.py

@ -51,6 +51,6 @@ class StackedLine(Line):
(x_pos[i], v) (x_pos[i], v)
for i, v in enumerate(accumulation)] for i, v in enumerate(accumulation)]
if serie.points and self.interpolate: if serie.points and self.interpolate:
serie.interpolated = self._interpolate(accumulation, x_pos) serie.interpolated = self._interpolate(x_pos, accumulation)
else: else:
serie.interpolated = [] serie.interpolated = []

3
pygal/graph/xy.py

@ -75,8 +75,7 @@ class XY(Line):
vals = list(zip(*sorted( vals = list(zip(*sorted(
filter(lambda t: None not in t, filter(lambda t: None not in t,
serie.points), key=lambda x: x[0]))) serie.points), key=lambda x: x[0])))
serie.interpolated = self._interpolate( serie.interpolated = self._interpolate(vals[0], vals[1])
vals[1], vals[0], xy=True, xy_xmin=xmin, xy_rng=xrng)
if self.interpolate and xrng: if self.interpolate and xrng:
self.xvals = [val[0] self.xvals = [val[0]

73
pygal/interpolate.py

@ -17,45 +17,46 @@
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with pygal. If not, see <http://www.gnu.org/licenses/>. # along with pygal. If not, see <http://www.gnu.org/licenses/>.
""" """
Interpolation using scipy Interpolation
""" """
from pygal.util import ident from __future__ import division
scipy = None
try:
import scipy
from scipy import interpolate
except ImportError:
pass
KINDS = ['cubic', 'univariate', 'quadratic', 'slinear', 'nearest', 'zero'] def cubic_interpolate(x, a, precision=250):
"""Takes a list of (x, a) and returns an iterator over
the natural cubic spline of points with `precision` points between them"""
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³
b = [0] * (n + 1)
c = [0] * (n + 1)
d = [0] * (n + 1)
m = [0] * (n + 1)
z = [0] * (n + 1)
h = [x2 - x1 for x1, x2 in zip(x, x[1:])]
k = [a2 - a1 for a1, a2 in zip(a, a[1:])]
g = [k[i] / h[i] if h[i] else 1 for i in range(n)]
def interpolation(x, y, kind): for i in range(1, n):
"""Make the interpolation function""" j = i - 1
assert scipy is not None, ( l = 1 / (2 * (x[i + 1] - x[j]) - h[j] * m[j])
'You must have scipy installed to use interpolation') m[i] = h[i] * l
order = None z[i] = (3 * (g[i] - g[j]) - h[j] * z[j]) * l
if len(y) < len(x):
x = x[:len(y)]
pack = list(zip(*filter(lambda t: None not in t, zip(x, y)))) for j in reversed(range(n)):
if len(pack) == 0: if h[j] == 0:
return ident continue
x, y = pack c[j] = z[j] - (m[j] * c[j + 1])
if len(x) < 2: b[j] = g[j] - (h[j] * (c[j + 1] + 2 * c[j])) / 3
return ident d[j] = (c[j + 1] - c[j]) / (3 * h[j])
if isinstance(kind, int):
order = kind for i in range(n + 1):
elif kind in KINDS: yield x[i], a[i]
order = {'nearest': 0, 'zero': 0, 'slinear': 1, if i == n or h[i] == 0:
'quadratic': 2, 'cubic': 3, 'univariate': 3}[kind] continue
if order and len(x) <= order: for s in range(1, precision):
kind = len(x) - 1 X = s * h[i] / precision
if kind == 'krogh': X2 = X * X
return interpolate.KroghInterpolator(x, y) X3 = X2 * X
elif kind == 'barycentric': yield x[i] + X, a[i] + b[i] * X + c[i] * X2 + d[i] * X3
return interpolate.BarycentricInterpolator(x, y)
elif kind == 'univariate':
return interpolate.InterpolatedUnivariateSpline(x, y)
return interpolate.interp1d(x, y, kind=kind, bounds_error=False)

21
pygal/test/test_config.py

@ -31,6 +31,16 @@ def test_config_behaviours():
line1.add('_', [1, 2, 3]) line1.add('_', [1, 2, 3])
l1 = line1.render() l1 = line1.render()
q = line1.render_pyquery()
assert len(q(".axis.x")) == 1
assert len(q(".axis.y")) == 1
assert len(q(".plot .series path")) == 1
assert len(q(".legend")) == 0
assert len(q(".x.axis .guides")) == 3
assert len(q(".y.axis .guides")) == 21
assert len(q(".dots")) == 3
assert q(".axis.x text").map(texts) == ['a', 'b', 'c']
line2 = Line( line2 = Line(
show_legend=False, show_legend=False,
fill=True, fill=True,
@ -56,6 +66,17 @@ def test_config_behaviours():
l4 = line4.render() l4 = line4.render()
assert l1 == l4 assert l1 == l4
line_config = Config()
line_config.show_legend = False
line_config.fill = True
line_config.pretty_print = True
line_config.x_labels = ['a', 'b', 'c']
line5 = Line(line_config)
line5.add('_', [1, 2, 3])
l5 = line5.render()
assert l1 == l5
def test_config_alterations_class(): def test_config_alterations_class():
class LineConfig(Config): class LineConfig(Config):

Loading…
Cancel
Save