diff --git a/demo/simple_test.py b/demo/simple_test.py index 924a42d..4497815 100755 --- a/demo/simple_test.py +++ b/demo/simple_test.py @@ -21,19 +21,24 @@ from pygal import * from pygal.style import * from math import cos, sin -lnk = lambda v: {'value': v, 'xlink': 'javascript:alert("Test %s")' % v} +lnk = lambda v, l=None: {'value': v, 'xlink': 'javascript:alert("Test %s")' % v, 'label': l} bar = Bar(style=styles['neon']) bar.add('1234', [ {'value': 10, 'label': 'Ten', 'xlink': 'http://google.com?q=10'}, {'value': 20, 'label': 'Twenty', 'xlink': 'http://google.com?q=20'}, - {'value': 30, 'label': 'Thirty', 'xlink': 'http://google.com?q=30'}, + 30, {'value': 40, 'label': 'Forty', 'xlink': 'http://google.com?q=40'} ]) -bar.add('4321', [40, 30, 20, 10]) +bar.add('4321', [40, {'value': 30, 'label': 'Thirty', 'xlink': 'http://google.com?q=30'}, 20, 10]) bar.x_labels = map(str, range(1, 5)) +bar.included_js = [] +bar.external_js = [ + 'http://localhost:7575/svg.jquery.js', + 'http://localhost:7575/pygal.js', +] bar.fill = True bar.render_to_file('out-bar.svg') @@ -123,12 +128,17 @@ xy.title = "XY test" # xy.render_to_file('out-xy.svg') pie = Pie(Config(style=NeonStyle)) -pie.add('test', [lnk(11), 8, 21]) +pie.add('test', [lnk(11, 'LOL'), {'value': 8, 'label': 'Lol2'}, 21]) pie.add('test2', [lnk(29), None, 9]) pie.add('test3', [24, 10, 32]) pie.add('test4', [20, lnk(18), 9]) pie.add('test5', [17, 5, 10]) pie.add('test6', [None, None, 10]) +pie.included_js = [] +pie.external_js = [ + 'http://localhost:7575/svg.jquery.js', + 'http://localhost:7575/pygal.js', +] # pie.add('test', {'value': 11, 'xlink': 'javascript:alert("lol 11")'}) # pie.add('test2', 1) # pie.add('test3', 5) diff --git a/pygal/config.py b/pygal/config.py index 6404ba9..28bb737 100644 --- a/pygal/config.py +++ b/pygal/config.py @@ -43,11 +43,10 @@ class Config(object): #: If set to a filename, this will replace the default css base_css = None #: or default js - included_js = [os.path.join(os.path.dirname(__file__), 'js', 'graph.js')] + included_js = [] external_js = [ - # 'http://code.jquery.com/jquery.min.js', - # 'http://keith-wood.name/js/jquery.svg.js', - # 'http://keith-wood.name/js/jquery.svgdom.js' + 'https://raw.github.com/Kozea/pygal.js/master/svg.jquery.js', + 'https://raw.github.com/Kozea/pygal.js/master/pygal-tooltips.js' ] #: Style holding values injected in css style = DefaultStyle diff --git a/pygal/css/graph.css b/pygal/css/graph.css index 366c46d..668d0f5 100644 --- a/pygal/css/graph.css +++ b/pygal/css/graph.css @@ -172,4 +172,12 @@ text.no_data { font-size: {{ font_sizes.tooltip }}; } +#tooltip text tspan.label { + fill-opacity: .8; +} + +a:visited { + fill: none; +} + {{ style.colors }} diff --git a/pygal/graph/bar.py b/pygal/graph/bar.py index 58b58a9..100bd97 100644 --- a/pygal/graph/bar.py +++ b/pygal/graph/bar.py @@ -102,7 +102,7 @@ class Bar(Graph): width=bar_inner_width, height=height, class_='rect reactive tooltip-trigger') - self.svg.node(bar, 'desc', class_="values").text = val + self.svg.node(bar, 'desc', class_="value").text = val tooltip_positions = map( str, (x + bar_inner_width / 2, y + height / 2)) self.svg.node(bar, 'desc', diff --git a/pygal/graph/graph.py b/pygal/graph/graph.py index d0bd027..2b7a6e4 100644 --- a/pygal/graph/graph.py +++ b/pygal/graph/graph.py @@ -83,11 +83,14 @@ class Graph(BaseGraph): id="tooltip", transform='translate(0 0)') - self.svg.node(self.nodes['tooltip'], 'rect', + a = self.svg.node(self.nodes['tooltip'], 'a') + self.svg.node(a, 'rect', id="tooltip-box", rx=5, ry=5, ) - self.svg.node(self.nodes['tooltip'], 'text') + text = self.svg.node(a, 'text', class_='text') + self.svg.node(text, 'tspan', class_='label') + self.svg.node(text, 'tspan', class_='value') def _x_axis(self): """Make the x axis: labels and guides""" diff --git a/pygal/graph/pie.py b/pygal/graph/pie.py index 2f18015..bed33af 100644 --- a/pygal/graph/pie.py +++ b/pygal/graph/pie.py @@ -35,6 +35,7 @@ class Pie(Graph): slices = self.svg.node(serie_node['plot'], class_="slices") serie_angle = 0 + total_perc = 0 original_start_angle = start_angle center = ((self.width - self.margin.x) / 2., (self.height - self.margin.y) / 2.) @@ -58,11 +59,14 @@ class Pie(Graph): self.svg.slice(serie_node, slice_, radius, small_radius, angle, start_angle, center, val) start_angle += angle + total_perc += perc if len(serie.values) > 1: + val = '{0:.2%}'.format(total_perc) self.svg.slice(serie_node, - slice_, radius * .9, 0, serie_angle, - original_start_angle, center, val) + self.svg.node(slices, class_="big_slice"), + radius * .9, 0, serie_angle, + original_start_angle, center, val) return serie_angle def _compute(self): diff --git a/pygal/js/graph.coffee b/pygal/js/graph.coffee deleted file mode 100644 index b250d57..0000000 --- a/pygal/js/graph.coffee +++ /dev/null @@ -1,149 +0,0 @@ -_ = (x) -> document.querySelectorAll(x) -__ = (x) -> document.getElementById(x) -padding = 5 -tooltip_timeout = 0 -tooltip_font_size = @config.tooltip_font_size -anim_steps = @config.animation_steps - -class Queue - constructor: (@delay) -> - @queue = [] - @running = false - - add: (f, args...) -> - @queue.push f: f, a: args - if (!@running) - @running = true - @_back() - - _run: (f) -> - if(!f) - @running = false - else - setTimeout (=> - f.f f.a... - @_back() - ), @delay - - _back: -> - @_run @queue.shift() - - clear: -> - if @running - @queue = [] - @running = false - -tooltip_anim_Q = new Queue 1 - -has_class = (e, class_name) -> - return if not e - cn = e.getAttribute('class').split(' ') - for cls, i in cn - if cls == class_name - return true - false - -add_class = (e, class_name) -> - return if not e - cn = e.getAttribute('class').split(' ') - if not has_class(e, class_name) - cn.push(class_name) - e.setAttribute('class', cn.join(' ')) - -rm_class = (e, class_name) -> - return if not e - cn = e.getAttribute('class').split(' ') - for cls, i in cn - if cls == class_name - cn.splice(i, 1) - e.setAttribute('class', cn.join(' ')) - -width = (e) -> (e.getBBox() and e.getBBox().width) or e.offsetWidth -height = (e) -> (e.getBBox() and e.getBBox().height) or e.offsetHeight - -svg = (tag) -> document.createElementNS('http://www.w3.org/2000/svg', tag) - -activate = (elements...) -> - for element in elements - add_class(element, 'active') - -deactivate = (elements...) -> - for element in elements - rm_class(element, 'active') - -Function.prototype.bind = (scope) -> - _fun = @ - -> _fun.apply(scope, arguments) - -hover = (elts, over, out) -> - for elt in elts - elt.addEventListener('mouseover', over.bind(elt) , false) - elt.addEventListener('mouseout', out.bind(elt) , false) - -tooltip = (elt) -> - tooltip_anim_Q.clear() - clearTimeout(tooltip_timeout) - _tooltip = __('tooltip') - _tooltip.setAttribute('display', 'inline') - _text = _tooltip.getElementsByTagName('text')[0] - _rect = _tooltip.getElementsByTagName('rect')[0] - value = elt.nextElementSibling - _text.textContent = value.textContent - w = width(_text) + 2 * padding - h = height(_text) + 2 * padding - _rect.setAttribute('width', w) - _rect.setAttribute('height', h) - _text.setAttribute('x', padding) - _text.setAttribute('y', padding + tooltip_font_size) - x_elt = value.nextElementSibling - y_elt = x_elt.nextElementSibling - x = parseInt(x_elt.textContent) - if has_class(x_elt, 'centered') - x -= w / 2 - else if has_class(x_elt, 'left') - x -= w - - y = parseInt(y_elt.textContent) - if has_class(y_elt, 'centered') - y -= h / 2 - else if has_class(y_elt, 'top') - y -= h - - [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 - 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 (-> - __('tooltip').setAttribute('display', 'none')), 1000 - -@svg_load = -> - for text in _('.text-overlay .series') - text.setAttribute('display', 'none') - hover _('.reactive'), (-> activate(@)), (-> deactivate(@)) - hover _('.activate-serie'), ( - -> - num = this.id.replace('activate-serie-', '') - for element in _('.text-overlay .serie-' + num) - element.setAttribute('display', 'inline') - for element in _('.serie-' + num + ' .reactive') - activate(element)), ( - -> - num = this.id.replace('activate-serie-', '') - for element in _('.text-overlay .serie-' + num) - element.setAttribute('display', 'none') - for element in _('.serie-' + num + ' .reactive') - deactivate(element)) - hover _('.tooltip-trigger'), (-> tooltip(@)), (-> untooltip()) diff --git a/pygal/js/graph.js b/pygal/js/graph.js deleted file mode 100644 index cef934c..0000000 --- a/pygal/js/graph.js +++ /dev/null @@ -1,274 +0,0 @@ -// Generated by CoffeeScript 1.2.1-pre -(function() { - 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) { - return document.querySelectorAll(x); - }; - - __ = function(x) { - return document.getElementById(x); - }; - - padding = 5; - - tooltip_timeout = 0; - - tooltip_font_size = this.config.tooltip_font_size; - - anim_steps = this.config.animation_steps; - - Queue = (function() { - - Queue.name = 'Queue'; - - function Queue(delay) { - this.delay = delay; - this.queue = []; - this.running = false; - } - - Queue.prototype.add = function() { - var args, f; - f = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; - this.queue.push({ - f: f, - a: args - }); - if (!this.running) { - this.running = true; - return this._back(); - } - }; - - Queue.prototype._run = function(f) { - var _this = this; - if (!f) { - return this.running = false; - } else { - return setTimeout((function() { - f.f.apply(f, f.a); - return _this._back(); - }), this.delay); - } - }; - - Queue.prototype._back = function() { - return this._run(this.queue.shift()); - }; - - Queue.prototype.clear = function() { - if (this.running) { - this.queue = []; - return this.running = false; - } - }; - - return Queue; - - })(); - - tooltip_anim_Q = new Queue(1); - - has_class = function(e, class_name) { - var cls, cn, i, _i, _len; - if (!e) return; - cn = e.getAttribute('class').split(' '); - for (i = _i = 0, _len = cn.length; _i < _len; i = ++_i) { - cls = cn[i]; - if (cls === class_name) return true; - } - return false; - }; - - add_class = function(e, class_name) { - var cn; - if (!e) return; - cn = e.getAttribute('class').split(' '); - if (!has_class(e, class_name)) cn.push(class_name); - return e.setAttribute('class', cn.join(' ')); - }; - - rm_class = function(e, class_name) { - var cls, cn, i, _i, _len; - if (!e) return; - cn = e.getAttribute('class').split(' '); - for (i = _i = 0, _len = cn.length; _i < _len; i = ++_i) { - cls = cn[i]; - if (cls === class_name) cn.splice(i, 1); - } - return e.setAttribute('class', cn.join(' ')); - }; - - width = function(e) { - return (e.getBBox() && e.getBBox().width) || e.offsetWidth; - }; - - height = function(e) { - return (e.getBBox() && e.getBBox().height) || e.offsetHeight; - }; - - svg = function(tag) { - return document.createElementNS('http://www.w3.org/2000/svg', tag); - }; - - activate = function() { - var element, elements, _i, _len, _results; - elements = 1 <= arguments.length ? __slice.call(arguments, 0) : []; - _results = []; - for (_i = 0, _len = elements.length; _i < _len; _i++) { - element = elements[_i]; - _results.push(add_class(element, 'active')); - } - return _results; - }; - - deactivate = function() { - var element, elements, _i, _len, _results; - elements = 1 <= arguments.length ? __slice.call(arguments, 0) : []; - _results = []; - for (_i = 0, _len = elements.length; _i < _len; _i++) { - element = elements[_i]; - _results.push(rm_class(element, 'active')); - } - return _results; - }; - - Function.prototype.bind = function(scope) { - var _fun; - _fun = this; - return function() { - return _fun.apply(scope, arguments); - }; - }; - - hover = function(elts, over, out) { - var elt, _i, _len, _results; - _results = []; - for (_i = 0, _len = elts.length; _i < _len; _i++) { - elt = elts[_i]; - elt.addEventListener('mouseover', over.bind(elt), false); - _results.push(elt.addEventListener('mouseout', out.bind(elt), false)); - } - return _results; - }; - - tooltip = function(elt) { - 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'); - _tooltip.setAttribute('display', 'inline'); - _text = _tooltip.getElementsByTagName('text')[0]; - _rect = _tooltip.getElementsByTagName('rect')[0]; - value = elt.nextElementSibling; - _text.textContent = value.textContent; - w = width(_text) + 2 * padding; - h = height(_text) + 2 * padding; - _rect.setAttribute('width', w); - _rect.setAttribute('height', h); - _text.setAttribute('x', padding); - _text.setAttribute('y', padding + tooltip_font_size); - x_elt = value.nextElementSibling; - y_elt = x_elt.nextElementSibling; - x = parseInt(x_elt.textContent); - if (has_class(x_elt, 'centered')) { - x -= w / 2; - } else if (has_class(x_elt, 'left')) { - x -= w; - } - y = parseInt(y_elt.textContent); - if (has_class(y_elt, 'centered')) { - y -= h / 2; - } else if (has_class(y_elt, 'top')) { - y -= h; - } - _ref = (function() { - var _i, _len, _ref, _results; - _ref = _tooltip.getAttribute('transform').replace('translate(', '').replace(')', '').split(' '); - _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - s = _ref[_i]; - _results.push(parseInt(s)); - } - return _results; - })(), current_x = _ref[0], current_y = _ref[1]; - if (current_x === x && current_y === y) return; - 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 + ")"); - } - }; - - untooltip = function() { - return tooltip_timeout = setTimeout((function() { - return __('tooltip').setAttribute('display', 'none'); - }), 1000); - }; - - this.svg_load = function() { - var text, _i, _len, _ref; - _ref = _('.text-overlay .series'); - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - text = _ref[_i]; - text.setAttribute('display', 'none'); - } - hover(_('.reactive'), (function() { - return activate(this); - }), (function() { - return deactivate(this); - })); - hover(_('.activate-serie'), (function() { - var element, num, _j, _k, _len2, _len3, _ref2, _ref3, _results; - num = this.id.replace('activate-serie-', ''); - _ref2 = _('.text-overlay .serie-' + num); - for (_j = 0, _len2 = _ref2.length; _j < _len2; _j++) { - element = _ref2[_j]; - element.setAttribute('display', 'inline'); - } - _ref3 = _('.serie-' + num + ' .reactive'); - _results = []; - for (_k = 0, _len3 = _ref3.length; _k < _len3; _k++) { - element = _ref3[_k]; - _results.push(activate(element)); - } - return _results; - }), (function() { - var element, num, _j, _k, _len2, _len3, _ref2, _ref3, _results; - num = this.id.replace('activate-serie-', ''); - _ref2 = _('.text-overlay .serie-' + num); - for (_j = 0, _len2 = _ref2.length; _j < _len2; _j++) { - element = _ref2[_j]; - element.setAttribute('display', 'none'); - } - _ref3 = _('.serie-' + num + ' .reactive'); - _results = []; - for (_k = 0, _len3 = _ref3.length; _k < _len3; _k++) { - element = _ref3[_k]; - _results.push(deactivate(element)); - } - return _results; - })); - return hover(_('.tooltip-trigger'), (function() { - return tooltip(this); - }), (function() { - return untooltip(); - })); - }; - -}).call(this); diff --git a/pygal/svg.py b/pygal/svg.py index a3f3323..2dd3cbc 100644 --- a/pygal/svg.py +++ b/pygal/svg.py @@ -48,7 +48,8 @@ class Svg(object): None: self.ns, 'xlink': 'http://www.w3.org/1999/xlink', }, - onload="svg_load();") + # onload="svg_load(evt);" +) self.root.append(etree.Comment( u'Generated with pygal %s ©Kozea 2012' % __version__)) self.root.append(etree.Comment(u'http://github.com/Kozea/pygal')) diff --git a/pygal/util.py b/pygal/util.py index 32d6d6f..133251d 100644 --- a/pygal/util.py +++ b/pygal/util.py @@ -193,11 +193,12 @@ def decorate(svg, node, metadata): xlink = metadata.xlink if not isinstance(xlink, dict): xlink = {'href': xlink} - return svg.node(node, 'a', **xlink) + node = svg.node(node, 'a', **xlink) for key in dir(metadata): - if key not in ('xlink', 'value') and not key.startswith('_'): - svg.node(node, 'desc', class_=key).text = str( - getattr(metadata, key)) + if key not in ('value') and not key.startswith('_'): + value = getattr(metadata, key) + if value: + svg.node(node, 'desc', class_=key).text = str(value) return node