Browse Source

Externalize js

pull/8/head
Florian Mounier 13 years ago
parent
commit
ac8b007227
  1. 18
      demo/simple_test.py
  2. 7
      pygal/config.py
  3. 8
      pygal/css/graph.css
  4. 2
      pygal/graph/bar.py
  5. 7
      pygal/graph/graph.py
  6. 8
      pygal/graph/pie.py
  7. 149
      pygal/js/graph.coffee
  8. 274
      pygal/js/graph.js
  9. 3
      pygal/svg.py
  10. 9
      pygal/util.py

18
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)

7
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

8
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 }}

2
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',

7
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"""

8
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):

149
pygal/js/graph.coffee

@ -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())

274
pygal/js/graph.js

@ -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);

3
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'))

9
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

Loading…
Cancel
Save