#! /usr/bin/env python """ swfcharts.py Version: 1.0 Wrapper class for XML/SWF Charts http://www.maani.us/xml_charts/index.php Written by: Justin Israel (justinisrael@gmail.com) June 2009 """ import re from collections import defaultdict ############################################################# ############################################################# ######## SwfChart ############################################################# ############################################################# class SwfChartException(Exception): """ Exception for SwfChart class """ pass class SwfChart(object) : """ SUMMARY: A wrapper class around the XML/SWF Charts XML api. Represents the attributes and data model for a graph and will output the proper XML data to pass into the flash SWF Charts application. All class properties correspond to the existing properties from the XML api, and return the formatted XML code based on the attributes that were set with the methods. Once you set all your desired settings using the methods, the class can be printed or treated like a string to represent the entire XML document, or you can call getXML() Some of the methods take a very large list of optional key/value arguments. The available args are listed in each method, but for detailed descriptions of the attributes, please refer to the XML Charts documentation: http://www.maani.us/xml_charts/index.php?menu=Reference """ def __init__(self): """ __init__() """ self._setProperties = [] self._properties = defaultdict(str) self._rowLength = 0 self._colLength = 0 def __str__(self): """ The class instance string representation is the complete XML output, from all currently set attributes. """ chart = [''] for prop in self._setProperties: chart.append(getattr(self, prop)) chart.append('') chartStr = '\n'.join(chart) chartStr = re.sub(r'\n+', r'\n', chartStr) return chartStr def _setPropertySet(self, prop): """ ACTION: Indicate that a property has been set, and should be included when the XML is printed INPUT: str prop - a property of the class, that is being marked as set OUTPUT: NONE """ if not prop in self._setProperties: self._setProperties.append(prop) def _setPropertyFromDict(self, prop, **d): """ ACTION: Private method takes a dictionary of kwargs, sets them as XML under the given property name, and marks the property as set for the final XML output INPUT: str prop - a property to set the values of dict d - key/value pairs to set as attributes in the XML tag OUTPUT: NONE """ if not d: return chartStr = '\t<%s' % prop for k,v in d.iteritems(): chartStr += "\n\t\t %s='%s'" % (str(k), str(v)) chartStr += '\n\t\t />' self._properties[prop] = chartStr self._setPropertySet(prop) ######################### # Properties # # Each property reflects the properties in the XML Charts api # and returns the block of xml formatted from setting the methods # @property def license(self): return self._properties['license'] @property def chart_border(self): return self._properties['chart_border'] @property def chart_data(self): """ Returns the formatted xml section """ rows = self._properties['rows'] if not rows: return '' cols = self._properties['cols'] if self._colLength < self._rowLength: # there must be the same number of cols as row data for i in xrange(self._rowLength - self._colLength): cols += "\n\t\t\tUndef" colStr = "\t\t\n\t\t\t\n%s\n\t\t" % cols chartData = ["\t"] chartData.append(colStr) chartData.append(rows) chartData.append("\t") return '\n'.join(chartData) @property def chart_grid_h(self): return self._properties['chart_grid_h'] @property def chart_grid_v(self): return self._properties['chart_grid_v'] @property def chart_guide(self): return self._properties['chart_guide'] @property def chart_label(self): return self._properties['chart_label'] @property def chart_note(self): return self._properties['chart_note'] @property def chart_pref(self): return self._properties['chart_pref'] @property def chart_rect(self): return self._properties['chart_rect'] @property def chart_transition(self): return self._properties['chart_transition'] @property def chart_type(self): return self._properties['chart_type'] @property def series(self): return self._properties['series'] @property def series_color(self): return self._properties['series_color'] @property def series_explode(self): return self._properties['series_explode'] @property def axis_category(self): return self._properties['axis_category'] @property def axis_category_label(self): return self._properties['axis_category_label'] @property def axis_ticks(self): return self._properties['axis_ticks'] @property def axis_value(self): return self._properties['axis_value'] @property def axis_value_label(self): return self._properties['axis_value_label'] @property def draw(self): """ returns the formatted XML element """ dStr = self._properties['drawElements'] if not dStr: return '' drawData = ['\t'] drawData.append(dStr) drawData.append('\t') drawStr = '\n'.join(drawData) return drawStr @property def filter(self): """ returns the formatted XML element """ fStr = self._properties['filters'] if not fStr: return '' filterData = ['\t'] filterData.append(fStr) filterData.append('\t') filterStr = '\n'.join(filterData) return filterStr @property def context_menu(self): return self._properties['context_menu'] @property def legend(self): return self._properties['legend'] @property def link(self): """ returns the formatted XML element """ lStr = self._properties['links'] if not lStr: return '' linkData = ['\t'] linkData.append(lStr) linkData.append('\t') linkStr = '\n'.join(linkData) return linkStr @property def link_data(self): return self._properties['link_data'] @property def scroll(self): return self._properties['scroll'] @property def tooltip(self): return self._properties['tooltip'] @property def update(self): return self._properties['update'] # End Properties ######################### def addColumnLabel(self, *labels): """ ACTION: Add one or more string labels for the columns in the graph. There needs to be enough column labels to correspond to the length of the data in the rows. Otherwise, they will be labeled "Undef". INPUT: str label, [str label], [...] OUTPUT: NONE """ self._colLength += len(labels) colData = [] for label in labels: colData.append("\t\t\t%s" % label) colStr = '\n'.join(colData) self._properties['cols'] = '\n'.join( (self._properties['cols'], colStr) ) def addDrawElement(self, element, **kwargs): """ ACTION: Creates a new draw element in the graph. Draw elements are a few defined graphic objects that can be inserted into either the background or foreground of the graph. The draw elements each have their own respective attributes for controlling how and where they are displayed. For element type 'text', specify the text with the key 'value'. So, value='some text' Reference the API doc for details on each elements types attributes. INPUT: str element - (circle, image, line, rect, text, button) Key=Value - attributes for the draw element type OUTPUT: NONE """ elementTypes = ('circle', 'image', 'line', 'rect', 'text', 'button') if not element in elementTypes: raise SwfChartException( "Element type '%s' is not one of the valid types in: %s" % (element, elementTypes)) chartStr = '\t\t<%s' % element for k,v in kwargs.iteritems(): if not k=='url': val = str(v).lower() else: val = str(v) chartStr += "\n\t\t\t %s='%s'" % (k, val) if element == 'text': chartStr += '\n\t\t\t>%s' % kwargs.get('value', '') else: chartStr += '\n\t\t\t />' self._properties['drawElements'] = '\n'.join( (self._properties['drawElements'], chartStr) ) self._setPropertySet('draw') def addFilter(self, filterType, id, **kwargs): """ ACTION: Adds a new filter to the list of defined filters. Valid filter types are shadow, bevel, glow, and blur. You can defined multiple types of each, and give them unique ids, to be referenced in other methods. Each filter type has its own set of key/value attributes, so refer to the API doc for specifics. INPUT: str filterType - 'shadow', 'bevel', 'glow', or 'blur' str id - a unique string id to be known as ('shadow1') key = value - attributes for the specific filter type OUTPUT: NONE """ filterTypes = ('shadow', 'bevel', 'glow', 'blur') if not filterType in filterTypes: raise SwfChartException( "mode '%s' is not one of the allowed types: %s" % (filterType, filterTypes)) chartStr = '\t\t<%s' % filterType chartStr += "\n\t\t\t id='%s'" % id for k,v in kwargs.iteritems(): chartStr += "\n\t\t\t %s='%s'" % (k, str(v).lower()) chartStr += '\n\t\t\t />' self._properties['filters'] = '\n'.join( (self._properties['filters'], chartStr) ) self._setPropertySet('filter') def addLink(self, **kwargs): """ ACTION: holds any number of areas, each defining a rectangle and a URL to go to when the user clicks inside the rectangle. This can also be used to assign functions to mouse clicks, including chart updates, printing, etc INPUT: int x - x position of upper left corner of rectangle int y - y position of upper left corner of rectange int width - width of rectangle int height - height of rectable str url - relative or absolute URL to go to when clicked int priority - determined how to handle links that overlap if 0, priority is determined by order of links if 1, link will be activated during overlap str target - where to open the URL when clicked Window options: (_self, _blank, _parent, _top) Other options: (update, print, toggle_fullscreen, scroll) * If any other value is specified, the URL will be opened in a window named . If it does not exist, it will be created first. str tooltip - tooltip text to display over link * Valid if target = 'update' int timeout - time to wait for update before failing; seconds int retry - number of times to retry when failing bool spinning_wheel - if True, show a spinning wheel while updating OUTPUT: NONE """ chartStr = '\t\t'] rowData.append("\t\t\t%s" % label) for item in data: if isinstance(item, dict): valStr = '' if not item.has_key('value'): raise SwfChartException, 'data dict argument must at least have a "value" key for the value' value = item['value'] for k,v in item.iteritems(): if k=='value': continue valStr += " %s='%s'" % (k,v) if value != None: lineStr = "\t\t\t%s" % (valStr, value) rowData.append(lineStr) else: rowData.append("\t\t\t") # rowData.append("\t\t\t0") else: if item == None: rowData.append("\t\t\t") # rowData.append("\t\t\t0") else: rowData.append("\t\t\t%s" % item) rowData.append('\t\t') rowStr = '\n'.join(rowData) self._properties['rows'] = '\n'.join( (self._properties['rows'], rowStr) ) self._setPropertySet('chart_data') def clearDrawElements(self): """ ACTION: Clear the current draw elements INPUT: NONE OUTPUT: NONE """ self._properties['drawElements'] = '' if 'draw' in self._setProperties: self._setProperties.remove('draw') def clearFilters(self): """ ACTION: Clear the current filters INPUT: NONE OUTPUT: NONE """ self._properties['filters'] = '' if 'filter' in self._setProperties: self._setProperties.remove('filter') def clearLinks(self): """ ACTION: Clear the current links INPUT: NONE OUTPUT: NONE """ self._properties['links'] = '' if 'link' in self._setProperties: self._setProperties.remove('link') def clearRows(self): """ ACTION: Clear the current rows data INPUT: NONE OUTPUT: NONE """ self._properties['rows'] = '' if 'chart_data' in self._setProperties: self._setProperties.remove('chart_data') def getXML(self): """ ACTION: Return the final formatted XML of all attributes, as a string Same as treating the class instance as a string. INPUT: NONE OUTPUT: NONE """ return str(self) def setLicense(self, lic): """ ACTION: set the license code for the chart, if you have one INPUT: str lic - license reg code OUTPUT : NONE """ chartStr = '\t%s' % lic self._properties['license'] = chartStr self._setPropertySet('license') def setChartBorder(self, top=0, bottom=0, left=0, right=0, color='000000'): """ ACTION: Set the border thickness and color for the chart INPUT: int top - thickness of top int bottom - thickness of bottom int left - thickness of left int right - thickness of right str color - hex color ('000000', or 'FFFFFF' format) OUTPUT : NONE """ chartStr = '\t