Programmatically generate SVG (vector) images, animations, and interactive Jupyter widgets
1# drawSvg 2 3A Python 3 library for programmatically generating SVG images (vector drawings) and rendering them or displaying them in an iPython notebook. 4 5Most common SVG tags are supported and others can easily be added by writing a small subclass of `DrawableBasicElement` or `DrawableParentElement`. 6 7An interactive Jupyter widget, `drawSvg.widgets.DrawingWidget`, is included that can update drawings based on mouse events. 8 9# Install 10drawSvg is available on PyPI: 11 12``` 13$ pip3 install drawSvg 14``` 15 16# Examples 17 18### Basic drawing elements 19```python 20import drawSvg as draw 21 22d = draw.Drawing(200, 100, origin='center') 23 24d.append(draw.Lines(-80, -45, 25 70, -49, 26 95, 49, 27 -90, 40, 28 close=False, 29 fill='#eeee00', 30 stroke='black')) 31 32d.append(draw.Rectangle(0,0,40,50, fill='#1248ff')) 33d.append(draw.Circle(-40, -10, 30, 34 fill='red', stroke_width=2, stroke='black')) 35 36p = draw.Path(stroke_width=2, stroke='green', 37 fill='black', fill_opacity=0.5) 38p.M(-30,5) # Start path at point (-30, 5) 39p.l(60,30) # Draw line to (60, 30) 40p.h(-70) # Draw horizontal line to x=-70 41p.Z() # Draw line to start 42d.append(p) 43 44d.append(draw.ArcLine(60,-20,20,60,270, 45 stroke='red', stroke_width=5, fill='red', fill_opacity=0.2)) 46d.append(draw.Arc(60,-20,20,60,270,cw=False, 47 stroke='green', stroke_width=3, fill='none')) 48d.append(draw.Arc(60,-20,20,270,60,cw=True, 49 stroke='blue', stroke_width=1, fill='black', fill_opacity=0.3)) 50 51d.setPixelScale(2) # Set number of pixels per geometry unit 52#d.setRenderSize(400,200) # Alternative to setPixelScale 53d.saveSvg('example.svg') 54d.savePng('example.png') 55 56# Display in iPython notebook 57d.rasterize() # Display as PNG 58d # Display as SVG 59``` 60 61![Example output image](https://raw.githubusercontent.com/cduck/drawSvg/master/examples/example1.png) 62 63### Gradients 64```python 65import drawSvg as draw 66 67d = draw.Drawing(1.5, 0.8, origin='center') 68 69d.draw(draw.Rectangle(-0.75,-0.5,1.5,1, fill='#ddd')) 70 71# Create gradient 72gradient = draw.RadialGradient(0,-0.35,0.7*10) 73gradient.addStop(0.5/0.7/10, 'green', 1) 74gradient.addStop(1/10, 'red', 0) 75 76# Draw a shape to fill with the gradient 77p = draw.Path(fill=gradient, stroke='black', stroke_width=0.002) 78p.arc(0,-0.35,0.7,30,120) 79p.arc(0,-0.35,0.5,120,30,cw=True, includeL=True) 80p.Z() 81d.append(p) 82 83# Draw another shape to fill with the same gradient 84p = draw.Path(fill=gradient, stroke='red', stroke_width=0.002) 85p.arc(0,-0.35,0.75,130,160) 86p.arc(0,-0.35,0,160,130,cw=True, includeL=True) 87p.Z() 88d.append(p) 89 90# Another gradient 91gradient2 = draw.LinearGradient(0.1,-0.35,0.1+0.6,-0.35+0.2) 92gradient2.addStop(0, 'green', 1) 93gradient2.addStop(1, 'red', 0) 94d.append(draw.Rectangle(0.1,-0.35,0.6,0.2, 95 stroke='black', stroke_width=0.002, 96 fill=gradient2)) 97 98# Display 99d.setRenderSize(w=600) 100d 101``` 102 103![Example output image](https://raw.githubusercontent.com/cduck/drawSvg/master/examples/example2.png) 104 105### Duplicate geometry and clip paths 106```python 107import drawSvg as draw 108 109d = draw.Drawing(1.4, 1.4, origin='center') 110 111# Define clip path 112clip = draw.ClipPath() 113clip.append(draw.Rectangle(-.25,.25-1,1,1)) 114 115# Draw a cropped circle 116c = draw.Circle(0,0,0.5, stroke_width='0.01', stroke='black', 117 fill_opacity=0.3, clip_path=clip, 118 id='circle') 119d.append(c) 120 121# Make a transparent copy, cropped again 122g = draw.Group(opacity=0.5, clip_path=clip) 123g.append(draw.Use('circle', 0.25,0.1)) 124d.append(g) 125 126# Display 127d.setRenderSize(400) 128d.rasterize() 129``` 130 131![Example output image](https://raw.githubusercontent.com/cduck/drawSvg/master/examples/example3.png) 132 133### Implementing other SVG tags 134```python 135import drawSvg as draw 136 137# Subclass DrawingBasicElement if it cannot have child nodes 138# Subclass DrawingParentElement otherwise 139# Subclass DrawingDef if it must go between <def></def> tags in an SVG 140class Hyperlink(draw.DrawingParentElement): 141 TAG_NAME = 'a' 142 def __init__(self, href, target=None, **kwargs): 143 # Other init logic... 144 # Keyword arguments to super().__init__() correspond to SVG node 145 # arguments: stroke_width=5 -> stroke-width="5" 146 super().__init__(href=href, target=target, **kwargs) 147 148d = draw.Drawing(1, 1.2, origin='center') 149 150# Create hyperlink 151hlink = Hyperlink('https://www.python.org', target='_blank', 152 transform='skewY(-30)') 153# Add child elements 154hlink.append(draw.Circle(0,0,0.5, fill='green')) 155hlink.append(draw.Text('Hyperlink',0.2, 0,0, center=0.6, fill='white')) 156 157# Draw and display 158d.append(hlink) 159d.setRenderSize(200) 160d 161``` 162 163![Example output image](https://github.com/cduck/drawSvg/blob/master/examples/example4.svg) 164 165### Interactive Widget 166```python 167import drawSvg as draw 168from drawSvg.widgets import DrawingWidget 169import hyperbolic.poincare.shapes as hyper # pip3 install hyperbolic 170 171# Create drawing 172d = draw.Drawing(2, 2, origin='center') 173d.setRenderSize(500) 174d.append(draw.Circle(0, 0, 1, fill='orange')) 175group = draw.Group() 176d.append(group) 177 178# Update the drawing based on user input 179click_list = [] 180def redraw(points): 181 group.children.clear() 182 for x1, y1 in points: 183 for x2, y2 in points: 184 if (x1, y1) == (x2, y2): continue 185 p1 = hyper.Point.fromEuclid(x1, y1) 186 p2 = hyper.Point.fromEuclid(x2, y2) 187 if p1.distanceTo(p2) <= 2: 188 line = hyper.Line.fromPoints(*p1, *p2, segment=True) 189 group.draw(line, hwidth=0.2, fill='white') 190 for x, y in points: 191 p = hyper.Point.fromEuclid(x, y) 192 group.draw(hyper.Circle.fromCenterRadius(p, 0.1), 193 fill='green') 194redraw(click_list) 195 196# Create interactive widget and register mouse events 197widget = DrawingWidget(d) 198@widget.mousedown 199def mousedown(widget, x, y, info): 200 if (x**2 + y**2) ** 0.5 + 1e-5 < 1: 201 click_list.append((x, y)) 202 redraw(click_list) 203 widget.refresh() 204@widget.mousemove 205def mousemove(widget, x, y, info): 206 if (x**2 + y**2) ** 0.5 + 1e-5 < 1: 207 redraw(click_list + [(x, y)]) 208 widget.refresh() 209widget 210``` 211 212![Example output image](https://raw.githubusercontent.com/cduck/drawSvg/master/examples/example5.gif) 213 214### Animation 215```python 216import drawSvg as draw 217 218# Draw a frame of the animation 219def draw_frame(t): 220 d = draw.Drawing(2, 6.05, origin=(-1,-1.05)) 221 d.setRenderSize(h=300) 222 d.append(draw.Rectangle(-2, -2, 4, 8, fill='white')) 223 d.append(draw.Rectangle(-1, -1.05, 2, 0.05, fill='brown')) 224 t = (t + 1) % 2 - 1 225 y = 4 - t**2 * 4 226 d.append(draw.Circle(0, y, 1, fill='lime')) 227 return d 228 229with draw.animate_jupyter(draw_frame, delay=0.05) as anim: 230# Or: 231#with draw.animate_video('example6.gif', draw_frame, duration=0.05 232# ) as anim: 233 # Add each frame to the animation 234 for i in range(20): 235 anim.draw_frame(i/10) 236 for i in range(20): 237 anim.draw_frame(i/10) 238 for i in range(20): 239 anim.draw_frame(i/10) 240``` 241 242![Example output image](https://raw.githubusercontent.com/cduck/drawSvg/master/examples/example6.gif) 243