From 154636df6aed2264623be88a6b909e768847acba Mon Sep 17 00:00:00 2001 From: Florian Mounier Date: Tue, 13 Mar 2012 11:20:04 +0100 Subject: [PATCH] Add render_to_file and bug fix --- demo/moulinrouge/__init__.py | 4 ++-- pygal/__init__.py | 2 +- pygal/config.py | 2 ++ pygal/css/graph.css | 6 +++--- pygal/graph/base.py | 6 ++++++ pygal/graph/graph.py | 15 +------------- pygal/js/graph.coffee | 29 ++++++++++++++------------- pygal/js/graph.js | 39 ++++++++++++++++++++---------------- pygal/svg.py | 6 ++++-- pygal/test/test_graph.py | 33 ++++++++++++++++++++++++++++++ 10 files changed, 89 insertions(+), 53 deletions(-) create mode 100644 pygal/test/test_graph.py diff --git a/demo/moulinrouge/__init__.py b/demo/moulinrouge/__init__.py index 5d87fc8..3e4a8e3 100644 --- a/demo/moulinrouge/__init__.py +++ b/demo/moulinrouge/__init__.py @@ -117,7 +117,7 @@ def create_app(): config = Config() config.width = width config.height = height - config.fill = True + config.fill = bool(random.randrange(0, 2)) config.human_readable = True config.style = styles[style] config.x_labels = [random_label() for i in range(data)] @@ -148,7 +148,7 @@ def create_app(): series = b64encode(pickle.dumps(_random_series(type, data, order))) labels = [random_label() for i in range(data)] svgs = [] - config.show_legend = bool(random.randrange(0, 1)) + config.show_legend = bool(random.randrange(0, 2)) for angle in range(0, 91, 5): config.title = "%d rotation" % angle config.x_labels = labels diff --git a/pygal/__init__.py b/pygal/__init__.py index e5bcbe0..dca6d8a 100644 --- a/pygal/__init__.py +++ b/pygal/__init__.py @@ -16,7 +16,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . -__version__ = '0.9.11' +__version__ = '0.9.12' from collections import namedtuple from pygal.graph.bar import Bar diff --git a/pygal/config.py b/pygal/config.py index c837c53..e341143 100644 --- a/pygal/config.py +++ b/pygal/config.py @@ -88,6 +88,8 @@ class Config(object): print_values = True # Print zeroes when graph is in non interactive mode print_zeroes = False + # Animate tooltip steps (0 disable animation) + animation_steps = 0 def __init__(self, **kwargs): """Can be instanciated with config kwargs""" diff --git a/pygal/css/graph.css b/pygal/css/graph.css index 712d331..30c32c7 100644 --- a/pygal/css/graph.css +++ b/pygal/css/graph.css @@ -117,9 +117,9 @@ text.no_data { } .axis.y .guides:hover .guide.line, -.Line .axis.x .guides:hover .guide.line, -.StackedLine .axis.x .guides:hover .guide.line, -.XY .axis.x .guides:hover .guide.line { +.line-graph .axis.x .guides:hover .guide.line, +.stackedline-graph .axis.x .guides:hover .guide.line, +.xy-graph .axis.x .guides:hover .guide.line { stroke: {{ style.foreground_light }}; opacity: 1; } diff --git a/pygal/graph/base.py b/pygal/graph/base.py index 5a6de84..8b1869d 100644 --- a/pygal/graph/base.py +++ b/pygal/graph/base.py @@ -39,6 +39,8 @@ class BaseGraph(object): def _pos(self, min_, max_, scale, min_scale=4, max_scale=20): if min_ == 0 and max_ == 0: return [0] + if max_ - min_ == 0: + return [min_] if self.logarithmic: return self._pos_logarithmic(min_, max_) order = round(log10(max(abs(min_), abs(max_)))) - 1 @@ -143,3 +145,7 @@ class BaseGraph(object): def render_response(self): from flask import Response return Response(self.render(), mimetype='image/svg+xml') + + def render_to_file(self, filename): + with open(filename, 'w') as f: + f.write(self.render()) diff --git a/pygal/graph/graph.py b/pygal/graph/graph.py index f248387..66e157c 100644 --- a/pygal/graph/graph.py +++ b/pygal/graph/graph.py @@ -22,7 +22,7 @@ class Graph(BaseGraph): def _make_graph(self): self.graph_node = self.svg.node( - class_='graph %s' % self.__class__.__name__) + class_='graph %s-graph' % self.__class__.__name__.lower()) self.svg.node(self.graph_node, 'rect', class_='background', x=0, y=0, @@ -51,19 +51,6 @@ class Graph(BaseGraph): self.margin.left, self.margin.top)) self.tooltip_node = self.svg.node(tooltip_overlay, id="tooltip", transform='translate(0 0)') - # self.svg.node( - # self.tooltip_node, 'animateTransform', - # id="tooltip-slide", - # attributeName='transform', - # attributeType='XML', - # type='translate', - # calcMode='spline', - # dur='100ms', - # end='indefinite', - # begin='indefinite', - # from_="0 0", - # to="100 100", - # fill='freeze') self.svg.node(self.tooltip_node, 'rect', id="tooltip-box", diff --git a/pygal/js/graph.coffee b/pygal/js/graph.coffee index 85a2aa7..388fcdc 100644 --- a/pygal/js/graph.coffee +++ b/pygal/js/graph.coffee @@ -3,6 +3,7 @@ __ = (x) -> document.getElementById(x) padding = 5 tooltip_timeout = 0 tooltip_font_size = parseInt("{{ font_sizes.tooltip }}") +anim_steps = parseInt("{{ animation_steps }}") class Queue constructor: (@delay) -> @@ -32,7 +33,7 @@ class Queue @queue = [] @running = false -tooltip_anim_Q = new Queue 1 +tooltip_anim_Q = new Queue 100 has_class = (e, class_name) -> return if not e @@ -106,19 +107,19 @@ tooltip = (elt) -> [current_x, current_y] = (parseInt(s) for s in _tooltip.getAttribute('transform').replace('translate(', '').replace(')', '').split ' ') return if current_x == x and current_y == y - step = 12 - x_step = (x - current_x) / step - y_step = (y - current_y) / step - anim_x = current_x - anim_y = current_y - for i in [0..step] - anim_x += x_step - anim_y += y_step - tooltip_anim_Q.add ((_x, _y) -> - _tooltip.setAttribute('transform', "translate(#{_x} #{_y})")), anim_x, anim_y - tooltip_anim_Q.add ((_x, _y) -> - _tooltip.setAttribute('transform', "translate(#{_x} #{_y})")), x, y - + if anim_steps + x_step = (x - current_x) / (anim_steps + 1) + y_step = (y - current_y) / (anim_steps + 1) + anim_x = current_x + anim_y = current_y + for i in [0..anim_steps] + anim_x += x_step + anim_y += y_step + tooltip_anim_Q.add ((_x, _y) -> + _tooltip.setAttribute('transform', "translate(#{_x} #{_y})")), anim_x, anim_y + tooltip_anim_Q.add -> _tooltip.setAttribute('transform', "translate(#{x} #{y})") + else + _tooltip.setAttribute('transform', "translate(#{x} #{y})") untooltip = -> tooltip_timeout = setTimeout (-> diff --git a/pygal/js/graph.js b/pygal/js/graph.js index 37c93b6..1f03362 100644 --- a/pygal/js/graph.js +++ b/pygal/js/graph.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript 1.2.1-pre (function() { - var Queue, activate, add_class, deactivate, has_class, height, hover, padding, rm_class, svg, tooltip, tooltip_anim_Q, tooltip_font_size, tooltip_timeout, untooltip, width, _, __, + var Queue, activate, add_class, anim_steps, deactivate, has_class, height, hover, padding, rm_class, svg, tooltip, tooltip_anim_Q, tooltip_font_size, tooltip_timeout, untooltip, width, _, __, __slice = [].slice; _ = function(x) { @@ -17,6 +17,8 @@ tooltip_font_size = parseInt("{{ font_sizes.tooltip }}"); + anim_steps = parseInt("{{ animation_steps }}"); + Queue = (function() { Queue.name = 'Queue'; @@ -67,7 +69,7 @@ })(); - tooltip_anim_Q = new Queue(1); + tooltip_anim_Q = new Queue(100); has_class = function(e, class_name) { var cls, cn, i, _i, _len; @@ -153,7 +155,7 @@ }; tooltip = function(elt) { - var anim_x, anim_y, current_x, current_y, h, i, s, step, value, w, x, x_elt, x_step, y, y_elt, y_step, _i, _rect, _ref, _text, _tooltip; + var anim_x, anim_y, current_x, current_y, h, i, s, value, w, x, x_elt, x_step, y, y_elt, y_step, _i, _rect, _ref, _text, _tooltip; tooltip_anim_Q.clear(); clearTimeout(tooltip_timeout); _tooltip = __('tooltip'); @@ -185,21 +187,24 @@ return _results; })(), current_x = _ref[0], current_y = _ref[1]; if (current_x === x && current_y === y) return; - step = 12; - x_step = (x - current_x) / step; - y_step = (y - current_y) / step; - anim_x = current_x; - anim_y = current_y; - for (i = _i = 0; 0 <= step ? _i <= step : _i >= step; i = 0 <= step ? ++_i : --_i) { - anim_x += x_step; - anim_y += y_step; - tooltip_anim_Q.add((function(_x, _y) { - return _tooltip.setAttribute('transform', "translate(" + _x + " " + _y + ")"); - }), anim_x, anim_y); + if (anim_steps) { + x_step = (x - current_x) / (anim_steps + 1); + y_step = (y - current_y) / (anim_steps + 1); + anim_x = current_x; + anim_y = current_y; + for (i = _i = 0; 0 <= anim_steps ? _i <= anim_steps : _i >= anim_steps; i = 0 <= anim_steps ? ++_i : --_i) { + anim_x += x_step; + anim_y += y_step; + tooltip_anim_Q.add((function(_x, _y) { + return _tooltip.setAttribute('transform', "translate(" + _x + " " + _y + ")"); + }), anim_x, anim_y); + } + return tooltip_anim_Q.add(function() { + return _tooltip.setAttribute('transform', "translate(" + x + " " + y + ")"); + }); + } else { + return _tooltip.setAttribute('transform', "translate(" + x + " " + y + ")"); } - return tooltip_anim_Q.add((function(_x, _y) { - return _tooltip.setAttribute('transform', "translate(" + _x + " " + _y + ")"); - }), x, y); }; untooltip = function() { diff --git a/pygal/svg.py b/pygal/svg.py index 2bc8c24..90320f1 100644 --- a/pygal/svg.py +++ b/pygal/svg.py @@ -59,8 +59,10 @@ class Svg(object): script = self.node(self.root, 'script', type='text/javascript') with open(js) as f: templ = template( - f.read(), - font_sizes=self.graph.font_sizes(False)) + f.read(), + font_sizes=self.graph.font_sizes(False), + animation_steps=self.graph.animation_steps + ) script.text = templ.decode('utf-8') def node(self, parent=None, tag='g', attrib=None, **extras): diff --git a/pygal/test/test_graph.py b/pygal/test/test_graph.py new file mode 100644 index 0000000..258741b --- /dev/null +++ b/pygal/test/test_graph.py @@ -0,0 +1,33 @@ +# -*- 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 . +import os +from pygal import Line + + +def test_render_to_file(): + file_name = '/tmp/test_graph.svg' + if os.path.exists(file_name): + os.remove(file_name) + + line = Line() + line.add('Serie 1', [1]) + line.render_to_file(file_name) + with open(file_name) as f: + assert 'pygal' in f.read() + os.remove(file_name)