For six chapters we've been building with toy examples — three stations here, four lines there. Each example was
designed to illustrate one concept. Now we put them all together and render a network fragment complex enough to
feel like a real transit system.
This is Metro Lumina — our fictional metro. It has four metro lines and one tram line, a shared
central corridor, branches going north, south, and east, a perpendicular crossing, and a tram that runs along
the waterfront. It's not based on any specific city, but it has the structural complexity of a real mid-sized
system.
We'll build it in one editor, using every function from the previous chapters. The code is longer than anything
we've written so far — but it's all familiar pieces.
That's a metro map. Four metro lines and a tram, with shared corridors, branches, a crossing, and twenty
stations. Built entirely from the functions we developed in Chapters 3 through 5.
Let's walk through what's happening:
The Shared Trunk
Lines L1, L2, and L3 share the central corridor from Riverside through Market Square, Central, and Old Town.
That's three parallel colored lines — amber, pink, and blue — running together. The corridor is the visual spine
of the network.
The Northern Branch
L1 and L2 share a northern branch from Northgate up to Hillcrest. At Northgate, L3 joins from the west (arriving
from Westfield/Airport). The three-line corridor forms naturally at Riverside where all three paths converge.
The Split at Old Town
At Old Town, the three-line corridor breaks apart. L1 and L3 continue east to Gardens and Harbour. L2 turns
south to University and Stadium. The fork is smooth — L2 peels away with a bezier curve because its next station
(University) is directly below.
The Perpendicular Crossing
L4 (green) runs north-south, crossing the central corridor at Central station. The station marker at Central is
a large pill with four transfer dots (amber, pink, blue, green) — showing all four metro lines that serve it.
The Tram
T1 (purple, dashed line) runs along the waterfront from Marina through Pier 7, connecting to Harbour (shared
with L1 and L3) and continuing to Beach. The dashed stroke distinguishes tram from metro.
What Didn't Change
The rendering engine is identical to Chapter 6. Not a single function was modified to handle this more complex
network. The only new thing is data — more stations, more lines, more connections. The system scales
by adding data, not by adding code.
That's the payoff of the connect-the-dots architecture. The complexity lives in the data, not in the renderer.
Design Decisions
A few choices we made for Metro Lumina that apply to any transit map:
Schematic vs Geographic
Station positions are schematic — arranged for readability, not geographic accuracy. Central is
in the center of the canvas. The northern branch goes straight up. The eastern branch goes straight right. Real
geography would overlap stations and create messy angles. Schematic layout sacrifices geographic truth for
visual clarity.
Label Placement Is Manual
We hand-positioned every label with specific offsets. Automatic label placement algorithms exist, but for a
network this size, manual positioning takes five minutes and produces better results. This is a pragmatic
choice: automate the hard geometry (parallel lines, bezier curves), hand-tune the easy aesthetics (label
positions).
Tram vs Metro Distinction
The dashed line for the tram isn't a technical necessity — it's a design convention. Most transit maps visually
distinguish service types. Dashed for tram/light rail, solid for metro/heavy rail. Some systems use thinner
strokes for trams instead. The key is consistency.
The Data Structure
Look at the data at the top of the editor. The entire network is defined by two objects:
S — a map of station IDs to positions and names. Twenty stations, each with an
x, y, and name.
lines — an array of line objects, each with an id,
color, and stops (ordered array of station IDs).
That's it. No junction definitions, no corridor mappings, no path geometry. The rendering functions derive
everything else from these two data structures.
Try This
Add a new station to the network. In the S object, add:
campus: { name:"Campus", x:530, y:300 }
Then extend L2's stops from ["...","stadium"] to ["...","stadium","campus"].
A new station appears. L2 extends to it. The station gets a simple dot (single line). The bezier curve from
Stadium to Campus draws itself. No rendering code was touched — you only added data.
This is the network we'll keep using for the rest of the guide. In the next chapter, we'll show how a data
structure like this can be generated from real-world GTFS transit data — turning any city's published feed into
a map.