diff options
Diffstat (limited to 'gn2/utility/svg.py')
-rw-r--r-- | gn2/utility/svg.py | 1179 |
1 files changed, 1179 insertions, 0 deletions
diff --git a/gn2/utility/svg.py b/gn2/utility/svg.py new file mode 100644 index 00000000..912cd04c --- /dev/null +++ b/gn2/utility/svg.py @@ -0,0 +1,1179 @@ +# Copyright (C) University of Tennessee Health Science Center, Memphis, TN. +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU Affero General Public License for more details. +# +# This program is available from Source Forge: at GeneNetwork Project +# (sourceforge.net/projects/genenetwork/). +# +# Contact Drs. Robert W. Williams and Xiaodong Zhou (2010) +# at rwilliams@uthsc.edu and xzhou15@uthsc.edu +# +# +# +# This module is used by GeneNetwork project (www.genenetwork.org) +# +# Created by GeneNetwork Core Team 2010/08/10 +# +# Last updated by GeneNetwork Core Team 2010/10/20 + +#!/usr/bin/env python +# Copyright (c) 2002, Fedor Baart & Hans de Wit (Stichting Farmaceutische Kengetallen) +# All rights reserved. +## +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +## +# Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +## +# Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation and/or +# other materials provided with the distribution. +## +# Neither the name of the Stichting Farmaceutische Kengetallen nor the names of +# its contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. +## +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# Thanks to Gerald Rosennfellner for his help and useful comments. + +import sys +import exceptions +__doc__ = """Use SVGdraw to generate your SVGdrawings. + +SVGdraw uses an object model drawing and a method toXML to create SVG graphics +by using easy to use classes and methods usualy you start by creating a drawing eg + + d=drawing() + # then you create a SVG root element + s=svg() + # then you add some elements eg a circle and add it to the svg root element + c=circle() + # you can supply attributes by using named arguments. + c=circle(fill='red',stroke='blue') + # or by updating the attributes attribute: + c.attributes['stroke-width']=1 + s.addElement(c) + # then you add the svg root element to the drawing + d.setSVG(s) + # and finaly you xmlify the drawing + d.toXml() + + +this results in the svg source of the drawing, which consists of a circle +on a white background. Its as easy as that;) +This module was created using the SVG specification of www.w3c.org and the +O'Reilly (www.oreilly.com) python books as information sources. A svg viewer +is available from www.adobe.com""" + +__version__ = "1.0" + +# there are two possibilities to generate svg: +# via a dom implementation and directly using <element>text</element> strings +# the latter is way faster (and shorter in coding) +# the former is only used in debugging svg programs +# maybe it will be removed alltogether after a while +# with the following variable you indicate whether to use the dom implementation +# Note that PyXML is required for using the dom implementation. +# It is also possible to use the standard minidom. But I didn't try that one. +# Anyway the text based approach is about 60 times faster than using the full dom implementation. +use_dom_implementation = 0 + + +if use_dom_implementation != 0: + try: + from xml.dom import implementation + from xml.dom.ext import PrettyPrint + except: + raise exceptions.ImportError( + "PyXML is required for using the dom implementation") +# The implementation is used for the creating the XML document. +# The prettyprint module is used for converting the xml document object to a xml file + +sys.setrecursionlimit = 50 +# The recursion limit is set conservative so mistakes like s=svg() s.addElement(s) +# won't eat up too much processor time. + +# the following code is pasted form xml.sax.saxutils +# it makes it possible to run the code without the xml sax package installed +# To make it possible to have <rubbish> in your text elements, it is necessary to escape the texts + + +def _escape(data, entities={}): + """Escape &, <, and > in a string of data. + + You can escape other strings of data by passing a dictionary as + the optional entities parameter. The keys and values must all be + strings; each key will be replaced with its corresponding value. + """ + # data = data.replace("&", "&") + data = data.replace("<", "<") + data = data.replace(">", ">") + for chars, entity in list(entities.items()): + data = data.replace(chars, entity) + return data + + +def _quoteattr(data, entities={}): + """Escape and quote an attribute value. + + Escape &, <, and > in a string of data, then quote it for use as + an attribute value. The \" character will be escaped as well, if + necessary. + + You can escape other strings of data by passing a dictionary as + the optional entities parameter. The keys and values must all be + strings; each key will be replaced with its corresponding value. + """ + data = _escape(data, entities) + if '"' in data: + if "'" in data: + data = '"%s"' % data.replace('"', """) + else: + data = "'%s'" % data + else: + data = '"%s"' % data + return data + + +def _xypointlist(a): + """formats a list of xy pairs""" + s = '' + for e in a: # this could be done more elegant + s += str(e)[1:-1] + ' ' + return s + + +def _viewboxlist(a): + """formats a tuple""" + s = '' + for e in a: + s += str(e) + ' ' + return s + + +def _pointlist(a): + """formats a list of numbers""" + return str(a)[1:-1] + + +class pathdata: + """class used to create a pathdata object which can be used for a path. + although most methods are pretty straightforward it might be useful to look at the SVG specification.""" + # I didn't test the methods below. + + def __init__(self, x=None, y=None): + self.path = [] + if x is not None and y is not None: + self.path.append('M ' + str(x) + ' ' + str(y)) + + def closepath(self): + """ends the path""" + self.path.append('z') + + def move(self, x, y): + """move to absolute""" + self.path.append('M ' + str(x) + ' ' + str(y)) + + def relmove(self, x, y): + """move to relative""" + self.path.append('m ' + str(x) + ' ' + str(y)) + + def line(self, x, y): + """line to absolute""" + self.path.append('L ' + str(x) + ' ' + str(y)) + + def relline(self, x, y): + """line to relative""" + self.path.append('l ' + str(x) + ' ' + str(y)) + + def hline(self, x): + """horizontal line to absolute""" + self.path.append('H' + str(x)) + + def relhline(self, x): + """horizontal line to relative""" + self.path.append('h' + str(x)) + + def vline(self, y): + """verical line to absolute""" + self.path.append('V' + str(y)) + + def relvline(self, y): + """vertical line to relative""" + self.path.append('v' + str(y)) + + def bezier(self, x1, y1, x2, y2, x, y): + """bezier with xy1 and xy2 to xy absolut""" + self.path.append('C' + str(x1) + ',' + str(y1) + ' ' + str(x2) + + ',' + str(y2) + ' ' + str(x) + ',' + str(y)) + + def relbezier(self, x1, y1, x2, y2, x, y): + """bezier with xy1 and xy2 to xy relative""" + self.path.append('c' + str(x1) + ',' + str(y1) + ' ' + str(x2) + + ',' + str(y2) + ' ' + str(x) + ',' + str(y)) + + def smbezier(self, x2, y2, x, y): + """smooth bezier with xy2 to xy absolut""" + self.path.append('S' + str(x2) + ',' + str(y2) + \ + ' ' + str(x) + ',' + str(y)) + + def relsmbezier(self, x2, y2, x, y): + """smooth bezier with xy2 to xy relative""" + self.path.append('s' + str(x2) + ',' + str(y2) + \ + ' ' + str(x) + ',' + str(y)) + + def qbezier(self, x1, y1, x, y): + """quadratic bezier with xy1 to xy absolut""" + self.path.append('Q' + str(x1) + ',' + str(y1) + \ + ' ' + str(x) + ',' + str(y)) + + def relqbezier(self, x1, y1, x, y): + """quadratic bezier with xy1 to xy relative""" + self.path.append('q' + str(x1) + ',' + str(y1) + \ + ' ' + str(x) + ',' + str(y)) + + def smqbezier(self, x, y): + """smooth quadratic bezier to xy absolut""" + self.path.append('T' + str(x) + ',' + str(y)) + + def relsmqbezier(self, x, y): + """smooth quadratic bezier to xy relative""" + self.path.append('t' + str(x) + ',' + str(y)) + + def ellarc(self, rx, ry, xrot, laf, sf, x, y): + """elliptival arc with rx and ry rotating with xrot using large-arc-flag and sweep-flag to xy absolut""" + self.path.append('A' + str(rx) + ',' + str(ry) + ' ' + str(xrot) + + ' ' + str(laf) + ' ' + str(sf) + ' ' + str(x) + ' ' + str(y)) + + def relellarc(self, rx, ry, xrot, laf, sf, x, y): + """elliptival arc with rx and ry rotating with xrot using large-arc-flag and sweep-flag to xy relative""" + self.path.append('a' + str(rx) + ',' + str(ry) + ' ' + str(xrot) + + ' ' + str(laf) + ' ' + str(sf) + ' ' + str(x) + ' ' + str(y)) + + def __repr__(self): + return ' '.join(self.path) + + +class SVGelement: + """SVGelement(type,attributes,elements,text,namespace,**args) + Creates a arbitrary svg element and is intended to be subclassed not used on its own. + This element is the base of every svg element it defines a class which resembles + a xml-element. The main advantage of this kind of implementation is that you don't + have to create a toXML method for every different graph object. Every element + consists of a type, attribute, optional subelements, optional text and an optional + namespace. Note the elements==None, if elements = None:self.elements=[] construction. + This is done because if you default to elements=[] every object has a reference + to the same empty list.""" + + def __init__(self, type='', attributes=None, elements=None, text='', namespace='', cdata=None, **args): + self.type = type + if attributes == None: + self.attributes = {} + else: + self.attributes = attributes + if elements == None: + self.elements = [] + else: + self.elements = elements + self.text = text + self.namespace = namespace + self.cdata = cdata + for arg in list(args.keys()): + arg2 = arg.replace("__", ":") + arg2 = arg2.replace("_", "-") + self.attributes[arg2] = args[arg] + + def addElement(self, SVGelement): + """adds an element to a SVGelement + + SVGelement.addElement(SVGelement) + """ + self.elements.append(SVGelement) + + def toXml(self, level, f): + f.write('\t' * level) + f.write('<' + self.type) + for attkey in list(self.attributes.keys()): + f.write(' ' + _escape(str(attkey)) + '=' + + _quoteattr(str(self.attributes[attkey]))) + if self.namespace: + f.write(' xmlns="' + _escape(str(self.namespace)) + + '" xmlns:xlink="http://www.w3.org/1999/xlink"') + if self.elements or self.text or self.cdata: + f.write('>') + if self.elements: + f.write('\n') + for element in self.elements: + element.toXml(level + 1, f) + if self.cdata: + f.write('\n' + '\t' * (level + 1) + '<![CDATA[') + for line in self.cdata.splitlines(): + f.write('\n' + '\t' * (level + 2) + line) + f.write('\n' + '\t' * (level + 1) + ']]>\n') + if self.text: + if isinstance(self.text, type('')): # If the text is only text + f.write(_escape(str(self.text))) + else: # If the text is a spannedtext class + f.write(str(self.text)) + if self.elements: + f.write('\t' * level + '</' + self.type + '>\n') + elif self.text: + f.write('</' + self.type + '>\n') + elif self.cdata: + f.write('\t' * level + '</' + self.type + '>\n') + else: + f.write('/>\n') + + +class tspan(SVGelement): + """ts=tspan(text='',**args) + + a tspan element can be used for applying formatting to a textsection + usage: + ts=tspan('this text is bold') + ts.attributes['font-weight']='bold' + st=spannedtext() + st.addtspan(ts) + t=text(3,5,st) + """ + + def __init__(self, text=None, **args): + SVGelement.__init__(self, 'tspan', **args) + if self.text != None: + self.text = text + + def __repr__(self): + s = "<tspan" + for key, value in list(self.attributes.items()): + s += ' %s="%s"' % (key, value) + s += '>' + s += self.text + s += '</tspan>' + return s + + +class tref(SVGelement): + """tr=tref(link='',**args) + + a tref element can be used for referencing text by a link to its id. + usage: + tr=tref('#linktotext') + st=spannedtext() + st.addtref(tr) + t=text(3,5,st) + """ + + def __init__(self, link, **args): + SVGelement.__init__(self, 'tref', {'xlink:href': link}, **args) + + def __repr__(self): + s = "<tref" + + for key, value in list(self.attributes.items()): + s += ' %s="%s"' % (key, value) + s += '/>' + return s + + +class spannedtext: + """st=spannedtext(textlist=[]) + + a spannedtext can be used for text which consists of text, tspan's and tref's + You can use it to add to a text element or path element. Don't add it directly + to a svg or a group element. + usage: + + ts=tspan('this text is bold') + ts.attributes['font-weight']='bold' + tr=tref('#linktotext') + tr.attributes['fill']='red' + st=spannedtext() + st.addtspan(ts) + st.addtref(tr) + st.addtext('This text is not bold') + t=text(3,5,st) + """ + + def __init__(self, textlist=None): + if textlist == None: + self.textlist = [] + else: + self.textlist = textlist + + def addtext(self, text=''): + self.textlist.append(text) + + def addtspan(self, tspan): + self.textlist.append(tspan) + + def addtref(self, tref): + self.textlist.append(tref) + + def __repr__(self): + s = "" + for element in self.textlist: + s += str(element) + return s + + +class rect(SVGelement): + """r=rect(width,height,x,y,fill,stroke,stroke_width,**args) + + a rectangle is defined by a width and height and a xy pair + """ + + def __init__(self, x=None, y=None, width=None, height=None, fill=None, stroke=None, stroke_width=None, **args): + if width == None or height == None: + raise ValueError('both height and width are required') + + SVGelement.__init__( + self, 'rect', {'width': width, 'height': height}, **args) + if x != None: + self.attributes['x'] = x + if y != None: + self.attributes['y'] = y + if fill != None: + self.attributes['fill'] = fill + if stroke != None: + self.attributes['stroke'] = stroke + if stroke_width != None: + self.attributes['stroke-width'] = stroke_width + + +class ellipse(SVGelement): + """e=ellipse(rx,ry,x,y,fill,stroke,stroke_width,**args) + + an ellipse is defined as a center and a x and y radius. + """ + + def __init__(self, cx=None, cy=None, rx=None, ry=None, fill=None, stroke=None, stroke_width=None, **args): + if rx == None or ry == None: + raise ValueError('both rx and ry are required') + + SVGelement.__init__(self, 'ellipse', {'rx': rx, 'ry': ry}, **args) + if cx != None: + self.attributes['cx'] = cx + if cy != None: + self.attributes['cy'] = cy + if fill != None: + self.attributes['fill'] = fill + if stroke != None: + self.attributes['stroke'] = stroke + if stroke_width != None: + self.attributes['stroke-width'] = stroke_width + + +class circle(SVGelement): + """c=circle(x,y,radius,fill,stroke,stroke_width,**args) + + The circle creates an element using a x, y and radius values eg + """ + + def __init__(self, cx=None, cy=None, r=None, fill=None, stroke=None, stroke_width=None, **args): + if r == None: + raise ValueError('r is required') + SVGelement.__init__(self, 'circle', {'r': r}, **args) + if cx != None: + self.attributes['cx'] = cx + if cy != None: + self.attributes['cy'] = cy + if fill != None: + self.attributes['fill'] = fill + if stroke != None: + self.attributes['stroke'] = stroke + if stroke_width != None: + self.attributes['stroke-width'] = stroke_width + + +class point(circle): + """p=point(x,y,color) + + A point is defined as a circle with a size 1 radius. It may be more efficient to use a + very small rectangle if you use many points because a circle is difficult to render. + """ + + def __init__(self, x, y, fill='black', **args): + circle.__init__(self, x, y, 1, fill, **args) + + +class line(SVGelement): + """l=line(x1,y1,x2,y2,stroke,stroke_width,**args) + + A line is defined by a begin x,y pair and an end x,y pair + """ + + def __init__(self, x1=None, y1=None, x2=None, y2=None, stroke=None, stroke_width=None, **args): + SVGelement.__init__(self, 'line', **args) + if x1 != None: + self.attributes['x1'] = x1 + if y1 != None: + self.attributes['y1'] = y1 + if x2 != None: + self.attributes['x2'] = x2 + if y2 != None: + self.attributes['y2'] = y2 + if stroke_width != None: + self.attributes['stroke-width'] = stroke_width + if stroke != None: + self.attributes['stroke'] = stroke + + +class polyline(SVGelement): + """pl=polyline([[x1,y1],[x2,y2],...],fill,stroke,stroke_width,**args) + + a polyline is defined by a list of xy pairs + """ + + def __init__(self, points, fill=None, stroke=None, stroke_width=None, **args): + SVGelement.__init__(self, 'polyline', { + 'points': _xypointlist(points)}, **args) + if fill != None: + self.attributes['fill'] = fill + if stroke_width != None: + self.attributes['stroke-width'] = stroke_width + if stroke != None: + self.attributes['stroke'] = stroke + + +class polygon(SVGelement): + """pl=polyline([[x1,y1],[x2,y2],...],fill,stroke,stroke_width,**args) + + a polygon is defined by a list of xy pairs + """ + + def __init__(self, points, fill=None, stroke=None, stroke_width=None, **args): + SVGelement.__init__( + self, 'polygon', {'points': _xypointlist(points)}, **args) + if fill != None: + self.attributes['fill'] = fill + if stroke_width != None: + self.attributes['stroke-width'] = stroke_width + if stroke != None: + self.attributes['stroke'] = stroke + + +class path(SVGelement): + """p=path(path,fill,stroke,stroke_width,**args) + + a path is defined by a path object and optional width, stroke and fillcolor + """ + + def __init__(self, pathdata, fill=None, stroke=None, stroke_width=None, id=None, **args): + SVGelement.__init__(self, 'path', {'d': str(pathdata)}, **args) + if stroke != None: + self.attributes['stroke'] = stroke + if fill != None: + self.attributes['fill'] = fill + if stroke_width != None: + self.attributes['stroke-width'] = stroke_width + if id != None: + self.attributes['id'] = id + + +class text(SVGelement): + """t=text(x,y,text,font_size,font_family,**args) + + a text element can bge used for displaying text on the screen + """ + + def __init__(self, x=None, y=None, text=None, font_size=None, font_family=None, text_anchor=None, **args): + SVGelement.__init__(self, 'text', **args) + if x != None: + self.attributes['x'] = x + if y != None: + self.attributes['y'] = y + if font_size != None: + self.attributes['font-size'] = font_size + if font_family != None: + self.attributes['font-family'] = font_family + if text != None: + self.text = text + if text_anchor != None: + self.attributes['text-anchor'] = text_anchor + + +class textpath(SVGelement): + """tp=textpath(text,link,**args) + + a textpath places a text on a path which is referenced by a link. + """ + + def __init__(self, link, text=None, **args): + SVGelement.__init__(self, 'textPath', {'xlink:href': link}, **args) + if text != None: + self.text = text + + +class pattern(SVGelement): + """p=pattern(x,y,width,height,patternUnits,**args) + + A pattern is used to fill or stroke an object using a pre-defined + graphic object which can be replicated ("tiled") at fixed intervals + in x and y to cover the areas to be painted. + """ + + def __init__(self, x=None, y=None, width=None, height=None, patternUnits=None, **args): + SVGelement.__init__(self, 'pattern', **args) + if x != None: + self.attributes['x'] = x + if y != None: + self.attributes['y'] = y + if width != None: + self.attributes['width'] = width + if height != None: + self.attributes['height'] = height + if patternUnits != None: + self.attributes['patternUnits'] = patternUnits + + +class title(SVGelement): + """t=title(text,**args) + + a title is a text element. The text is displayed in the title bar + add at least one to the root svg element + """ + + def __init__(self, text=None, **args): + SVGelement.__init__(self, 'title', **args) + if text != None: + self.text = text + + +class description(SVGelement): + """d=description(text,**args) + + a description can be added to any element and is used for a tooltip + Add this element before adding other elements. + """ + + def __init__(self, text=None, **args): + SVGelement.__init__(self, 'desc', **args) + if text != None: + self.text = text + + +class lineargradient(SVGelement): + """lg=lineargradient(x1,y1,x2,y2,id,**args) + + defines a lineargradient using two xy pairs. + stop elements van be added to define the gradient colors. + """ + + def __init__(self, x1=None, y1=None, x2=None, y2=None, id=None, **args): + SVGelement.__init__(self, 'linearGradient', **args) + if x1 != None: + self.attributes['x1'] = x1 + if y1 != None: + self.attributes['y1'] = y1 + if x2 != None: + self.attributes['x2'] = x2 + if y2 != None: + self.attributes['y2'] = y2 + if id != None: + self.attributes['id'] = id + + +class radialgradient(SVGelement): + """rg=radialgradient(cx,cy,r,fx,fy,id,**args) + + defines a radial gradient using a outer circle which are defined by a cx,cy and r and by using a focalpoint. + stop elements van be added to define the gradient colors. + """ + + def __init__(self, cx=None, cy=None, r=None, fx=None, fy=None, id=None, **args): + SVGelement.__init__(self, 'radialGradient', **args) + if cx != None: + self.attributes['cx'] = cx + if cy != None: + self.attributes['cy'] = cy + if r != None: + self.attributes['r'] = r + if fx != None: + self.attributes['fx'] = fx + if fy != None: + self.attributes['fy'] = fy + if id != None: + self.attributes['id'] = id + + +class stop(SVGelement): + """st=stop(offset,stop_color,**args) + + Puts a stop color at the specified radius + """ + + def __init__(self, offset, stop_color=None, **args): + SVGelement.__init__(self, 'stop', {'offset': offset}, **args) + if stop_color != None: + self.attributes['stop-color'] = stop_color + + +class style(SVGelement): + """st=style(type,cdata=None,**args) + + Add a CDATA element to this element for defing in line stylesheets etc.. + """ + + def __init__(self, type, cdata=None, **args): + SVGelement.__init__(self, 'style', {'type': type}, cdata=cdata, **args) + + +class image(SVGelement): + """im=image(url,width,height,x,y,**args) + + adds an image to the drawing. Supported formats are .png, .jpg and .svg. + """ + + def __init__(self, url, x=None, y=None, width=None, height=None, **args): + if width == None or height == None: + raise ValueError('both height and width are required') + SVGelement.__init__( + self, 'image', {'xlink:href': url, 'width': width, 'height': height}, **args) + if x != None: + self.attributes['x'] = x + if y != None: + self.attributes['y'] = y + + +class cursor(SVGelement): + """c=cursor(url,**args) + + defines a custom cursor for a element or a drawing + """ + + def __init__(self, url, **args): + SVGelement.__init__(self, 'cursor', {'xlink:href': url}, **args) + + +class marker(SVGelement): + """m=marker(id,viewbox,refX,refY,markerWidth,markerHeight,**args) + + defines a marker which can be used as an endpoint for a line or other pathtypes + add an element to it which should be used as a marker. + """ + + def __init__(self, id=None, viewBox=None, refx=None, refy=None, markerWidth=None, markerHeight=None, **args): + SVGelement.__init__(self, 'marker', **args) + if id != None: + self.attributes['id'] = id + if viewBox != None: + self.attributes['viewBox'] = _viewboxlist(viewBox) + if refx != None: + self.attributes['refX'] = refx + if refy != None: + self.attributes['refY'] = refy + if markerWidth != None: + self.attributes['markerWidth'] = markerWidth + if markerHeight != None: + self.attributes['markerHeight'] = markerHeight + + +class group(SVGelement): + """g=group(id,**args) + + a group is defined by an id and is used to contain elements + g.addElement(SVGelement) + """ + + def __init__(self, id=None, **args): + SVGelement.__init__(self, 'g', **args) + if id != None: + self.attributes['id'] = id + + +class symbol(SVGelement): + """sy=symbol(id,viewbox,**args) + + defines a symbol which can be used on different places in your graph using + the use element. A symbol is not rendered but you can use 'use' elements to + display it by referencing its id. + sy.addElement(SVGelement) + """ + + def __init__(self, id=None, viewBox=None, **args): + SVGelement.__init__(self, 'symbol', **args) + if id != None: + self.attributes['id'] = id + if viewBox != None: + self.attributes['viewBox'] = _viewboxlist(viewBox) + + +class defs(SVGelement): + """d=defs(**args) + + container for defining elements + """ + + def __init__(self, **args): + SVGelement.__init__(self, 'defs', **args) + + +class switch(SVGelement): + """sw=switch(**args) + + Elements added to a switch element which are "switched" by the attributes + requiredFeatures, requiredExtensions and systemLanguage. + Refer to the SVG specification for details. + """ + + def __init__(self, **args): + SVGelement.__init__(self, 'switch', **args) + + +class use(SVGelement): + """u=use(link,x,y,width,height,**args) + + references a symbol by linking to its id and its position, height and width + """ + + def __init__(self, link, x=None, y=None, width=None, height=None, **args): + SVGelement.__init__(self, 'use', {'xlink:href': link}, **args) + if x != None: + self.attributes['x'] = x + if y != None: + self.attributes['y'] = y + + if width != None: + self.attributes['width'] = width + if height != None: + self.attributes['height'] = height + + +class link(SVGelement): + """a=link(url,**args) + + a link is defined by a hyperlink. add elements which have to be linked + a.addElement(SVGelement) + """ + + def __init__(self, link='', **args): + SVGelement.__init__(self, 'a', {'xlink:href': link}, **args) + + +class view(SVGelement): + """v=view(id,**args) + + a view can be used to create a view with different attributes""" + + def __init__(self, id=None, **args): + SVGelement.__init__(self, 'view', **args) + if id != None: + self.attributes['id'] = id + + +class script(SVGelement): + """sc=script(type,type,cdata,**args) + + adds a script element which contains CDATA to the SVG drawing + + """ + + def __init__(self, type, cdata=None, **args): + SVGelement.__init__( + self, 'script', {'type': type}, cdata=cdata, **args) + + +class animate(SVGelement): + """an=animate(attribute,from,to,during,**args) + + animates an attribute. + """ + + def __init__(self, attribute, fr=None, to=None, dur=None, **args): + SVGelement.__init__( + self, 'animate', {'attributeName': attribute}, **args) + if fr != None: + self.attributes['from'] = fr + if to != None: + self.attributes['to'] = to + if dur != None: + self.attributes['dur'] = dur + + +class animateMotion(SVGelement): + """an=animateMotion(pathdata,dur,**args) + + animates a SVGelement over the given path in dur seconds + """ + + def __init__(self, pathdata, dur, **args): + SVGelement.__init__(self, 'animateMotion', **args) + if pathdata != None: + self.attributes['path'] = str(pathdata) + if dur != None: + self.attributes['dur'] = dur + + +class animateTransform(SVGelement): + """antr=animateTransform(type,from,to,dur,**args) + + transform an element from and to a value. + """ + + def __init__(self, type=None, fr=None, to=None, dur=None, **args): + SVGelement.__init__(self, 'animateTransform', { + 'attributeName': 'transform'}, **args) + # As far as I know the attributeName is always transform + if type != None: + self.attributes['type'] = type + if fr != None: + self.attributes['from'] = fr + if to != None: + self.attributes['to'] = to + if dur != None: + self.attributes['dur'] = dur + + +class animateColor(SVGelement): + """ac=animateColor(attribute,type,from,to,dur,**args) + + Animates the color of a element + """ + + def __init__(self, attribute, type=None, fr=None, to=None, dur=None, **args): + SVGelement.__init__(self, 'animateColor', { + 'attributeName': attribute}, **args) + if type != None: + self.attributes['type'] = type + if fr != None: + self.attributes['from'] = fr + if to != None: + self.attributes['to'] = to + if dur != None: + self.attributes['dur'] = dur + + +class set(SVGelement): + """st=set(attribute,to,during,**args) + + sets an attribute to a value for a + """ + + def __init__(self, attribute, to=None, dur=None, **args): + SVGelement.__init__(self, 'set', {'attributeName': attribute}, **args) + if to != None: + self.attributes['to'] = to + if dur != None: + self.attributes['dur'] = dur + + +class svg(SVGelement): + """s=svg(viewbox,width,height,**args) + + a svg or element is the root of a drawing add all elements to a svg element. + You can have different svg elements in one svg file + s.addElement(SVGelement) + + eg + d=drawing() + s=svg((0,0,100,100),'100%','100%') + c=circle(50,50,20) + s.addElement(c) + d.setSVG(s) + d.toXml() + """ + + def __init__(self, viewBox=None, width=None, height=None, **args): + SVGelement.__init__(self, 'svg', **args) + if viewBox != None: + self.attributes['viewBox'] = _viewboxlist(viewBox) + if width != None: + self.attributes['width'] = width + if height != None: + self.attributes['height'] = height + self.namespace = "http://www.w3.org/2000/svg" + + +class drawing: + """d=drawing() + + this is the actual SVG document. It needs a svg element as a root. + Use the addSVG method to set the svg to the root. Use the toXml method to write the SVG + source to the screen or to a file + d=drawing() + d.addSVG(svg) + d.toXml(optionalfilename) + """ + + def __init__(self, entity={}): + self.svg = None + self.entity = entity + + def setSVG(self, svg): + self.svg = svg + # Voeg een element toe aan de grafiek toe. + if use_dom_implementation == 0: + def toXml(self, filename='', compress=False): + import io + xml = io.StringIO() + xml.write("<?xml version='1.0' encoding='UTF-8'?>\n") + xml.write( + "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\" \"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\"") + if self.entity: + xml.write(" [\n") + for item in list(self.entity.keys()): + xml.write("<!ENTITY %s \"%s\">\n" % + (item, self.entity[item])) + xml.write("]") + xml.write(">\n") + self.svg.toXml(0, xml) + if not filename: + if compress: + import gzip + f = io.StringIO() + zf = gzip.GzipFile(fileobj=f, mode='wb') + zf.write(xml.getvalue()) + zf.close() + f.seek(0) + return f.read() + else: + return xml.getvalue() + else: + if filename[-4:] == 'svgz': + import gzip + f = gzip.GzipFile(filename=filename, + mode="wb", compresslevel=9) + f.write(xml.getvalue()) + f.close() + else: + f = file(filename, 'w') + f.write(xml.getvalue()) + f.close() + + else: + def toXml(self, filename='', compress=False): + """drawing.toXml() ---->to the screen + drawing.toXml(filename)---->to the file + writes a svg drawing to the screen or to a file + compresses if filename ends with svgz or if compress is true + """ + doctype = implementation.createDocumentType( + 'svg', "-//W3C//DTD SVG 1.0//EN""", 'http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd ') + + global root + # root is defined global so it can be used by the appender. Its also possible to use it as an arugument but + # that is a bit messy. + root = implementation.createDocument(None, None, doctype) + # Create the xml document. + global appender + + def appender(element, elementroot): + """This recursive function appends elements to an element and sets the attributes + and type. It stops when alle elements have been appended""" + if element.namespace: + e = root.createElementNS(element.namespace, element.type) + else: + e = root.createElement(element.type) + if element.text: + textnode = root.createTextNode(element.text) + e.appendChild(textnode) + # in element.attributes is supported from python 2.2 + for attribute in list(element.attributes.keys()): + e.setAttribute(attribute, str( + element.attributes[attribute])) + if element.elements: + for el in element.elements: + e = appender(el, e) + elementroot.appendChild(e) + return elementroot + root = appender(self.svg, root) + if not filename: + import io + xml = io.StringIO() + PrettyPrint(root, xml) + if compress: + import gzip + f = io.StringIO() + zf = gzip.GzipFile(fileobj=f, mode='wb') + zf.write(xml.getvalue()) + zf.close() + f.seek(0) + return f.read() + else: + return xml.getvalue() + else: + try: + if filename[-4:] == 'svgz': + import gzip + import io + xml = io.StringIO() + PrettyPrint(root, xml) + f = gzip.GzipFile(filename=filename, + mode='wb', compresslevel=9) + f.write(xml.getvalue()) + f.close() + else: + f = open(filename, 'w') + PrettyPrint(root, f) + f.close() + except: + print(("Cannot write SVG file: " + filename)) + + def validate(self): + try: + import xml.parsers.xmlproc.xmlval + except: + raise exceptions.ImportError( + 'PyXml is required for validating SVG') + svg = self.toXml() + xv = xml.parsers.xmlproc.xmlval.XMLValidator() + try: + xv.feed(svg) + except: + raise Exception("SVG is not well formed, see messages above") + else: + print("SVG well formed") + + +if __name__ == '__main__': + + d = drawing() + s = svg((0, 0, 100, 100)) + r = rect(-100, -100, 300, 300, 'cyan') + s.addElement(r) + + t = title('SVGdraw Demo') + s.addElement(t) + g = group('animations') + e = ellipse(0, 0, 5, 2) + g.addElement(e) + c = circle(0, 0, 1, 'red') + g.addElement(c) + pd = pathdata(0, -10) + for i in range(6): + pd.relsmbezier(10, 5, 0, 10) + pd.relsmbezier(-10, 5, 0, 10) + an = animateMotion(pd, 10) + an.attributes['rotate'] = 'auto-reverse' + an.attributes['repeatCount'] = "indefinite" + g.addElement(an) + s.addElement(g) + for i in range(20, 120, 20): + u = use('#animations', i, 0) + s.addElement(u) + for i in range(0, 120, 20): + for j in range(5, 105, 10): + c = circle(i, j, 1, 'red', 'black', .5) + s.addElement(c) + d.setSVG(s) + + print((d.toXml())) |