spinagram

Python guide

Build diagrams with a few lines of Python.

Python is the main way to write spinagram diagrams. A Diagram is a builder: create boxes, connect them, group them, then save. Because it is ordinary Python, you can use loops, functions and imports to build large diagrams from small parts.

from spinagram import Diagram

d = Diagram("D01", "Checkout", subtitle="How an order is placed")
web, api, db = d.box("Web"), d.box("API"), d.box("Orders DB")
web >> api >> db
d.save()                     # out/D01-checkout.svg

Diagrams

d = Diagram(
    "D01",                   # id: file names, slide order, `--only D01`
    "Checkout",              # title
    subtitle="How an order is placed",
    source="Source: platform team",   # footer line
    layout={"engine": "elk", "direction": "down"},   # optional: see Automatic layout
)

If no node has coordinates, the diagram is laid out automatically with the flex engine, flowing left to right. Pass layout= only to pick another engine or direction.

Catalog metadata

A category and tags group and filter diagrams in spinagram serve and on the CLI (--category, --tag):

d = Diagram("N01", "Hub-and-spoke networking", category="Networking", tags=["aws", "transit"])

Tags are trimmed, lower-cased, de-duplicated and sorted; a single string is one tag. A _defaults.yaml category fills diagrams that set none, and its tags join each diagram's tags.

Icons add provider tags without any metadata, and a diagram with no category falls into the provider it uses most (ties go to the first tag A–Z):

Icons fromTagCategory
aws: (and aws-* group styles)awsAWS
gcp:gcpGoogle Cloud
k8s:kubernetesKubernetes
azure:azureAzure
oci:ociOCI
openstack:openstackOpenStack
selfhosted:self-hostedSelf-hosted
network:networkNetwork

A declared category always wins; a diagram with neither is Uncategorised.

Nodes

api = d.box("API", text="FastAPI on ECS", icon="terrastruct:python")
d.box("Payments", style="dark")              # default | dark | soft | warning | alert | neutral
d.note("Retries are idempotent")             # a sticky note
d.diamond("Fraud?")                          # a decision
d.box(["Two-line", "title"])                 # a list is one line per entry
ArgumentMeaning
titleFirst positional argument; also generates the node id ("Orders DB" → orders-db)
textBody text under the title
iconpack:name from an icon pack, or a path to an SVG or PNG
styleA node style from the theme
idExplicit id, for YAML-style edges like d.edge("api -> db")
x, y, w, hFixed position and size; see Manual layout

Arrows

Operators connect nodes and return the right-hand side, so they chain:

users >> lb >> api           # users -> lb -> api
api << worker                # worker -> api
api - sidecar                # a plain line, no arrow head
lb >> [web1, web2] >> db     # fan out, then fan in

Use .to() when the arrow needs a label or a style. It also returns its target:

users.to(lb, "HTTPS") >> api
api.to(queue, "publish", dashed=True, color="warning")

d.edge() is the long form, and the only one that takes side anchors or waypoints:

d.edge(api, db, "SQL")
d.edge(api.right, db.left)               # leave and enter on these sides
d.edge(api.bottom, cache.top, label="miss")
d.edge(api.at("right", 0.25), db)        # a quarter of the way along the right side
d.edge("api -> db: SQL")                 # YAML shorthand, by node id
d.chain(a, b, c, d2)                     # a -> b -> c -> d2
Edge optionMeaning
labelText on the arrow
colorEdge style (default, dark, alert, warning, gray, light), a theme token or hex
dashedDashed line
arrowend (default), start, both or none
viaWaypoints, [(x, y), ...]

Groups

Everything created inside with d.group(...) belongs to that group. Groups nest, and each one is drawn as a zone with its label and, for AWS styles, its corner icon:

with d.group("us-east-1", style="aws-region"):
    with d.group("VPC", style="aws-vpc", direction="right"):
        for zone in "abc":
            with d.group(f"us-east-1{zone}", style="aws-az"):
                d.box(f"Vault {zone}", icon="aws:backup-backup-vault")
OptionMeaning
stylesoft, warning, alert, neutral, aws-cloud, aws-account, aws-region, aws-vpc, aws-az, aws-subnet; soft when left out
directionright (a row) or down (a column)
columnsLay children out as a grid
gap, padding, alignSpacing and cross-axis alignment
iconCorner icon, overriding the style's

A group with only groups inside is a row by default; any other group is a column.

Build diagrams with code

Diagrams are code, so repetition is a loop and a shared shape is a function:

from spinagram import Diagram

def service(d, name, lang):
    return d.box(name, text=lang, icon=f"terrastruct:{lang}")

d = Diagram("S01", "Services")
gateway = d.box("Gateway", icon="aws:api-gateway")
services = [service(d, n, l) for n, l in [("Users", "java"), ("Orders", "python"), ("Search", "rust")]]
gateway >> services

A Python file can hold several diagrams: define diagrams = [d1, d2], or a build() function that returns them. Helpers in the same folder (_shapes.py) can be imported from the spec.

Render

d.save()                                          # out/<id>-<slug>.svg
d.save("build/", formats=["svg", "png", "pptx"], theme="themes/acme")
svg = d.svg()                                     # the SVG as a string
d                                                 # in Jupyter: shown inline

To render many files at once, use the CLI: spinagram render specs/ -f svg,pptx -o out/ loads every *.py that defines diagram, diagrams or build(). spinagram serve specs/ previews them and re-renders on save.

The lower-level API works on lists of diagrams:

import spinagram

diagrams = spinagram.load("specs/")               # Python and YAML specs
spinagram.render(diagrams, "out/", formats=["svg", "pptx"], gallery=True)

Tables and legends

Tables and legends are placed at fixed positions, usually on manually laid out slides:

d.table(["Signal", "Source", "Effect"],
        [["Chaos test failed", "Stress cluster", "Window or approval"]],
        x=80, y=760, widths=[400, 400, 600])
d.set_legend([("default", "automatic"), ("warning", "review")], x=100, y=960)

On this page