drawsvg

repository·master·Indexed 20 days ago

https://github.com/cduck/drawsvg

A Python 3 library for programmatically generating SVG images and animations, optimized for rendering within Jupyter notebooks and Jupyter lab. It supports basic shapes, paths, patterns, gradients, and SVG-native animations with playback controls. The library also provides utilities for frame-by-frame animations (GIF, MP4) and supports exporting to SVG, PNG, and HTML.

Tokens
10K
Snippets
46
Records
51
Agent score
72%

What's inside drawsvg

  1. Configure colors and opacity

    master

    Colors can be specified using standard CSS names (e.g., red, blue, aqua), hexadecimal codes (#rrggbb), or rgb() values (e.g., rgb(255,128,64) or rgb(60%,20%,60%)).

    Opacity: Use fill_opacity and stroke_opacity to control transparency. Values range from 0 (fully transparent) to 1 (fully solid).

    d.append(dw.Rectangle(0, 0, 50, 50, fill='red', fill_opacity=0.5))
    d.append(dw.Line(0, 0, 100, 0, stroke='black', stroke_opacity=0.8))
  2. Organize and duplicate elements with Groups and Use

    master

    To manage complex drawings, use draw.Group and draw.Use:

    • draw.Group: Acts as a container. Children inherit the group's coordinate system (transform) and attribute values (like fill or opacity).
    • draw.Use: Creates a duplicate of an existing element. You can specify an offset (x, y) and override attributes like stroke or stroke_width for the duplicate.
    import drawsvg as draw
    
    d = draw.Drawing(300, 100)
    
    # Grouping elements
    group = draw.Group(fill='orange', transform='rotate(-20)')
    group.append(draw.Rectangle(0, 10, 20, 40))
    group.append(draw.Circle(30, 40, 10))
    d.append(group)
    
    # Duplicating the group with different styles
    d.append(draw.Use(group, 80, 0, stroke='black', stroke_width=1))
    d.append(draw.Use(group, 80, 20, stroke='blue', stroke_width=2))
  3. How SVG attributes are handled in drawsvg

    master

    The library supports nearly all SVG attributes via Python keyword arguments. To map a Python keyword argument to an SVG attribute, use underscores instead of hyphens.

    For example, the Python keyword argument fill_opacity=0.5 will be rendered as the SVG attribute fill-opacity="0.5".

  4. Use patterns and gradients for fills

    master

    You can fill shapes using draw.Pattern, draw.RadialGradient, or draw.LinearGradient.

    • Patterns: Define a pattern by appending elements to a draw.Pattern object, then use it as the fill argument in a drawing command.
    • Gradients: Use add_stop(offset, color, opacity) to define color stops in a gradient object.
    import drawsvg as draw
    
    d = draw.Drawing(1.5, 0.8, origin='center')
    
    # Radial 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)
    
    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)
  5. Use Cartesian coordinates (upward-increasing Y)

    master

    By default, SVG coordinates increase downward. To use a Cartesian coordinate system where Y increases upward, you can apply a transformation to a dw.Group or the entire dw.Drawing object using translate and scale.

    To set the origin to the bottom-left corner of a drawing with height H:

    1. Translate the drawing by (0, H).
    2. Apply scale(1, -1) to flip the Y-axis.

    Alternatively, you can apply transform='scale(1,-1)' directly to the dw.Drawing instance.

    # Using a Group to create a Cartesian coordinate system
    g = dw.Group(transform='translate(0,100) scale(1,-1)')
    
    # Applying it to the entire Drawing
    d = dw.Drawing(100, 100, id_prefix='cart3', transform='scale(1,-1)')
  6. How to use ClipPath for clipping shapes

    master

    A dw.ClipPath() defines a region that limits the visibility of other elements.

    To use a clip path:

    1. Create a dw.ClipPath() object.
    2. .append() the shape(s) you want to use as the clipping boundary to the ClipPath object.
    3. Pass the ClipPath instance to the clip_path argument of the element you want to clip.

    Example:

    clip = dw.ClipPath()
    clip.append(dw.Rectangle(100, 100, 100, 100))
    d.append(dw.Circle(100, 100, 100, fill='cyan', clip_path=clip))
    # Apply rect as clip to circle
    clip = dw.ClipPath()
    clip.append(dw.Rectangle(100, 100, 100, 100))
    d.append(dw.Circle(100, 100, 100,
                       fill='cyan', clip_path=clip))
  7. How Mask works and how to use it

    master

    A dw.Mask() uses the transparency of its child elements to mask another object.

    • Opaque pixels in the mask make the corresponding parts of the masked object visible.
    • Transparent pixels in the mask make the corresponding parts of the masked object invisible.

    To use a mask:

    1. Create a dw.Mask() object.
    2. .append() shapes to the mask. You can use color (where opacity determines visibility) or fill_opacity to control the mask effect.
    3. Pass the Mask instance to the mask argument of the element you want to mask.

    Example using a gradient for transparency:

    gradient = dw.LinearGradient(*[0,0], *[1,0], gradientUnits='objectBoundingBox')
    gradient.add_stop(0, 'white')
    gradient.add_stop(1, 'black')
    
    mask = dw.Mask()
    box = dw.Rectangle(30, 0, 100, 100, fill=gradient)
    mask.append(box)
    
    rect = dw.Rectangle(0, 0, 200, 100, fill='pink', stroke='red', stroke_width=2, mask=mask)
    d.append(rect)
    gradient = dw.LinearGradient(*[0,0], *[1,0], gradientUnits='objectBoundingBox')
    gradient.add_stop(0, 'white')
    gradient.add_stop(1, 'black')
    
    mask = dw.Mask()
    box = dw.Rectangle(30, 0, 100, 100, fill=gradient)
    mask.append(box)
    
    # Initial shape
    rect = dw.Rectangle(0, 0, 200, 100,
                        fill='cyan', stroke='blue', stroke_width=2)
    d.append(rect)
    
    # After mask
    rect = dw.Rectangle(0, 0, 200, 100,
                        fill='pink', stroke='red', stroke_width=2,
                        mask=mask)
    d.append(rect)
  8. Automatic inclusion of elements in <defs>

    master

    In drawsvg, elements that are not explicitly appended to the Drawing object but are referenced by other elements (e.g., via dw.Use) are automatically included in the SVG <defs> section. If an element does not have an id set, a default ID is generated.

    Example:

    d = dw.Drawing(200, 200, id_prefix='defs')
    
    # Do not append `bond` to the drawing
    bond = dw.Line(0, 0, 10, 10, stroke='black')
    
    # `bond` is automatically added into <defs>
    d.append(dw.Use(bond, 20, 50))
    d.append(dw.Use(bond, 50, 50))
    d = dw.Drawing(200, 200, id_prefix='defs')
    
    # Do not append `bond` to the drawing
    bond = dw.Line(0, 0, 10, 10, stroke='black')
    
    # `bond` is automatically added into <defs>
    d.append(dw.Use(bond, 20, 50))
    d.append(dw.Use(bond, 50, 50))
    d.append(dw.Use(bond, 80, 50))
    
    print(d.as_svg())
  9. Create multi-line text

    master

    Drawsvg supports multi-line text in two ways:

    1. Pass a list of strings to dw.Text().
    2. Pass a single string containing newline characters (\n) to dw.Text().
    # Using a list
    tl = ['this is', 'a', 'multiline text']
    d.append(dw.Text(tl, 14, 50, 20))
    
    # Using newline characters
    ts = 'this is\na\nmultiline text'
    d.append(dw.Text(ts, 14, 150, 20))
  10. Create frame-by-frame animations

    master

    For generating GIFs, MP4s, or spritesheets, use the draw.frame_animate_* context managers. You provide a function that returns a draw.Drawing object representing a single frame.

    import drawsvg as draw
    
    def draw_frame(t):
        d = draw.Drawing(2, 6.05, origin=(-1, -5))
        d.set_render_size(h=300)
        d.append(draw.Rectangle(-2, -6, 4, 8, fill='white'))
        # ... logic to change drawing based on t ...
        return d
    
    # To create a Jupyter animation:
    with draw.frame_animate_jupyter(draw_frame, delay=0.05) as anim:
        for i in range(20):
            anim.draw_frame(i/10)
    
    # To create a GIF:
    # with draw.frame_animate_video('example6.gif', draw_frame, duration=0.05) as anim:
    #     ... 
  11. Migrate from drawsvg 1.x to 2.x

    master

    If you are upgrading from version 1.x to 2.x, be aware of the following breaking changes:

    • Naming Convention: Method and argument names have changed from camelCase to snake_case. However, arguments that correspond directly to camelCase SVG attributes retain their original casing.
    • Coordinate System: The default y-axis now matches the standard SVG coordinate system, where y increases downwards and x increases to the right.
    • Package Name: The package name is now lowercase (drawsvg).

    Fixing ModuleNotFoundError: No module named 'drawSvg': If your code or environment expects the old casing, you must either:

    1. Revert to the old version: pip install "drawSvg~=1.9"
    2. Update your code to the 2.x API (e.g., change imports from drawSvg to drawsvg and method calls like d.saveSvg to d.save_svg).
  12. Install Cairo for image output support

    master

    To enable drawsvg to output PNG and other image formats, you must install the cairo system library using your platform's package manager. pip does not handle this dependency.

    • Ubuntu: Use apt.
    • macOS: Use homebrew (ensure Python is also installed via brew if necessary).
    • Any platform: Use conda (ensure Python and cairo are in the same environment).
    # Ubuntu
    sudo apt install libcairo2
    
    # macOS
    brew install cairo
    
    # Any platform (Anaconda)
    conda install -c anaconda cairo