Diagrams

You can generate visual diagrams from any StateChart — useful for documentation, debugging, or sharing your machine’s structure with teammates.

Installation

Diagram generation requires pydot and Graphviz:

pip install python-statemachine[diagrams]  # installs pydot

You also need the dot command-line tool from Graphviz. On Debian/Ubuntu:

sudo apt install graphviz

For other systems, see the Graphviz downloads page.

Generating diagrams

Every state machine instance exposes a _graph() method that returns a pydot.Dot graph object:

from tests.examples.order_control_machine import OrderControl

sm = OrderControl()
graph = sm._graph()  # returns a pydot.Dot object

Highlighting the current state

The diagram automatically highlights the current state of the instance. Send events to advance the machine and see the active state change:

from tests.examples.traffic_light_machine import TrafficLightMachine

sm = TrafficLightMachine()
sm.send("cycle")
sm._graph().write_png("traffic_light_yellow.png")
TrafficLightMachine TrafficLightMachine cluster___atomic_130952760390864 __initial_130952760390864 green Green __initial_130952760390864->green yellow Yellow green->yellow Cycle red Red yellow->red Cycle red->green Cycle
TrafficLightMachine after one cycle

Exporting to a file

The pydot.Dot object supports writing to many formats — use write_png(), write_svg(), write_pdf(), etc.:

sm = OrderControl()
sm._graph().write_png("order_control.png")
OrderControl OrderControl cluster___atomic_130952760390976 __initial_130952760390976 waiting_for_payment Waiting for payment entry / on_enter_waiting_for_payment __initial_130952760390976->waiting_for_payment waiting_for_payment->waiting_for_payment Add to order waiting_for_payment->waiting_for_payment Receive payment [!payments_enough] processing Processing waiting_for_payment->processing Receive payment [payments_enough] shipping Shipping processing->shipping Process order [payment_received] completed Completed shipping->completed Ship order
OrderControl

For higher resolution PNGs, set the DPI before exporting:

graph = sm._graph()
graph.set_dpi(300).write_png("order_control_300dpi.png")

Note

Supported formats include dia, dot, fig, gif, jpg, pdf, png, ps, svg, and many others. See Graphviz output formats for the complete list.

Text representations

State machines support multiple text-based output formats, all accessible through Python’s built-in format() protocol, the formatter API, or the command line.

Format

Aliases

Description

Dependencies

mermaid

Mermaid stateDiagram-v2 source

None [1]

md

markdown

Transition table (pipe-delimited Markdown)

None

rst

Transition table (RST grid table)

None

dot

Graphviz DOT language source

pydot

svg

SVG markup (generated via DOT)

pydot, Graphviz

Using format()

Use f-strings or the built-in format() function — no diagram imports needed:

>>> from tests.examples.traffic_light_machine import TrafficLightMachine
>>> sm = TrafficLightMachine()
>>> print(f"{sm:mermaid}")
stateDiagram-v2
    direction LR
    state "Green" as green
    state "Yellow" as yellow
    state "Red" as red
    [*] --> green
    green --> yellow : Cycle
    yellow --> red : Cycle
    red --> green : Cycle

    classDef active fill:#40E0D0,stroke:#333
    green:::active


>>> print(f"{sm:md}")
| State  | Event | Guard | Target |
| ------ | ----- | ----- | ------ |
| Green  | Cycle |       | Yellow |
| Yellow | Cycle |       | Red    |
| Red    | Cycle |       | Green  |


Works on classes too (no active-state highlighting):

>>> print(f"{TrafficLightMachine:mermaid}")
stateDiagram-v2
    direction LR
    state "Green" as green
    state "Yellow" as yellow
    state "Red" as red
    [*] --> green
    green --> yellow : Cycle
    yellow --> red : Cycle
    red --> green : Cycle


The dot format returns the Graphviz DOT language source:

>>> print(f"{sm:dot}")
digraph TrafficLightMachine {
...
}

An empty format spec (e.g., f"{sm:}") falls back to repr().

Using the formatter API

The formatter object is the programmatic entry point for rendering state machines in any registered text format:

>>> from statemachine.contrib.diagram import formatter
>>> from tests.examples.traffic_light_machine import TrafficLightMachine

>>> print(formatter.render(TrafficLightMachine, "mermaid"))
stateDiagram-v2
    direction LR
    state "Green" as green
    state "Yellow" as yellow
    state "Red" as red
    [*] --> green
    green --> yellow : Cycle
    yellow --> red : Cycle
    red --> green : Cycle


>>> formatter.supported_formats()
['dot', 'markdown', 'md', 'mermaid', 'rst', 'svg']

Both format() and the Sphinx directive delegate to this same formatter under the hood.

Registering custom formats

The formatter is extensible — register your own format with a decorator and it becomes available everywhere (format(), CLI, Sphinx directive):

from statemachine.contrib.diagram import formatter

@formatter.register_format("plantuml", "puml")
def _render_plantuml(machine_or_class):
    # your PlantUML renderer here
    ...

After registration, f"{sm:plantuml}" and --format plantuml work immediately.

Command line

You can generate diagrams without writing Python code:

python -m statemachine.contrib.diagram <classpath> <output_file>

The output format is inferred from the file extension:

python -m statemachine.contrib.diagram tests.examples.traffic_light_machine.TrafficLightMachine diagram.png

To highlight the current state, use --events to instantiate the machine and send events before rendering:

python -m statemachine.contrib.diagram tests.examples.traffic_light_machine.TrafficLightMachine diagram.png --events cycle cycle cycle

Use --format to produce a text format instead of a Graphviz image:

# Mermaid stateDiagram-v2
python -m statemachine.contrib.diagram tests.examples.traffic_light_machine.TrafficLightMachine output.mmd --format mermaid

# DOT source
python -m statemachine.contrib.diagram tests.examples.traffic_light_machine.TrafficLightMachine output.dot --format dot

# Markdown transition table
python -m statemachine.contrib.diagram tests.examples.traffic_light_machine.TrafficLightMachine output.md --format md

# RST transition table
python -m statemachine.contrib.diagram tests.examples.traffic_light_machine.TrafficLightMachine output.rst --format rst

Use - as the output file to write to stdout (handy for piping):

python -m statemachine.contrib.diagram tests.examples.traffic_light_machine.TrafficLightMachine - --format mermaid

Auto-expanding docstrings

Use {statechart:FORMAT} placeholders in your class docstring to embed a live representation of the state machine. The placeholder is replaced at class definition time, so the docstring always reflects the actual states and transitions:

>>> from statemachine.statemachine import StateChart
>>> from statemachine.state import State

>>> class TrafficLight(StateChart):
...     """A traffic light.
...
...     {statechart:md}
...     """
...     green = State(initial=True)
...     yellow = State()
...     red = State()
...     cycle = green.to(yellow) | yellow.to(red) | red.to(green)

>>> print(TrafficLight.__doc__)
A traffic light.

| State  | Event | Guard | Target |
| ------ | ----- | ----- | ------ |
| Green  | Cycle |       | Yellow |
| Yellow | Cycle |       | Red    |
| Red    | Cycle |       | Green  |



Any registered format works: {statechart:rst}, {statechart:mermaid}, {statechart:dot}, etc.

Choosing the right format

Context

Recommended format

Sphinx with RST (autodoc default)

{statechart:rst}

Sphinx with MyST Markdown

{statechart:md}

help() in terminal / IDE

Either works; md reads more cleanly

Sphinx autodoc integration

Since the placeholder is expanded at class definition time, Sphinx autodoc sees the final rendered text — no extra configuration needed.

For example, this class uses {statechart:rst} in its docstring:

class SimpleSC(StateChart):
    """A simple three-state machine.

    {statechart:rst}
    """

    idle = State(initial=True)
    running = State()
    done = State(final=True)

    start = idle.to(running)
    finish = running.to(done)

And here is the rendered autodoc output:

class tests.machines.showcase_simple.SimpleSC(model: TModel | None = None, state_field: str = 'state', start_value: Any = None, listeners: List[object] | None = None, **kwargs: Any)[source]

A simple three-state machine.

State

Event

Guard

Target

Idle

Start

Running

Running

Finish

Done

Sphinx directive

If you use Sphinx to build your documentation, the statemachine-diagram directive renders diagrams inline — no need to generate image files manually.

Setup

Add the extension to your conf.py:

extensions = [
    ...
    "statemachine.contrib.diagram.sphinx_ext",
]

Basic usage

Reference any importable StateChart class by its fully qualified path:

```{statemachine-diagram} myproject.machines.OrderControl
```
OrderControl OrderControl cluster___atomic_130952777280976 __initial_130952777280976 waiting_for_payment Waiting for payment entry / on_enter_waiting_for_payment __initial_130952777280976->waiting_for_payment waiting_for_payment->waiting_for_payment Add to order waiting_for_payment->waiting_for_payment Receive payment [!payments_enough] processing Processing waiting_for_payment->processing Receive payment [payments_enough] shipping Shipping processing->shipping Process order [payment_received] completed Completed shipping->completed Ship order

Highlighting a specific state

Pass :events: to instantiate the machine and send events before rendering. This highlights the current state after processing:

```{statemachine-diagram} myproject.machines.TrafficLight
:events: cycle
:caption: Traffic light after one cycle
```
TrafficLightMachine TrafficLightMachine cluster___atomic_130952760486480 __initial_130952760486480 green Green __initial_130952760486480->green yellow Yellow green->yellow Cycle red Red yellow->red Cycle red->green Cycle
Traffic light after one cycle

Enabling zoom

For complex diagrams, add :target: (without a value) to make the diagram clickable — it opens the full SVG in a new browser tab where users can zoom and pan freely:

```{statemachine-diagram} myproject.machines.OrderControl
:target:
```
OrderControl OrderControl cluster___atomic_130952777282768 __initial_130952777282768 waiting_for_payment Waiting for payment entry / on_enter_waiting_for_payment __initial_130952777282768->waiting_for_payment waiting_for_payment->waiting_for_payment Add to order waiting_for_payment->waiting_for_payment Receive payment [!payments_enough] processing Processing waiting_for_payment->processing Receive payment [payments_enough] shipping Shipping processing->shipping Process order [payment_received] completed Completed shipping->completed Ship order
Click to open full-size SVG

Mermaid format

Use :format: mermaid to render via sphinxcontrib-mermaid instead of Graphviz SVG — useful when you don’t want to install Graphviz in your docs build environment:

```{statemachine-diagram} myproject.machines.TrafficLight
:format: mermaid
:caption: Rendered as Mermaid
```
        stateDiagram-v2
    direction LR
    state "Green" as green
    state "Yellow" as yellow
    state "Red" as red
    [*] --> green
    green --> yellow : Cycle
    yellow --> red : Cycle
    red --> green : Cycle

    

TrafficLightMachine (Mermaid)

Directive options

The directive supports the same layout options as the standard image and figure directives, plus state-machine-specific ones.

State-machine options:

:events: (comma-separated string)

Events to send in sequence. When present, the machine is instantiated and each event is sent before rendering.

:format: (string)

Output format. Use mermaid to render via sphinxcontrib-mermaid instead of Graphviz SVG. Default: DOT/SVG.

Image/figure options:

:caption: (string)

Caption text; wraps the image in a figure node.

:alt: (string)

Alt text for the image. Defaults to the class name.

:width: (CSS length, e.g. 400px, 80%)

Explicit width for the diagram.

:height: (CSS length)

Explicit height for the diagram.

:scale: (integer percentage, e.g. 50%)

Uniform scaling relative to the intrinsic size.

:align: (left | center | right)

Image alignment. Defaults to center.

:target: (URL or empty)

Makes the diagram clickable. When set without a value, the raw SVG is saved as a file and linked so users can open it in a new tab for full-resolution zooming — useful for large or complex diagrams.

:class: (space-separated strings)

Extra CSS classes for the wrapper element.

:figclass: (space-separated strings)

Extra CSS classes for the figure element (only when :caption: is set).

:name: (string)

Reference target name for cross-referencing with {ref}.

Note

The directive imports the state machine class at Sphinx parse time. Machines defined inline in doctest blocks cannot be referenced — use the _graph() method for those cases.

Jupyter integration

State machine instances are automatically rendered as diagrams in JupyterLab cells — no extra code needed:

Approval machine on JupyterLab

Online generation (QuickChart)

If you prefer not to install Graphviz locally, you can generate diagrams using the QuickChart online service:

statemachine.contrib.diagram.quickchart_write_svg(sm, path: str)[source]

If the default dependency of GraphViz installed locally doesn’t work for you. As an option, you can generate the image online from the output of the dot language, using one of the many services available.

To get the dot representation of your state machine is as easy as follows:

>>> from tests.examples.order_control_machine import OrderControl
>>> sm = OrderControl()
>>> print(sm._graph().to_string())
digraph OrderControl {
...
}

To give you an example, we included this method that will serialize the dot, request the graph to https://quickchart.io, and persist the result locally as an .svg file.

Warning

Quickchart is an external graph service that supports many formats to generate diagrams.

By using this method, you should trust http://quickchart.io.

Please read https://quickchart.io/documentation/faq/ for more information.

>>> quickchart_write_svg(sm, "docs/images/oc_machine_processing.svg")

Customizing the output

The DotGraphMachine class gives you control over the diagram’s visual properties. Subclass it and override the class attributes to customize fonts, colors, and layout:

from statemachine.contrib.diagram import DotGraphMachine
from tests.examples.order_control_machine import OrderControl

Available attributes:

Attribute

Default

Description

graph_rankdir

"LR"

Graph direction ("LR" left-to-right, "TB" top-to-bottom)

font_name

"Helvetica"

Font face for labels

state_font_size

"10"

State label font size

state_active_penwidth

2

Border width of the active state

state_active_fillcolor

"turquoise"

Fill color of the active state

transition_font_size

"9"

Transition label font size

For example, to generate a top-to-bottom diagram with a custom active state color:

class CustomDiagram(DotGraphMachine):
    graph_rankdir = "TB"
    state_active_fillcolor = "lightyellow"

sm = OrderControl()
sm.receive_payment(10)

graph = CustomDiagram(sm)
dot = graph()
dot.write_svg("order_control_custom.svg")

DotGraphMachine also works with classes (not just instances) to generate diagrams without an active state:

dot = DotGraphMachine(OrderControl)()
dot.write_png("order_control_class.png")

Visual showcase

This section shows how each state machine feature is rendered in diagrams. Each example includes the class definition, diagrams in both Graphviz and Mermaid formats, and instance diagrams with the current state highlighted after sending events.

Simple states

A minimal state machine with three atomic states and linear transitions.

class SimpleSC(StateChart):
    """A simple three-state machine.

    {statechart:rst}
    """

    idle = State(initial=True)
    running = State()
    done = State(final=True)

    start = idle.to(running)
    finish = running.to(done)
SimpleSC SimpleSC cluster___atomic_130952759168816 __initial_130952759168816 idle Idle __initial_130952759168816->idle running Running idle->running Start done Done running->done Finish
Class (Graphviz)
        stateDiagram-v2
    direction LR
    state "Idle" as idle
    state "Running" as running
    state "Done" as done
    [*] --> idle
    done --> [*]
    idle --> running : Start
    running --> done : Finish

    

Class (Mermaid)

SimpleSC SimpleSC cluster___atomic_130952759171504 __initial_130952759171504 idle Idle __initial_130952759171504->idle running Running idle->running Start done Done running->done Finish
Initial
SimpleSC SimpleSC cluster___atomic_130952759171840 __initial_130952759171840 idle Idle __initial_130952759171840->idle running Running idle->running Start done Done running->done Finish
Running
SimpleSC SimpleSC cluster___atomic_130952759172176 __initial_130952759172176 idle Idle __initial_130952759172176->idle running Running idle->running Start done Done running->done Finish
Done (final)

Entry and exit actions

States can declare entry / exit callbacks, shown in the state label.

class ActionsSC(StateChart):
    off = State(initial=True)
    on = State()
    done = State(final=True)

    power_on = off.to(on)
    shutdown = on.to(done)

    def on_exit_off(self): ...
    def on_enter_on(self): ...
    def on_exit_on(self): ...
    def on_enter_done(self): ...
ActionsSC ActionsSC cluster___atomic_130952759172512 __initial_130952759172512 off Off exit / on_exit_off __initial_130952759172512->off on On entry / on_enter_on exit / on_exit_on off->on Power on done Done entry / on_enter_done on->done Shutdown
Class (Graphviz)
        stateDiagram-v2
    direction LR
    state "Off" as off
    off : exit / on_exit_off
    state "On" as on
    on : entry / on_enter_on
    on : exit / on_exit_on
    state "Done" as done
    done : entry / on_enter_done
    [*] --> off
    done --> [*]
    off --> on : Power on
    on --> done : Shutdown

    

Class (Mermaid)

ActionsSC ActionsSC cluster___atomic_130952759173520 __initial_130952759173520 off Off exit / on_exit_off __initial_130952759173520->off on On entry / on_enter_on exit / on_exit_on off->on Power on done Done entry / on_enter_done on->done Shutdown
Active: On

Guard conditions

Transitions can have cond guards, shown in brackets on the edge label.

class GuardSC(StateChart):
    pending = State(initial=True)
    approved = State(final=True)
    rejected = State(final=True)

    def is_valid(self):
        return True

    def is_invalid(self):
        return False

    review = pending.to(approved, cond="is_valid") | pending.to(rejected, cond="is_invalid")
GuardSC GuardSC cluster___atomic_130952759174416 __initial_130952759174416 pending Pending __initial_130952759174416->pending approved Approved pending->approved Review [is_valid] rejected Rejected pending->rejected Review [is_invalid]
Class (Graphviz)
        stateDiagram-v2
    direction LR
    state "Pending" as pending
    state "Approved" as approved
    state "Rejected" as rejected
    [*] --> pending
    approved --> [*]
    rejected --> [*]
    pending --> approved : Review [is_valid]
    pending --> rejected : Review [is_invalid]

    

Class (Mermaid)

GuardSC GuardSC cluster___atomic_130952759174528 __initial_130952759174528 pending Pending __initial_130952759174528->pending approved Approved pending->approved Review [is_valid] rejected Rejected pending->rejected Review [is_invalid]
Active: Pending

Self-transitions

A transition from a state back to itself.

class SelfTransitionSC(StateChart):
    counting = State(initial=True)
    done = State(final=True)

    increment = counting.to.itself()
    stop = counting.to(done)
SelfTransitionSC SelfTransitionSC cluster___atomic_130952759175424 __initial_130952759175424 counting Counting __initial_130952759175424->counting counting->counting Increment done Done counting->done Stop
Class (Graphviz)
        stateDiagram-v2
    direction LR
    state "Counting" as counting
    state "Done" as done
    [*] --> counting
    done --> [*]
    counting --> counting : Increment
    counting --> done : Stop

    

Class (Mermaid)

SelfTransitionSC SelfTransitionSC cluster___atomic_130952759174528 __initial_130952759174528 counting Counting __initial_130952759174528->counting counting->counting Increment done Done counting->done Stop
Active: Counting

Internal transitions

Internal transitions execute actions without exiting/entering the state.

class InternalSC(StateChart):
    monitoring = State(initial=True)
    done = State(final=True)

    def log_status(self): ...

    check = monitoring.to.itself(internal=True, on="log_status")
    stop = monitoring.to(done)
InternalSC InternalSC cluster___atomic_130952759172288 __initial_130952759172288 monitoring Monitoring check / log_status __initial_130952759172288->monitoring done Done monitoring->done Stop
Class (Graphviz)
        stateDiagram-v2
    direction LR
    state "Monitoring" as monitoring
    monitoring : check / log_status
    state "Done" as done
    [*] --> monitoring
    done --> [*]
    monitoring --> done : Stop

    

Class (Mermaid)

InternalSC InternalSC cluster___atomic_130952759171392 __initial_130952759171392 monitoring Monitoring check / log_status __initial_130952759171392->monitoring done Done monitoring->done Stop
Active: Monitoring

Compound states

A compound state contains child states. Entering the compound activates its initial child.

class CompoundSC(StateChart):
    class active(State.Compound, name="Active"):
        idle = State(initial=True)
        working = State()
        begin = idle.to(working)

    off = State(initial=True)
    done = State(final=True)

    turn_on = off.to(active)
    turn_off = active.to(done)
CompoundSC CompoundSC cluster_active Active cluster___atomic_130952759008608 cluster___atomic_130952759168480 __initial_130952759168480 off Off __initial_130952759168480->off off->active_anchor_in Turn on __initial_130952759008608 idle Idle __initial_130952759008608->idle working Working idle->working Begin done Done active_anchor_out->done Turn off
Class (Graphviz)
        stateDiagram-v2
    direction LR
    state "Active" as active {
        [*] --> idle
        state "Idle" as idle
        state "Working" as working
        idle --> working : Begin
    }
    state "Off" as off
    state "Done" as done
    [*] --> off
    done --> [*]
    active --> done : Turn off
    off --> active : Turn on

    

Class (Mermaid)

CompoundSC CompoundSC cluster_active Active cluster___atomic_130952759005488 cluster___atomic_130952759176768 __initial_130952759176768 off Off __initial_130952759176768->off off->active_anchor_in Turn on __initial_130952759005488 idle Idle __initial_130952759005488->idle working Working idle->working Begin done Done active_anchor_out->done Turn off
Off
CompoundSC CompoundSC cluster_active Active cluster___atomic_130952759003168 cluster___atomic_130952759177104 __initial_130952759177104 off Off __initial_130952759177104->off off->active_anchor_in Turn on __initial_130952759003168 idle Idle __initial_130952759003168->idle working Working idle->working Begin done Done active_anchor_out->done Turn off
Active/Idle
CompoundSC CompoundSC cluster_active Active cluster___atomic_130952759510432 cluster___atomic_130952759177440 __initial_130952759177440 off Off __initial_130952759177440->off off->active_anchor_in Turn on __initial_130952759510432 idle Idle __initial_130952759510432->idle working Working idle->working Begin done Done active_anchor_out->done Turn off
Active/Working

Parallel states

A parallel state activates all its regions simultaneously.

class ParallelSC(StateChart):
    class both(State.Parallel, name="Both"):
        class left(State.Compound, name="Left"):
            l1 = State(initial=True)
            l2 = State(final=True)
            go_l = l1.to(l2)

        class right(State.Compound, name="Right"):
            r1 = State(initial=True)
            r2 = State(final=True)
            go_r = r1.to(r2)

    start = State(initial=True)
    end = State(final=True)

    enter = start.to(both)
    done_state_both = both.to(end)
ParallelSC ParallelSC cluster_both Both cluster_left Left cluster___atomic_130952759514672 cluster_right Right cluster___atomic_130952759514912 cluster___atomic_130952759179344 __initial_130952759179344 start Start __initial_130952759179344->start start->both_anchor_in Enter __initial_130952759514672 l1 L1 __initial_130952759514672->l1 l2 L2 l1->l2 Go l __initial_130952759514912 r1 R1 __initial_130952759514912->r1 r2 R2 r1->r2 Go r end End both_anchor_out->end Done state both
Class (Graphviz)
        stateDiagram-v2
    direction LR
    state "Both" as both {
        state "Left" as left {
            [*] --> l1
            state "L1" as l1
            state "L2" as l2
            l1 --> l2 : Go l
            l2 --> [*]
        }
        --
        state "Right" as right {
            [*] --> r1
            state "R1" as r1
            state "R2" as r2
            r1 --> r2 : Go r
            r2 --> [*]
        }
    }
    state "Start" as start
    state "End" as end
    [*] --> start
    end --> [*]
    both --> end : Done state both
    start --> both : Enter

    

Class (Mermaid)

ParallelSC ParallelSC cluster_both Both cluster_left Left cluster___atomic_130952759001728 cluster_right Right cluster___atomic_130952759001968 cluster___atomic_130952759179456 __initial_130952759179456 start Start __initial_130952759179456->start start->both_anchor_in Enter __initial_130952759001728 l1 L1 __initial_130952759001728->l1 l2 L2 l1->l2 Go l __initial_130952759001968 r1 R1 __initial_130952759001968->r1 r2 R2 r1->r2 Go r end End both_anchor_out->end Done state both
Both active
ParallelSC ParallelSC cluster_both Both cluster_left Left cluster___atomic_130952759002288 cluster_right Right cluster___atomic_130952759003328 cluster___atomic_130952759177104 __initial_130952759177104 start Start __initial_130952759177104->start start->both_anchor_in Enter __initial_130952759002288 l1 L1 __initial_130952759002288->l1 l2 L2 l1->l2 Go l __initial_130952759003328 r1 R1 __initial_130952759003328->r1 r2 R2 r1->r2 Go r end End both_anchor_out->end Done state both
Left done

Parallel with cross-boundary transitions

A transition targeting a compound state inside a parallel region triggers a rendering bug in Mermaid (mermaid-js/mermaid#4052). The Mermaid renderer works around this by redirecting the arrow to the compound’s initial child — compare the rebuild arrow in both diagrams below.

class ParallelCompoundSC(StateChart):
    """Parallel regions with a cross-boundary transition into an inner compound.

    The ``rebuild`` transition targets ``pipeline.build`` — a compound state
    inside a parallel region.  This is the exact pattern that triggers
    `mermaid-js/mermaid#4052 <https://github.com/mermaid-js/mermaid/issues/4052>`_;
    the Mermaid renderer works around it by redirecting the arrow to the
    compound's initial child.

    {statechart:rst}
    """

    class pipeline(State.Parallel, name="Pipeline"):
        class build(State.Compound, name="Build"):
            compile = State(initial=True)
            link = State(final=True)
            do_build = compile.to(link)

        class test(State.Compound, name="Test"):
            unit = State(initial=True)
            e2e = State(final=True)
            do_test = unit.to(e2e)

    idle = State(initial=True)
    review = State()

    start = idle.to(pipeline)
    done_state_pipeline = pipeline.to(review)
    rebuild = review.to(pipeline.build)
    accept = review.to(idle)
ParallelCompoundSC ParallelCompoundSC cluster_pipeline Pipeline cluster_build Build cluster___atomic_130952759013248 cluster_test Test cluster___atomic_130952759014368 cluster___atomic_130952759180576 __initial_130952759180576 idle Idle __initial_130952759180576->idle idle->pipeline_anchor_in Start __initial_130952759013248 compile Compile __initial_130952759013248->compile link Link compile->link Do build __initial_130952759014368 unit Unit __initial_130952759014368->unit e2e E2e unit->e2e Do test review Review pipeline_anchor_out->review Done state pipeline review->idle Accept review->build_anchor Rebuild
Class (Graphviz) — ``rebuild`` points to the Build compound border
        stateDiagram-v2
    direction LR
    state "Pipeline" as pipeline {
        state "Build" as build {
            [*] --> compile
            state "Compile" as compile
            state "Link" as link
            compile --> link : Do build
            link --> [*]
        }
        --
        state "Test" as test {
            [*] --> unit
            state "Unit" as unit
            state "E2e" as e2e
            unit --> e2e : Do test
            e2e --> [*]
        }
    }
    state "Idle" as idle
    state "Review" as review
    [*] --> idle
    pipeline --> review : Done state pipeline
    idle --> pipeline : Start
    review --> compile : Rebuild
    review --> idle : Accept

    

Class (Mermaid) — ``rebuild`` is redirected to Compile (initial child of Build)

ParallelCompoundSC ParallelCompoundSC cluster_pipeline Pipeline cluster_build Build cluster___atomic_130952759517232 cluster_test Test cluster___atomic_130952759517472 cluster___atomic_130952759181136 __initial_130952759181136 idle Idle __initial_130952759181136->idle idle->pipeline_anchor_in Start __initial_130952759517232 compile Compile __initial_130952759517232->compile link Link compile->link Do build __initial_130952759517472 unit Unit __initial_130952759517472->unit e2e E2e unit->e2e Do test review Review pipeline_anchor_out->review Done state pipeline review->idle Accept review->build_anchor Rebuild
Build done
ParallelCompoundSC ParallelCompoundSC cluster_pipeline Pipeline cluster_build Build cluster___atomic_130952759520112 cluster_test Test cluster___atomic_130952759520352 cluster___atomic_130952757690672 __initial_130952757690672 idle Idle __initial_130952757690672->idle idle->pipeline_anchor_in Start __initial_130952759520112 compile Compile __initial_130952759520112->compile link Link compile->link Do build __initial_130952759520352 unit Unit __initial_130952759520352->unit e2e E2e unit->e2e Do test review Review pipeline_anchor_out->review Done state pipeline review->idle Accept review->build_anchor Rebuild
Pipeline done → Review

History states (shallow)

A history pseudo-state remembers the last active child of a compound state.

class HistorySC(StateChart):
    class process(State.Compound, name="Process"):
        step1 = State(initial=True)
        step2 = State()
        advance = step1.to(step2)
        h = HistoryState()

    paused = State(initial=True)

    pause = process.to(paused)
    resume = paused.to(process.h)
    begin = paused.to(process)
HistorySC HistorySC cluster_process Process cluster___atomic_130952759522272 cluster___atomic_130952760488608 __initial_130952760488608 paused Paused __initial_130952760488608->paused h H paused->h Resume paused->process_anchor_in Begin __initial_130952759522272 step1 Step1 __initial_130952759522272->step1 step2 Step2 step1->step2 Advance process_anchor_out->paused Pause
Class (Graphviz)
        stateDiagram-v2
    direction LR
    state "Process" as process {
        [*] --> step1
        state "Step1" as step1
        state "Step2" as step2
        state "H" as h
        step1 --> step2 : Advance
    }
    state "Paused" as paused
    [*] --> paused
    process --> paused : Pause
    paused --> h : Resume
    paused --> process : Begin

    

Class (Mermaid)

HistorySC HistorySC cluster_process Process cluster___atomic_130952759524912 cluster___atomic_130952757692240 __initial_130952757692240 paused Paused __initial_130952757692240->paused h H paused->h Resume paused->process_anchor_in Begin __initial_130952759524912 step1 Step1 __initial_130952759524912->step1 step2 Step2 step1->step2 Advance process_anchor_out->paused Pause
Step2
HistorySC HistorySC cluster_process Process cluster___atomic_130952759519792 cluster___atomic_130952759179344 __initial_130952759179344 paused Paused __initial_130952759179344->paused h H paused->h Resume paused->process_anchor_in Begin __initial_130952759519792 step1 Step1 __initial_130952759519792->step1 step2 Step2 step1->step2 Advance process_anchor_out->paused Pause
Paused
HistorySC HistorySC cluster_process Process cluster___atomic_130952759510272 cluster___atomic_130952759180576 __initial_130952759180576 paused Paused __initial_130952759180576->paused h H paused->h Resume paused->process_anchor_in Begin __initial_130952759510272 step1 Step1 __initial_130952759510272->step1 step2 Step2 step1->step2 Advance process_anchor_out->paused Pause
Resumed (→Step2)

Deep history

Deep history remembers the exact leaf state across nested compounds.

class DeepHistorySC(StateChart):
    class outer(State.Compound, name="Outer"):
        class inner(State.Compound, name="Inner"):
            a = State(initial=True)
            b = State()
            go = a.to(b)

        start = State(initial=True)
        enter_inner = start.to(inner)
        h = HistoryState(type="deep")

    away = State(initial=True)

    dive = away.to(outer)
    leave = outer.to(away)
    restore = away.to(outer.h)
DeepHistorySC DeepHistorySC cluster_outer Outer cluster_inner Inner cluster___atomic_130952759013648 cluster___atomic_130952769836192 cluster___atomic_130952759180688 __initial_130952759180688 away Away __initial_130952759180688->away h H* away->h Restore away->outer_anchor_in Dive __initial_130952769836192 start Start __initial_130952769836192->start start->inner_anchor Enter inner __initial_130952759013648 a A __initial_130952759013648->a b B a->b Go outer_anchor_out->away Leave
Class (Graphviz)
        stateDiagram-v2
    direction LR
    state "Outer" as outer {
        [*] --> start
        state "Inner" as inner {
            [*] --> a
            state "A" as a
            state "B" as b
            a --> b : Go
        }
        state "Start" as start
        state "H*" as h
        start --> inner : Enter inner
    }
    state "Away" as away
    [*] --> away
    outer --> away : Leave
    away --> outer : Dive
    away --> h : Restore

    

Class (Mermaid)

DeepHistorySC DeepHistorySC cluster_outer Outer cluster_inner Inner cluster___atomic_130952757772688 cluster___atomic_130952759001168 cluster___atomic_130952757693472 __initial_130952757693472 away Away __initial_130952757693472->away h H* away->h Restore away->outer_anchor_in Dive __initial_130952759001168 start Start __initial_130952759001168->start start->inner_anchor Enter inner __initial_130952757772688 a A __initial_130952757772688->a b B a->b Go outer_anchor_out->away Leave
Inner/B
DeepHistorySC DeepHistorySC cluster_outer Outer cluster_inner Inner cluster___atomic_130952757775328 cluster___atomic_130952757774768 cluster___atomic_130952757693920 __initial_130952757693920 away Away __initial_130952757693920->away h H* away->h Restore away->outer_anchor_in Dive __initial_130952757774768 start Start __initial_130952757774768->start start->inner_anchor Enter inner __initial_130952757775328 a A __initial_130952757775328->a b B a->b Go outer_anchor_out->away Leave
Restored (→Inner/B)