Architecture
nf-metro turns a Mermaid graph LR definition, augmented with %%metro directives, into a metro-map-style SVG.
The pipeline has three stages:
Parse -> Layout -> RenderEach stage hands a single MetroGraph, defined in src/nf_metro/parser/model.py, to the next.
Each stage mutates it in place.
Parsing fills in stations, edges, lines, sections, and ports.
Layout writes coordinates onto those objects, and rendering reads them.
Stages
Section titled “Stages”Parse (src/nf_metro/parser/)
Section titled “Parse (src/nf_metro/parser/)”parse_metro_mermaid in parser/mermaid.py is the driver.
The Lark grammar in parser/grammar.py turns source text into typed statements, which the driver applies in source order.
Mermaid subgraphs become sections, nodes become stations, arrows become edges, and %%metro directives supply the extensions.
A post-parse pass then infers layout and rewrites inter-section edges into port and junction chains through _resolve_sections.
See Parser for the directive model and the parse-then-resolve flow.
Layout (src/nf_metro/layout/)
Section titled “Layout (src/nf_metro/layout/)”compute_layout in layout/engine.py assigns an (x, y) to every station, port, junction, and section bbox.
It chains a numbered sequence of phases, split into a structural anchor-setting layer and a content-placement layer.
The phase implementations live in layout/phases/, and the per-phase preconditions, postconditions, and invariants are documented in src/nf_metro/layout/CONTRACT.md.
Edge routing lives in layout/routing/.
It uses horizontal runs plus 45-degree diagonal transitions, with L-shaped inter-section routing.
See Layout pipeline for the full phase-by-phase walkthrough and Routing for the route families.
Render (src/nf_metro/render/)
Section titled “Render (src/nf_metro/render/)”render_svg in render/svg.py uses the drawsvg library to draw section boxes, routed edges with curved corners, pill-shaped station markers, labels, and the legend.
Visual properties come from a Theme in render/style.py, and themes are registered in themes/__init__.py.
render/animate.py adds traveling balls along the routed paths, behind the --animate CLI flag.
render/video.py exports that motion as a looping GIF, WebP, MP4, or WebM, sampling the same paths one frame at a time.
Reference docs
Section titled “Reference docs”The topology stress fixture inventory is in examples/topologies/README.md.
Data model
Section titled “Data model”The central structure is MetroGraph (parser/model.py), holding:
lines(MetroLine): colored routes, with an optionalstyleofsolid,dashed, ordotted.stations(Station): mutable dataclasses. Layout writesx,y,layer, andtrackdirectly onto them.is_portstations take part in layout but are invisible at render time.edges(Edge): directed, each tagged with aline_id.sections(Section): subgraph groupings with adirectionofLR,RL, orTB, plus a grid position and a bbox.ports(Port): synthetic entry and exit points on section boundaries, created during_resolve_sections.