Skip to content

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 -> Render

Each 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.

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.

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_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.

The topology stress fixture inventory is in examples/topologies/README.md.

The central structure is MetroGraph (parser/model.py), holding:

  • lines (MetroLine): colored routes, with an optional style of solid, dashed, or dotted.
  • stations (Station): mutable dataclasses. Layout writes x, y, layer, and track directly onto them. is_port stations take part in layout but are invisible at render time.
  • edges (Edge): directed, each tagged with a line_id.
  • sections (Section): subgraph groupings with a direction of LR, RL, or TB, plus a grid position and a bbox.
  • ports (Port): synthetic entry and exit points on section boundaries, created during _resolve_sections.