Browse Source

Library now uses lxml for XML processing. This technique has a much cleaner and robust implementation. I tried using the Python 2.5 element tree, but it doesn't support processing instructions (as far as I can tell).

pull/8/head
jaraco 16 years ago
parent
commit
e95aead46c
  1. 2
      setup.py
  2. 9
      src/svg/charts/bar.py
  3. 230
      src/svg/charts/graph.py
  4. 28
      src/svg/charts/line.py
  5. 74
      src/svg/charts/pie.py
  6. 23
      src/svg/charts/plot.py
  7. 6
      src/svg/charts/schedule.py

2
setup.py

@ -17,7 +17,7 @@ setup(name = "svg.charts",
install_requires=[
'python-dateutil>=1.4',
'cssutils>=0.9.5.1',
# TODO: consider lxml
'lxml>=2.0',
],
license = "MIT",
long_description = """\

9
src/svg/charts/bar.py

@ -1,6 +1,7 @@
#!python
from svg.charts.graph import Graph
from itertools import chain
from lxml import etree
from svg.charts.graph import Graph
__all__ = ('VerticalBar', 'HorizontalBar')
@ -178,14 +179,13 @@ class VerticalBar(Bar):
if self.stack == 'side':
left += bar_width * dataset_count
rect = self._create_element('rect', {
rect = etree.SubElement(self.graph, 'rect', {
'x': str(left),
'y': str(top),
'width': str(bar_width),
'height': str(length),
'class': 'fill%s' % (dataset_count+1),
})
self.graph.appendChild(rect)
self.make_datapoint_text(left + bar_width/2.0, top-6, value)
@ -236,14 +236,13 @@ class HorizontalBar(Bar):
# left is 0 if value is negative
left = (abs(min_value) + min(value, 0)) * unit_size
rect = self._create_element('rect', {
rect = etree.SubElement(self.graph, 'rect', {
'x': str(left),
'y': str(top),
'width': str(length),
'height': str(bar_height),
'class': 'fill%s' % (dataset_count+1),
})
self.graph.appendChild(rect)
self.make_datapoint_text(left+length+5, top+y_mod, value,
"text-anchor: start; ")

230
src/svg/charts/graph.py

@ -1,17 +1,17 @@
#!python
# -*- coding: UTF-8 -*-
from xml.dom import minidom as dom
from operator import itemgetter
from itertools import islice
import cssutils
import pkg_resources
from lxml import etree
try:
import zlib
__have_zlib = True
except ImportError:
__have_zlib = False
zlib = None
def sort_multiple(arrays):
"sort multiple lists (of equal size) using the first list for the sort keys"
@ -102,6 +102,8 @@ class Graph(object):
top_align = top_font = right_align = right_font = 0
compress = False
def __init__(self, config = {}):
"""Initialize the graph object with the graph settings."""
if self.__class__ is Graph:
@ -147,34 +149,39 @@ class Graph(object):
self.data = []
def burn(self):
"""This method processes the template with the data and
"""
This method processes the template with the data and
config which has been set and returns the resulting SVG.
This method will croak unless at least one data set has
been added to the graph object.
Ex: graph.burn()"""
Ex: graph.burn()
"""
if not self.data: raise ValueError("No data available")
if hasattr(self, 'calculations'): self.calculations()
self.start_svg()
self.calculate_graph_dimensions()
self.foreground = self._create_element("g")
self.foreground = etree.Element("g")
self.draw_graph()
self.draw_titles()
self.draw_legend()
self.draw_data()
self.graph.appendChild(self.foreground)
self.graph.append(self.foreground)
self.render_inline_styles()
data = self._doc.toprettyxml()
return self._burn_compressed()
def _burn_compressed(self):
if self.compress and not zlib:
self.root.addprevious(etree.Comment('Python zlib not available for SVGZ'))
if hasattr(self, 'compress') and self.compress:
if __have_zlib:
data = etree.tostring(self.root, pretty_print=True, xml_declaration=True, encoding='utf-8')
if self.compress and zlib:
data = zlib.compress(data)
else:
data += '<!-- Python zlib not available for SVGZ -->'
return data
@ -225,30 +232,29 @@ class Graph(object):
"Adds pop-up point information to a graph."
txt_width = len(label) * self.font_size * 0.6 + 10
tx = x + [5,-5][int(x+txt_width > self.width)]
t = self._create_element('text')
anchor = ['start', 'end'][x+txt_width > self.width]
style = 'fill: #000; text-anchor: %s;' % anchor
id = 'label-%s' % label
attributes = {'x': str(tx),
t = etree.SubElement(self.foreground, 'text', {
'x': str(tx),
'y': str(y - self.font_size),
'visibility': 'hidden',
'style': style,
'text': label,
'id': id
}
map(lambda a: t.setAttribute(*a), attributes.items())
self.foreground.appendChild(t)
})
# Note, prior to the etree conversion, this circle element was never
# added to anything (now it's added to the foreground)
visibility = "document.getElementById(%s).setAttribute('visibility', %%s)" % id
t = self._create_element('circle')
attributes = {'cx': str(x),
t = etree.SubElement(self.foreground, 'circle', {
'cx': str(x),
'cy': str(y),
'r': 10,
'r': str(10),
'style': 'opacity: 0;',
'onmouseover': visibility % 'visible',
'onmouseout': visibility % 'hidden',
}
map(lambda a: t.setAttribute(*a), attributes.items())
})
def calculate_bottom_margin(self):
"""Override this (and call super) to change the margin to the bottom
@ -270,28 +276,27 @@ class Graph(object):
def draw_graph(self):
transform = 'translate (%s %s)' % (self.border_left, self.border_top)
self.graph = self._create_element('g', {'transform': transform})
self.root.appendChild(self.graph)
self.graph = etree.SubElement(self.root, 'g', transform=transform)
self.graph.appendChild(self._create_element('rect', {
etree.SubElement(self.graph, 'rect', {
'x': '0',
'y': '0',
'width': str(self.graph_width),
'height': str(self.graph_height),
'class': 'graphBackground'
}))
})
#Axis
self.graph.appendChild(self._create_element('path', {
etree.SubElement(self.graph, 'path', {
'd': 'M 0 0 v%s' % self.graph_height,
'class': 'axis',
'id': 'xAxis'
}))
self.graph.appendChild(self._create_element('path', {
})
etree.SubElement(self.graph, 'path', {
'd': 'M 0 %s h%s' % (self.graph_height, self.graph_width),
'class': 'axis',
'id': 'yAxis'
}))
})
self.draw_x_labels()
self.draw_y_labels()
@ -303,21 +308,22 @@ class Graph(object):
def make_datapoint_text(self, x, y, value, style=''):
if self.show_data_values:
e = self._create_element('text', {
# first lay down the text in a wide white stroke to
# differentiate it from the background
e = etree.SubElement(self.foreground, 'text', {
'x': str(x),
'y': str(y),
'class': 'dataPointLabel',
'style': '%(style)s stroke: #fff; stroke-width: 2;' % vars(),
})
e.appendChild(self._doc.createTextNode(str(value)))
self.foreground.appendChild(e)
e = self._create_element('text', {
e.text = str(value)
# then lay down the text in the specified style
e = etree.SubElement(self.foreground, 'text', {
'x': str(x),
'y': str(y),
'class': 'dataPointLabel'})
e.appendChild(self._doc.createTextNode(str(value)))
if style: e.setAttribute('style', style)
self.foreground.appendChild(e)
e.text = str(value)
if style: e.set('style', style)
def draw_x_labels(self):
"Draw the X axis labels"
@ -334,9 +340,8 @@ class Graph(object):
def draw_x_label(self, label):
label_width = self.field_width()
index, label = label
text = self._create_element('text', {'class': 'xAxisLabels'})
text.appendChild(self._doc.createTextNode(label))
self.graph.appendChild(text)
text = etree.SubElement(self.graph, 'text', {'class': 'xAxisLabels'})
text.text = label
x = index * label_width + self.x_label_offset(label_width)
y = self.graph_height + self.x_label_font_size + 3
@ -346,22 +351,21 @@ class Graph(object):
stagger = self.x_label_font_size + 5
y += stagger
graph_height = self.graph_height
path = self._create_element('path', {
path = etree.SubElement(self.graph, 'path', {
'd': 'M%(x)f %(graph_height)f v%(stagger)d' % vars(),
'class': 'staggerGuideLine'
})
self.graph.appendChild(path)
text.setAttribute('x', str(x))
text.setAttribute('y', str(y))
text.set('x', str(x))
text.set('y', str(y))
if self.rotate_x_labels:
transform = 'rotate(90 %d %d) translate(0 -%d)' % \
(x, y-self.x_label_font_size, self.x_label_font_size/4)
text.setAttribute('transform', transform)
text.setAttribute('style', 'text-anchor: start')
text.set('transform', transform)
text.set('style', 'text-anchor: start')
else:
text.setAttribute('style', 'text-anchor: middle')
text.set('style', 'text-anchor: middle')
def y_label_offset(self, height):
"""Where in the Y area the label is drawn
@ -400,9 +404,8 @@ class Graph(object):
def draw_y_label(self, label):
label_height = self.field_height()
index, label = label
text = self._create_element('text', {'class': 'yAxisLabels'})
text.appendChild(self._doc.createTextNode(label))
self.graph.appendChild(text)
text = etree.SubElement(self.graph, 'text', {'class': 'yAxisLabels'})
text.text = label
y = self.y_offset - (label_height * index)
x = {True: 0, False:-3}[self.rotate_y_labels]
@ -410,23 +413,22 @@ class Graph(object):
if self.stagger_y_labels and (index % 2):
stagger = self.y_label_font_size + 5
x -= stagger
path = self._create_element('path', {
path = etree.SubElement(self.graph, 'path', {
'd': 'M%(x)f %(y)f h%(stagger)d' % vars(),
'class': 'staggerGuideLine'
})
self.graph.appendChild(path)
text.setAttribute('x', str(x))
text.setAttribute('y', str(y))
text.set('x', str(x))
text.set('y', str(y))
if self.rotate_y_labels:
transform = 'translate(-%d 0) rotate (90 %d %d)' % \
(self.font_size, x, y)
text.setAttribute('transform', transform)
text.setAttribute('style', 'text-anchor: middle')
text.set('transform', transform)
text.set('style', 'text-anchor: middle')
else:
text.setAttribute('y', str(y - self.y_label_font_size/2))
text.setAttribute('style', 'text-anchor: end')
text.set('y', str(y - self.y_label_font_size/2))
text.set('style', 'text-anchor: end')
def draw_x_guidelines(self, label_height, count):
"Draw the X-axis guidelines"
@ -435,11 +437,9 @@ class Graph(object):
for count in range(1,count):
start = label_height*count
stop = self.graph_height
path = self._create_element('path', {
path = etree.SubElement(self.graph, 'path', {
'd': 'M %(start)s 0 v%(stop)s' % vars(),
'class': 'guideLines'})
self.graph.appendChild(path)
def draw_y_guidelines(self, label_height, count):
"Draw the Y-axis guidelines"
@ -447,10 +447,9 @@ class Graph(object):
for count in range(1, count):
start = self.graph_height - label_height*count
stop = self.graph_width
path = self._create_element('path', {
path = etree.SubElement(self.graph, 'path', {
'd': 'M 0 %(start)s h%(stop)s' % vars(),
'class': 'guideLines'})
self.graph.appendChild(path)
def draw_titles(self):
"Draws the graph title and subtitle"
@ -460,23 +459,21 @@ class Graph(object):
if self.show_y_title: self.draw_y_title()
def draw_graph_title(self):
text = self._create_element('text', {
text = etree.SubElement(self.root, 'text', {
'x': str(self.width / 2),
'y': str(self.title_font_size),
'class': 'mainTitle'})
text.appendChild(self._doc.createTextNode(self.graph_title))
self.root.appendChild(text)
text.text = self.graph_title
def draw_graph_subtitle(self):
y_subtitle_options = [subtitle_font_size, title_font_size+10]
y_subtitle = y_subtitle_options[self.show_graph_title]
text = self._create_element('text', {
text = etree.SubElement(self.root, 'text', {
'x': str(self.width/2),
'y': str(y_subtitle),
'class': 'subTitle',
})
text.appendChild(self._doc.createTextNode(self.graph_title))
self.root.appendChild(text)
text.text = self.graph_title
def draw_x_title(self):
y = self.graph_height + self.border_top + self.x_title_font_size
@ -486,13 +483,12 @@ class Graph(object):
y += y_size
x = self.width / 2
text = self._create_element('text', {
text = etree.SubElement(self.root, 'text', {
'x': str(x),
'y': str(y),
'class': 'xAxisTitle',
})
text.appendChild(self._doc.createTextNode(self.x_title))
self.root.appendChild(text)
text.text = self.x_title
def draw_y_title(self):
x = self.y_title_font_size
@ -503,46 +499,42 @@ class Graph(object):
x -= 3
rotate = 90
y = self.height / 2
text = self._create_element('text', {
text = etree.SubElement(self.root, 'text', {
'x': str(x),
'y': str(y),
'class': 'yAxisTitle',
})
text.appendChild(self._doc.createTextNode(self.y_title))
text.setAttribute('transform', 'rotate(%(rotate)d, %(x)s, %(y)s)' % vars())
self.root.appendChild(text)
text.text = self.y_title
text.set('transform', 'rotate(%(rotate)d, %(x)s, %(y)s)' % vars())
def keys(self):
return map(itemgetter('title'), self.data)
def draw_legend(self):
if self.key:
group = self._create_element('g')
self.root.appendChild(group)
group = etree.SubElement(self.root, 'g')
for key_count, key_name in enumerate(self.keys()):
y_offset = (self.KEY_BOX_SIZE * key_count) + (key_count * 5)
rect = self._create_element('rect', {
etree.SubElement(group, 'rect', {
'x': '0',
'y': str(y_offset),
'width': str(self.KEY_BOX_SIZE),
'height': str(self.KEY_BOX_SIZE),
'class': 'key%s' % (key_count + 1),
})
group.appendChild(rect)
text = self._create_element('text', {
text = etree.SubElement(group, 'text', {
'x': str(self.KEY_BOX_SIZE + 5),
'y': str(y_offset + self.KEY_BOX_SIZE),
'class': 'keyText'})
text.appendChild(self._doc.createTextNode(key_name))
group.appendChild(text)
text.text = key_name
if self.key_position == 'right':
x_offset = self.graph_width + self.border_left + 10
y_offset = self.border_top + 20
if self.key_position == 'bottom':
x_offset, y_offset = self.calculate_offsets_bottom()
group.setAttribute('transform', 'translate(%(x_offset)d %(y_offset)d)' % vars())
group.set('transform', 'translate(%(x_offset)d %(y_offset)d)' % vars())
def calculate_offsets_bottom(self):
x_offset = self.border_left + 20
@ -587,46 +579,52 @@ class Graph(object):
def start_svg(self):
"Base SVG Document Creation"
impl = dom.getDOMImplementation()
self._doc = impl.createDocument(None, 'svg', None)
self.root = self._doc.documentElement
if hasattr(self, 'style_sheet_href'):
pi = self._doc.createProcessingInstruction('xml-stylesheet',
'href="%s" type="text/css"' % self.style_sheet_href)
attributes = {
SVG_NAMESPACE = 'http://www.w3.org/2000/svg'
SVG = '{%s}' % SVG_NAMESPACE
NSMAP = {
None: SVG_NAMESPACE,
'xlink': 'http://www.w3.org/1999/xlink',
'a3': 'http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/',
}
self.root = etree.Element(SVG+"svg", attrib={
'width': str(self.width),
'height': str(self.height),
'viewBox': '0 0 %s %s' % (self.width, self.height),
'xmlns': 'http://www.w3.org/2000/svg',
'xmlns:xlink': 'http://www.w3.org/1999/xlink',
'xmlns:a3': 'http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/',
'a3:scriptImplementation': 'Adobe'}
map(lambda a: self.root.setAttribute(*a), attributes.items())
self.root.appendChild(self._doc.createComment(' Created with SVG.Graph '))
self.root.appendChild(self._doc.createComment(' SVG.Graph by Jason R. Coombs '))
self.root.appendChild(self._doc.createComment(' Based on SVG::Graph by Sean E. Russel '))
self.root.appendChild(self._doc.createComment(' Based on Perl SVG:TT:Graph by Leo Lapworth & Stephan Morgan '))
self.root.appendChild(self._doc.createComment(' '+'/'*66))
defs = self._create_element('defs')
'{http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/}scriptImplementation': 'Adobe',
}, nsmap=NSMAP)
if hasattr(self, 'style_sheet_href'):
pi = etree.ProcessingInstruction(
'xml-stylesheet',
'href="%s" type="text/css"' % self.style_sheet_href
)
self.root.addprevious(pi)
comment_strings = (
' Created with SVG.Graph ',
' SVG.Graph by Jason R. Coombs ',
' Based on SVG::Graph by Sean E. Russel ',
' Based on Perl SVG:TT:Graph by Leo Lapworth & Stephan Morgan ',
' '+'/'*66,
)
map(self.root.append, map(etree.Comment, comment_strings))
defs = etree.SubElement(self.root, 'defs')
self.add_defs(defs)
self.root.appendChild(defs)
if not hasattr(self, 'style_sheet_href') and not self.css_inline:
self.root.appendChild(self._doc.createComment(' include default stylesheet if none specified '))
style = self._create_element('style', {'type': 'text/css'})
defs.appendChild(style)
style_data = self._doc.createCDATASection(self.get_stylesheet().cssText)
style.appendChild(style_data)
self.root.appendChild(self._doc.createComment('SVG Background'))
rect = self._create_element('rect', {
self.root.append(etree.Comment(' include default stylesheet if none specified '))
style = etree.SubElement(defs, 'style', type='text/css')
# TODO: the text was previously escaped in a CDATA declaration... how
# to do that with etree?
style.text = self.get_stylesheet().cssText
self.root.append(etree.Comment('SVG Background'))
rect = etree.SubElement(self.root, 'rect', {
'width': str(self.width),
'height': str(self.height),
'x': '0',
'y': '0',
'class': 'svgBackground'})
self.root.appendChild(rect)
def calculate_graph_dimensions(self):
self.calculate_left_margin()
@ -661,12 +659,6 @@ class Graph(object):
def css_file(self):
return self.__class__.__name__.lower() + '.css'
def _create_element(self, nodeName, attributes={}):
"Create an XML node and set the attributes from a dict"
node = self._doc.createElement(nodeName)
map(lambda a: node.setAttribute(*a), attributes.items())
return node
class class_dict(object):
"Emulates a dictionary, but retrieves class attributes"
def __init__(self, obj):

28
src/svg/charts/line.py

@ -3,10 +3,10 @@
# $Id$
from operator import itemgetter, add
from util import flatten
from lxml import etree
from util import flatten, float_range
from svg.charts.graph import Graph
from util import float_range
class Line(Graph):
""" === Create presentation quality SVG line graphs easily
@ -193,7 +193,6 @@ class Line(Graph):
area_path = "V#@graph_height"
origin = coord_format(get_coords(0,0))
p = self._create_element('path')
d = ' '.join((
'M',
origin,
@ -202,29 +201,28 @@ class Line(Graph):
area_path,
'Z'
))
p.setAttribute('d', d)
p.setAttribute('class', 'fill%(line_n)s' % vars())
self.graph.appendChild(p)
etree.SubElement(self.graph, 'path', {
'class': 'fill%(line_n)s' % vars(),
'd': d,
})
# now draw the line itself
p = self._create_element('path')
p.setAttribute('d', 'M0 '+self.graph_height+' L'+line_path)
p.setAttribute('class', 'line%(line_n)s' % vars())
self.graph.appendChild(p)
etree.SubElement(self.graph, 'path', {
'd': 'M0 '+self.graph_height+' L'+line_path,
'class': 'line%(line_n)s' % vars(),
})
if self.show_data_points or self.show_data_values:
for i, value in enumerate(cum_sum):
if self.show_data_points:
circle = self._create_element(
circle = etree.SubElement(
self.graph,
'circle',
dict(
{'class': 'dataPoint%(line_n)s' % vars()},
cx = str(field_width*i),
cy = str(self.graph_height - value*field_height),
r = '2.5',
)
)
circle.setAttribute('class', 'dataPoint%(line_n)s' % vars())
self.graph.appendChild(circle)
self.make_datapoint_text(
field_width*i,
self.graph_height - value*field_height - 6,

74
src/svg/charts/pie.py

@ -4,6 +4,7 @@
import math
from operator import add
from lxml import etree
from svg.charts.graph import Graph
def robust_add(a,b):
@ -121,23 +122,19 @@ class Pie(Graph):
def add_defs(self, defs):
"Add svg definitions"
gradient = self._create_element(
etree.SubElement(
defs,
'filter',
dict(
id='dropshadow',
width='1.2',
height='1.2',
)
)
defs.appendChild(gradient)
blur = self._create_element(
etree.SubElement(
defs,
'feGaussianBlur',
dict(
stdDeviation='4',
result='blur',
)
)
gradient.appendChild(blur)
def draw_graph(self):
"Here we don't need the graph (consider refactoring)"
@ -149,7 +146,7 @@ class Pie(Graph):
def get_x_labels(self):
"Okay. I'll refactor after this"
['']
return ['']
def keys(self):
total = reduce(add, self.data)
@ -164,12 +161,10 @@ class Pie(Graph):
return map(key, self.fields, self.data)
def draw_data(self):
self.graph = self._create_element('g')
self.root.appendChild(self.graph)
background = self._create_element('g')
self.graph.appendChild(background)
midground = self._create_element('g')
self.graph.appendChild(midground)
self.graph = etree.SubElement(self.root, 'g')
background = etree.SubElement(self.graph, 'g')
# midground is somewhere between the background and the foreground
midground = etree.SubElement(self.graph, 'g')
is_expanded = (self.expanded or self.expand_greatest)
diameter = min(self.graph_width, self.graph_height)
@ -183,7 +178,7 @@ class Pie(Graph):
yoff = (self.height - self.border_bottom - diameter)
yoff -= 10 * int(self.show_shadow)
transform = 'translate(%(xoff)s %(yoff)s)' % vars()
self.graph.setAttribute('transform', transform)
self.graph.set('transform', transform)
wedge_text_pad = 5
wedge_text_pad = 20 * int(self.show_percent) * int(self.show_data_labels)
@ -214,14 +209,14 @@ class Pie(Graph):
"%(x_end)s %(y_end)s Z"))
path = path % vars()
wedge = self._create_element(
wedge = etree.SubElement(
self.foreground,
'path',
dict({
{
'd': path,
'class': 'fill%s' % (index+1),
})
}
)
self.foreground.appendChild(wedge)
translate = None
tx = 0
@ -230,38 +225,35 @@ class Pie(Graph):
radians = half_percent * rad_mult
if self.show_shadow:
shadow = self._create_element(
shadow = etree.SubElement(
background,
'path',
dict(
d=path,
filter='url(#dropshadow)',
style='fill: #ccc; stroke: none',
)
)
background.appendChild(shadow)
clear = self._create_element(
clear = etree.SubElement(
midground,
'path',
dict(
d=path,
# note, this probably only works when the background
# is also #fff
# consider getting the style from the stylesheet
style="fill:#fff; stroke:none;",
)
)
midground.appendChild(clear)
if self.expanded or (self.expand_greatest and value == max_value):
tx = (math.sin(radians) * self.expand_gap)
ty = -(math.cos(radians) * self.expand_gap)
translate = "translate(%(tx)s %(ty)s)" % vars()
wedge.setAttribute('transform', translate)
clear.setAttribute('transform', translate)
wedge.set('transform', translate)
clear.set('transform', translate)
if self.show_shadow:
shadow_tx = self.shadow_offset + tx
shadow_ty = self.shadow_offset + ty
translate = 'translate(%(shadow_tx)s %(shadow_ty)s)' % vars()
shadow.setAttribute('transform', translate)
shadow.set('transform', translate)
if self.show_data_labels and value != 0:
label = []
@ -282,28 +274,28 @@ class Pie(Graph):
tx += (msr * self.expand_gap)
ty -= (mcr * self.expand_gap)
label_node = self._create_element(
label_node = etree.SubElement(
self.foreground,
'text',
dict({
{
'x':str(tx),
'y':str(ty),
'class':'dataPointLabel',
'style':'stroke: #fff; stroke-width: 2;',
})
}
)
label_node.appendChild(self._doc.createTextNode(label))
self.foreground.appendChild(label_node)
label_node.text = label
label_node = self._create_element(
label_node = etree.SubElement(
self.foreground,
'text',
dict({
{
'x':str(tx),
'y':str(ty),
'class': 'dataPointLabel',
})
}
)
label_node.appendChild(self._doc.createTextNode(label))
self.foreground.appendChild(label_node)
label_node.text = label
prev_percent += percent

23
src/svg/charts/plot.py

@ -1,6 +1,8 @@
#!/usr/bin/env python
from svg.charts.graph import Graph
from itertools import izip, count, chain
from lxml import etree
from svg.charts.graph import Graph
from util import float_range
@ -236,15 +238,13 @@ class Plot(Graph):
lpath = self.get_lpath(graph_points)
if self.area_fill:
graph_height = self.graph_height
path = self._create_element('path', {
path = etree.SubElement(self.graph, 'path', {
'd': 'M%(x_start)f %(graph_height)f %(lpath)s V%(graph_height)f Z' % vars(),
'class': 'fill%(line)d' % vars()})
self.graph.appendChild(path)
if self.draw_lines_between_points:
path = self._create_element('path', {
path = etree.SubElement(self.graph, 'path', {
'd': 'M%(x_start)f %(y_start)f %(lpath)s' % vars(),
'class': 'line%(line)d' % vars()})
self.graph.appendChild(path)
self.draw_data_points(line, data_points, graph_points)
self._draw_constant_lines()
del self.__transform_parameters
@ -261,18 +261,16 @@ class Plot(Graph):
"Draw a constant line on the y-axis with the label"
start = self.transform_output_coordinates((0, value))[1]
stop = self.graph_width
path = self._create_element('path', {
path = etree.SubElement(self.graph, 'path', {
'd': 'M 0 %(start)s h%(stop)s' % vars(),
'class': 'constantLine'})
if style:
path['style'] = style
self.graph.appendChild(path)
text = self._create_element('text', {
path.set('style', style)
text = etree.SubElement(self.graph, 'text', {
'x': str(2),
'y': str(start - 2),
'class': 'constantLine'})
text.appendChild(self._doc.createTextNode(label))
self.graph.appendChild(text)
text.text = label
def load_transform_parameters(self):
"Cache the parameters necessary to transform x & y coordinates"
@ -308,12 +306,11 @@ class Plot(Graph):
and not self.show_data_values: return
for ((dx,dy),(gx,gy)) in izip(data_points, graph_points):
if self.show_data_points:
circle = self._create_element('circle', {
etree.SubElement(self.graph, 'circle', {
'cx': str(gx),
'cy': str(gy),
'r': '2.5',
'class': 'dataPoint%(line)s' % vars()})
self.graph.appendChild(circle)
if self.show_data_values:
self.add_popup(gx, gy, self.format(dx, dy))
self.make_datapoint_text(gx, gy-6, dy)

6
src/svg/charts/schedule.py

@ -3,6 +3,7 @@ import re
from dateutil.parser import parse
from dateutil.relativedelta import relativedelta
from lxml import etree
from svg.charts.graph import Graph
from util import grouper, date_range, divide_timedelta_float, TimeScale
@ -236,14 +237,13 @@ class Schedule(Graph):
bar_width = scale*(x_end-x_start)
bar_start = scale*(x_start-x_min)
rect = self._create_element('rect', {
etree.SubElement(self.graph, 'rect', {
'x': str(bar_start),
'y': str(y),
'width': str(bar_width),
'height': str(subbar_height),
'class': 'fill%s' % (count+1), # TODO: doublecheck that +1 is correct (that's what's in the Ruby code)
'class': 'fill%s' % (count+1),
})
self.graph.appendChild(rect)
def _x_range(self):

Loading…
Cancel
Save