1# Drawsvg Quick Reference
2
3Repository: [https://github.com/cduck/drawsvg](https://github.com/cduck/drawsvg)
4
5
6```python
7import drawsvg as dw
8```
9
10
11## Canvas and Document Structure
12
13```python
14d = dw.Drawing(width, height, origin=(0, 0),
15 context: drawsvg.types.Context = None, animation_config=None,
16 id_prefix='d', **svg_args)
17```
18
19It is recommended to use a unique `id_prefix` for each svg if you embed multiple on a web page.
20
21```python
22d = dw.Drawing(400, 300, id_prefix='pic')
23```
24
25
26## Basic Shapes
27
28### One Line
29
30```python
31dw.Line(sx, sy, ex, ey, **kwargs)
32```
33
34```python
35line = dw.Line(30, 30, 90, 90, stroke='black')
36d.append(line)
37```
38
39
40
41
42### Multiple Lines
43
44This is SVG's `polyline` (but drawsvg renders as path with multiple L).
45
46```python
47dw.Lines(sx, sy, *points, close=False, **kwargs)
48```
49
50```python
51lines = dw.Lines(10, 90, 10, 10, 80, 90, 80, 10,
52 fill='none', stroke='black')
53d.append(lines)
54```
55
56
57
58
59```python
60x = [30 + x*10 for x in range(20)]
61y = [80, 20]*10
62xy = [item for sublist in zip(x, y) for item in sublist]
63d.append(dw.Lines(*xy, stroke='black', stroke_width=5, fill='none'))
64```
65
66
67
68
69### Polygon
70
71SVG `Polygon` is drawsvg `Lines` with `close=True`.
72
73```python
74polygon = dw.Lines(15, 10, 55, 10, 45, 20, 5, 20,
75 fill='red', stroke='black', close='true')
76star = dw.Lines(48, 16, 16, 96, 96, 48, 0, 48, 88, 96,
77 stroke='black', fill='none', close='true')
78d.append(star)
79```
80
81
82
83
84### Rectangle
85
86```python
87dw.Rectangle(x, y, width, height, **kwargs)
88```
89
90```python
91# Black interior, no outline
92d.append(dw.Rectangle(10, 10, 90, 150))
93# No interior, black outline
94d.append(dw.Rectangle(120, 10, 60, 120,
95 fill='none', stroke='black'))
96# Blue interior, thick semi-transparent red outline
97d.append(dw.Rectangle(210, 10, 75, 90,
98 fill='#0000ff', stroke='red',
99 stroke_width=7, stroke_opacity=0.5))
100# Semi-transparent yellow interior, dashed green outline
101d.append(dw.Rectangle(300, 10, 105, 60,
102 fill='yellow', fill_opacity=0.5,
103 stroke='green', stroke_width=2,
104 stroke_dasharray='5,2'))
105```
106
107
108
109Rounded corners:
110
111```python
112# Define both rx and ry
113d.append(dw.Rectangle(10, 10, 80, 180, rx='10', ry='10',
114 stroke='black', fill='none'))
115# If only one is given, it applies to both
116d.append(dw.Rectangle(110, 10, 80, 180, ry='20',
117 stroke='black', fill='none'))
118d.append(dw.Rectangle(210, 10, 80, 180, rx='40',
119 stroke='black', fill='none'))
120# Rx and ry unequal
121d.append(dw.Rectangle(310, 10, 80, 180, rx='30', ry='10',
122 stroke='black', fill='none'))
123d.append(dw.Rectangle(410, 10, 80, 180, rx='10', ry='30',
124 stroke='black', fill='none'))
125```
126
127
128
129
130### Circle
131
132```python
133dw.Circle(cx, cy, r, **kwargs)
134```
135
136cx and cy point to circle's center, r refer to its radius
137
138```python
139d.append(dw.Circle(50, 50, 40))
140d.append(dw.Circle(150, 50, 40,
141 stroke='black', fill='none'))
142d.append(dw.Circle(250, 50, 40,
143 stroke='black', fill='none',
144 stroke_width=15))
145```
146
147
148
149
150### Ellipse
151
152```python
153dw.Ellipse(cx, cy, rx, ry, **kwarg)
154```
155(cx,cy) points to the center and (rx,ry) tells its radius
156
157```python
158d.append(dw.Ellipse(50, 50, 50, 30))
159d.append(dw.Ellipse(160, 50, 50, 30,
160 stroke='black', fill='none'))
161d.append(dw.Ellipse(250, 50, 30, 45,
162 stroke='black',fill='none'))
163```
164
165
166
167
168
169## Color and Painting Properties
170
171For a full list, see [W3C specifications](https://www.w3.org/TR/SVG11/styling.html).
172
173
174### fill and stroke\_color
175
176Some color keyword names are:
177aqua, black, blue, fuchsia, gray, green, lime, maroon, navy, olive, purple, red, silver, teal, white, and yellow.
178
179Also supported is `#rrggbb`, `#rgb` (hexadecimal), or `rgb(R,G,B)` with 0-255 or with 0-100% for each value.
180
181```python
182c = ['red', '#9f9', '#9999ff', 'rgb(255,128,64)', 'rgb(60%,20%,60%)']
183for i in range(5):
184 y = (i + 1)*10
185 d.append(dw.Line(10, y, 80, y,
186 stroke=c[i], stroke_width=5))
187```
188
189
190
191
192### fill\_opacity and stroke\_opacity
193
194Value range from 0 = transparent to 1 = solid.
195
196```python
197for i in range(5):
198 y = (i + 1)*10
199 d.append(dw.Line(0, y, 290, y,
200 stroke='black', stroke_width=5,
201 stroke_opacity=i/5 + 0.1))
202 d.append(dw.Rectangle(i*60, 70, 50, 50,
203 fill='red', fill_opacity=i/5+0.1))
204```
205
206
207
208
209### stroke\_dasharray
210
211```python
212# Nine-pixel dash, five-pixel gap
213d.append(dw.Line(10, 10, 100, 10,
214 stroke='black', stroke_width=2,
215 stroke_dasharray='9,5'))
216# Five-pixel dash, three-pixel gap, nine-pixel dash, two-pixel gap
217d.append(dw.Line(10, 20, 100, 20,
218 stroke='black', stroke_width=2,
219 stroke_dasharray='5,3,9,2'))
220# Odd number of entries alternates dashes and gaps
221d.append(dw.Line(10, 30, 100, 30,
222 stroke='black', stroke_width=2,
223 stroke_dasharray='9,3,5'))
224```
225
226
227
228
229### stroke\_width
230
231```python
232for i in range(20):
233 d.append(dw.Line((i+1)*15, 10, (i+1)*15, 90,
234 stroke='black', stroke_width=abs(10-i)+1))
235```
236
237
238
239
240### stroke\_linecap
241
242`stroke_linecap` can be set to `butt`, `round`, or `square`.
243Note that the latter two extend beyond the end coordinates.
244
245```python
246d.append(dw.Line(10, 15, 50, 15,
247 stroke='black', stroke_width=15,
248 stroke_linecap='butt'))
249d.append(dw.Line(10, 45, 50, 45,
250 stroke='black', stroke_width=15,
251 stroke_linecap='round'))
252d.append(dw.Line(10, 75, 50, 75,
253 stroke='black', stroke_width=15,
254 stroke_linecap='square'))
255# Guide lines
256d.append(dw.Lines(10, 0, 10, 100, stroke='#999'))
257d.append(dw.Lines(50, 0, 50, 100, stroke='#999'))
258```
259
260
261
262
263### stroke\_linejoin
264
265Define the way lines connect at a corner with `stroke-linejoin`: `miter` (pointed), `round`, or `bevel` (flat).
266
267```python
268d.append(dw.Line(0, 20, 300, 20, stroke='gray'))
269g = dw.Group(stroke_width=20, stroke='black', fill='none')
270g.append(dw.Lines(10, 80, 50, 20, 90, 80,
271 stroke_linejoin='miter'))
272g.append(dw.Lines(110, 80, 150, 20, 190, 80,
273 stroke_linejoin='round'))
274g.append(dw.Lines(210, 80, 250, 20, 290, 80,
275 stroke_linejoin='bevel'))
276d.append(g)
277```
278
279
280
281
282### stroke\_miterlimit
283
284When two line segments meet at a sharp angle and miter joins have been specified for `stroke-linejoin`,
285it is possible for the miter to extend far beyond the thickness of the line stroking the path.
286The `stroke-miterlimit` imposes a limit on the ratio of the miter length to the `stroke-width`.
287When the limit is exceeded, the join is converted from a miter to a bevel.
288(From [W3C doc](https://www.w3.org/TR/SVG11/painting.html#StrokeMiterlimitProperty))
289
290```python
291d.append(dw.Line(0, 30, 300, 30, stroke='gray'))
292g = dw.Group(stroke_width=20, stroke='black',
293 fill='none', stroke_linejoin='miter')
294g.append(dw.Lines(10, 90, 40, 30, 70, 90))
295g.append(dw.Lines(100, 90, 130, 30, 160, 90,
296 stroke_miterlimit=2.3))
297g.append(dw.Lines(190, 90, 220, 30, 250, 90,
298 stroke_miterlimit=1))
299d.append(g)
300```
301
302
303
304
305## Path
306
307```python
308path = dw.Path(**kwargs)
309```
310
311The following Path specifiers are also available as lowercase characters.
312In that case, their movements are relative to current location.
313
314
315### M: moveto
316
317```python
318path.M(x, y)
319```
320
321Move to `x, y` (and draw nothing).
322
323
324### L: lineto
325
326```python
327path.L(x, y)
328```
329
330Draw a straight line to `x, y`.
331
332```python
333g = dw.Group(stroke='black', fill='none')
334
335p = dw.Path()
336p.M(10, 10).L(100, 10)
337g.append(p)
338
339p = dw.Path()
340p.M(10, 20).L(100, 20).L(100, 50)
341g.append(p)
342
343p = dw.Path()
344p.M(40, 60).L(10, 60).L(40, 42)
345p.M(60, 60).L(90, 60).L(60, 42)
346g.append(p)
347
348d.append(g)
349```
350
351
352
353
354### H: horizontal line
355
356```python
357path.H(x)
358```
359
360Draw a horizontal line to the new `x` location.
361
362
363### V: vertical line
364
365```python
366path.V(y)
367```
368
369Draw a vertical line to the new `y` location.
370
371```python
372p = dw.Path(stroke='black', fill='none')
373d.append(p.M(10, 10).H(100))
374d.append(p.M(10, 20).H(100).V(50))
375```
376
377
378
379### Q: quadratic Bézier curve (one control point)
380
381```python
382path.Q(x_ctl, y_ctl, x_end, y_end)
383```
384
385Draw a quadratic Bézier curve from current location to `x_end, y_end` by means of `x_ctl, y_ctl`.
386
387```python
388# Curve only (left)
389p = dw.Path(stroke='black', fill='none', stroke_width=3)
390d.append(p.M(30, 75).Q(240, 30, 300, 120))
391# With control point and construction lines
392d.append(dw.Use(p, 300, 0))
393g = dw.Group(stroke='gray', fill='gray')
394g.append(dw.Circle(330, 75, 3))
395g.append(dw.Circle(600, 120, 3))
396g.append(dw.Circle(540, 30, 3))
397g.append(dw.Line(330, 75, 540, 30))
398g.append(dw.Line(540, 30, 600, 120))
399g.append(dw.Line(330, 75, 600, 120, stroke_dasharray='5,5'))
400g.append(dw.Circle(435, 52.5, 3))
401g.append(dw.Circle(570, 75, 3))
402g.append(dw.Line(435, 52.5, 570, 75))
403g.append(dw.Circle(502.5, 63.75, 4, fill='none'))
404d.append(g)
405```
406
407
408
409
410### T: smooth quadratic Bézier curve (generated control point)
411
412```python
413path.T(x, y)
414```
415
416Draws a quadratic Bézier curve from the current point to (x, y).
417The control point is assumed to be the reflection of the control point on the previous command relative to the current point.
418If there is no previous command or if the previous command was not a Q, q, T or t, assume the control point is coincident with the current point.
419(From [W3C Doc](https://www.w3.org/TR/SVG11/paths.html#PathDataQuadraticBezierCommands))
420
421```python
422# Curve sequence (left)
423p = dw.Path(stroke='black', fill='none', stroke_width=3)
424d.append(p.M(30, 60).Q(80, -10, 100, 60).Q(130, 25, 200, 40))
425# With smooth continuation (right)
426p = dw.Path(stroke='black', fill='none', stroke_width=3,
427 transform='translate(200,0)')
428d.append(p.M(30, 60).Q(80, -10, 100, 60).T(200, 40))
429```
430
431
432
433
434### C: cubic Bézier curve (two control points)
435
436```python
437path.C(x_ctl_1, y_ctl_1, x_ctl_2, y_ctl_2, x_end, y_end)
438```
439
440Draw a cubic Bézier curve by means of two control points (one for start and one for end).
441
442```python
443pnt_1 = (40, 50)
444pnt_2 = (110, 50)
445ctl_1_x = (10, 60, 110, 110, 60, 110)
446ctls_2 = ((140, 10), (90, 10), (40, 10), (40, 10), (90, 90), (40, 90))
447
448for i in range(6):
449 trans = f'translate({i*100},0)'
450 p = dw.Path(stroke='black', fill='none',
451 stroke_width=3, transform=trans)
452 ctl_1 = (ctl_1_x[i], 10)
453 ctl_2 = ctls_2[i]
454 p.M(*pnt_1)
455 p.C(*ctl_1, *ctl_2, *pnt_2)
456 d.append(p)
457 g = dw.Group(stroke='gray', fill='gray',
458 stroke_width=1, transform=trans)
459 g.append(dw.Circle(*ctl_1, 2))
460 g.append(dw.Circle(*ctl_2, 2))
461 g.append(dw.Line(*pnt_1, *ctl_1))
462 g.append(dw.Line(*pnt_2, *ctl_2))
463 d.append(g)
464```
465
466
467
468
469### S: smooth cubic Bézier (one control point)
470
471Similar to `T` in quadratic Bézier curve. The first control point is calculated as reflection of the previous second control point.
472
473```python
474path.S(x_ctl_2, y_ctl_2, x_end, y_end)
475```
476
477```python
478pnt_1 = (30, 100)
479pnt_2 = (100, 100)
480pnt_3 = (200, 80)
481ctl_1 = (50, 30)
482ctl_2 = (70, 50)
483ctl_3 = (150, 40)
484
485p = dw.Path(stroke='black', fill='none', stroke_width=3)
486p.M(*pnt_1)
487p.C(*ctl_1, *ctl_2, *pnt_2)
488p.S(*ctl_3, *pnt_3)
489d.append(p)
490
491for pnt, ctl in zip((pnt_1, pnt_2, pnt_3), (ctl_1, ctl_2, ctl_3)):
492 d.append(dw.Circle(*pnt, 4))
493 d.append(dw.Circle(*ctl, 2, stroke='gray', fill='gray'))
494 d.append(dw.Line(*pnt, *ctl, stroke='gray'))
495```
496
497
498
499
500### A: elliptical Arc
501
502```python
503path.A(rx, ry, rot, largeArc, sweep, ex, ey)
504
505 rx, ry: radius
506 rot: x-axis rotation
507 largeArc: True or False
508 sweep: True (positive) or False (negative) angle
509 ex, ey: end point
510```
511
512```python
513p = dw.Path(stroke='red')
514d.append(p.M(125, 75).A(100, 50, rot=0, large_arc=0, sweep=0, ex=225, ey=125))
515p = dw.Path(stroke='blue')
516d.append(p.M(125, 75).A(100, 50, rot=0, large_arc=0, sweep=1, ex=225, ey=125))
517p = dw.Path(stroke='rgb(0 80 255)',stroke_dasharray='5 3')
518d.append(p.M(125, 75).A(100, 50, rot=0, large_arc=1, sweep=0, ex=225, ey=125))
519p = dw.Path(stroke='rgb(255 80 0)',stroke_dasharray='5 3')
520d.append(p.M(125, 75).A(100, 50, rot=0, large_arc=1, sweep=1, ex=225, ey=125))
521```
522
523
524
525
526### Z: closepath
527
528```python
529path.Z()
530```
531
532Close the path.
533
534```python
535p = dw.Path(stroke='black', fill='none')
536d.append(p.M(10, 10).h(30).v(50).h(-30).Z())
537d.append(p.M(50, 10).h(30).v(50).Z())
538```
539
540
541
542
543## Text
544```python
545dw.Text(text, fontSize, x=None, y=None, *, center=False,
546 line_height=1, line_offset=0, path=None,
547 start_offset=None, path_args=None, tspan_args=None,
548 cairo_fix=True, **kwargs)
549```
550
551### Fill and Outline
552
553Default is black as fill color and no outline.
554
555```python
556# Reference lines
557l = dw.Path(stroke='gray')
558l.M(20, 0).V(370)
559for i in range(1, 7):
560 l.M(10, i*60).H(500)
561d.append(l)
562
563d.append(dw.Text('Simplest Text', font_size=50, x=20, y=60))
564d.append(dw.Text('Outline / Filled', font_size=50, x=20, y=120, stroke='black'))
565d.append(dw.Text('Too big stroke', font_size=50, x=20, y=180, stroke='black', stroke_width=5))
566d.append(dw.Text('Outlined only', font_size=50, x=20, y=240, stroke='black', stroke_width=0.5, fill='none'))
567d.append(dw.Text('Outlined and colored', font_size=50, x=20, y=300, stroke='black', fill='red'))
568d.append(dw.Text('Colored fill only', font_size=50, x=20, y=360, fill='blue'))
569```
570
571
572
573
574### Weight, Style, Decoration, Spacing
575
576```python
577d.append(dw.Text('bold', font_size=30, x=20, y=35, font_weight='bold'))
578d.append(dw.Text('italic', font_size=30, x=20, y=75, font_style='italic'))
579d.append(dw.Text('under', font_size=30, x=20, y=115, text_decoration='underline'))
580d.append(dw.Text('over', font_size=30, x=20, y=155, text_decoration='overline'))
581d.append(dw.Text('through', font_size=30, x=20, y=195, text_decoration='line-through'))
582d.append(dw.Text('normal word space', font_size=30, x=200, y=35))
583d.append(dw.Text('more word space', font_size=30, x=200, y=75, word_spacing=10))
584d.append(dw.Text('less word space', font_size=30, x=200, y=115, word_spacing=-5))
585d.append(dw.Text('wide letter space', font_size=30, x=200, y=155, letter_spacing=8))
586d.append(dw.Text('narrow letter space', font_size=30, x=200, y=195, letter_spacing=-2))
587```
588
589
590
591
592### Text Alignment
593
594Horizontal alignment (`text_anchor`) can be `'start'`, `'middle'` or `'end'`.
595
596Vertical alignment (`dominant_baseline`) can be `'auto'`, `'middle'` or `'hanging'`
597(and more, see [here](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/dominant-baseline)).
598
599```python
600d.append(dw.Line(75, 100, 75, 0, stroke='gray'))
601d.append(dw.Line(140, 30, 250, 30, stroke='gray'))
602d.append(dw.Line(140, 60, 250, 60, stroke='gray'))
603d.append(dw.Line(140, 90, 250, 90, stroke='gray'))
604d.append(dw.Text('Start', 24, 75, 30, text_anchor='start'))
605d.append(dw.Text('Middle', 24, 75, 60, text_anchor='middle'))
606d.append(dw.Text('End', 24, 75, 90, text_anchor='end'))
607d.append(dw.Text('Auto', 24, 150, 30, dominant_baseline='auto'))
608d.append(dw.Text('Middle', 24, 150, 60, dominant_baseline='middle'))
609d.append(dw.Text('Hanging', 24, 150, 90, dominant_baseline='hanging'))
610```
611
612
613
614
615### TSpan
616
617Continues a `Text` element.
618
619```python
620txt = dw.Text('Switch among ', 24, 10, 40)
621txt.append(dw.TSpan('italic', font_style='italic'))
622txt.append(dw.TSpan(', normal, and '))
623txt.append(dw.TSpan('bold', font_weight='bold'))
624txt.append(dw.TSpan(' text.'))
625d.append(txt)
626```
627
628
629
630
631```python
632txt = dw.Text('F', 24, 10, 30)
633txt.append(dw.TSpan('a', dy=5))
634txt.append(dw.TSpan('l', dy=31, dx=21))
635txt.append(dw.TSpan('l', dy=89, dx=54))
636d.append(txt)
637```
638
639
640
641The same could be achieved by a list of dx/dy values:
642
643```python
644d.append(dw.Text('Fall', 24, 10, 30,
645 dx='0,0,21,54', dy='0,5,21,54'))
646```
647
648
649
650
651### Rotate
652
653Either one angle (degrees), or a list which is applied to all characters.
654If the list is smaller than the number of characters, the last angle persists.
655
656```python
657d.append(dw.Text('Rotate', 20, 20, 20, letter_spacing=20, rotate='90'))
658d.append(dw.Text('Rotate', 20, 20, 80, letter_spacing=20, rotate='0 90 180 270'))
659```
660
661
662
663`TSpan` can also be used:
664
665```python
666import random
667random.seed(1)
668
669txt = dw.Text('', 20, 20, 50, letter_spacing=20)
670txt.append(dw.TSpan('R', rotate=random.randrange(360)))
671txt.append(dw.TSpan('OT', rotate='50 20'))
672rotate = ' '.join([str(random.randrange(360)) for i in range(3)])
673txt.append(dw.TSpan('ATE', rotate=rotate))
674d.append(txt)
675```
676
677
678
679
680### Setting Text Length
681
682```python
683s = 'Two words'
684d.append(dw.Text(s, 20, 20, 30, textLength=250, lengthAdjust='spacing'))
685d.append(dw.Text(s, 20, 20, 70, textLength=250, lengthAdjust='spacingAndGlyphs'))
686d.append(dw.Text(s+' (normal length)', 20, 20, 110))
687d.append(dw.Text(s, 20, 20, 150, textLength=80, lengthAdjust='spacing'))
688d.append(dw.Text(s, 20, 20, 190, textLength=80, lengthAdjust='spacingAndGlyphs'))
689
690d.append(dw.Line(20, 10, 20, 195, stroke='gray'))
691d.append(dw.Line(270, 80, 270, 10, stroke='gray'))
692d.append(dw.Line(100, 130, 100, 195, stroke='gray'))
693```
694
695
696
697
698### Text on a Path
699
700```python
701curve_path = dw.Path(stroke='gray', fill='none')
702curve_path.M(30, 50).C(50, 20, 70, 20, 120, 50).S(150, 10, 200, 50)
703
704round_corner = dw.Path(stroke='gray', fill='none')
705round_corner.M(250, 30).L(300, 30).A(30, 30, 0, 0, 1, 330, 60).L(330, 110)
706
707sharp_corner = dw.Path(stroke='gray', fill='none')
708sharp_corner.M(30, 110).L(100, 110).L(100, 160)
709
710discontinuous = dw.Path(stroke='gray', fill='none')
711discontinuous.M(150, 110).A(40, 30, 0, 1, 0, 230, 110).M(250, 110).L(270, 140)
712
713center_curve = dw.Path(stroke='gray', fill='none')
714center_curve.M(330, 130).L(330, 160).A(30, 30, 0, 0, 1, 300, 180).L(200, 180)
715
716d.append(curve_path)
717d.append(round_corner)
718d.append(sharp_corner)
719d.append(discontinuous)
720d.append(center_curve)
721
722t_cp = dw.Text('Following a cubic Bézier curve', 14, path=curve_path)
723t_rc = dw.Text("Going 'round the bend", 14, path=round_corner)
724t_sc = dw.Text('Making a quick turn', 14, path=sharp_corner)
725t_dc = dw.Text('Text along a broken path', 14, path=discontinuous)
726t_ct = dw.Text('centered', 14, path=center_curve, offset='50%', text_anchor='middle')
727
728d.append(t_cp)
729d.append(t_rc)
730d.append(t_sc)
731d.append(t_dc)
732d.append(t_ct)
733```
734
735
736
737
738### Multi Line Text
739
740This is a particular feature of drawsvg: A list of strings as input for Text()
741is rendered as multi-line text.
742
743```python
744tl = ['this is', 'a', 'multiline text', 'given as a', 'list']
745d.append(dw.Text(tl, 14, 50, 20, text_anchor='middle'))
746
747ts = 'this is\na\nmultiline text\ngiven as a\nstring'
748d.append(dw.Text(ts, 14, 150, 20, text_anchor='middle'))
749```
750
751
752
753
754### Fonts
755
756Specify fonts via `font_family`.
757
758```python
759d.append(dw.Text('Some text in Times New Roman.', 30, 10, 35, font_family='Times New Roman'))
760d.append(dw.Text('Some text in Arial Black.', 30, 10, 75, font_family='Arial Black'))
761d.append(dw.Text('Some text in Georgia.', 30, 10, 115, font_family='Georgia'))
762```
763
764
765
766Specify a default font.
767
768```python
769d = dw.Drawing(600, 120, font_family='Times New Roman')
770d.append(dw.Text('Some text in global setting (Times New Roman).', 30, 10, 35))
771d.append(dw.Text('Some text in Arial Black.', 30, 10, 75, font_family='Arial Black'))
772d.append(dw.Text('Some text in Georgia.', 30, 10, 115, font_family='Georgia'))
773```
774
775
776
777
778
779## Gradient, Clip, Mask
780
781### Linear Gradient
782
783```python
784gradient = dw.LinearGradient(x1, y1, x2, y2, gradientUnits='userSpaceOnUse', **kwargs)
785gradient.add_stop(offset, color, opacity=None)
786```
787
788```python
789grad = dw.LinearGradient(150, 0, 0, 0)
790grad.add_stop(0, 'green')
791grad.add_stop(1, 'yellow')
792d.append(dw.Rectangle(10, 10, 150, 60,
793 stroke='black', fill=grad))
794```
795
796
797
798
799### Radial Gradient
800
801```python
802gradient = dw.RadialGradient(cx, cy, r, **kwargs)
803gradient.add_stop(offset, color, opacity=None)
804```
805
806```python
807gradient = dw.RadialGradient(200, 100, 100)
808gradient.add_stop(0, 'green', 1)
809gradient.add_stop(1, 'orange', 1)
810bg = dw.Rectangle(x=0, y=0, width='100%', height='100%', fill=gradient)
811d.append(bg)
812```
813
814
815
816
817### Clip
818
819```python
820clip_name = dw.ClipPath()
821```
822
823To add shape as Clip, use `.append()` method.
824To apply Clip, fill `clip_path` argument with `clip_name`.
825
826```python
827# Show both shapes as they are
828d.append(dw.Rectangle(100, 100, 100, 100,
829 stroke='gray', fill='none'))
830d.append(dw.Circle(100, 100, 100,
831 fill='none', stroke='gray', stroke_dasharray='5 5'))
832# Apply rect as clip to circle
833clip = dw.ClipPath()
834clip.append(dw.Rectangle(100, 100, 100, 100))
835d.append(dw.Circle(100, 100, 100,
836 fill='cyan', clip_path=clip))
837```
838
839
840
841Another example:
842
843```python
844# Draw a random path in the left half of the canvas
845p = dw.Path(stroke='black', stroke_width=2, fill='none')
846p.M(150, 150)
847import random
848random.seed(1)
849for i in range(40):
850 p.L(random.randint(0, 300), random.randint(0, 200))
851d.append(p)
852
853# Circle as clipping shape
854circ = dw.Circle(150, 100, 75)
855c = dw.ClipPath()
856c.append(circ)
857
858# Repeat lines in the right half and apply clipping
859d.append(dw.Use(p, 300, 0, clip_path=c))
860```
861
862
863
864Complex clip path:
865
866```python
867curve1 = dw.Path(stroke='black', stroke_width=1, stroke_dasharray='3 2', fill='none')
868curve1.M(5, 55).C(25, 5, 45, -25, 75, 55).C(85, 85, 20, 105, 40, 55).Z()
869
870curveClip = dw.ClipPath()
871curveClip.append(dw.Use(curve1, 0, 0))
872
873text1 = dw.Text('CLIP', 48, 20, 20, font_weight='bold', transform='rotate(60)',
874 stroke='black', stroke_width=1, stroke_dasharray='3 2', fill='none')
875textClip = dw.ClipPath()
876textClip.append(dw.Use(text1, 0, 0))
877
878shapes = dw.Group()
879shapes.append(dw.Rectangle(0, 50, 90, 60, fill='#999'))
880shapes.append(dw.Circle(25, 25, 25, fill='#666'))
881shapes.append(dw.Lines(30, 0, 80, 0, 80, 100, close='true', fill='#ccc'))
882
883# draw shapes with clip path
884d.append(dw.Use(shapes, 0, 0, clip_path=curveClip))
885
886# show clip path
887g = dw.Group(transform='translate(100,0)')
888g.append(shapes)
889g.append(dw.Use(curve1, 0, 0))
890d.append(g)
891
892# draw shapes with text as clip path
893d.append(dw.Use(shapes, 0, 150, clip_path=textClip))
894
895# show text clip path
896g = dw.Group(transform='translate(100,150)')
897g.append(shapes)
898g.append(dw.Use(text1, 0, 0))
899d.append(g)
900```
901
902
903
904
905### Mask
906
907```python
908mask_name = dw.Mask()
909```
910
911The transparency of the masking object is transfered to the masked object.
912Opaque pixels of the mask produce opaque pixels of the masked object.
913Transparent parts of the mask make the corresponding parts of the masked object invisible.
914
915```python
916gradient = dw.LinearGradient(*[0,0], *[1,0], gradientUnits='objectBoundingBox')
917gradient.add_stop(0, 'white')
918gradient.add_stop(1, 'black')
919
920mask = dw.Mask()
921box = dw.Rectangle(30, 0, 100, 100, fill=gradient)
922mask.append(box)
923
924# Initial shape
925rect = dw.Rectangle(0, 0, 200, 100,
926 fill='cyan', stroke='blue', stroke_width=2)
927d.append(rect)
928
929# After mask
930rect = dw.Rectangle(0, 0, 200, 100,
931 fill='pink', stroke='red', stroke_width=2,
932 mask=mask)
933d.append(rect)
934```
935
936
937
938Mask using opaque colors:
939
940```python
941# Define the masks
942redmask = dw.Mask(maskContentUnits='objectBoundingBox')
943redmask.append(dw.Rectangle(0, 0, 1, 1, fill='#f00'))
944greenmask = dw.Mask(maskContentUnits='objectBoundingBox')
945greenmask.append(dw.Rectangle(0, 0, 1, 1, fill='#0f0'))
946bluemask = dw.Mask(maskContentUnits='objectBoundingBox')
947bluemask.append(dw.Rectangle(0, 0, 1, 1, fill='#00f'))
948whitemask = dw.Mask(maskContentUnits='objectBoundingBox')
949whitemask.append(dw.Rectangle(0, 0, 1, 1, fill='#fff'))
950
951# Display the colors
952d.append(dw.Rectangle(10, 10, 50, 50, fill='#f00'))
953d.append(dw.Rectangle(70, 10, 50, 50, fill='#0f0'))
954d.append(dw.Rectangle(130, 10, 50, 50, fill='#00f'))
955d.append(dw.Rectangle(190, 10, 50, 50, fill='#fff', stroke='black'))
956
957# Mask
958g = dw.Group(mask=redmask)
959g.append(dw.Circle(35,115,25,fill='black'))
960g.append(dw.Text('Red',14,35,80,text_anchor='middle'))
961d.append(g)
962g = dw.Group(mask=greenmask)
963g.append(dw.Circle(95, 115, 25, fill='black'))
964g.append(dw.Text('Green', 14, 95, 80, text_anchor='middle'))
965d.append(g)
966g = dw.Group(mask=bluemask)
967g.append(dw.Circle(155, 115, 25, fill='black'))
968g.append(dw.Text('Blue', 14, 155, 80, text_anchor='middle'))
969d.append(g)
970g = dw.Group(mask=whitemask)
971g.append(dw.Circle(215, 115, 25, fill='black'))
972g.append(dw.Text('White', 14, 215, 80, text_anchor='middle'))
973d.append(g)
974```
975
976
977
978Mask alpha using opacity only:
979
980```python
981fullmask = dw.Mask(maskContentUnits='objectBoundingBox')
982fullmask.append(dw.Rectangle(0, 0, 1, 1, fill_opacity=1.0, fill='white'))
983three_fourths = dw.Mask(maskContentUnits='objectBoundingBox')
984three_fourths.append(dw.Rectangle(0, 0, 1, 1, fill_opacity=0.75, fill='white'))
985one_half = dw.Mask(maskContentUnits='objectBoundingBox')
986one_half.append(dw.Rectangle(0, 0, 1, 1, fill_opacity=0.5, fill='white'))
987one_fourth = dw.Mask(maskContentUnits='objectBoundingBox')
988one_fourth.append(dw.Rectangle(0, 0, 1, 1, fill_opacity=0.25, fill='white'))
989
990g = dw.Group(mask=fullmask)
991g.append(dw.Circle(35, 35, 25))
992g.append(dw.Text('100%', 14, 35, 80, text_anchor='middle'))
993d.append(g)
994g = dw.Group(mask=three_fourths)
995g.append(dw.Circle(95, 35, 25))
996g.append(dw.Text('50%', 14, 95, 80, text_anchor='middle'))
997d.append(g)
998g = dw.Group(mask=one_half)
999g.append(dw.Circle(155, 35, 25))
1000g.append(dw.Text('50%', 14, 155, 80, text_anchor='middle'))
1001d.append(g)
1002g = dw.Group(mask=one_fourth)
1003g.append(dw.Circle(215, 35, 25))
1004g.append(dw.Text('25%', 14, 215, 80, text_anchor='middle'))
1005d.append(g)
1006```
1007
1008
1009
1010
1011## Group, Use, Defs, Image
1012
1013### Group
1014
1015```python
1016dw.Group(**kwargs)
1017```
1018
1019Any style specified in the g tag will apply to all child elements in the group.
1020
1021```python
1022g_house = dw.Group(id='house', fill='none', stroke='black')
1023g_house.append(dw.Rectangle(6, 50, 60, 60))
1024g_house.append(dw.Lines(6, 50, 36, 9, 66, 50))
1025g_house.append(dw.Lines(36, 110, 36, 80, 50, 80, 50, 110))
1026d.append(g_house)
1027
1028g_man = dw.Group(id='man', fill='none', stroke='blue')
1029g_man.append(dw.Circle(85, 56, 10))
1030g_man.append(dw.Line(85, 66, 85, 80))
1031g_man.append(dw.Lines(76, 104, 85, 80, 94, 104))
1032g_man.append(dw.Lines(76, 70, 85, 76, 94, 70))
1033d.append(g_man)
1034
1035g_woman = dw.Group(id='woman', fill='none', stroke='red')
1036g_woman.append(dw.Circle(110, 56, 10))
1037g_woman.append(dw.Lines(110, 66, 110, 80, 100, 90, 120, 90, 110, 80))
1038g_woman.append(dw.Line(104, 104, 108, 90))
1039g_woman.append(dw.Line(112, 90, 116, 104))
1040g_woman.append(dw.Lines(101, 70, 110, 76, 119, 70))
1041d.append(g_woman)
1042```
1043
1044
1045
1046
1047### Use
1048
1049```python
1050dw.Use(other_elem, x, y, **kwargs)
1051```
1052
1053```python
1054g_house = dw.Group(id='house', fill='none', stroke='black')
1055g_house.append(dw.Rectangle(6, 50, 60, 60))
1056g_house.append(dw.Lines(6, 50, 36, 9, 66, 50))
1057g_house.append(dw.Lines(36, 110, 36, 80, 50, 80, 50, 110))
1058d.append(g_house)
1059
1060# Use id which is set
1061d.append(dw.Use('house', 100, 50))
1062# Or use variable name
1063d.append(dw.Use(g_house, 150, 20))
1064```
1065
1066
1067
1068
1069### Defs
1070
1071Elements that are not appended to the drawing but are referenced by other elements will automatically be included in `<defs></defs>`.
1072([source](https://github.com/cduck/drawsvg/issues/46))
1073
1074```python
1075d = dw.Drawing(200, 200, id_prefix='defs')
1076
1077# Do not append `bond` to the drawing
1078bond = dw.Line(0, 0, 10, 10, stroke='black')
1079
1080# `bond` is automatically added into <defs>
1081# A default `id` is generated if one isn't set
1082d.append(dw.Use(bond, 20, 50))
1083d.append(dw.Use(bond, 50, 50))
1084d.append(dw.Use(bond, 80, 50))
1085
1086print(d.as_svg())
1087```
1088
1089Output:
1090
1091```svg
1092<?xml version="1.0" encoding="UTF-8"?>
1093<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
1094 width="200" height="200" viewBox="0 0 200 200">
1095<defs>
1096<path d="M0,0 L10,10" stroke="black" id="defs0" />
1097</defs>
1098<use xlink:href="#defs0" x="20" y="50" />
1099<use xlink:href="#defs0" x="50" y="50" />
1100<use xlink:href="#defs0" x="80" y="50" />
1101</svg>
1102```
1103
1104
1105### Image
1106
1107```python
1108dw.Image(x, y, width, height, path=None, data=None,
1109 embed=False, mimeType=None, **kwargs)
1110```
1111
1112```python
1113d.append(dw.Image(0, 0, 200, 200, 'example1.png', embed=True))
1114```
1115
1116
1117
1118
1119## Transformations
1120
1121### Translate
1122
1123```python
1124transform = 'translate(x,y)'
1125```
1126
1127This attribute can be added to many objects. Simple example:
1128
1129```python
1130d.append(dw.Rectangle(0, 0, 40, 40))
1131d.append(dw.Rectangle(0, 0, 40, 40, fill='red', transform='translate(50,50)'))
1132```
1133
1134
1135
1136
1137### Scale
1138
1139```python
1140transform = 'scale(x_mult[, y_mult])'
1141```
1142
1143Note that scaling also affects stroke width.
1144
1145```python
1146square = dw.Rectangle(0, 0, 40, 40, fill='none', stroke_width=2)
1147d.append(dw.Use(square, 10, 10, stroke='black'))
1148d.append(dw.Use(square, 10, 10, stroke='red', transform='scale(2)'))
1149```
1150
1151
1152
1153It is possible to specify x and y scale separately:
1154
1155```python
1156square = dw.Rectangle(0, 0, 40, 40, fill='none', stroke_width=2)
1157d.append(dw.Use(square, 10, 10, stroke='black'))
1158d.append(dw.Use(square, 10, 10, stroke='red', transform='scale(3,1.5)'))
1159```
1160
1161
1162
1163Scaling around a center point:
1164
1165```python
1166# Center of scaling: (100, 100)
1167d.append(dw.Circle(100, 100, 4, fill='black'))
1168# Non-scaled rectangle
1169rect = dw.Rectangle(70, 80, 60, 40, stroke='black', fill='none')
1170d.append(rect)
1171# Scaled rectangles
1172d.append(dw.Use(rect, 0, 0, transform='translate(-100,-100) scale(2)', stroke_width=0.5))
1173d.append(dw.Use(rect, 0, 0, transform='translate(-150,-150) scale(2.5)', stroke_width=0.4))
1174d.append(dw.Use(rect, 0, 0, transform='translate(-200,-200) scale(3)', stroke_width=0.33))
1175```
1176
1177
1178
1179
1180### Rotate
1181
1182```python
1183transform = 'rotate(angle,cx=0,cy=0)'
1184```
1185
1186`angle` counts clockwise in degrees.
1187`cx`/`cy` are the center of rotation.
1188
1189```python
1190# Show frame border
1191d.append(dw.Rectangle(0, 0, 200, 200, stroke='gray', fill='none'))
1192# Rotation is around (0, 0)
1193d.append(dw.Rectangle(70, 30, 40, 40, fill='silver'))
1194d.append(dw.Rectangle(70, 30, 40, 40, fill='gray', transform='rotate(22.5)'))
1195d.append(dw.Rectangle(70, 30, 40, 40, fill='black', transform='rotate(45)'))
1196```
1197
1198
1199
1200```python
1201# Center of rotation
1202d.append(dw.Circle(100, 100, 3, fill='black'))
1203# Non-rotated arrow
1204arrow = dw.Group(id='arrow')
1205arrow.append(dw.Line(110, 100, 160, 100))
1206arrow.append(dw.Lines(160, 100, 155, 95, 155, 105))
1207d.append(dw.Use(arrow, 0, 0, stroke='black', fill='black'))
1208# Rotated arrows
1209g = dw.Group(stroke='red', fill='red')
1210g.append(dw.Use(arrow, 0, 0, transform='rotate (60,100,100)'))
1211g.append(dw.Use(arrow, 0, 0, transform='rotate (-90,100,100)'))
1212g.append(dw.Use(arrow, 0, 0, transform='rotate (-150,100,100)'))
1213d.append(g)
1214```
1215
1216
1217
1218
1219### Skew
1220
1221```python
1222transform = 'skewX(angle)'
1223transform = 'skewY(angle)'
1224```
1225
1226```python
1227g = dw.Group(stroke='gray', stroke_dasharray='4 4')
1228g.append(dw.Line(0, 0, 200, 0))
1229g.append(dw.Line(20, 0, 20, 90))
1230g.append(dw.Line(120, 0, 120, 90))
1231d.append(g)
1232
1233h = dw.Group(transform='translate(20,0)')
1234h1 = dw.Group(transform='skewX(30)')
1235h1.append(dw.Lines(50, 0, 0, 0, 0, 50,
1236 stroke='black', fill='none', stroke_width=2))
1237h1.append(dw.Text('skewX', 16, 0, 60))
1238h.append(h1)
1239d.append(h)
1240
1241i = dw.Group(transform='translate(120,0)')
1242i1 = dw.Group(transform='skewY(30)')
1243i1.append(dw.Lines(50, 0, 0, 0, 0, 50,
1244 stroke='black', fill='none', stroke_width=2))
1245i1.append(dw.Text('skewY', 16, 0, 60))
1246i.append(i1)
1247d.append(i)
1248```
1249
1250
1251
1252
1253### Cartesian Coordinates
1254
1255A drawing which can be translated to Cartesian coordinates
1256(where y-coordinates increase upward, not downward)
1257by setting the translate-y value to the drawing's height, and also applying `scale(1,-1)`.
1258
1259Trapezoid with origin to top left (the default):
1260
1261```python
1262d.append(dw.Lines(0, 100, 0, 0, 100, 0,
1263 fill='none', stroke='green', stroke_width=2))
1264d.append(dw.Lines(40, 40, 100, 40, 70, 70, 40, 70,
1265 fill='silver', stroke='black', stroke_width=2))
1266d.append(dw.Text('downward y', 12, 5, 95))
1267```
1268
1269
1270
1271Translated origin to bottom left and upward-y:
1272
1273```python
1274g = dw.Group(transform='translate(0,100) scale(1,-1)')
1275g.append(dw.Lines(0, 100, 0, 0, 100, 0,
1276 fill='none', stroke='green', stroke_width=2))
1277g.append(dw.Lines(40, 40, 100, 40, 70, 70, 40, 70,
1278 fill='silver', stroke='black', stroke_width=2))
1279g.append(dw.Text('upward y', 12, 5, 95))
1280d.append(g)
1281```
1282
1283
1284
1285Alternatively, apply `scale(1,-1)` to the whole drawing:
1286
1287```python
1288d = dw.Drawing(100, 100, id_prefix='cart3', transform='scale(1,-1)')
1289d.append(dw.Lines(0, 100, 0, 0, 100, 0,
1290 fill='none', stroke='green', stroke_width=2))
1291d.append(dw.Lines(40, 40, 100, 40, 70, 70, 40, 70,
1292 fill='silver', stroke='black', stroke_width=2))
1293d.append(dw.Text('upward y', 12, 5, 95))
1294```
1295
1296
1297
1298
1299## Credits
1300
1301Written by joachim heintz 2023. Edited by Casey Duckering.
1302
1303Most examples are based on J. David Eisenberg, SVG Essentials, O'Reilly 2002.
1304
1305Thanks to [Ahmad Aufar Husaini](https://github.com/aufarah) for his fork (draw2Svg) and for providing some documentation [here](https://draw2svg.netlify.app/) (some examples are used in this Quick Reference).
1306
1307Thanks to [Casey Duckering](https://github.com/cduck) for drawsvg and many helpful explanations on its [discussion page](https://github.com/cduck/drawsvg/discussions).