Skip to content

API reference

Everything below is importable directly from the top-level svg2mpl package.

Rendering

add_svg

add_svg(
    source: str | Path,
    xy: tuple[float, float] = (0, 0),
    width: float | None = None,
    height: float | None = None,
    scale: tuple[float, float] | float = (1, 1),
    origin: tuple[float, float] = (0.5, 0.5),
    rotation: float = 0,
    transform=None,
    flip_y: bool = True,
    place: bool = True,
    exclude: str | Iterable[str] = (),
    bbox_exclude: str | Iterable[str] = (),
    grayscale: bool = False,
    force_patches: bool = False,
    ax=None,
    per_path_kwargs: Mapping[str, Mapping[str, Any]]
    | None = None,
    **global_kwargs: Any,
)

Render an SVG into a matplotlib axes.

Parameters:

Name Type Description Default
source str or Path

Path to an SVG file, or a string of SVG markup (must start with <).

required
xy tuple

Where to place the drawing's origin anchor, in data coordinates.

(0, 0)
width float or None

Final size of the drawing's bounding box. If only one is given the other is derived to preserve the aspect ratio. If both are None and place is True, the drawing keeps its intrinsic size.

None
height float or None

Final size of the drawing's bounding box. If only one is given the other is derived to preserve the aspect ratio. If both are None and place is True, the drawing keeps its intrinsic size.

None
scale float or tuple

An extra scaling factor applied on top of width/height.

(1, 1)
origin tuple

Anchor point inside the bounding box, normalized (default center).

(0.5, 0.5)
rotation float

Rotation in degrees.

0
transform Transform

A matplotlib transform to compose with (default ax.transData).

None
flip_y bool

Flip SVG's y-down axis to matplotlib's y-up so the drawing appears as in a browser. Set False to keep raw SVG coordinates. Default True.

True
place bool

When True (default) the drawing is positioned/scaled via xy, width/height, origin, rotation. When False the drawing is rendered in its own coordinates (only flip_y applies), which faithfully reproduces the SVG's absolute layout.

True
exclude str or Iterable[str]

Regular expressions; paths whose id matches are not drawn.

()
bbox_exclude str or Iterable[str]

Regular expressions for paths to ignore when computing the placement bounding box.

()
grayscale bool

Convert fill colors to grayscale.

False
force_patches bool

Return individual :class:~matplotlib.patches.PathPatch objects instead of a single :class:~matplotlib.collections.PathCollection.

False
ax Axes

Axes to draw on (default current axes).

None
per_path_kwargs Mapping of Mapping

Per-path overrides: {property: {path_id: value}}.

None
**global_kwargs Any

Keyword arguments applied to every path.

{}

Returns:

Type Description
PathCollection or dict

A PathCollection when the paths can be batched, else a dict mapping path id to PathPatch.

Source code in src/svg2mpl/render.py
def add_svg(
    source: str | FilePath,
    xy: tuple[float, float] = (0, 0),
    width: float | None = None,
    height: float | None = None,
    scale: tuple[float, float] | float = (1, 1),
    origin: tuple[float, float] = (0.5, 0.5),
    rotation: float = 0,
    transform=None,
    flip_y: bool = True,
    place: bool = True,
    exclude: str | Iterable[str] = (),
    bbox_exclude: str | Iterable[str] = (),
    grayscale: bool = False,
    force_patches: bool = False,
    ax=None,
    per_path_kwargs: Mapping[str, Mapping[str, Any]] | None = None,
    **global_kwargs: Any,
):
    """Render an SVG into a matplotlib axes.

    Parameters
    ----------
    source : str or Path
        Path to an SVG file, or a string of SVG markup (must start with ``<``).
    xy : tuple, optional
        Where to place the drawing's ``origin`` anchor, in data coordinates.
    width, height : float or None, optional
        Final size of the drawing's bounding box. If only one is given the other
        is derived to preserve the aspect ratio. If both are ``None`` and
        ``place`` is ``True``, the drawing keeps its intrinsic size.
    scale : float or tuple, optional
        An extra scaling factor applied on top of ``width``/``height``.
    origin : tuple, optional
        Anchor point inside the bounding box, normalized (default center).
    rotation : float, optional
        Rotation in degrees.
    transform : Transform, optional
        A matplotlib transform to compose with (default ``ax.transData``).
    flip_y : bool, optional
        Flip SVG's y-down axis to matplotlib's y-up so the drawing appears as in
        a browser. Set ``False`` to keep raw SVG coordinates. Default ``True``.
    place : bool, optional
        When ``True`` (default) the drawing is positioned/scaled via ``xy``,
        ``width``/``height``, ``origin``, ``rotation``. When ``False`` the
        drawing is rendered in its own coordinates (only ``flip_y`` applies),
        which faithfully reproduces the SVG's absolute layout.
    exclude : str or Iterable[str], optional
        Regular expressions; paths whose id matches are not drawn.
    bbox_exclude : str or Iterable[str], optional
        Regular expressions for paths to ignore when computing the placement
        bounding box.
    grayscale : bool, optional
        Convert fill colors to grayscale.
    force_patches : bool, optional
        Return individual :class:`~matplotlib.patches.PathPatch` objects instead
        of a single :class:`~matplotlib.collections.PathCollection`.
    ax : Axes, optional
        Axes to draw on (default current axes).
    per_path_kwargs : Mapping of Mapping, optional
        Per-path overrides: ``{property: {path_id: value}}``.
    **global_kwargs
        Keyword arguments applied to every path.

    Returns
    -------
    PathCollection or dict
        A ``PathCollection`` when the paths can be batched, else a dict mapping
        path id to ``PathPatch``.
    """
    scene, cache_key = _resolve_source(source)

    exclude = _as_tuple(exclude)
    bbox_exclude = _as_tuple(bbox_exclude)

    path_dicts = get_path_dicts(
        scene,
        flip_y=flip_y,
        exclude=exclude,
        grayscale=grayscale,
        per_path_kwargs=per_path_kwargs,
        **global_kwargs,
    )
    if not path_dicts and not scene.texts:
        raise ValueError(f"No paths to draw from {source!r} (all were excluded?).")

    if ax is None:
        ax = plt.gca()
    base_transform = ax.transData if transform is None else transform

    affine2d = Affine2D()  # identity when not placing
    if place and path_dicts:
        if cache_key is not None:
            bbox = _cached_bbox(*cache_key, flip_y, exclude, bbox_exclude)
        else:
            bbox = get_bbox([d["path"] for d in path_dicts.values()])
        affine2d = get_affine_matrix(
            bbox=bbox,
            source_xy=origin,
            target_xy=xy,
            rotation=rotation,
            width=width,
            height=height,
            scale=scale,
        )
    full_transform = affine2d + base_transform

    # Gradient-filled shapes can't be batched (they need a clipped image each).
    has_gradients = any("_gradient" in d for d in path_dicts.values())

    result = _emit(
        ax,
        path_dicts,
        full_transform,
        force_patches=force_patches or has_gradients,
    )

    if has_gradients:
        _add_gradient_fills(ax, scene, path_dicts, affine2d, base_transform)
    if scene.texts:
        from .text import add_texts

        flip = flip_y_transform(_flip_height(scene)) if flip_y else None
        add_texts(
            ax, scene, affine2d=affine2d, flip=flip, base_transform=base_transform
        )

    ax.autoscale_view()
    return result

Loading

load

load(source: str | Path) -> Scene

Parse an SVG file into a (cached) :class:~svg2mpl.scene.Scene.

Paths in the returned scene are in SVG user space (y points down); the optional y-flip is applied by :func:add_svg. The result is shared and cached -- callers must not mutate it.

Source code in src/svg2mpl/render.py
def load(source: str | FilePath) -> Scene:
    """Parse an SVG file into a (cached) :class:`~svg2mpl.scene.Scene`.

    Paths in the returned scene are in SVG user space (y points down); the
    optional y-flip is applied by :func:`add_svg`. The result is shared and
    cached -- callers must not mutate it.
    """
    p = FilePath(source)
    return _load_cached(str(p), p.stat().st_mtime)

load_string

load_string(text: str) -> Scene

Parse an SVG document from a string (not cached).

Source code in src/svg2mpl/render.py
def load_string(text: str) -> Scene:
    """Parse an SVG document from a string (not cached)."""
    return parse_string(text)

Scene model

Scene dataclass

Scene(
    shapes: list[Shape] = list(),
    texts: list[Text] = list(),
    width: float = 0.0,
    height: float = 0.0,
    gradients: dict[str, Any] = dict(),
)

A parsed SVG: its drawables, size, and gradient definitions.

Shape dataclass

Shape(
    id: str,
    path: Path,
    style: dict[str, str],
    kwargs: dict[str, Any],
)

A filled/stroked path with its resolved matplotlib style.

Text dataclass

Text(
    id: str,
    x: float,
    y: float,
    content: str,
    style: dict[str, str],
    transform: Affine2D,
)

A piece of text with its anchor point and resolved style.

Geometry helpers

These lower-level helpers back add_svg and are exposed for advanced use.

get_bbox

get_bbox(paths: Iterable[Any]) -> Bbox

Return the bounding box enclosing a collection of paths.

Source code in src/svg2mpl/render.py
def get_bbox(paths: Iterable[Any]) -> Bbox:
    """Return the bounding box enclosing a collection of paths."""
    extents = np.array([p.get_extents() for p in paths])
    if extents.size == 0:
        raise ValueError("Cannot compute a bounding box of an empty collection.")
    mins = extents[:, 0].min(axis=0)
    maxs = extents[:, 1].max(axis=0)
    return Bbox([mins, maxs])

get_affine_matrix

get_affine_matrix(
    bbox: Bbox,
    source_xy: tuple[float, float],
    target_xy: tuple[float, float],
    rotation: float,
    width: float | None,
    height: float | None,
    scale: tuple[float, float] | float = (1.0, 1.0),
) -> Affine2D

Return an affine that places bbox at target_xy in data coords.

source_xy is the anchor inside the bounding box in normalized coordinates ((0, 0) lower-left, (1, 1) upper-right). width/height set the final size (None preserves aspect ratio); scale is an extra factor; rotation is in degrees.

Source code in src/svg2mpl/render.py
def get_affine_matrix(
    bbox: Bbox,
    source_xy: tuple[float, float],
    target_xy: tuple[float, float],
    rotation: float,
    width: float | None,
    height: float | None,
    scale: tuple[float, float] | float = (1.0, 1.0),
) -> Affine2D:
    """Return an affine that places ``bbox`` at ``target_xy`` in data coords.

    ``source_xy`` is the anchor inside the bounding box in normalized coordinates
    (``(0, 0)`` lower-left, ``(1, 1)`` upper-right). ``width``/``height`` set the
    final size (``None`` preserves aspect ratio); ``scale`` is an extra factor;
    ``rotation`` is in degrees.
    """
    (x0, y0), (x1, y1) = np.asarray(bbox)
    w, h = x1 - x0, y1 - y0

    affine2d = Affine2D()
    affine2d.translate(-x0 - w * source_xy[0], -y0 - h * source_xy[1])

    if width is not None and height is not None:
        affine2d.scale(width / w, height / h)
    elif width is not None:
        affine2d.scale(width / w)
    elif height is not None:
        affine2d.scale(height / h)

    if np.ndim(scale) == 0:
        affine2d.scale(scale, scale)
    else:
        affine2d.scale(*scale)

    affine2d.rotate_deg(rotation)
    affine2d.translate(*target_xy)
    return affine2d

get_is_included

get_is_included(
    id_: str, exclude: str | Iterable[str]
) -> bool

Return whether id_ survives the exclude regular expressions.

Source code in src/svg2mpl/render.py
def get_is_included(id_: str, exclude: str | Iterable[str]) -> bool:
    """Return whether ``id_`` survives the ``exclude`` regular expressions."""
    if isinstance(exclude, str):
        exclude = (exclude,)
    return not any(re.match(r, id_) for r in exclude)

vectorize_dicts

vectorize_dicts(
    dicts: Mapping[str, Mapping[str, Any]],
) -> dict[str, list] | None

Transpose a mapping of dicts into a dict of lists for PathCollection.

Returns None if the input is empty or the property dicts do not share the same keys (so a single PathCollection cannot represent them).

Source code in src/svg2mpl/render.py
def vectorize_dicts(dicts: Mapping[str, Mapping[str, Any]]) -> dict[str, list] | None:
    """Transpose a mapping of dicts into a dict of lists for ``PathCollection``.

    Returns ``None`` if the input is empty or the property dicts do not share the
    same keys (so a single ``PathCollection`` cannot represent them).
    """
    if not dicts:
        return None
    it = iter(dicts.values())
    k0 = set(next(it).keys())
    if any(set(d.keys()) != k0 for d in it):
        return None
    return {k: [d[k] for d in dicts.values()] for k in k0}