# drawsvg writes SVG as a Python object tree, with real keyframe animation

> drawsvg is a Python 3 library for generating SVG images, animations and an interactive Jupyter widget, with nearly all SVG attributes exposed as keyword arguments. It suits people who want diagram or illustration code in a notebook rather than a design tool, and its raster and video output depends on optional Cairo and ffmpeg extras.

**cduck/drawsvg** — Moving to https://tangled.org/cduck.me/drawsvg/  Programmatically generate SVG (vector) images, animations, and interactive Jupyter widgets

- Repository: https://github.com/cduck/drawsvg
- Stars: 694 · Forks: 68
- Language: Python
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/cduck-drawsvg

## An element tree that maps Python keywords onto SVG attributes

drawsvg is 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. The model is object-oriented and familiar if you have used any drawing API.

A Drawing is a container, and you append element objects to it. The set of elements covers most common SVG tags, including Lines, Rectangle, Circle, Path, Text and Arc, and the README notes that other tags can be added by writing a small subclass of DrawableBasicElement or DrawableParentElement. That extension point is what makes the library more than a fixed catalogue.

The attribute mapping is the part that removes friction. Nearly all SVG attributes are supported through keyword arguments, and the rule is mechanical: the Python keyword argument fill_opacity=0.5 becomes the SVG attribute fill-opacity="0.5". A word separator becomes a hyphen and camelCase attribute names stay camelCase, so there is no mapping table to memorise. Titles are added the same way, with append_title on an element producing a tooltip.

## Drawing a shape, a path and a text block

The basic example in the README shows the shape of the API. You create a Drawing with a size and an origin, then append elements:

```python
import drawsvg as draw
d = draw.Drawing(200, 100, origin='center')
r = draw.Rectangle(-80, -50, 40, 50, fill='#1248ff')
r.append_title("Our first rectangle")  # Add a tooltip
d.append(r)
d.append(draw.Circle(-40, 10, 30,
        fill='red', stroke_width=2, stroke='black'))
```

Paths are built with a fluent API rather than a string. You create a Path, call M to move to a start point, then chain commands such as C for a curve, and hand the whole object to the drawing. Text can be placed at absolute coordinates or attached to a path with a text_anchor, and a list of strings produces multi-line text, with center=True available for alignment.

The origin argument is worth understanding early, because it decides whether your coordinates are measured from the top-left corner of the canvas or from its centre. Most example code uses origin='center' so that shapes are positioned around zero.

There is also a scale decision at the end of a drawing: set_pixel_scale sets the number of pixels per geometry unit, and set_render_size is the alternative if you would rather state the output size directly. The drawing is then written with save_svg, and raster output is available through save_png.

## Keyframe animation with playback controls, in SVG itself

The animation support is native SVG rather than a JavaScript overlay, which is the design decision that makes the output shareable as a single file. A Drawing is created with an animation_config, and the example uses draw.types.SyncedAnimationConfig with a duration in seconds plus show_playback_progress and show_playback_controls, which renders the transport bar into the SVG:

```python
d = draw.Drawing(400, 200, origin='center',
        animation_config=draw.types.SyncedAnimationConfig(
            duration=8,  # Seconds
            show_playback_progress=True,
            show_playback_controls=True))
```

Elements then get key frames. A circle is created with a radius of zero and given add_key_frame calls at 0, 2, 4, 6 and 8 seconds, moving it around a path while it grows and shrinks, and a rectangle does the same with x, y, width and height animated together. Text can be animated as a sequence with draw.native_animation.animate_text_sequence, which changes the characters at given times.

The result saves as a standalone animated SVG with save_svg or as HTML with save_html. The README also lists as_gif, as_mp4 and as_spritesheet for rendering video and image sequences, and display_inline for showing the interactive version in a notebook. One caveat is documented plainly: GitHub blocks the playback controls in its image preview, so you have to download the SVG and open it in a browser to try it.

## Raster, video and colour output all depend on optional extras

The plain install gives you SVG generation and nothing else. The README says drawsvg is available on PyPI and gives the install command:

```bash
$ python3 -m pip install "drawsvg~=2.0"
```

Raster image support for PNG, MP4 and GIF needs a separate full-feature install, and the setup.py shows exactly what that means. The extras are declared as follows:

```python
    extras_require = {
        'raster': [
            'cairoSVG~=2.3',
            'numpy~=1.16',
            'imageio~=2.5',
            'imageio_ffmpeg~=0.4',
        ],
```

There is a second extra, colour, which pulls in pwkit and numpy, and an all extra that combines everything. The core install_requires list is empty, so the base package has no third-party dependencies at all.

That structure is a sensible way to package the project but it does mean a notebook user will hit an import error or a missing renderer the first time they call save_png, as_gif or as_mp4, unless the extras are installed up front. The cairoSVG and ffmpeg dependencies are also the parts most likely to be awkward to install on some platforms, so it is worth deciding early whether you need video output at all.

There is a documented rendering gap too. The pattern example in the README notes that patterns are not supported by Cairo, so d.rasterize() will not show them, even though they are valid in the SVG output.

## The interactive widget works in notebook, not in Jupyter lab

The library includes an interactive Jupyter notebook widget, drawsvg.widgets.DrawingWidget, which can update drawings based on mouse events. That is the feature that turns a static diagram into something a reader can drag.

The limitation is stated in the README without hedging: the widget does not yet work in Jupyter lab. Since Jupyter lab is where a lot of notebook work has moved, that constraint decides the audience for the widget more than any other factor. The plain drawing objects still display in lab, and the README describes the library as able to render and display drawings in a Jupyter notebook or Jupyter lab, so the gap is specific to the interactive widget.

The broader Jupyter support comes through a second layer. Besides display_inline for the interactive SVG, the README lists display_image for displaying SVG as an image, which will not be interactive, and display_iframe as an alternative for interactive SVG. That gives you a fallback when inline interactive rendering does not work in the environment you are in.

## The 1.x to 2.x upgrade changed names and the y-axis direction

The README has a dedicated section on upgrading from version 1.x, and the changes are substantial enough to matter if you have older code.

Method and argument names that were camelCase are now snake_case, and the package name is lowercase, with the exception of arguments that correspond to camelCase SVG attributes. The default coordinate system changed so the y-axis now matches the SVG coordinate system, meaning y increases down the screen while x increases right. That change alone can flip every drawing you have, because objects that were placed by assuming an upward y-axis will be mirrored vertically.

The README also gives the fix for the specific error people hit first: to resolve ModuleNotFoundError: No module named 'drawSvg' with the capital S, either install "drawSvg~=1.9" or update the code for 2.x, for example changing drawSvg to drawsvg and d.saveSvg to d.save_svg.

The version in setup.py is 2.4.0, and the repository has no GitHub releases, so the package version on PyPI is the only version reference to pin against. The licence is MIT, in a LICENSE.txt file. The repository also contains a docs/ directory with a mkdocs configuration and an examples/ directory with notebooks and rendered outputs, and the project description notes that the documentation is moving to a new address.

## Conclusion

Adopt drawsvg when your SVG needs to be produced by code you can put in a notebook or a test, since the element classes, the keyword argument mapping for SVG attributes and the keyframe animation API cover far more than a template engine would. Skip it if you need the interactive widget in Jupyter lab, which the README says it does not yet support, or if you want PNG and MP4 output without pulling in cairoSVG, numpy, imageio and imageio_ffmpeg. Before you commit to it, read the 1.x to 2.x upgrade notes, because the package name and method names changed and the default y-axis direction changed with them, check whether the raster extras install cleanly on your platform, and note that the project version in setup.py is 2.4.0 while the repository publishes no GitHub releases, so pin your dependency by package version.

## FAQ

### How do I install drawsvg and save a PNG?

Install the package with python3 -m pip install "drawsvg~=2.0", and note that PNG, MP4 and GIF output need the optional full-feature install with the raster extras, which include cairoSVG, numpy, imageio and imageio_ffmpeg. The base install has no third-party dependencies and produces SVG only.

### How do I add an SVG attribute such as fill-opacity in drawsvg?

Pass it as a Python keyword argument and a word separator becomes a hyphen in the output, so fill_opacity=0.5 produces the SVG attribute fill-opacity="0.5". The README states that nearly all SVG attributes are supported this way.

### Does the drawsvg interactive widget work in Jupyter lab?

No. The README says the interactive Jupyter widget drawsvg.widgets.DrawingWidget does not yet work in Jupyter lab, although it updates drawings based on mouse events in a Jupyter notebook. Plain drawings still display in lab.

### What changed when moving from drawsvg 1.x to 2.x?

camelCase method and argument names became snake_case, the package name became lowercase except for arguments matching camelCase SVG attributes, and the default coordinate system changed so the y-axis now matches the SVG system with y increasing down the screen. The old capital S import is fixed by installing "drawSvg~=1.9" or updating the code to drawsvg and save_svg.

### Can drawsvg produce animated SVG with playback controls?

Yes, by passing an animation_config such as draw.types.SyncedAnimationConfig with a duration, show_playback_progress and show_playback_controls, and adding key frames to elements with add_key_frame. The result saves as a standalone animated SVG with save_svg or as HTML with save_html, though GitHub blocks the controls in its preview.

## Sources

- [cduck/drawsvg on GitHub](https://github.com/cduck/drawsvg)
- [Issues](https://github.com/cduck/drawsvg/issues)
- [License: MIT](https://github.com/cduck/drawsvg/blob/master/LICENSE)
- [README](https://github.com/cduck/drawsvg/blob/master/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/cduck-drawsvg
