The moment it becomes a real transit map: parallel lines through shared corridors.
Everything up to now has been a single line. One color, one path, one sequence of stations. But a metro system
isn't a line — it's a network. And networks mean shared infrastructure. Two lines running through the same
station. Three lines sharing the same tunnel. Four lines converging on a central hub, then fanning out in
different directions.
This is the chapter where our drawings start to look like real transit maps. The concept is straightforward:
when multiple lines share a station pair, draw them parallel to each other with consistent
spacing. The implementation uses the same perpendicular-to-the-track idea from Chapter 2, applied to
line placement instead of label placement.
The Problem, Visually
Let's start with what goes wrong when you naively draw two lines through the same stations:
Live Editor — Two lines, one on top of the other
Line 2 exists in the data but is invisible — it's drawn at the exact same coordinates as Line 1, completely
hidden behind it. You might catch a sliver of pink at the stroke edges, but that's it. This is useless as a map.
The Fix: Perpendicular Offset
The solution: offset each line perpendicular to the track direction. If the track runs
horizontally, Line 1 goes slightly above center and Line 2 goes slightly below. If the track runs diagonally,
the offset follows the perpendicular of that diagonal.
For N lines sharing a segment, center them around the track's geometric path:
offset[i] = (i - (N-1)/2) × spacing
With 2 lines and 4px spacing: Line 1 gets offset -2px, Line 2 gets +2px. With 3 lines:
-4px, 0px, +4px. The formula always centers the group.
Live Editor — Parallel offset (adjust spacing below)
5px
Drag the slider. At 0px the lines overlap completely (the same problem as before). At
5px they're distinct but tight — the typical spacing for a transit map. At
20px they're comically far apart, losing the visual association of shared track.
The magic is in the perpOffset() function — it takes two station positions, computes the track
angle between them, then returns the x/y displacement perpendicular to that angle. Four lines of math, and it
works for any track direction.
Adding a Third Line
The same formula scales to any number of lines. Let's add Line 3, and while we're at it, make the track diagonal
to prove the offset works in all orientations:
Live Editor — Three parallel lines on a diagonal
Three lines, diagonal track, smooth beziers, consistent parallel spacing. The same code works whether the track
is horizontal, vertical, diagonal, or curved. The perpXY() offset doesn't care about orientation —
it always pushes perpendicular to the local track direction.
The Simpler Model
At first, it's tempting to imagine a "corridor detection algorithm" — something that analyzes route
polylines, discovers shared segments, computes centerlines, and resolves partial overlaps.
But for a transit map, that work is mostly unnecessary. We already know which lines share which stations,
because the stop sequences tell us directly.
That's the core insight of this guide. Connect the dots. For each line, for each consecutive
station pair, draw the segment with the appropriate offset. The shared corridors emerge naturally from the
fact that the same station pair appears in multiple lines' stop sequences.
No corridor detection. No computational geometry. Just: which lines go from A to B? Offset them. Draw
them.
The Interesting Part: Lines That Split
Parallel lines through shared stations are solved. But transit networks aren't just parallel — lines diverge.
Line 1 and Line 2 share Harbour → Central, then Line 1 continues to Park North while Line 2 turns south to the
Airport.
Here's the beautiful thing: the connect-the-dots model handles this automatically. No special
junction logic needed.
Live Editor — Lines sharing track, then splitting
Look at what happened at Central. Lines 1 and 2 arrive together — parallel, evenly spaced — then peel apart. L1
curves upward to Park North. L2 curves downward to Airport. We didn't write any junction code.
The divergence happened naturally because:
On the Harbour→Central segment, both lines share the corridor, so they get offset -3px and
+3px respectively. On the Central→Park segment, only L1 exists, so it gets offset 0px
(centered). On the Central→Airport segment, only L2 exists, offset 0px. The bezier smoothing
handles the transition between the offset position and the centered position gracefully.
This is the connect the dots philosophy in action. Each line only knows its own station
sequence. The parallel spacing and the smooth splitting are emergent properties of the offset math and the
bezier smoothing working together.
The Key Function: getSegmentLines()
The crucial addition in this editor is getSegmentLines(stationA, stationB) — it answers: "how
many lines travel between these two stations?" This determines how many parallel offsets we need for
that segment. A segment shared by 3 lines needs 3 offsets. A segment with only 1 line needs 0 offset
(centered).
The rest is just applying the offset formula from earlier. The function that seemed simple — "which lines
share this segment?" — is the actual architectural insight of the entire system.
Four Lines, Full Complexity
Let's push it further. Four lines, a shared central corridor, and two branch points:
Live Editor — Four lines, shared trunk, two splits
Four lines. A shared trunk from Riverside through Central. L2 peels north to Hillside. L4 peels south to Beach.
L1 and L3 continue east. All from the same 15 lines of offset + smoothing logic. No junction detection. No
special cases. Connect the dots.
Study the shared trunk between Old Town and Central — four parallel colored lines, evenly spaced, with a glow.
Then watch them fan out at the edges. This is the visual structure of a real transit map, built entirely from
the "which lines share this segment?" question.
What We've Learned
This chapter is the conceptual peak of the guide. Everything before was setup. Everything after is refinement.
The core idea fits in one sentence:
For each line, for each consecutive station pair, compute the perpendicular offset based on how many
other lines share that segment. Draw a smooth path through the offset points.
That's it. That's the whole algorithm. The parallel corridors, the smooth junctions, the fan-outs — they're all
emergent properties of this simple rule applied consistently.
In the next chapter, we'll make the station nodes worthy of the lines passing through them. Right now they're
plain circles sitting on the centerline, ignoring the parallel tracks flowing around them. We need pills,
transfers, and dots.