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)
try:
import wdb
from wdb.ext import WdbMiddleware, add_w_builtin
except ImportError:
pass
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)

2
pygal/config.py

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

2
pygal/graph/base.py

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

3
pygal/graph/datey.py

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

35
pygal/graph/graph.py

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

18
pygal/graph/radar.py

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

2
pygal/graph/stackedline.py

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

3
pygal/graph/xy.py

@ -75,8 +75,7 @@ class XY(Line):
vals = list(zip(*sorted(
filter(lambda t: None not in t,
serie.points), key=lambda x: x[0])))
serie.interpolated = self._interpolate(
vals[1], vals[0], xy=True, xy_xmin=xmin, xy_rng=xrng)
serie.interpolated = self._interpolate(vals[0], vals[1])
if self.interpolate and xrng:
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
# 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):
"""Make the interpolation function"""
assert scipy is not None, (
'You must have scipy installed to use interpolation')
order = None
if len(y) < len(x):
x = x[:len(y)]
for i in range(1, n):
j = i - 1
l = 1 / (2 * (x[i + 1] - x[j]) - h[j] * m[j])
m[i] = h[i] * l
z[i] = (3 * (g[i] - g[j]) - h[j] * z[j]) * l
pack = list(zip(*filter(lambda t: None not in t, zip(x, y))))
if len(pack) == 0:
return ident
x, y = pack
if len(x) < 2:
return ident
if isinstance(kind, int):
order = kind
elif kind in KINDS:
order = {'nearest': 0, 'zero': 0, 'slinear': 1,
'quadratic': 2, 'cubic': 3, 'univariate': 3}[kind]
if order and len(x) <= order:
kind = len(x) - 1
if kind == 'krogh':
return interpolate.KroghInterpolator(x, y)
elif kind == 'barycentric':
return interpolate.BarycentricInterpolator(x, y)
elif kind == 'univariate':
return interpolate.InterpolatedUnivariateSpline(x, y)
return interpolate.interp1d(x, y, kind=kind, bounds_error=False)
for j in reversed(range(n)):
if h[j] == 0:
continue
c[j] = z[j] - (m[j] * c[j + 1])
b[j] = g[j] - (h[j] * (c[j + 1] + 2 * c[j])) / 3
d[j] = (c[j + 1] - c[j]) / (3 * h[j])
for i in range(n + 1):
yield x[i], a[i]
if i == n or h[i] == 0:
continue
for s in range(1, precision):
X = s * h[i] / precision
X2 = X * X
X3 = X2 * X
yield x[i] + X, a[i] + b[i] * X + c[i] * X2 + d[i] * X3

21
pygal/test/test_config.py

@ -31,6 +31,16 @@ def test_config_behaviours():
line1.add('_', [1, 2, 3])
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(
show_legend=False,
fill=True,
@ -56,6 +66,17 @@ def test_config_behaviours():
l4 = line4.render()
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():
class LineConfig(Config):

Loading…
Cancel
Save