Files

3.2 KiB

Tube-Map Mode — a graph as a metro map

scripts/tubemap.py restyles a graph as a London-Underground-style metro map: thick coloured lines, octilinear routing (horizontal / vertical / 45° only), white interchange circles, small station stops, and offset labels — the instantly-readable transit-map aesthetic.

Read this when the user asks for a "metro map", "subway map", "tube map", or wants a system / pipeline / journey drawn as coloured transit lines instead of a boxes-and- arrows diagram.

python3 <this-skill-dir>/scripts/tubemap.py metro.json -o metro.drawio
# then the normal workflow: validate.py → preview PNG → self-check → export

When it fits

A tube map reads best when the graph is a set of overlapping paths that share a few interchange nodes — pipelines, user journeys, request flows, a product's subsystems, a roadmap of parallel workstreams. It is not the right choice for a dense mesh or a strict hierarchy (use autolayout.py / a diagram-type preset for those).

Input schema

You (the model) compose the metro JSON — from a system description, or by reading an existing diagram's structure and grouping its edges into a handful of named "lines".

{
  "stations": {
    "nl":     {"label": "Natural language", "gx": 0, "gy": 2},
    "layout": {"label": "Auto-layout",      "gx": 4, "gy": 2, "interchange": true},
    "drawio": {"label": ".drawio",          "gx": 6, "gy": 2, "interchange": true}
  },
  "lines": [
    {"name": "Author", "color": "#0098d4", "stations": ["nl", "layout", "drawio"]},
    {"name": "Import", "stations": ["code", "extract", "layout"]}
  ]
}
Field Required Notes
stations.<id>.label no (defaults to id) Station name; XML-escaped automatically
stations.<id>.gx / gy yes Integer grid coordinates (not pixels); --grid sets the pitch (default 110px)
stations.<id>.interchange no true → white-fill black-ring circle (a transfer station); else a small stop
lines[].name no For your own reference
lines[].color no #rrggbb; a line without one gets the next colour from the built-in tube palette
lines[].stations yes Ordered station ids the line passes through; a station id may appear on several lines (that's an interchange)

The one layout rule: keep segments octilinear

For the crispest map, place each line's consecutive stations so they are aligned horizontally, vertically, or on a 45° diagonal (same gx, same gy, or equal |Δgx| == |Δgy|). When two stations aren't aligned, the script inserts one bend — it runs the 45° diagonal for the shorter delta, then a straight axis segment into the target — so the line still reads as a metro line rather than an arbitrary curve. Mark the nodes where lines cross as interchange: true.

Limitation

tubemap.py honours the grid coordinates you give it — it does not solve the (NP-hard) octilinear metro-layout problem for you. Placing the stations on a sensible grid is the authoring step; the script does the drawing, routing, markers, and colours. Parallel lines sharing the exact same segment are drawn on top of each other (offset them by a grid row if you need both visible).