Browse Source

Moved acknowledgements into main readme file

pull/8/head
jaraco 15 years ago
parent
commit
4b78b18761
  1. 18
      readme.txt
  2. 248
      svg/charts/graph.py

18
readme.txt

@ -6,7 +6,8 @@
Status and License Status and License
------------------ ------------------
``svg.charts`` is a port of the SVG::Graph Ruby package by Sean E. Russel. ``svg.charts`` is a pure-python library for generating charts and graphs
in SVG, originally based on the SVG::Graph Ruby package by Sean E. Russel.
``svg.charts`` supercedes ``svg_charts`` 1.1 and 1.2. ``svg.charts`` supercedes ``svg_charts`` 1.1 and 1.2.
@ -19,6 +20,21 @@ You can install it with ``easy_install svg.charts``, or from the
<https://py-svg.svn.sourceforge.net/svnroot/py-svg/trunk#egg=svg.charts-dev>`_ with <https://py-svg.svn.sourceforge.net/svnroot/py-svg/trunk#egg=svg.charts-dev>`_ with
``easy_install svg.charts==dev``. ``easy_install svg.charts==dev``.
Acknowledgements
----------------
``svg.charts`` depends heavily on lxml and cssutils. Thanks to the
contributors of those projects for stable, performant, standards-based
packages.
Sean E. Russel for creating the SVG::Graph Ruby package from which this
Python port was originally derived.
Leo Lapworth for creating the SVG::TT::Graph package which the Ruby
port was based on.
Stephen Morgan for creating the TT template and SVG.
Getting Started Getting Started
--------------- ---------------

248
svg/charts/graph.py

@ -23,43 +23,22 @@ def sort_multiple(arrays):
class Graph(object): class Graph(object):
""" """
=== Base object for generating SVG Graphs Base object for generating SVG Graphs
== Synopsis Synopsis
This class is only used as a superclass of specialized charts. Do not
attempt to use this class directly, unless creating a new chart type.
For examples of how to subclass this class, see the existing specific
subclasses, such as svn.charts.Pie.
== Description
This package should be used as a base for creating SVG graphs.
== Acknowledgements
Sean E. Russel for creating the SVG::Graph Ruby package from which this
Python port is derived.
Leo Lapworth for creating the SVG::TT::Graph package which the Ruby
port is based on.
Stephen Morgan for creating the TT template and SVG. This class is only used as a superclass of specialized charts. Do not
attempt to use this class directly, unless creating a new chart type.
== See For examples of how to subclass this class, see the existing specific
subclasses, such as svn.charts.Pie.
* svn.charts.bar * svg.charts.bar
* svg.charts.line * svg.charts.line
* svg.charts.pie * svg.charts.pie
* svg.charts.plot * svg.charts.plot
* svg.charts.time_series * svg.charts.time_series
== Author
Jason R. Coombs <jaraco@jaraco.com>
Copyright © 2008 Jason R. Coombs
""" """
width= 500 width= 500
height= 300 height= 300
@ -118,8 +97,9 @@ class Graph(object):
self.__dict__.update(config) self.__dict__.update(config)
def add_data(self, conf): def add_data(self, conf):
"""This method allows you do add data to the graph object. """
It can be called several times to add more data sets in. Add data to the graph object. May be called several times to add
additional data sets.
>>> data_sales_02 = [12, 45, 21] # doctest: +SKIP >>> data_sales_02 = [12, 45, 21] # doctest: +SKIP
>>> graph.add_data({ # doctest: +SKIP >>> graph.add_data({ # doctest: +SKIP
@ -143,7 +123,8 @@ class Graph(object):
def process_data(self, data): pass def process_data(self, data): pass
def clear_data(self): def clear_data(self):
"""This method removes all data from the object so that you can """
This method removes all data from the object so that you can
reuse it to create a new graph but with the same config options. reuse it to create a new graph but with the same config options.
>>> graph.clear_data() # doctest: +SKIP >>> graph.clear_data() # doctest: +SKIP
@ -152,13 +133,11 @@ class Graph(object):
def burn(self): def burn(self):
""" """
This method processes the template with the data and Process the template with the data and
config which has been set and returns the resulting SVG. config which has been set and return the resulting SVG.
This method will croak unless at least one data set has Raises ValueError when no data set has
been added to the graph object. been added to the graph object.
Ex: graph.burn()
""" """
if not self.data: raise ValueError("No data available") if not self.data: raise ValueError("No data available")
@ -190,8 +169,10 @@ class Graph(object):
KEY_BOX_SIZE = 12 KEY_BOX_SIZE = 12
def calculate_left_margin(self): def calculate_left_margin(self):
"""Override this (and call super) to change the margin to the left """
of the plot area. Results in border_left being set.""" Calculates the margin to the left of the plot area, setting
border_left.
"""
bl = 7 bl = 7
# Check for Y labels # Check for Y labels
if self.rotate_y_labels: if self.rotate_y_labels:
@ -206,14 +187,18 @@ class Graph(object):
self.border_left = bl self.border_left = bl
def max_y_label_width_px(self): def max_y_label_width_px(self):
"""Calculates the width of the widest Y label. This will be the """
character height if the Y labels are rotated.""" Calculate the width of the widest Y label. This will be the
character height if the Y labels are rotated.
"""
if self.rotate_y_labels: if self.rotate_y_labels:
return self.font_size return self.font_size
def calculate_right_margin(self): def calculate_right_margin(self):
"""Override this (and call super) to change the margin to the right """
of the plot area. Results in border_right being set.""" Calculate the margin in pixels to the right of the plot area,
setting border_right.
"""
br = 7 br = 7
if self.key and self.key_position == 'right': if self.key and self.key_position == 'right':
max_key_len = max(map(len, self.keys())) max_key_len = max(map(len, self.keys()))
@ -223,15 +208,19 @@ class Graph(object):
self.border_right = br self.border_right = br
def calculate_top_margin(self): def calculate_top_margin(self):
"""Override this (and call super) to change the margin to the top """
of the plot area. Results in border_top being set.""" Calculate the margin in pixels above the plot area, setting
border_top.
"""
self.border_top = 5 self.border_top = 5
if self.show_graph_title: self.border_top += self.title_font_size if self.show_graph_title: self.border_top += self.title_font_size
self.border_top += 5 self.border_top += 5
if self.show_graph_subtitle: self.border_top += self.subtitle_font_size if self.show_graph_subtitle: self.border_top += self.subtitle_font_size
def add_popup(self, x, y, label): def add_popup(self, x, y, label):
"Adds pop-up point information to a graph." """
Add pop-up information to a point on the graph.
"""
txt_width = len(label) * self.font_size * 0.6 + 10 txt_width = len(label) * self.font_size * 0.6 + 10
tx = x + [5,-5][int(x+txt_width > self.width)] tx = x + [5,-5][int(x+txt_width > self.width)]
anchor = ['start', 'end'][x+txt_width > self.width] anchor = ['start', 'end'][x+txt_width > self.width]
@ -259,8 +248,10 @@ class Graph(object):
}) })
def calculate_bottom_margin(self): def calculate_bottom_margin(self):
"""Override this (and call super) to change the margin to the bottom """
of the plot area. Results in border_bottom being set.""" Calculate the margin in pixels below the plot area, setting
border_bottom.
"""
bb = 7 bb = 7
if self.key and self.key_position == 'bottom': if self.key and self.key_position == 'bottom':
bb += len(self.data) * (self.font_size + 5) bb += len(self.data) * (self.font_size + 5)
@ -277,6 +268,11 @@ class Graph(object):
self.border_bottom = bb self.border_bottom = bb
def draw_graph(self): def draw_graph(self):
"""
The central logic for drawing the graph.
Sets self.graph (the 'g' element in the SVG root)
"""
transform = 'translate (%s %s)' % (self.border_left, self.border_top) transform = 'translate (%s %s)' % (self.border_left, self.border_top)
self.graph = etree.SubElement(self.root, 'g', transform=transform) self.graph = etree.SubElement(self.root, 'g', transform=transform)
@ -304,28 +300,35 @@ class Graph(object):
self.draw_y_labels() self.draw_y_labels()
def x_label_offset(self, width): def x_label_offset(self, width):
"""Where in the X area the label is drawn """
Centered in the field, should be width/2. Start, 0.""" Return an offset for drawing the x label. Currently returns 0.
"""
# consider width/2 for centering the labels
return 0 return 0
def make_datapoint_text(self, x, y, value, style=''): def make_datapoint_text(self, x, y, value, style=None):
if self.show_data_values: """
# first lay down the text in a wide white stroke to Add text for a datapoint
# differentiate it from the background """
e = etree.SubElement(self.foreground, 'text', { if not self.show_data_values:
'x': str(x), # do nothing
'y': str(y), return
'class': 'dataPointLabel', # first lay down the text in a wide white stroke to
'style': '%(style)s stroke: #fff; stroke-width: 2;' % vars(), # differentiate it from the background
}) e = etree.SubElement(self.foreground, 'text', {
e.text = str(value) 'x': str(x),
# then lay down the text in the specified style 'y': str(y),
e = etree.SubElement(self.foreground, 'text', { 'class': 'dataPointLabel',
'x': str(x), 'style': '%(style)s stroke: #fff; stroke-width: 2;' % vars(),
'y': str(y), })
'class': 'dataPointLabel'}) e.text = str(value)
e.text = str(value) # then lay down the text in the specified style
if style: e.set('style', style) e = etree.SubElement(self.foreground, 'text', {
'x': str(x),
'y': str(y),
'class': 'dataPointLabel'})
e.text = str(value)
if style: e.set('style', style)
def draw_x_labels(self): def draw_x_labels(self):
"Draw the X axis labels" "Draw the X axis labels"
@ -370,8 +373,10 @@ class Graph(object):
text.set('style', 'text-anchor: middle') text.set('style', 'text-anchor: middle')
def y_label_offset(self, height): def y_label_offset(self, height):
"""Where in the Y area the label is drawn """
Centered in the field, should be width/2. Start, 0.""" Return an offset for drawing the y label. Currently returns 0.
"""
# Consider height/2 to center within the field.
return 0 return 0
def get_field_width(self): def get_field_width(self):
@ -386,18 +391,20 @@ class Graph(object):
def draw_y_labels(self): def draw_y_labels(self):
"Draw the Y axis labels" "Draw the Y axis labels"
if self.show_y_labels: if not self.show_y_labels:
labels = self.get_y_labels() # do nothing
count = len(labels) return
labels = enumerate(iter(labels)) labels = self.get_y_labels()
start = int(not self.step_include_first_y_label) count = len(labels)
labels = islice(labels, start, None, self.step_y_labels)
map(self.draw_y_label, labels) labels = enumerate(iter(labels))
self.draw_y_guidelines(self.field_height(), count) start = int(not self.step_include_first_y_label)
labels = islice(labels, start, None, self.step_y_labels)
map(self.draw_y_label, labels)
self.draw_y_guidelines(self.field_height(), count)
def get_y_offset(self): def get_y_offset(self):
#result = self.graph_height + self.y_label_offset(label_height)
result = self.graph_height + self.y_label_offset(self.field_height()) result = self.graph_height + self.y_label_offset(self.field_height())
if not self.rotate_y_labels: result += self.font_size/1.2 if not self.rotate_y_labels: result += self.font_size/1.2
return result return result
@ -513,30 +520,33 @@ class Graph(object):
return map(itemgetter('title'), self.data) return map(itemgetter('title'), self.data)
def draw_legend(self): def draw_legend(self):
if self.key: if not self.key:
group = etree.SubElement(self.root, 'g') # do nothing
return
for key_count, key_name in enumerate(self.keys()):
y_offset = (self.KEY_BOX_SIZE * key_count) + (key_count * 5) group = etree.SubElement(self.root, 'g')
etree.SubElement(group, 'rect', {
'x': '0', for key_count, key_name in enumerate(self.keys()):
'y': str(y_offset), y_offset = (self.KEY_BOX_SIZE * key_count) + (key_count * 5)
'width': str(self.KEY_BOX_SIZE), etree.SubElement(group, 'rect', {
'height': str(self.KEY_BOX_SIZE), 'x': '0',
'class': 'key%s' % (key_count + 1), 'y': str(y_offset),
}) 'width': str(self.KEY_BOX_SIZE),
text = etree.SubElement(group, 'text', { 'height': str(self.KEY_BOX_SIZE),
'x': str(self.KEY_BOX_SIZE + 5), 'class': 'key%s' % (key_count + 1),
'y': str(y_offset + self.KEY_BOX_SIZE), })
'class': 'keyText'}) text = etree.SubElement(group, 'text', {
text.text = key_name 'x': str(self.KEY_BOX_SIZE + 5),
'y': str(y_offset + self.KEY_BOX_SIZE),
if self.key_position == 'right': 'class': 'keyText'})
x_offset = self.graph_width + self.border_left + 10 text.text = key_name
y_offset = self.border_top + 20
if self.key_position == 'bottom': if self.key_position == 'right':
x_offset, y_offset = self.calculate_offsets_bottom() x_offset = self.graph_width + self.border_left + 10
group.set('transform', 'translate(%(x_offset)d %(y_offset)d)' % vars()) y_offset = self.border_top + 20
if self.key_position == 'bottom':
x_offset, y_offset = self.calculate_offsets_bottom()
group.set('transform', 'translate(%(x_offset)d %(y_offset)d)' % vars())
def calculate_offsets_bottom(self): def calculate_offsets_bottom(self):
x_offset = self.border_left + 20 x_offset = self.border_left + 20
@ -556,18 +566,23 @@ class Graph(object):
def render_inline_styles(self): def render_inline_styles(self):
"Hard-code the styles into the SVG XML if style sheets are not used." "Hard-code the styles into the SVG XML if style sheets are not used."
if self.css_inline: if not self.css_inline:
styles = self.parse_css() # do nothing
for node in xpath.Evaluate('//*[@class]', self.root): return
cl = node.getAttribute('class')
style = styles[cl] styles = self.parse_css()
if node.hasAttribute('style'): for node in xpath.Evaluate('//*[@class]', self.root):
style += node.getAttribute('style') cl = node.getAttribute('class')
node.setAttribute('style', style) style = styles[cl]
if node.hasAttribute('style'):
style += node.getAttribute('style')
node.setAttribute('style', style)
def parse_css(self): def parse_css(self):
"""Take a .css file (classes only please) and parse it into a dictionary """
of class/style pairs.""" Take a .css file (classes only please) and parse it into a dictionary
of class/style pairs.
"""
# todo: save the prefs for use later # todo: save the prefs for use later
#orig_prefs = cssutils.ser.prefs #orig_prefs = cssutils.ser.prefs
cssutils.ser.prefs.useMinified() cssutils.ser.prefs.useMinified()
@ -576,8 +591,9 @@ class Graph(object):
return result return result
def add_defs(self, defs): def add_defs(self, defs):
"Override and place code to add defs here" """
pass Override and place code to add defs here. TODO: what are defs?
"""
def start_svg(self): def start_svg(self):
"Base SVG Document Creation" "Base SVG Document Creation"

Loading…
Cancel
Save