spinagram

Behavior diagrams

Flowcharts, state machines and activity diagrams, alone or inside any diagram.

Three builders add the symbols and verbs of behavior diagrams. Each is a Diagram (use it on its own) and defaults to the built-in layered engine, which ranks nodes along the flow, routes edges orthogonally and draws loops round the side.

Flowchart

examples/behavior/flowchart.py
"""Flowchart: `spinagram render examples/behavior/flowchart.py -f svg,pptx -o out/`."""
from spinagram import Flowchart

fc = Flowchart("B01", "Checkout", subtitle="Flowchart builder, laid out by the layered engine",
               layout={"engine": "layered", "direction": "right", "rank_gap": 48},
               css="node::title { font-size: 21 }")  # a touch smaller so all eight steps fit one slide

start = fc.start()
card = fc.io("Read card")
valid = fc.decision("Card valid?")
charge = fc.process("Charge card")
paid = fc.decision("Charged?")
receipt = fc.document("Send receipt")
error = fc.io("Show error")

start >> card >> valid
valid.yes(charge) >> paid
valid.no(error)
paid.yes(receipt) >> fc.end()
paid.no(error)
error.to(card, "retry")

diagram = fc

Checkout flowchart

VerbSymbol
start(), end()terminator
process(title)process
decision(title) with .yes(x), .no(x), .to(x, "label")decision
io(title)input/output parallelogram
document(title), predefined(title), data(title)document, predefined process, data store

State machine

examples/behavior/state_machine.py
"""State machine: composite state, orthogonal regions, history, choice and self-transition."""
from spinagram import StateMachine

sm = StateMachine("B02", "Order lifecycle", subtitle="StateMachine builder", direction="right",
                  layout={"engine": "layered", "direction": "right", "rank_gap": 56},
                  css="state::title { font-size: 21 } state::body { font-size: 16 }")

created = sm.state("Created", entry="reserve stock")
paid = sm.state("Paid", do="notify warehouse")
cancelled = sm.state("Cancelled")
sm.initial >> created
created.on("pay", paid, guard="amount ok", action="capture")
created.on("cancel", cancelled)
paid.on("retry", paid)

with sm.composite("Fulfilment") as f:
    with f.region() as picking:
        pick = picking.state("Picking")
        picking.initial >> pick
    with f.region() as billing:
        bill = billing.state("Invoicing")
        billing.initial >> bill

paid.on("ship", pick)
done = sm.choice()
bill >> done
done.to(sm.final, "[delivered]")
done.to(cancelled, "[lost]")
cancelled >> sm.final

diagram = sm

Order lifecycle state machine

  • sm.state(title, entry=, do=, exit=) lists the state's activities.
  • s.on(event, target, guard=, action=) labels the transition event [guard] / action; a transition to itself is drawn as a loop.
  • sm.initial and sm.final are one pseudostate per scope.
  • with sm.composite(title) as c: nests substates; with c.region() as r: adds orthogonal regions (dashed boxes); c.history(deep=False) and sm.choice() add pseudostates.

Activity

examples/behavior/activity.py
"""Activity: swimlanes, a sequence and a parallel block (fork and join bars)."""
from spinagram import Activity

act = Activity("B03", "Order fulfilment", subtitle="Activity builder with swimlanes", direction="right",
               layout={"engine": "layered", "direction": "right", "rank_gap": 44})

with act.sequence():
    with act.lane("Customer"):
        act.start()
        act.action("Place order")
    with act.lane("Shop"):
        act.action("Check stock")
        with act.parallel() as par:
            with par.branch():
                act.action("Pick items")
                pack = act.action("Pack")
            with par.branch():
                act.action("Send invoice")
    with act.lane("Warehouse"):
        act.send("Ship parcel")
        act.end()

with act.lane("Shop"):
    slip = act.object("Packing slip")
pack >> slip

diagram = act

Order fulfilment activity with swimlanes

  • with act.lane("Shop"): puts the nodes created inside into that swimlane (re-entering reuses it).
  • with act.sequence(): connects nodes in creation order.
  • with act.parallel() as p: + with p.branch(): draws a fork bar, the branches and a join bar.
  • action, decision, merge, start, end, flow_end, send, receive, object; edges to object nodes are dashed.

Several diagram types on one slide

Any diagram can embed typed regions. A region is a box laid out by its own engine (layered by default, or layout={"engine": ..., "direction": ...}), connected to the rest with normal arrows.

examples/behavior/mixed.py
"""One slide, two diagram types: an architecture view and the state machine of one of its services."""
from spinagram import Diagram

d = Diagram("B04", "Orders service and its lifecycle", subtitle="Typed regions in one diagram")

with d.group("Platform", style="soft", direction="down"):
    web = d.box("Web shop", text="React", icon="terrastruct:react")
    api = d.box("Orders API", text="Python", icon="terrastruct:python")
    db = d.box("Orders DB", text="PostgreSQL", icon="terrastruct:postgres")
web >> api >> db

with d.state_machine("Order lifecycle", direction="down") as sm:
    created = sm.state("Created")
    paid = sm.state("Paid")
    shipped = sm.state("Shipped")
    sm.initial >> created
    created.on("pay", paid)
    paid.on("ship", shipped)
    paid.on("refund", created)
    shipped >> sm.final

api.to(created, "creates")

diagram = d

Architecture and state machine on one slide

Checks

spinagram check reports decisions with fewer than two branches, forks with one branch and state regions without an initial state. Edges out of a final node or into an initial node fail when the diagram is built.

On this page