Browse Source

Add stack bar and pie bugfix

pull/8/head
Florian Mounier 13 years ago
parent
commit
022efcb7f8
  1. 45
      demo/moulinrouge/__init__.py
  2. 54
      out.py
  3. 1
      pygal/__init__.py
  4. 15
      pygal/base.py
  5. 38
      pygal/stackedbar.py
  6. 58
      pygal/svg.py
  7. 7
      relauncher

45
demo/moulinrouge/__init__.py

@ -2,13 +2,9 @@
from flask import Flask, Response, render_template, url_for from flask import Flask, Response, render_template, url_for
from log_colorizer import make_colored_stream_handler from log_colorizer import make_colored_stream_handler
from logging import getLogger, INFO, DEBUG from logging import getLogger, INFO, DEBUG
from moulinrouge.data import labels, series import pygal
# from pygal.bar import VerticalBar, HorizontalBar
from pygal.line import Line
from pygal.bar import Bar
from pygal.config import Config from pygal.config import Config
from pygal.style import styles from pygal.style import styles
# from pygal.pie import Pie
import string import string
import random import random
@ -25,14 +21,6 @@ def random_value(min=0, max=15):
return random.randrange(min, max, 1) return random.randrange(min, max, 1)
# def generate_vbar(**opts):
# g = VerticalBar(labels, opts)
# for serie, values in series.items():
# g.add_data({'data': values, 'title': serie})
# return Response(g.burn(), mimetype='image/svg+xml')
def create_app(): def create_app():
"""Creates the pygal test web app""" """Creates the pygal test web app"""
@ -57,23 +45,22 @@ def create_app():
config.width = 600 config.width = 600
config.height = 400 config.height = 400
config.style = styles[style] config.style = styles[style]
config.x_labels = [random_label() for i in range(data)] if type != 'Pie':
config.x_labels = [random_label() for i in range(data)]
config.title = "%d - %d" % (min, max) config.title = "%d - %d" % (min, max)
if type == 'bar': g = getattr(pygal, type)(config)
g = Bar(config)
# elif type == 'hbar':
# g = HorizontalBar(labels)
# elif type == 'pie':
# series = 1
# g = Pie({'fields': labels})
elif type == 'line':
g = Line(config)
else:
return
for i in range(random.randrange(1, 10)): for i in range(random.randrange(1, 10)):
values = [random_value((-max, min)[random.randrange(0, 2)], if type == 'Pie':
max) for i in range(data)] values = random_value(min, max)
elif type == 'XY':
values = [(
random_value((-max, min)[random.randrange(0, 2)], max),
random_value((-max, min)[random.randrange(0, 2)], max))
for i in range(data)]
else:
values = [random_value((-max, min)[random.randrange(0, 2)],
max) for i in range(data)]
g.add(random_label(), values) g.add(random_label(), values)
return Response(g.render(), mimetype='image/svg+xml') return Response(g.render(), mimetype='image/svg+xml')
@ -83,7 +70,7 @@ def create_app():
width, height = 600, 400 width, height = 600, 400
svgs = [url_for('all_svg', type=type, style=style) svgs = [url_for('all_svg', type=type, style=style)
for style in styles for style in styles
for type in ('bar', 'line')] for type in ('Bar', 'Line', 'XY', 'Pie', 'StackedBar')]
return render_template('svgs.jinja2', return render_template('svgs.jinja2',
svgs=svgs, svgs=svgs,
width=width, width=width,
@ -108,7 +95,7 @@ def create_app():
@app.route("/bigline.svg") @app.route("/bigline.svg")
def big_line_svg(): def big_line_svg():
g = Line(600, 400) g = pygal.Line(600, 400)
g.x_labels = ['a', 'b', 'c', 'd'] g.x_labels = ['a', 'b', 'c', 'd']
g.add('serie', [11, 50, 133, 2]) g.add('serie', [11, 50, 133, 2])
return Response(g.render(), mimetype='image/svg+xml') return Response(g.render(), mimetype='image/svg+xml')

54
out.py

@ -0,0 +1,54 @@
#!/usr/bin/env python
from pygal import Line, Bar, XY, Pie, StackedBar, Config
from math import cos, sin
bar = Bar()
rng = [-3, -32, -39]
bar.add('test1', rng)
bar.add('test2', map(abs, rng))
bar.x_labels = map(str, rng)
bar.title = "Bar test"
with open('out-bar.svg', 'w') as f:
f.write(bar.render())
stackedbar = StackedBar()
rng = [3, 32, 39, 12]
stackedbar.add('test1', rng)
rng2 = [24, 8, 18, 12]
stackedbar.add('test2', rng2)
rng3 = [6, 1, -10, 0]
stackedbar.add('test3', rng3)
stackedbar.x_labels = map(lambda x: '%s / %s / %s' % x,
zip(map(str, rng),
map(str, rng2),
map(str, rng3)))
stackedbar.title = "Stackedbar test"
with open('out-stackedbar.svg', 'w') as f:
f.write(stackedbar.render())
line = Line(Config(y_scale=.0005))
rng = range(-30, 31, 5)
line.add('test1', [cos(x / 10.) for x in rng])
line.add('test2', [sin(x / 10.) for x in rng])
line.add('test3', [cos(x / 10.) - sin(x / 10.) for x in rng])
line.x_labels = map(str, rng)
line.title = "Line test"
with open('out-line.svg', 'w') as f:
f.write(line.render())
xy = XY(Config(x_scale=1))
xy.add('test1', [(1981, 1), (2004, 2), (2003, 10), (2012, 8), (1999, -4)])
xy.add('test2', [(1988, -1), (1986, 12), (2007, 7), (2010, 4), (1999, 2)])
xy.title = "XY test"
with open('out-xy.svg', 'w') as f:
f.write(xy.render())
pie = Pie()
pie.add('test', 121)
pie.add('test2', 29)
# pie.add('test3', 242)
# pie.add('test4', 90)
# pie.add('test5', 175)
pie.title = "Pie test"
with open('out-pie.svg', 'w') as f:
f.write(pie.render())

1
pygal/__init__.py

@ -1,6 +1,7 @@
from collections import namedtuple from collections import namedtuple
from pygal.bar import Bar from pygal.bar import Bar
from pygal.stackedbar import StackedBar
from pygal.line import Line from pygal.line import Line
from pygal.xy import XY from pygal.xy import XY
from pygal.pie import Pie from pygal.pie import Pie

15
pygal/base.py

@ -66,7 +66,20 @@ class BaseGraph(object):
return self.svg.render() return self.svg.render()
except Exception: except Exception:
from traceback import format_exc from traceback import format_exc
return format_exc() error_svg = (
'<?xml version="1.0" standalone="no"?>'
'<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" '
'"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">'
'<svg xmlns="http://www.w3.org/2000/svg" version="1.1">')
trace = (format_exc()
.replace('&', '&amp;')
.replace('<', '&lt;')
.replace('>', '&gt;'))
for i, line in enumerate(trace.split('\n')):
error_svg += '<text y="%d">%s</text>' % (
(i + 1) * 25, line)
error_svg += '</svg>'
return error_svg
def validate(self): def validate(self):
if self.x_labels: if self.x_labels:

38
pygal/stackedbar.py

@ -0,0 +1,38 @@
from pygal.base import BaseGraph
class StackedBar(BaseGraph):
"""Stacked Bar graph"""
def _draw(self):
transposed = zip(*[serie.values for serie in self.series])
vals = [sum(val) for val in transposed]
ymin, ymax = min(min(vals), 0), max(max(vals), 0)
length = len(self.series[0].values)
x_pos = [x / float(length) for x in range(length + 1)
] if length > 1 else [0, 1] # Center if only one value
y_pos = self._pos(
ymin, ymax, self.y_scale) if not self.y_labels else map(
int, self.y_labels)
x_ranges = zip(x_pos, x_pos[1:])
x_labels = self.x_labels and zip(self.x_labels, [
sum(x_range) / 2 for x_range in x_ranges])
y_labels = zip(map(str, y_pos), y_pos)
self._compute_margin(x_labels, y_labels)
self.svg.set_view(ymin, ymax)
self.svg.make_graph()
self.svg.x_axis(x_labels)
self.svg.y_axis(y_labels)
self.svg.legend([serie.title for serie in self.series])
self.svg.title()
stack_vals = [0] * length
for serie in self.series:
serie_node = self.svg.serie(serie.index)
stack_vals = self.svg.stackbar(
serie_node, serie, [
tuple((x_ranges[i][j], v) for j in range(2))
for i, v in enumerate(serie.values)],
stack_vals)

58
pygal/svg.py

@ -133,10 +133,9 @@ class Svg(object):
return self.node( return self.node(
self.plot, class_='series serie-%d color-%d' % (serie, serie)) self.plot, class_='series serie-%d color-%d' % (serie, serie))
def line(self, serie_node, values, origin=None): def line(self, serie_node, values):
view_values = map(self.view, values) view_values = map(self.view, values)
if origin == None: origin = '%f %f' % view_values[0]
origin = '%f %f' % view_values[0]
dots = self.node(serie_node, class_="dots") dots = self.node(serie_node, class_="dots")
for i, (x, y) in enumerate(view_values): for i, (x, y) in enumerate(view_values):
@ -148,7 +147,7 @@ class Svg(object):
self.node(serie_node, 'path', self.node(serie_node, 'path',
d='M%s L%s' % (origin, svg_values), class_='line') d='M%s L%s' % (origin, svg_values), class_='line')
def bar(self, serie_node, serie, values, origin=None): def bar(self, serie_node, serie, values):
"""Draw a bar graph for a serie""" """Draw a bar graph for a serie"""
# value here is a list of tuple range of tuple coord # value here is a list of tuple range of tuple coord
@ -188,26 +187,67 @@ class Svg(object):
y=y_txt, y=y_txt,
).text = str(values[i][1][1]) ).text = str(values[i][1][1])
def stackbar(self, serie_node, serie, values, stack_vals):
"""Draw a bar graph for a serie"""
# value here is a list of tuple range of tuple coord
def view(rng):
"""Project range"""
return (self.view(rng[0]), self.view(rng[1]))
bars = self.node(serie_node, class_="bars")
view_values = map(view, values)
for i, ((x, y), (X, Y)) in enumerate(view_values):
# x and y are left range coords and X, Y right ones
width = X - x
padding = .1 * width
inner_width = width - 2 * padding
height = self.view.y(0) - y
x = x + padding
y_txt = y + height / 2
shift = stack_vals[i]
stack_vals[i] += height
if height < 0:
y = y + height
height = -height
y_txt = y + height / 2
bar = self.node(bars, class_='bar')
self.node(bar, 'rect',
x=x,
y=y - shift,
rx=self.graph.rounded_bars * 1,
ry=self.graph.rounded_bars * 1,
width=inner_width,
height=height,
class_='rect')
self.node(bar, 'text',
x=x + inner_width / 2,
y=y_txt - shift,
).text = str(values[i][1][1])
return stack_vals
def slice(self, serie_node, start_angle, angle, perc): def slice(self, serie_node, start_angle, angle, perc):
slices = self.node(serie_node, class_="slices") slices = self.node(serie_node, class_="slices")
slice_ = self.node(slices, class_="slice") slice_ = self.node(slices, class_="slice")
center = ((self.graph.width - self.graph.margin.x) / 2., center = ((self.graph.width - self.graph.margin.x) / 2.,
(self.graph.height - self.graph.margin.y) / 2.) (self.graph.height - self.graph.margin.y) / 2.)
r = min(center) - 20 r = min(center)
center_str = '%f %f' % center center_str = '%f %f' % center
rxy = '%f %f' % tuple([r] * 2) rxy = '%f %f' % tuple([r] * 2)
to = '%f %f' % (r * sin(angle), r * (1 - cos(angle))) to = '%f %f' % (r * sin(angle), r * (1 - cos(angle)))
self.node(slice_, 'path', self.node(slice_, 'path',
d='M%s v%f a%s 0 0 1 %s z' % ( d='M%s v%f a%s 0 %d 1 %s z' % (
center_str, -center[1] + 20, center_str, -r,
rxy, to), rxy,
1 if angle > pi else 0,
to),
transform='rotate(%f %s)' % ( transform='rotate(%f %s)' % (
start_angle * 180 / pi, center_str), start_angle * 180 / pi, center_str),
class_='slice') class_='slice')
text_angle = pi / 2. - (start_angle + angle / 2.) text_angle = pi / 2. - (start_angle + angle / 2.)
text_r = min(center) text_r = min(center)
self.node(slice_, 'text', self.node(slice_, 'text',
x=center[0] + text_r * cos(text_angle) * 1.05, x=center[0] + text_r * cos(text_angle),
y=center[1] - text_r * sin(text_angle), y=center[1] - text_r * sin(text_angle),
).text = '{:.2%}'.format(perc) ).text = '{:.2%}'.format(perc)

7
relauncher

@ -1,4 +1,5 @@
#!/bin/zsh #!/bin/zsh
while inotifywait -e modify **/*.py >> ~/.log/inotifywait.log 2>&1; do livereload&
py.test pygal/test reload ./out.py&
done python -m SimpleHTTPServer 1515&
chromium http://localhost:1515/&

Loading…
Cancel
Save