Browse Source

To javascript

pull/8/head
Florian Mounier 13 years ago
parent
commit
791b168de4
  1. 10
      demo/simple_test.py
  2. 2
      pygal/config.py
  3. 32
      pygal/css/graph.css
  4. 30
      pygal/graph/bar.py
  5. 23
      pygal/graph/graph.py
  6. 14
      pygal/graph/line.py
  7. 29
      pygal/graph/pie.py
  8. 57
      pygal/js/graph.coffee
  9. 99
      pygal/js/graph.js
  10. 10
      pygal/svg.py
  11. 6
      relauncher

10
demo/simple_test.py

@ -102,11 +102,11 @@ with open('out-xy.svg', 'w') as f:
f.write(xy.render())
pie = Pie(Config(style=NeonStyle))
pie.add('test', [11])
# pie.add('test2', [29, 21, 9])
# pie.add('test3', [24, 10, 32])
# pie.add('test4', [20, 18, 9])
# pie.add('test5', [17, 5, 10])
pie.add('test', [11, 8, 21])
pie.add('test2', [29, 21, 9])
pie.add('test3', [24, 10, 32])
pie.add('test4', [20, 18, 9])
pie.add('test5', [17, 5, 10])
pie.title = "Pie test"
with open('out-pie.svg', 'w') as f:
f.write(pie.render())

2
pygal/config.py

@ -34,6 +34,8 @@ class Config(object):
y_scale = 1
# If set to a filename, this will replace the default css
base_css = None
# or default js
base_js = None
# Style holding values injected in css
style = DefaultStyle
# Various font sizes

32
pygal/css/graph.css

@ -55,18 +55,10 @@ svg * {
font-size: {{ font_sizes.legend }};
}
.legends .legend rect {
fill-opacity: {{ style.opacity }};
}
.legends .legend:hover text {
stroke: {{ style.foreground_light }};
}
.legends .legend:hover rect {
fill-opacity: 1
}
.axis text {
font-size: {{ font_sizes.label }};
font-family: sans;
@ -117,10 +109,14 @@ svg * {
stroke-width: 5px;
}
.series .rect, .series .slice {
.reactive {
fill-opacity: {{ style.opacity }};
}
.reactive.active {
fill-opacity: {{ fill_opacity_hover }};
}
.series text {
opacity: 0;
font-size: {{ font_sizes.values }};
@ -130,23 +126,13 @@ svg * {
text-shadow: 0 0 16px {{ style.background }};
}
.series .dot:hover text, .series .bar:hover text, .series .slice:hover text {
.series text.active {
opacity: 1;
}
.series .bar:hover .rect, .series .slice:hover path, .series .slice:hover circle{
fill-opacity: 1;
}
.series .line {
stroke-width: 1px;
fill-opacity: {{ fill_opacity }};
}
.series .line:hover {
stroke-width: 2px;
fill-opacity: {{ fill_opacity_hover }};
}
/* .series .line { */
/* fill-opacity: {{ fill_opacity }}; */
/* } */

30
pygal/graph/bar.py

@ -33,9 +33,10 @@ class Bar(Graph):
fun = swap if self._horizontal else ident
return (self.view(fun(t)), self.view(fun(T)))
bars = self.svg.node(serie_node, class_="bars")
bars = self.svg.node(serie_node['plot'], class_="bars")
view_values = map(view, values)
for i, ((x, y), (X, Y)) in enumerate(view_values):
tag = '%d_%d' % (serie.index, i)
# x and y are left range coords and X, Y right ones
if self._horizontal:
x, y, X, Y = Y, X, y, x
@ -64,22 +65,27 @@ class Bar(Graph):
height = -height
bar = self.svg.node(bars, class_='bar')
self.svg.transposable_node(bar, 'rect',
x=x,
y=y - shift,
rx=self.rounded_bars * 1,
ry=self.rounded_bars * 1,
width=bar_inner_width,
height=height,
class_='rect')
self.svg.transposable_node(
bar, 'rect',
x=x,
y=y - shift,
rx=self.rounded_bars * 1,
ry=self.rounded_bars * 1,
width=bar_inner_width,
height=height,
id="active-%s" % tag,
class_='rect reactive')
if self._horizontal:
x += .3 * self.values_font_size
y += height / 2
else:
y += height / 2 + .3 * self.values_font_size
self.svg.transposable_node(bar, 'text',
x=x + bar_inner_width / 2,
y=y - shift,
self.svg.transposable_node(
bar, 'text',
x=x + bar_inner_width / 2,
y=y - shift,
id="reactive-%s" % tag,
class_='reactive-text'
).text = str(values[i][1][1])
return stack_vals

23
pygal/graph/graph.py

@ -36,6 +36,10 @@ class Graph(BaseGraph):
x=0, y=0,
width=self.view.width,
height=self.view.height)
self.overlay = self.svg.node(
self.graph_node, class_="plot overlay",
transform="translate(%d, %d)" % (
self.margin.left, self.margin.top))
def _x_axis(self):
if not self._x_labels:
@ -102,11 +106,12 @@ class Graph(BaseGraph):
for i, title in enumerate(self._legends):
legend = self.svg.node(legends, class_='legend')
self.svg.node(legend, 'rect',
x=0,
y=1.5 * i * self.legend_box_size,
width=self.legend_box_size,
height=self.legend_box_size,
class_="color-%d" % i,
x=0,
y=1.5 * i * self.legend_box_size,
width=self.legend_box_size,
height=self.legend_box_size,
class_="color-%d activate-serie reactive" % i,
id="activate-serie-%d" % i
).text = title
# Serious magical numbers here
self.svg.node(legend, 'text',
@ -124,5 +129,9 @@ class Graph(BaseGraph):
).text = self.title
def _serie(self, serie):
return self.svg.node(
self.plot, class_='series serie-%d color-%d' % (serie, serie))
return dict(
plot=self.svg.node(
self.plot, class_='series serie-%d color-%d' % (serie, serie)),
overlay=self.svg.node(
self.overlay, class_='series serie-%d color-%d' % (
serie, serie)))

14
pygal/graph/line.py

@ -46,11 +46,16 @@ class Line(Graph):
view_values = map(self.view, serie.points)
if self.show_dots:
dots = self.svg.node(serie_node, class_="dots")
dots = self.svg.node(serie_node['overlay'], class_="dots")
for i, (x, y) in enumerate(view_values):
dot = self.svg.node(dots, class_='dot')
self.svg.node(dot, 'circle', cx=x, cy=y, r=2.5)
self.svg.node(dot, 'text', x=x, y=y
tag = '%d_%d' % (serie.index, i)
self.svg.node(dot, 'circle', cx=x, cy=y, r=2.5,
id="active-%s" % tag,
class_='reactive')
self.svg.node(dot, 'text', x=x, y=y,
id="reactive-%s" % tag,
class_='reactive-text'
).text = self._get_value(serie.points, i)
if self.stroke:
@ -59,7 +64,8 @@ class Line(Graph):
if self.fill:
view_values = self._fill(view_values)
self.svg.line(
serie_node, view_values, class_='line')
serie_node['plot'], view_values,
class_='line reactive')
def _compute(self):
self._x_pos = [x / float(self._len - 1) for x in range(self._len)

29
pygal/graph/pie.py

@ -23,9 +23,9 @@ from math import cos, sin, pi
class Pie(Graph):
"""Pie graph"""
def slice(self, serie_node, start_angle, angle, perc,
def slice(self, serie_node, start_angle, angle, perc, tag,
small=False):
slices = self.svg.node(serie_node, class_="slices")
slices = self.svg.node(serie_node['plot'], class_="slices")
slice_ = self.svg.node(slices, class_="slice")
center = ((self.width - self.margin.x) / 2.,
(self.height - self.margin.y) / 2.)
@ -38,7 +38,8 @@ class Pie(Graph):
cx=center[0],
cy=center[1],
r=r,
class_='slice')
id="active-%s" % tag,
class_='slice reactive')
else:
rxy = '%f %f' % tuple([r] * 2)
to = '%f %f' % (r * sin(angle), r * (1 - cos(angle)))
@ -48,14 +49,17 @@ class Pie(Graph):
rxy,
1 if angle > pi else 0,
to),
id="active-%s" % tag,
transform='rotate(%f %s)' % (
start_angle * 180 / pi, center_str),
class_='slice')
class_='slice reactive')
text_angle = pi / 2. - (start_angle + angle / 2.)
text_r = min(center) * .8
self.svg.node(slice_, 'text',
x=center[0] + text_r * cos(text_angle),
y=center[1] - text_r * sin(text_angle),
text_r = r * .8
self.svg.node(serie_node['overlay'], 'text',
x=center[0] + text_r * cos(text_angle),
y=center[1] - text_r * sin(text_angle),
id="reactive-%s" % tag,
class_='reactive-text'
).text = '{:.2%}'.format(perc)
def _compute(self):
@ -74,14 +78,17 @@ class Pie(Graph):
self.slice(
self._serie(serie.index),
current_angle,
angle, sum(serie.values) / total)
angle, sum(serie.values) / total,
'%d' % serie.index)
if len(serie.values) > 1:
small_current_angle = current_angle
for val in serie.values:
for i, val in enumerate(serie.values):
small_angle = 2 * pi * val / total
self.slice(
self._serie(serie.index),
small_current_angle,
small_angle, val / total, True)
small_angle, val / total,
'%d_%d' % (serie.index, i),
True)
small_current_angle += small_angle
current_angle += angle

57
pygal/js/graph.coffee

@ -0,0 +1,57 @@
_ = (x) -> document.querySelectorAll(x)
add_class = (e, class_name) ->
return if not e
cn = e.getAttribute('class').split(' ')
if class_name not in cn
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(' '))
@svg_load = ->
for element in _('.reactive-text')
element.addEventListener('mouseover', ((e) ->
->
add_class(e, 'active')
add_class(document.getElementById(e.id.replace(/re/, '')), 'active')
)(element), false)
element.addEventListener('mouseout', ((e) ->
->
rm_class(e, 'active')
rm_class(document.getElementById(e.id.replace(/re/, '')), 'active')
)(element), false)
for element in _('.reactive')
element.addEventListener('mouseover', ((e) ->
->
add_class(e, 'active')
add_class(document.getElementById('re' + e.id), 'active')
)(element), false)
element.addEventListener('mouseout', ((e) ->
->
rm_class(e, 'active')
rm_class(document.getElementById('re' + e.id), 'active')
)(element), false)
for element in _('.activate-serie')
element.addEventListener('mouseover', ((e) ->
->
num = e.id.replace('activate-serie-', '')
for element in _('.serie-' + num + ' .reactive')
add_class(element, 'active')
add_class(document.getElementById('re' + element.id), 'active')
)(element), false)
element.addEventListener('mouseout', ((e) ->
->
num = e.id.replace('activate-serie-', '')
for element in _('.serie-' + num + ' .reactive')
rm_class(element, 'active')
rm_class(document.getElementById('re' + element.id), 'active')
)(element), false)

99
pygal/js/graph.js

@ -0,0 +1,99 @@
// Generated by CoffeeScript 1.2.1-pre
(function() {
var add_class, rm_class, _,
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
_ = function(x) {
return document.querySelectorAll(x);
};
add_class = function(e, class_name) {
var cn;
if (!e) return;
cn = e.getAttribute('class').split(' ');
if (__indexOf.call(cn, class_name) < 0) 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(' '));
};
this.svg_load = function() {
var element, _i, _j, _k, _len, _len2, _len3, _ref, _ref2, _ref3, _results;
_ref = _('.reactive-text');
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
element = _ref[_i];
element.addEventListener('mouseover', (function(e) {
return function() {
add_class(e, 'active');
return add_class(document.getElementById(e.id.replace(/re/, '')), 'active');
};
})(element), false);
element.addEventListener('mouseout', (function(e) {
return function() {
rm_class(e, 'active');
return rm_class(document.getElementById(e.id.replace(/re/, '')), 'active');
};
})(element), false);
}
_ref2 = _('.reactive');
for (_j = 0, _len2 = _ref2.length; _j < _len2; _j++) {
element = _ref2[_j];
element.addEventListener('mouseover', (function(e) {
return function() {
add_class(e, 'active');
return add_class(document.getElementById('re' + e.id), 'active');
};
})(element), false);
element.addEventListener('mouseout', (function(e) {
return function() {
rm_class(e, 'active');
return rm_class(document.getElementById('re' + e.id), 'active');
};
})(element), false);
}
_ref3 = _('.activate-serie');
_results = [];
for (_k = 0, _len3 = _ref3.length; _k < _len3; _k++) {
element = _ref3[_k];
element.addEventListener('mouseover', (function(e) {
return function() {
var element, num, _l, _len4, _ref4, _results2;
num = e.id.replace('activate-serie-', '');
_ref4 = _('.serie-' + num + ' .reactive');
_results2 = [];
for (_l = 0, _len4 = _ref4.length; _l < _len4; _l++) {
element = _ref4[_l];
add_class(element, 'active');
_results2.push(add_class(document.getElementById('re' + element.id), 'active'));
}
return _results2;
};
})(element), false);
_results.push(element.addEventListener('mouseout', (function(e) {
return function() {
var element, num, _l, _len4, _ref4, _results2;
num = e.id.replace('activate-serie-', '');
_ref4 = _('.serie-' + num + ' .reactive');
_results2 = [];
for (_l = 0, _len4 = _ref4.length; _l < _len4; _l++) {
element = _ref4[_l];
rm_class(element, 'active');
_results2.push(rm_class(document.getElementById('re' + element.id), 'active'));
}
return _results2;
};
})(element), false));
}
return _results;
};
}).call(this);

10
pygal/svg.py

@ -35,12 +35,15 @@ class Svg(object):
nsmap={
None: self.ns,
'xlink': 'http://www.w3.org/1999/xlink',
})
},
onload="svg_load();")
self.root.append(etree.Comment(u'Generated with pygal ©Kozea 2012'))
self.root.append(etree.Comment(u'http://github.com/Kozea/pygal'))
self.defs = self.node(tag='defs')
self.add_style(self.graph.base_css or os.path.join(
os.path.dirname(__file__), 'css', 'graph.css'))
self.add_script(self.graph.base_js or os.path.join(
os.path.dirname(__file__), 'js', 'graph.js'))
def add_style(self, css):
style = self.node(self.defs, 'style', type='text/css')
@ -56,6 +59,11 @@ class Svg(object):
if self.graph.fill else 0)
style.text = templ.decode('utf-8')
def add_script(self, js):
script = self.node(self.root, 'script', type='text/javascript')
with open(js) as f:
script.text = f.read()
def node(self, parent=None, tag='g', attrib=None, **extras):
if parent is None:
parent = self.root

6
relauncher

@ -1,6 +1,10 @@
#!/bin/zsh
pkill -f livereload
pkill -f reload
pkill -f SimpleHTTPServer
livereload&
reload ./demo/simple_test.py **/*.py&
reload ./demo/simple_test.py **/*.{py,js,css}&
python -m SimpleHTTPServer 1515&
sleep 1
chromium http://localhost:1515/&

Loading…
Cancel
Save