spinagram

Annotations

Legends, numbered steps, callouts and designed notes that explain a diagram.

Annotations explain a diagram on the slide itself. A legend decodes colours and line styles, numbered steps walk through the flow, callouts point at what matters, and notes carry the narrative. They are styled with the same CSS, placed by the layout so they do not overlap content, and drawn as native, editable shapes in SVG, PNG, PowerPoint and Figma.

from spinagram import Diagram

d = Diagram("A01", "Checkout", layout={"engine": "flex", "direction": "right"})
d.stylesheet("brand.css")
users, api, db = d.box("Users", class_="external"), d.box("API", class_="service"), d.box("Orders", class_="database")
users.to(api, "HTTPS", step="Users place an order")
api.to(db, "SQL", step="The API stores it")
d.callout(db, "Encrypted with KMS", title="At rest", kind="tip")
d.add_legend()

See the AWS, Kubernetes and Google Cloud diagrams in examples/showcase for every feature at work.

The annotation band

Legends and the steps panel sit in a band reserved at the right (default) or bottom of the layout area. Automatic layout shrinks the content area by the band, so the diagram never runs under it. With manual coordinates the band sits at the canvas edge and spinagram check reports anything it covers.

Right bandBottom band
Size360 px wide, plus a 32 px gapContent height, plus a 32 px gap
LegendOne entry per row (two columns when tall and the labels fit)Entries flow across the width
StepsBelow the legendTo the right of the legend

A custom layout.area is honoured: the band takes its right or bottom edge.

Legends

The legend's place and shape come from CSS, so one stylesheet can move every legend of a deck:

legend { placement: bottom; columns: 4; gap: 10; padding: 18; swatch-width: 36; swatch-height: 22 }
legend::body { font-size: 15px }
PropertyValuesDefault
placementright, left, top, bottom: the band's edgeright
widthBand width on the left or right, px360 (theme defaults.annotations.band)
columnsEntries per rowOne column on the side (two when tall and the labels fit); flowed by label width on top or bottom
gapSpace between rows, px12
paddingCard padding, px22
swatch-width, swatch-heightSwatch size, px46, 28
displaynone hides the legend (steps keep the band)block
font-size on legend::bodyEntry labels16

An explicit add_legend(position=...) (or position: in YAML) beats placement, the way inline styles beat stylesheets. The steps panel follows the legend's band.

d.add_legend() lists every node class, edge class and non-default node style the diagram uses, once each, in first-use order. Labels come from the names: .web-tier becomes Web tier, so name classes for what they mean. Each swatch is drawn with the computed style of the first element that carries it: fill, stroke, width and dash for nodes (the corner radius scaled to the swatch), colour, width, dash and markers for edges.

Pick entries and labels yourself with a mapping:

d.add_legend({".security": "Security control", "edge.async": "Async event", "aws:s3": "Object storage",
              "style:dark": "Engine"}, title="Key", position="bottom")
KeyEntry
.name or node.nameNode class
edge.nameEdge class
style:nameTheme node style
pack:iconIcon

A key that matches nothing is still drawn from the theme default, and check reports legend: unused entry.

Numbered steps

users.to(api, "HTTPS", step="Users place an order")   # while connecting
d.edge(api, db).step("The API stores it")             # on an edge
d.step("Retries on failure", on=api)                  # on a node's top-left corner
d.step("Bypass", on=("users", "db"), n=9)             # the first edge from users to db, numbered 9

Steps without n take the next number after the highest so far. Each step puts a numbered badge on the diagram (beside the edge label, at the edge's midpoint, or on a node's corner) and a line in the Steps panel, listed in number order. check reports duplicate numbers.

Callouts

d.callout(db, "Snapshots every 6 h", title="Backups", kind="tip", side="auto")

A callout is a bubble with a leader line to its node. kind sets the colour and glyph: info (primary), tip (success), warning (warning) or risk (alert). With side="auto" the bubble takes the first of top, right, bottom and left where it stays inside the area and clears nodes, panels and other callouts, trying centred, start- and end-aligned positions on each side. With no free side it goes on top and check reports callout: overlap. Give side to force one.

Notes

d.note("Aurora PostgreSQL", text="Managed backups and PITR", variant="card", icon="aws:amazon-aurora")
d.note("Werner Vogels", text="Everything fails, all the time.", variant="quote")
d.note("Open question", text="A second region before the EU launch?")   # sticky
VariantLook
sticky (default)The classic note
cardA white card with a shadow and a primary accent bar on its left edge
quoteA tinted panel with a large quote mark; the italic quote first, the title as its attribution

Styling

SelectorTargets
legend, legend::title, legend::bodyThe legend card, its title and entry labels
step, step::labelEvery step badge, in the diagram and in the panel
callout, callout.warning, callout::title, callout::bodyCallout bubbles by kind (info, tip, warning, risk) or class
note.card, note.quote, note.stickyNotes by variant
legend { fill: #0B1220; stroke: #326CE5 }
legend::title { color: #93C5FD }
step { fill: #232F3E }
step::label { color: #FF9900 }
callout.warning { fill: #FEF3C7 }

spinagram check --styles --select "legend, step" explains every value, and the live inspector lists the annotations like any other element.

YAML

nodes:
  - {id: why, kind: note, variant: card, title: Why Aurora, text: Managed backups}
annotations:
  legend: {title: Legend, position: right, items: {".web-tier": Web tier}}
  steps:
    - {on: "users -> api", text: Users place an order}
    - {on: db, n: 9, text: Stored}
  callouts:
    - {on: db, text: Snapshots every 6 h, title: Backups, kind: tip}

on: takes a node id or "a -> b" (the first edge from a to b). Leave out items for an automatic legend.

Checks

ReportMeaning
legend: unused entry '<key>'A legend key matches no element
steps: duplicate number <n>Two steps share a number
annotations: overflowThe panels do not fit the band
annotations: overlapOn a manual layout, the band covers a node or zone
callout: overlapA callout found no free side

On this page