Graphomaton

A Ruby toolkit for validating, analyzing, laying out, and exporting finite state machines as SVG, PNG, PDF, WebP, Mermaid.js HTML, GraphViz DOT, and PlantUML.
Installation
gem 'graphomaton'
bundle install
# or
gem install graphomaton
Quick start
require '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_states
Loading data
Build 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'))
JSON and YAML parsing is bounded by default to 10 MiB, 10,000 states, and 100,000 transitions. Lower or raise those limits explicitly for trusted input:
Graphomaton.from_json(
File.open('automaton.json'),
max_input_bytes: 2 * 1024 * 1024,
max_states: 2_000,
max_transitions: 20_000
)
YAML aliases are disabled by default. Only enable aliases: true for trusted
documents that require them.
Examples
Styled SVG with metadata
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
)
Manual coordinates
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)
Folded groups
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)
YAML input for the CLI
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: dashed
graphomaton --input automaton.yml --output runtime.svg --layout layered --direction lr --theme ocean
Export several formats
outputs = {
svg: 'diagram.svg',
html: 'diagram.html',
dot: 'diagram.dot',
plantuml: 'diagram.puml'
}
outputs.each do |format, path|
automaton.save(path, format: format)
end
CLI
graphomaton --input automaton.yml --output diagram.svg
graphomaton --input automaton.yml --output diagram.svg --layout-warnings
graphomaton --input automaton.json --output diagram.png --format png --theme dark --scale 2 --timeout 15 --max-output-bytes 33554432
graphomaton --input automaton.yml --output diagram.html --title "Automaton" --show-source --pan-zoom
graphomaton --input automaton.yml --output diagram.dot --rank-constraints
cat automaton.yml | graphomaton --input - --output - --format svg > diagram.svg
graphomaton --version
The command interface also supports structured workflows:
graphomaton render automaton.yml diagram.svg
graphomaton validate automaton.yml --diagnostics json
graphomaton validate automaton.yml --fail-on-warning
graphomaton list formats
graphomaton list layouts
graphomaton doctor
graphomaton completion zsh > _graphomaton
graphomaton man > graphomaton.1
Project defaults may be stored in .graphomaton.yml; command-line options override
environment variables, which override the config file:
svg:
theme: dark
layout: layered
labels:
wrap: true
The CLI validates state references and hierarchy by default. --no-validate is available for intentionally deferred or partial diagrams. Standard input auto-detects JSON documents that start with { or [, otherwise YAML is assumed; use --input-format to override it. --format is required when --output - is used. Input limits can be lowered with --max-input-bytes, --max-states, --max-transitions, --max-metadata-depth, --max-label-length, and --max-group-depth. Use --no-clobber to protect an existing output (and --force to opt back into replacement). Usage, parse, validation, conversion, and security failures use distinct exit statuses and are reported without backtraces.
Common 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.yml
Theme utilities:
graphomaton --list-themes
graphomaton --theme-gallery --output theme_gallery.html
graphomaton --theme-gallery --theme-gallery-animated --theme-file theme.yml --output theme_gallery.html
For a self-contained HTML file, provide trusted local classic-script assets:
graphomaton render automaton.yml diagram.html \
--cdn ./vendor/mermaid.min.js \
--self-contained --mermaid-sha256 HEX_DIGEST \
--nonce generated-nonce-123 --csp
--self-contained does not download assets. It embeds the local files named by
--cdn and, when MathJax is enabled, --mathjax-cdn. These files execute with the
document's authority and must be trusted. --nonce plus --csp applies the nonce
to every generated script and style and emits a CSP meta tag. The optional
--mermaid-sha256 and --mathjax-sha256 values detect local asset replacement
before inlining.
Themes
Native 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 output
SVG is Graphomaton's native renderer. It supports multiple layouts, styling options, metadata-driven annotations, and converter-backed raster/vector outputs.
Layouts
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.
Styling and labels
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:
- Default SVG root and marker IDs are deterministic for the graph, layout, and theme.
svg_id:sets an explicit prefix and should be unique when embedding duplicate diagrams in one HTML document. - Embedded CSS selectors and animation names are scoped to the SVG root ID.
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.
Metadata
State and transition metadata can enrich generated diagrams without changing state IDs:
automaton.add_state(
'q0',
label: 'Start',
kind: 'choice',
metadata: {
tooltip: 'Entry point',
url: 'https://example.com',
group: 'main',
icon: 'S'
}
)
automaton.add_transition(
'q0',
'q1',
'next',
metadata: {
tooltip: 'Main path',
bundle: 'primary'
}
)
kind: is format-independent and accepts normal, choice, fork, or join.
Exporter-specific shape overrides belong under metadata.svg, metadata.dot,
metadata.mermaid, or metadata.plantuml and do not affect other formats.
Metadata behavior:
labelchanges the display name while preserving the state ID for transitions.tooltipordescriptionbecomes SVG tooltip text.urlorhrefcreates SVG links and DOT URL attributes.- Link URLs may use
https,http,mailto, a relative path, or a fragment. Executable and local-file schemes such asjavascript:,data:, andfile:are rejected. - SVG
styleacceptsfill,stroke, stroke settings, opacity settings, font settings, andcolor; raw CSS properties,url(...), and declaration-breaking values are rejected. 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.kinddefines a format-independentchoice,fork, orjoinpseudostate. Format-specific nested metadata remains available for overrides.parentdefines semantic hierarchy andgroupdefines visual grouping; validation rejects setting both on one state, missing parents, and parent cycles.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.
Output formats
| 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. Uses librsvg plus ImageMagick when available, or ImageMagick directly. |
| HTML | save_html |
Mermaid.js state diagram in an HTML page. |
| DOT | save_dot |
GraphViz DOT source. |
| PlantUML | save_plantuml |
PlantUML state diagram source. |
Converter-backed formats
automaton.save_png('diagram.png', 800, 600, scale: 2.0, converter: :magick)
automaton.save_pdf('diagram.pdf', 800, 600, converter: :magick, timeout: 15, max_output_bytes: 32 * 1024 * 1024)
automaton.save_webp('diagram.webp', 800, 600, converter: :magick)
automaton.save_webp('diagram.webp', 800, 600, converter: :rsvg_magick)
Graphomaton.png_available?(converter: :auto)
Graphomaton.pdf_available?(converter: :auto)
Graphomaton.webp_available?(converter: :auto)
PNG scale: changes only output pixel density. The logical SVG viewBox, state size, and layout remain unchanged.
Converter processes default to a 30-second timeout, a 64 MiB output limit, and a 1 MiB diagnostic limit. Timeout and output-limit failures terminate the process group and raise the format-specific ConversionError.
HTML with Mermaid.js
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 the pinned Mermaid.js 10.9.8 module with Mermaid's strict security level. Remote asset URLs must use HTTPS; relative and absolute local paths are also accepted. offline: true requires cdn: to name a local classic .js build; ES module .mjs assets are rejected in offline and inline modes. inline_mermaid: true reads and embeds the complete file named by cdn:, so use it only with a trusted local JavaScript file. Generated UI is English by default and supports lang: 'en' or lang: 'ja'.
GraphViz DOT
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.svg
Use dot for ranked state-machine layouts. Use neato or sfdp for dense graphs where free spreading is preferred.
PlantUML
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.png
Contributing
Architecture, input schema, CLI, exporter capabilities, performance limits,
migration, release operations, and custom exporter integration are documented in
the docs/ directory. See Custom exporters
when adding an application-specific output format.
Bug reports and pull requests are welcome on GitHub at https://github.com/ydah/graphomaton.
License
The gem is available as open source under the terms of the MIT License.
Code of Conduct
Everyone interacting in the Graphomaton project's codebases, issue trackers, chat rooms, and mailing lists is expected to follow the code of conduct.