From 4e3d585334b8334fe29e46964b20dcae4f44f332 Mon Sep 17 00:00:00 2001 From: Florian Mounier Date: Fri, 30 Mar 2012 16:07:46 +0200 Subject: [PATCH] Compatibility with python 2.6 and 3.2. Yay! --- .gitignore | 1 + pygal/graph/bar.py | 7 ++++--- pygal/graph/base.py | 32 ++++++++++++++++++++++++++------ pygal/graph/graph.py | 19 +++++++++++++++++++ pygal/graph/line.py | 5 +++-- pygal/graph/pie.py | 5 +++-- pygal/graph/radar.py | 7 ++++--- pygal/graph/stackedbar.py | 3 ++- pygal/graph/xy.py | 3 ++- pygal/svg.py | 14 ++++++++------ pygal/test/test_config.py | 4 ++++ pygal/test/test_graph.py | 5 +++++ pygal/test/test_line.py | 7 ++++--- pygal/util.py | 13 +++++++------ pygal/view.py | 7 ++++--- setup.py | 2 ++ tox.ini | 8 ++++++++ 17 files changed, 106 insertions(+), 36 deletions(-) create mode 100644 tox.ini 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