Drawsvg
Moving to https://tangled.org/cduck.me/drawsvg/ Programmatically generate SVG (vector) images, animations, and interactive Jupyter widgets
Install / Use
/learn @cduck/DrawsvgREADME
A Python 3 library for programmatically generating SVG images and animations that can render and display your drawings in a Jupyter notebook or Jupyter lab.
Most common SVG tags are supported and others can easily be added by writing a small subclass of DrawableBasicElement or DrawableParentElement. Nearly all SVG attributes are supported via keyword args (e.g. Python keyword argument fill_opacity=0.5 becomes SVG attribute fill-opacity="0.5").
An interactive Jupyter notebook widget, drawsvg.widgets.DrawingWidget, is included that can update drawings based on mouse events. The widget does not yet work in Jupyter lab.
Install
Drawsvg is available on PyPI:
$ python3 -m pip install "drawsvg~=2.0"
To enable raster image support (PNG, MP4, and GIF), follow the full-feature install instructions.
Upgrading from version 1.x
Major breaking changes:
- camelCase method and argument names are now snake_case and the package name is lowercase (except for arguments that correspond to camelCase SVG attributes).
- The default coordinate system y-axis now matches the SVG coordinate system (y increases down the screen, x increases right)
- How to fix
ModuleNotFoundError: No module named 'drawSvg'(with a capital S)? Either pip install"drawSvg~=1.9"or update your code for drawsvg 2.x (for example, changedrawSvgtodrawsvgandd.saveSvgtod.save_svg).
Examples
Basic drawing elements
import drawsvg as draw
d = draw.Drawing(200, 100, origin='center')
# Draw an irregular polygon
d.append(draw.Lines(-80, 45,
70, 49,
95, -49,
-90, -40,
close=False,
fill='#eeee00',
stroke='black'))
# Draw a rectangle
r = draw.Rectangle(-80, -50, 40, 50, fill='#1248ff')
r.append_title("Our first rectangle") # Add a tooltip
d.append(r)
# Draw a circle
d.append(draw.Circle(-40, 10, 30,
fill='red', stroke_width=2, stroke='black'))
# Draw an arbitrary path (a triangle in this case)
p = draw.Path(stroke_width=2, stroke='lime', fill='black', fill_opacity=0.2)
p.M(-10, -20) # Start path at point (-10, -20)
p.C(30, 10, 30, -50, 70, -20) # Draw a curve to (70, -20)
d.append(p)
# Draw text
d.append(draw.Text('Basic text', 8, -10, -35, fill='blue')) # 8pt text at (-10, -35)
d.append(draw.Text('Path text', 8, path=p, text_anchor='start', line_height=1))
d.append(draw.Text(['Multi-line', 'text'], 8, path=p, text_anchor='end', center=True))
# Draw multiple circular arcs
d.append(draw.ArcLine(60, 20, 20, 60, 270,
stroke='red', stroke_width=5, fill='red', fill_opacity=0.2))
d.append(draw.Arc(60, 20, 20, 90, -60, cw=True,
stroke='green', stroke_width=3, fill='none'))
d.append(draw.Arc(60, 20, 20, -60, 90, cw=False,
stroke='blue', stroke_width=1, fill='black', fill_opacity=0.3))
# Draw arrows
arrow = draw.Marker(-0.1, -0.51, 0.9, 0.5, scale=4, orient='auto')
arrow.append(draw.Lines(-0.1, 0.5, -0.1, -0.5, 0.9, 0, fill='red', close=True))
p = draw.Path(stroke='red', stroke_width=2, fill='none',
marker_end=arrow) # Add an arrow to the end of a path
p.M(20, 40).L(20, 27).L(0, 20) # Chain multiple path commands
d.append(p)
d.append(draw.Line(30, 20, 0, 10,
stroke='red', stroke_width=2, fill='none',
marker_end=arrow)) # Add an arrow to the end of a line
d.set_pixel_scale(2) # Set number of pixels per geometry unit
#d.set_render_size(400, 200) # Alternative to set_pixel_scale
d.save_svg('example.svg')
d.save_png('example.png')
# Display in Jupyter notebook
#d.rasterize() # Display as PNG
d # Display as SVG
SVG-native animation with playback controls
import drawsvg as draw
d = draw.Drawing(400, 200, origin='center',
animation_config=draw.types.SyncedAnimationConfig(
# Animation configuration
duration=8, # Seconds
show_playback_progress=True,
show_playback_controls=True))
d.append(draw.Rectangle(-200, -100, 400, 200, fill='#eee')) # Background
d.append(draw.Circle(0, 0, 40, fill='green')) # Center circle
# Animation
circle = draw.Circle(0, 0, 0, fill='gray') # Moving circle
circle.add_key_frame(0, cx=-100, cy=0, r=0)
circle.add_key_frame(2, cx=0, cy=-100, r=40)
circle.add_key_frame(4, cx=100, cy=0, r=0)
circle.add_key_frame(6, cx=0, cy=100, r=40)
circle.add_key_frame(8, cx=-100, cy=0, r=0)
d.append(circle)
r = draw.Rectangle(0, 0, 0, 0, fill='silver') # Moving square
r.add_key_frame(0, x=-100, y=0, width=0, height=0)
r.add_key_frame(2, x=0-20, y=-100-20, width=40, height=40)
r.add_key_frame(4, x=100, y=0, width=0, height=0)
r.add_key_frame(6, x=0-20, y=100-20, width=40, height=40)
r.add_key_frame(8, x=-100, y=0, width=0, height=0)
d.append(r)
# Changing text
draw.native_animation.animate_text_sequence(
d,
[0, 2, 4, 6],
['0', '1', '2', '3'],
30, 0, 1, fill='yellow', center=True)
# Save as a standalone animated SVG or HTML
d.save_svg('playback-controls.svg')
d.save_html('playback-controls.html')
# Display in Jupyter notebook
#d.display_image() # Display SVG as an image (will not be interactive)
#d.display_iframe() # Display as interactive SVG (alternative)
#d.as_gif('orbit.gif', fps=10) # Render as a GIF image, optionally save to file
#d.as_mp4('orbig.mp4', fps=60, verbose=True) # Render as an MP4 video, optionally save to file
#d.as_spritesheet('orbit-spritesheet.png', row_length=10, fps=3) # Render as a spritesheet
d.display_inline() # Display as interactive SVG
Note: GitHub blocks the playback controls. Download the above SVG and open it in a web browser to try.
https://user-images.githubusercontent.com/2476062/221400434-1529d237-e9bf-4363-a143-0ece75cd349a.mp4
Patterns and gradients
import drawsvg as draw
d = draw.Drawing(1.5, 0.8, origin='center')
# Background pattern (not supported by Cairo, d.rasterize() will not show it)
pattern = draw.Pattern(width=0.13, height=0.23)
pattern.append(draw.Rectangle(0, 0, .1, .1, fill='yellow'))
pattern.append(draw.Rectangle(0, .1, .1, .1, fill='orange'))
d.draw(draw.Rectangle(-0.75, -0.5, 1.5, 1, fill=pattern, fill_opacity=0.4))
# Create gradient
gradient = draw.RadialGradient(0, 0.35, 0.7*10)
gradient.add_stop(0.5/0.7/10, 'green', 1)
gradient.add_stop(1/10, 'red', 0)
# Draw a shape to fill with the gradient
p = draw.Path(fill=gradient, stroke='black', stroke_width=0.002)
p.arc(0, 0.35, 0.7, -30, -120, cw=False)
p.arc(0, 0.35, 0.5, -120, -30, cw=True, include_l=True)
p.Z()
d.append(p)
# Draw another shape to fill with the same gradient
p = draw.Path(fill=gradient, stroke='red', stroke_width=0.002)
p.arc(0, 0.35, 0.75, -130, -160, cw=False)
p.arc(0, 0.35, 0, -160, -130, cw=True, include_l=True)
p.Z()
d.append(p)
# Another gradient
gradient2 = draw.LinearGradient(0.1, 0.35, 0.1+0.6, 0.35+0.2)
gradient2.add_stop(0, 'green', 1)
gradient2.add_stop(1, 'red', 0)
d.append(draw.Rectangle(0.1, 0.15, 0.6, 0.2,
stroke='black', stroke_width=0.002,
fill=gradient2))
# Display
d.set_render_size(w=600)
d
Duplicate geometry and clip paths
import drawsvg as draw
d = draw.Drawing(1.4, 1.4, origin='center')
# Define clip path
clip = draw.ClipPath()
clip.append(draw.Rectangle(-.25, -.25, 1, 1))
# Draw a cropped circle
circle = draw.Circle(0, 0, 0.5,
stroke_width='0.01', stroke='black',
fill_opacity=0.3, clip_path=clip)
d.append(circle)
# Make a transparent copy, cropped again
g = draw.Group(opacity=0.5, clip_path=clip)
# Here, circle is not directly appended to the drawing.
# drawsvg recognizes that `Use` references `circle` and automatically adds
# `circle` to the <defs></defs> section of the SVG.
g.append(draw.Use(circle, 0.25, -0.1))
d.append(g)
# Display
d.set_render_size(400)
#d.rasterize() # Display as PNG
d # Display as SVG
Organizing and duplicating drawing elements
import drawsvg as draw
d = draw.Drawing(300, 100)
d.set_pixel_scale(2)
# Use groups to contain other elements
# Children elements of groups inherit the coordinate system (transform)
# and attribute values
group = draw.Group(fill='orange', transform='rotate(-20)')
group.append(draw.Rectangle(0, 10, 20, 40)) # This rectangle will be orange
group.append(draw.Circle(30, 40, 10)) # This circle will also be orange
group.append(draw.Circle(50, 40, 10, fill='green')) # This circle will not
d.append(group)
# Use the Use element to make duplicates of elements
# Each duplicate can be placed at an offset (x, y) location and any additional
# attributes (like fill color) are inherited if the element didn't specify them.
d.append(draw.Use(group, 80, 0, stroke='black', stroke_width=1))
d.append(draw.Use(group, 80, 20, stroke='blue', stroke_width=2))
d.append(draw.Use(group, 80, 40,
Related Skills
node-connect
339.5kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
83.9kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
339.5kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
commit-push-pr
83.9kCommit, push, and open a PR


