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)