Thursday, 28 June 2012

Drawing vector diagrams with Python

What's the easiest way to draw nice vector diagrams with Python? I was asked this question some time ago by a bioinformatician who was moving from Perl to Python, and previously used the gd library. gd was widely used back in the day, but looks a bit clunky now.

Here's the solution I came up with, to use SVG through PySVG. It's pure-Python and so doesn't require any extra libraries, which is always a pain when dealing with graphics.

I used the code below to generate an SVG file, which was converted to the above PNG with Inkscape:
"""
Insert description here
"""
import random
import pdb
##from pysvg.filter import *
##from pysvg.gradient import *
##from pysvg.linking import *
##from pysvg.script import *
##from pysvg.shape import *
##from pysvg.structure import *
##from pysvg.style import *
from pysvg.text import *
from pysvg.builders import svg, ShapeBuilder, StyleBuilder
class Drawing(object):
def __init__(self):
self.canvas = svg()
def rectangle(self, *args, **kwargs):
"""0, 0, 400, 200, 12, 12, strokewidth=2, stroke='navy')"""
sb = ShapeBuilder()
rect = sb.createRect(*args, **kwargs)
self.canvas.addElement(rect)
def line(self, *args, **kwargs):
"""0, 0, 400, 200, strokewidth=2, stroke='navy')"""
sb = ShapeBuilder()
line = sb.createLine(*args, **kwargs)
self.canvas.addElement(line)
def text(self, x, y, line, color="blue"):
sb = StyleBuilder()
sb.setFontFamily(fontfamily="Verdana")
sb.setFontSize('5em')
sb.setFilling(color)
t1 = text(line, x, y)
t1.set_style(sb.getStyle())
self.canvas.addElement(t1)
def arrow(self, x, y, width, height, forward=True, stroke="blue", fill="blue", strokewidth=1):
sb = ShapeBuilder()
arrowshape = [(0, 0), (width, 0), (width, -height*0.2),
(width*1.2, height*0.5), (width, height*1.2), (width, height), (0, height)]
if not forward: # Change the direction
arrowshape = [(width*1.2 - a, b) for (a, b) in arrowshape]
arrowshape = [(a+x, b+y) for (a, b) in arrowshape] # Translate the arrow
pl = sb.createPolygon(points=sb.convertTupleArrayToPoints(arrowshape),strokewidth=strokewidth, stroke=stroke, fill=fill)
self.canvas.addElement(pl)
def save(self, filename):
self.canvas.save(filename)
def main():
data = [["1","G","9","NUK", "3"], ["1","3","G","9", "NUK"]]
genenames = set(data[0]) | set(data[1])
positions = {}
for genename in genenames:
pos = []
for i in range(2):
if genename in data[i]:
pos.append(data[i].index(genename) + 1)
else:
pos.append(0)
positions[genename] = pos
drawing = Drawing()
for genename in genenames:
pos = positions[genename]
for i in range(2):
p = pos[i]
drawing.rectangle(p*100, i*100, 60, 20, 5, 5, strokewidth=1, fill="red", stroke="navy")
drawing.text(p*100+5, i*100+13, genename)
drawing.arrow(p*100, (i+2)*100, 60, 20, fill="red", forward=(random.random() > 0.5))
drawing.line(pos[0]*100+30, 0*100+20,
pos[1]*100+30, 1*100,
strokewidth="1", stroke="black")
drawing.save('genes.svg')
if __name__ == "__main__":
main()
view raw genes.py hosted with ❤ by GitHub
Anyone else got any suggestions?

1 comment:

Unknown said...

I get an error. Your code imports svg from pysvg.builders and builders.py never defines it...