diff --git a/.gitignore b/.gitignore
index 571ce43..b57ef54 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
*.svg
.livereload
dist
+.tox
diff --git a/pygal/graph/bar.py b/pygal/graph/bar.py
index 43fda70..e5419e4 100644
--- a/pygal/graph/bar.py
+++ b/pygal/graph/bar.py
@@ -16,6 +16,7 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with pygal. If not, see .
+from __future__ import division
from pygal.graph.graph import Graph
from pygal.util import swap, ident
@@ -78,7 +79,7 @@ class Bar(Graph):
class_='rect reactive tooltip-trigger')
self.svg.node(bar, 'desc', class_="values").text = val
tooltip_positions = map(
- str, (x + bar_inner_width / 2., y + height / 2.))
+ str, (x + bar_inner_width / 2, y + height / 2))
self.svg.node(bar, 'desc',
class_="x centered"
).text = tooltip_positions[int(self._horizontal)]
@@ -103,14 +104,14 @@ class Bar(Graph):
self._box.ymin = min(min(self._values), self.zero)
self._box.ymax = max(max(self._values), self.zero)
x_step = len(self.series[0].values)
- x_pos = [x / float(x_step) for x in range(x_step + 1)
+ x_pos = [x / x_step for x in range(x_step + 1)
] if x_step > 1 else [0, 1] # Center if only one value
y_pos = self._compute_scale(self._box.ymin, self._box.ymax,
) 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])
+ 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):
diff --git a/pygal/graph/base.py b/pygal/graph/base.py
index 01fa5a5..1bccf3e 100644
--- a/pygal/graph/base.py
+++ b/pygal/graph/base.py
@@ -1,3 +1,23 @@
+# -*- coding: utf-8 -*-
+# This file is part of pygal
+#
+# A python svg graph plotting library
+# Copyright © 2012 Kozea
+#
+# This library is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option) any
+# later version.
+#
+# This library is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with pygal. If not, see .
+from __future__ import division
+import io
from pygal.serie import Serie
from pygal.view import Margin, Box
from pygal.util import round_to_scale, cut, rad, humanize
@@ -44,7 +64,7 @@ class BaseGraph(object):
detail /= 2
for order in range(min_order, max_order + 1):
for i in range(int(detail)):
- tick = (10 * i / detail or 1.) * 10 ** order
+ tick = (10 * i / detail or 1) * 10 ** order
tick = round_to_scale(tick, tick)
if min_ <= tick <= max_ and tick not in positions:
positions.append(tick)
@@ -61,7 +81,7 @@ class BaseGraph(object):
return log_scale
# else we fallback to normal scalling
order = round(log10(max(abs(min_), abs(max_)))) - 1
- while (max_ - min_) / float(10 ** order) < min_scale:
+ while (max_ - min_) / (10 ** order) < min_scale:
order -= 1
step = float(10 ** order)
while (max_ - min_) / step > max_scale:
@@ -159,9 +179,9 @@ class BaseGraph(object):
else:
self.svg._pre_render(True)
- def render(self):
+ def render(self, is_unicode=False):
self._render()
- return self.svg.render()
+ return self.svg.render(is_unicode=is_unicode)
def render_tree(self):
self._render()
@@ -180,8 +200,8 @@ class BaseGraph(object):
return Response(self.render(), mimetype='image/svg+xml')
def render_to_file(self, filename):
- with open(filename, 'w') as f:
- f.write(self.render())
+ with io.open(filename, 'w', encoding='utf-8') as f:
+ f.write(self.render(is_unicode=True))
def render_to_png(self, filename):
import cairosvg
diff --git a/pygal/graph/graph.py b/pygal/graph/graph.py
index 66e157c..b9ffe2b 100644
--- a/pygal/graph/graph.py
+++ b/pygal/graph/graph.py
@@ -1,3 +1,22 @@
+# -*- coding: utf-8 -*-
+# This file is part of pygal
+#
+# A python svg graph plotting library
+# Copyright © 2012 Kozea
+#
+# This library is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option) any
+# later version.
+#
+# This library is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with pygal. If not, see .
+from __future__ import division
from pygal.graph.base import BaseGraph
from pygal.view import View, LogView
from pygal.util import is_major
diff --git a/pygal/graph/line.py b/pygal/graph/line.py
index 5604cd8..cb3289b 100644
--- a/pygal/graph/line.py
+++ b/pygal/graph/line.py
@@ -16,6 +16,7 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with pygal. If not, see .
+from __future__ import division
from pygal.graph.graph import Graph
from pygal.util import cached_property
from pygal.interpolate import interpolation
@@ -55,9 +56,9 @@ class Line(Graph):
continue
classes = []
- if x > self.view.width / 2.:
+ if x > self.view.width / 2:
classes.append('left')
- if y > self.view.height / 2.:
+ if y > self.view.height / 2:
classes.append('top')
classes = ' '.join(classes)
diff --git a/pygal/graph/pie.py b/pygal/graph/pie.py
index c41ceda..7ef5453 100644
--- a/pygal/graph/pie.py
+++ b/pygal/graph/pie.py
@@ -16,6 +16,7 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with pygal. If not, see .
+from __future__ import division
from pygal.graph.graph import Graph
from math import cos, sin, pi
project = lambda rho, alpha: (
@@ -30,7 +31,7 @@ class Pie(Graph):
def slice(self, serie_node, start_angle, angle, perc,
small=False):
- val = '{:.2%}'.format(perc)
+ val = '{0:.2%}'.format(perc)
slices = self.svg.node(serie_node['plot'], class_="slices")
slice_ = self.svg.node(slices, class_="slice")
center = ((self.width - self.margin.x) / 2.,
@@ -64,7 +65,7 @@ class Pie(Graph):
self.svg.node(slice_, 'desc', class_="value").text = val
tooltip_position = map(
str, diff(center, project(
- (r + small_r) / 2., start_angle + angle / 2.)))
+ (r + small_r) / 2, start_angle + angle / 2)))
self.svg.node(slice_, 'desc',
class_="x centered").text = tooltip_position[0]
self.svg.node(slice_, 'desc',
diff --git a/pygal/graph/radar.py b/pygal/graph/radar.py
index 39d0743..7242f6e 100644
--- a/pygal/graph/radar.py
+++ b/pygal/graph/radar.py
@@ -16,6 +16,7 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with pygal. If not, see .
+from __future__ import division
from pygal.graph.line import Line
from pygal.view import PolarView
from pygal.util import deg, cached_property
@@ -67,7 +68,7 @@ class Radar(Line):
y=pos_text[1]
)
text.text = label
- angle = - theta + pi / 2.
+ angle = - theta + pi / 2
if cos(angle) < 0:
angle -= pi
text.attrib['transform'] = 'rotate(%f %s)' % (
@@ -92,7 +93,7 @@ class Radar(Line):
).text = label
def _compute(self):
- delta = 2 * pi / float(self._len)
+ delta = 2 * pi / self._len
self._x_pos = [.5 * pi + i * delta for i in range(self._len + 1)]
for serie in self.series:
vals = list(serie.values)
@@ -112,7 +113,7 @@ class Radar(Line):
interpolate = interpolation(
extended_x_pos, extended_vals, kind=self.interpolate)
serie.interpolated = []
- p = float(self.interpolation_precision)
+ p = self.interpolation_precision
for s in range(int(p + 1)):
x = .5 * pi + 2 * pi * (s / p)
serie.interpolated.append((float(interpolate(x)), x))
diff --git a/pygal/graph/stackedbar.py b/pygal/graph/stackedbar.py
index e8ee9b6..b43e441 100644
--- a/pygal/graph/stackedbar.py
+++ b/pygal/graph/stackedbar.py
@@ -16,6 +16,7 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with pygal. If not, see .
+from __future__ import division
from pygal.graph.bar import Bar
@@ -35,7 +36,7 @@ class StackedBar(Bar):
min(min(negative_vals), 0), max(max(positive_vals), 0))
self._length = len(self.series[0].values)
- x_pos = [x / float(self._length)
+ x_pos = [x / self._length
for x in range(self._length + 1)
] if self._length > 1 else [0, 1] # Center if only one value
y_pos = self._compute_scale(self._box.ymin, self._box.ymax
diff --git a/pygal/graph/xy.py b/pygal/graph/xy.py
index eb496a0..7c5ed3c 100644
--- a/pygal/graph/xy.py
+++ b/pygal/graph/xy.py
@@ -16,6 +16,7 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with pygal. If not, see .
+from __future__ import division
from pygal.graph.line import Line
from pygal.interpolate import interpolation
from math import isnan
@@ -48,7 +49,7 @@ class XY(Line):
serie_xmax = max(vals[0])
serie.interpolated = []
r = (max(xvals) - xmin)
- p = float(self.interpolation_precision)
+ p = self.interpolation_precision
for s in range(int(p + 1)):
x = xmin + r * (s / p)
if (serie_xmin <= x <= serie_xmax and not
diff --git a/pygal/svg.py b/pygal/svg.py
index 550c1c2..8a49f93 100644
--- a/pygal/svg.py
+++ b/pygal/svg.py
@@ -16,6 +16,8 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with pygal. If not, see .
+from __future__ import division
+import io
import os
from lxml import etree
from pygal.util import template
@@ -44,23 +46,23 @@ class Svg(object):
def add_style(self, css):
style = self.node(self.defs, 'style', type='text/css')
- with open(css) as f:
+ with io.open(css, encoding='utf-8') as f:
templ = template(
f.read(),
style=self.graph.style,
font_sizes=self.graph.font_sizes(),
hidden='y' if self.graph._horizontal else 'x')
- style.text = templ.decode('utf-8')
+ style.text = templ
def add_script(self, js):
script = self.node(self.defs, 'script', type='text/javascript')
- with open(js) as f:
+ with io.open(js, encoding='utf-8') as f:
templ = template(
f.read(),
font_sizes=self.graph.font_sizes(False),
animation_steps=self.graph.animation_steps
)
- script.text = templ.decode('utf-8')
+ script.text = templ
def node(self, parent=None, tag='g', attrib=None, **extras):
if parent is None:
@@ -119,11 +121,11 @@ class Svg(object):
class_='no_data')
no_data.text = self.graph.no_data_text
- def render(self):
+ def render(self, is_unicode=False):
svg = etree.tostring(
self.root, pretty_print=True,
xml_declaration=not self.graph.disable_xml_declaration,
encoding='utf-8')
- if self.graph.disable_xml_declaration:
+ if self.graph.disable_xml_declaration or is_unicode:
svg = svg.decode('utf-8')
return svg
diff --git a/pygal/test/test_config.py b/pygal/test/test_config.py
index 656a420..daa55c7 100644
--- a/pygal/test/test_config.py
+++ b/pygal/test/test_config.py
@@ -34,6 +34,10 @@ def test_logarithmic():
def test_logarithmic_bad_interpolation():
+ try:
+ import scipy
+ except ImportError:
+ return
line = Line(logarithmic=True, interpolate='cubic')
line.add('_', [.001, .00000001, 1])
q = line.render_pyquery()
diff --git a/pygal/test/test_graph.py b/pygal/test/test_graph.py
index c2be88e..98c2a44 100644
--- a/pygal/test/test_graph.py
+++ b/pygal/test/test_graph.py
@@ -48,6 +48,11 @@ def test_render_to_file():
def test_render_to_png():
+ try:
+ import cairosvg
+ except ImportError:
+ return
+
file_name = '/tmp/test_graph.png'
if os.path.exists(file_name):
os.remove(file_name)
diff --git a/pygal/test/test_line.py b/pygal/test/test_line.py
index a8986e7..5dd28a4 100644
--- a/pygal/test/test_line.py
+++ b/pygal/test/test_line.py
@@ -16,6 +16,7 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with pygal. If not, see .
+from __future__ import division
from pygal import Line
from pygal.test.utils import texts
from math import cos, sin
@@ -24,9 +25,9 @@ from math import cos, sin
def test_simple_line():
line = Line()
rng = range(-30, 31, 5)
- line.add('test1', [cos(x / 10.) for x in rng])
- line.add('test2', [sin(x / 10.) for x in rng])
- line.add('test3', [cos(x / 10.) - sin(x / 10.) for x in rng])
+ line.add('test1', [cos(x / 10) for x in rng])
+ line.add('test2', [sin(x / 10) for x in rng])
+ line.add('test3', [cos(x / 10) - sin(x / 10) for x in rng])
line.x_labels = map(str, rng)
line.title = "cos sin and cos - sin"
q = line.render_pyquery()
diff --git a/pygal/util.py b/pygal/util.py
index 1d4214f..1a9ad3f 100644
--- a/pygal/util.py
+++ b/pygal/util.py
@@ -16,6 +16,7 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with pygal. If not, see .
+from __future__ import division
from decimal import Decimal
from math import floor, pi, log, log10
ORDERS = u"yzafpnµm kMGTPEZY"
@@ -29,9 +30,9 @@ def humanize(number):
order = number and int(floor(log(abs(number)) / log(1000)))
human_readable = ORDERS.split(" ")[int(order > 0)]
if order == 0 or order > len(human_readable):
- return float_format(number / float(1000 ** int(order)))
+ return float_format(number / (1000 ** int(order)))
return (
- float_format(number / float(1000 ** int(order))) +
+ float_format(number / (1000 ** int(order))) +
human_readable[int(order) - int(order > 0)])
@@ -42,13 +43,13 @@ def is_major(number):
def round_to_int(number, precision):
precision = int(precision)
- rounded = (int(number) + precision / 2) / precision * precision
+ rounded = (int(number) + precision / 2) // precision * precision
return rounded
def round_to_float(number, precision):
- rounded = Decimal(
- floor((number + precision / 2) / precision)) * Decimal(str(precision))
+ rounded = Decimal(str(floor((number + precision / 2) // precision))
+ ) * Decimal(str(precision))
return float(rounded)
@@ -67,7 +68,7 @@ def cut(list_, index=0):
def rad(deg):
- return pi * deg / 180.
+ return pi * deg / 180
def deg(deg):
diff --git a/pygal/view.py b/pygal/view.py
index 25238fd..dd1c159 100644
--- a/pygal/view.py
+++ b/pygal/view.py
@@ -16,6 +16,7 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with pygal. If not, see .
+from __future__ import division
from math import sin, cos, log10
@@ -79,13 +80,13 @@ class View(object):
def x(self, x):
if x == None:
return None
- return self.width * (x - self.box.xmin) / float(self.box.width)
+ return self.width * (x - self.box.xmin) / 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))
+ (y - self.box.ymin) / self.box.height)
def __call__(self, xy):
x, y = xy
@@ -118,4 +119,4 @@ class LogView(View):
return None
return (self.height - self.height *
(log10(y) - self.log10_ymin)
- / float(self.log10_ymax - self.log10_ymin))
+ / (self.log10_ymax - self.log10_ymin))
diff --git a/setup.py b/setup.py
index 3406a71..bbed9a7 100644
--- a/setup.py
+++ b/setup.py
@@ -42,6 +42,7 @@ setup(
tests_require=["pytest", "pyquery", "flask", "cairosvg"],
package_data={'pygal': ['css/*', 'js/*']},
install_requires=['lxml'],
+ use_2to3=True,
classifiers=[
"Development Status :: 4 - Beta",
"Environment :: Console",
@@ -50,4 +51,5 @@ setup(
"GNU Library or Lesser General Public License (LGPL)",
"Operating System :: OS Independent",
"Programming Language :: Python :: 2",
+ "Programming Language :: Python :: 3",
"Topic :: Multimedia :: Graphics :: Presentation"])
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..09caee1
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,8 @@
+[tox]
+envlist = py26,py27,py32
+[testenv]
+changedir=demo
+commands=py.test --pyargs pygal []
+deps =
+ pytest
+ pyquery