A small Ruby library for generating finite state machine and automaton diagrams as SVG, PNG, PDF, WebP, HTML with Mermaid.js, GraphViz DOT, and PlantUML.
gem 'graphomaton'bundle install
# or
gem install graphomatonrequire 'graphomaton'
automaton = Graphomaton.new
automaton.add_state('q0', label: 'Start')
automaton.add_state('q1')
automaton.add_state('q2', label: 'Accept')
automaton.set_initial('q0')
automaton.add_final('q2')
automaton.add_transition('q0', 'q1', 'a')
automaton.add_transition('q1', 'q2', 'b')
automaton.add_transition('q0', 'q2', :epsilon)
automaton.save_svg('diagram.svg')
automaton.save_html('diagram.html')
automaton.save_dot('diagram.dot')
automaton.save_plantuml('diagram.puml')Use render and save when the format is selected dynamically:
automaton.render(format: :svg, width: 800, height: 600)
automaton.save('diagram.svg', format: :svg, width: 800, height: 600)Validate and inspect the automaton before rendering:
automaton.validate!
automaton.layout_warnings(800, 600)
automaton.reachable_states
automaton.dead_states
automaton.trap_statesBuild an automaton from Hash, JSON, or YAML:
automaton = Graphomaton.from_hash(
states: [
{ id: 'q0', label: 'Start', initial: true },
{ id: 'q1', final: true }
],
transitions: [
{ from: 'q0', to: 'q1', label: 'a', line_style: 'dashed' }
]
)
Graphomaton.from_json(File.read('automaton.json'))
Graphomaton.from_yaml(File.read('automaton.yml'))automaton = Graphomaton.new
automaton.add_state(
'idle',
label: 'Idle',
metadata: { tooltip: 'Waiting for work', group: 'runtime', icon: 'I' }
)
automaton.add_state(
'running',
label: 'Running',
metadata: { tooltip: 'Processing job', group: 'runtime', url: 'https://example.com/runbook' }
)
automaton.add_state('failed', label: 'Failed', style: { fill: '#fee2e2', stroke: '#dc2626' })
automaton.set_initial('idle')
automaton.add_transition('idle', 'running', 'start', metadata: { bundle: 'happy-path' })
automaton.add_transition('running', 'idle', 'finish', metadata: { bundle: 'happy-path' })
automaton.add_transition('running', 'failed', 'error', line_style: :dashed)
automaton.save_svg(
'runtime.svg',
900,
500,
layout: :layered,
direction: :lr,
theme: :ocean,
edge_style: :spline,
label_tooltips: true,
html_tooltips: true
)automaton = Graphomaton.new
automaton.add_state('north', 300, 80)
automaton.add_state('east', 520, 260)
automaton.add_state('south', 300, 440)
automaton.add_state('west', 80, 260)
automaton.add_transition('north', 'east', 'turn')
automaton.add_transition('east', 'south', 'turn')
automaton.add_transition('south', 'west', 'turn')
automaton.add_transition('west', 'north', 'turn')
automaton.save_svg('manual.svg', 600, 520, layout: :manual, fit: :contain)automaton = Graphomaton.new
automaton.add_state('parse', metadata: { group: 'frontend' })
automaton.add_state('validate', metadata: { group: 'frontend' })
automaton.add_state('execute', metadata: { group: 'backend' })
automaton.add_state('persist', metadata: { group: 'backend' })
automaton.add_transition('parse', 'validate', 'ok')
automaton.add_transition('validate', 'execute', 'accepted')
automaton.add_transition('execute', 'persist', 'done')
automaton.save_svg('folded.svg', layout: :layered, fold_groups: true)states:
- id: idle
label: Idle
initial: true
metadata:
group: runtime
tooltip: Waiting for work
- id: running
label: Running
metadata:
group: runtime
- id: done
final: true
transitions:
- from: idle
to: running
label: start
- from: running
to: done
label: finish
line_style: dashedgraphomaton --input automaton.yml --output runtime.svg --layout layered --direction lr --theme oceanoutputs = {
svg: 'diagram.svg',
html: 'diagram.html',
dot: 'diagram.dot',
plantuml: 'diagram.puml'
}
outputs.each do |format, path|
automaton.save(path, format: format)
endgraphomaton --input automaton.yml --output diagram.svg
graphomaton --input automaton.yml --output diagram.svg --validate --layout-warnings
graphomaton --input automaton.json --output diagram.png --format png --theme dark --scale 2
graphomaton --input automaton.yml --output diagram.html --title "Automaton" --show-source --pan-zoom
graphomaton --input automaton.yml --output diagram.dot --rank-constraintsCommon SVG options:
graphomaton --input automaton.yml --output diagram.svg --layout layered --direction lr
graphomaton --input automaton.yml --output diagram.svg --layout force --node-spacing 140 --force-iterations 80 --layout-seed 42
graphomaton --input automaton.yml --output diagram.svg --layout graphviz --graphviz-command dot
graphomaton --input automaton.yml --output diagram.svg --responsive --fit cover --auto-size
graphomaton --input automaton.yml --output diagram.svg --state-shape ellipse --edge-style spline --arrow-shape vee
graphomaton --input automaton.yml --output diagram.svg --wrap-labels --state-wrap --label-tooltips --html-tooltips
graphomaton --input automaton.yml --output diagram.svg --highlight-unreachable --unreachable-zone right --highlight-dead-states
graphomaton --input automaton.yml --output diagram.svg --scc-groups --fold-groups
graphomaton --input automaton.yml --output diagram.svg --theme-file theme.ymlTheme utilities:
graphomaton --list-themes
graphomaton --theme-gallery --output theme_gallery.html
graphomaton --theme-gallery --theme-gallery-animated --theme-file theme.yml --output theme_gallery.htmlNative SVG and SVG-backed outputs support named or custom themes:
automaton.save_svg('diagram.svg', theme: :dark)
automaton.save_png('diagram.png', theme: :forest)
automaton.save_svg('diagram.svg', theme: :auto) # follows prefers-color-scheme
theme = Graphomaton.theme_from_yaml(File.read('theme.yml'))
automaton.save_svg('diagram.svg', theme: theme)
Graphomaton::Theme.save_gallery_html('theme_gallery.html')Built-in themes:
light, dark, forest, ocean, high_contrast, color_blind, print, minimal, academic, presentation, auto
SVG is Graphomaton's native renderer. It supports multiple layouts, styling options, metadata-driven annotations, and converter-backed raster/vector outputs.
automaton.save_svg('diagram.svg', 800, 600, layout: :linear, direction: :lr)
automaton.save_svg('diagram.svg', 800, 600, layout: :circle)
automaton.save_svg('diagram.svg', 800, 600, layout: :grid)
automaton.save_svg('diagram.svg', 800, 600, layout: :layered)
automaton.save_svg('diagram.svg', 800, 600, layout: :bfs)
automaton.save_svg('diagram.svg', 800, 600, layout: :force, layout_seed: 42)
automaton.save_svg('diagram.svg', 800, 600, layout: :graphviz, graphviz_command: 'dot')
automaton.save_svg('diagram.svg', 800, 600, layout: :manual)Layout notes:
directionaccepts:lr,:tb,:rl, and:bt.layoutaccepts:linear,:circle,:grid,:layered,:bfs,:force,:graphviz,:dot, and:manual.:layeredand:bfsuse deterministic barycenter ordering to reduce crossings.:forceacceptspadding,node_spacing,rank_spacing,force_iterations, andlayout_seed.:graphvizand:dotcalldot -Tplainthroughgraphviz_command:and normalize returned node coordinates into the SVG canvas.preserve_manual_positions: falselets automatic layouts reposition states with explicit coordinates.fit: :containfits positions into the canvas;fit: :coverstretches positions to use the canvas.auto_size: trueexpands the SVG viewport around rendered positions.
automaton.save_svg('diagram.svg', state_shape: :ellipse)
automaton.save_svg('diagram.svg', state_stroke_width: 3, transition_stroke_width: 2)
automaton.save_svg('diagram.svg', edge_style: :orthogonal)
automaton.save_svg('diagram.svg', arrow_shape: :vee, arrow_size: 14)
automaton.save_svg('diagram.svg', state_effect: :shadow)
automaton.save_svg('diagram.svg', font_family: '"Noto Sans JP", sans-serif', state_font_weight: 700)
automaton.save_svg('diagram.svg', wrap: true, max_transition_label_width: 120)
automaton.save_svg('diagram.svg', state_wrap: true, max_state_label_width: 120)
automaton.save_svg('diagram.svg', label_tooltips: true, html_tooltips: true)
automaton.save_svg('diagram.svg', rotate_labels: true)
automaton.save_svg('diagram.svg', label_background: false)
automaton.save_svg('diagram.svg', label_padding: 16, label_radius: 8, label_border: true)Other SVG options:
svg_id:sets a stable SVG root and marker ID prefix.css_variables: trueemits theme values as CSS variables.embed_styles: falseskips the embedded style block.xml_declaration: true,pretty: true, andminify: truecontrol serialization.initial_arrow_length,initial_arrow_label,final_arrow_length,final_arrow_label, andshow_final_arrowscontrol native start/end arrows.
State and transition metadata can enrich generated diagrams without changing state IDs:
automaton.add_state(
'q0',
label: 'Start',
metadata: {
tooltip: 'Entry point',
url: 'https://example.com',
group: 'main',
icon: 'S',
mermaid: { shape: 'choice' }
}
)
automaton.add_transition(
'q0',
'q1',
'next',
metadata: {
tooltip: 'Main path',
bundle: 'primary'
}
)Metadata behavior:
labelchanges the display name while preserving the state ID for transitions.tooltipordescriptionbecomes SVG tooltip text.urlorhrefcreates SVG links and DOT URL attributes.grouporclusterrenders SVG background groups, Mermaid/PlantUML composite states, and DOT clusters.iconrenders a compact SVG icon label inside the state.bundleroutes native SVG edges through a shared control point and emitsdata-bundle.choice,fork, andjoinpseudostates can be requested withsvg,dot,mermaid,plantuml, or compatible shorthand metadata.fold_groups: truecollapses grouped SVG states into compound nodes, hides internal transitions, and rewrites external transitions to the folded node.scc_groups: truerenders SVG groups around strongly connected components.
| Format | Method | Notes |
|---|---|---|
| SVG | save_svg |
Native renderer. |
| PNG | save_png |
Converts native SVG. Requires rsvg-convert, magick, or convert. |
save_pdf |
Converts native SVG. Requires rsvg-convert, magick, or convert. |
|
| WebP | save_webp |
Converts native SVG. Requires ImageMagick magick or convert. |
| HTML | save_html |
Mermaid.js state diagram in an HTML page. |
| DOT | save_dot |
GraphViz DOT source. |
| PlantUML | save_plantuml |
PlantUML state diagram source. |
automaton.save_png('diagram.png', 800, 600, scale: 2.0, converter: :magick)
automaton.save_pdf('diagram.pdf', 800, 600, converter: :magick)
automaton.save_webp('diagram.webp', 800, 600, converter: :magick)
Graphomaton.png_available?(converter: :auto)
Graphomaton.pdf_available?(converter: :auto)
Graphomaton.webp_available?(converter: :auto)automaton.save_html('diagram.html')
automaton.save_html('diagram.html', show_source: true)
automaton.save_html('diagram.html', theme: :auto)
automaton.save_html('diagram.html', cdn: './mermaid.min.js', inline_mermaid: true)
automaton.save_html('diagram.html', pan_zoom: true)
automaton.save_html('diagram.html', mathjax: true)
automaton.save_html('diagram.html', notes: true, class_defs: true)By default, HTML output uses Mermaid.js from CDN. Use a local cdn: path with inline_mermaid: true for offline output.
automaton.save_dot('diagram.dot')
automaton.save_dot('diagram.dot', theme: :ocean)
automaton.save_dot('diagram.dot', rank_constraints: true)dot -Tpng diagram.dot -o diagram.png
dot -Tsvg diagram.dot -o diagram.svg
dot -Tpdf diagram.dot -o diagram.pdf
neato -Tsvg diagram.dot -o diagram-neato.svg
sfdp -Tsvg diagram.dot -o diagram-sfdp.svgUse dot for ranked state-machine layouts. Use neato or sfdp for dense graphs where free spreading is preferred.
automaton.save_plantuml('diagram.puml')
automaton.save_plantuml('diagram.puml', theme: :forest)
automaton.save_plantuml('diagram.puml', notes: true)java -jar plantuml.jar diagram.puml
curl -X POST --data-binary @diagram.puml https://www.plantuml.com/plantuml/png > diagram.pngBug reports and pull requests are welcome on GitHub at https://github.com/ydah/graphomaton.
The gem is available as open source under the terms of the MIT License.
Everyone interacting in the Graphomaton project's codebases, issue trackers, chat rooms, and mailing lists is expected to follow the code of conduct.
