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
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
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
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
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
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
243