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 log_colorizer import make_colored_stream_handler
from logging import getLogger, INFO, DEBUG
from moulinrouge.data import labels, series
# from pygal.bar import VerticalBar, HorizontalBar
from pygal.line import Line
from pygal.bar import Bar
import pygal
from pygal.config import Config
from pygal.style import styles
# from pygal.pie import Pie
import string
import random
@ -25,14 +21,6 @@ def random_value(min=0, max=15):
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():
"""Creates the pygal test web app"""
@ -57,23 +45,22 @@ def create_app():
config.width = 600
config.height = 400
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)
if type == 'bar':
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
g = getattr(pygal, type)(config)
for i in range(random.randrange(1, 10)):
values = [random_value((-max, min)[random.randrange(0, 2)],
max) for i in range(data)]
if type == 'Pie':
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)
return Response(g.render(), mimetype='image/svg+xml')
@ -83,7 +70,7 @@ def create_app():
width, height = 600, 400
svgs = [url_for('all_svg', type=type, style=style)
for style in styles
for type in ('bar', 'line')]
for type in ('Bar', 'Line', 'XY', 'Pie', 'StackedBar')]
return render_template('svgs.jinja2',
svgs=svgs,
width=width,
@ -108,7 +95,7 @@ def create_app():
@app.route("/bigline.svg")
def big_line_svg():
g = Line(600, 400)
g = pygal.Line(600, 400)
g.x_labels = ['a', 'b', 'c', 'd']
g.add('serie', [11, 50, 133, 2])
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 pygal.bar import Bar
from pygal.stackedbar import StackedBar
from pygal.line import Line
from pygal.xy import XY
from pygal.pie import Pie

15
pygal/base.py

@ -66,7 +66,20 @@ class BaseGraph(object):
return self.svg.render()
except Exception:
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):
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(
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)
if origin == None:
origin = '%f %f' % view_values[0]
origin = '%f %f' % view_values[0]
dots = self.node(serie_node, class_="dots")
for i, (x, y) in enumerate(view_values):
@ -148,7 +147,7 @@ class Svg(object):
self.node(serie_node, 'path',
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"""
# value here is a list of tuple range of tuple coord
@ -188,26 +187,67 @@ class Svg(object):
y=y_txt,
).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):
slices = self.node(serie_node, class_="slices")
slice_ = self.node(slices, class_="slice")
center = ((self.graph.width - self.graph.margin.x) / 2.,
(self.graph.height - self.graph.margin.y) / 2.)
r = min(center) - 20
r = min(center)
center_str = '%f %f' % center
rxy = '%f %f' % tuple([r] * 2)
to = '%f %f' % (r * sin(angle), r * (1 - cos(angle)))
self.node(slice_, 'path',
d='M%s v%f a%s 0 0 1 %s z' % (
center_str, -center[1] + 20,
rxy, to),
d='M%s v%f a%s 0 %d 1 %s z' % (
center_str, -r,
rxy,
1 if angle > pi else 0,
to),
transform='rotate(%f %s)' % (
start_angle * 180 / pi, center_str),
class_='slice')
text_angle = pi / 2. - (start_angle + angle / 2.)
text_r = min(center)
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),
).text = '{:.2%}'.format(perc)

7
relauncher

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

Loading…
Cancel
Save