Where lines meet, split, and part ways — and why no special code is needed.
We touched on line splitting briefly in Chapter 4 — two lines sharing a corridor, then diverging. But real
networks have richer junction patterns. Lines merge from different directions into a shared trunk. Three lines
enter a hub, two continue together and one branches off. A line terminates mid-corridor while others continue.
The remarkable thing is that our connect-the-dots system handles all of these without any
junction-specific code. Every junction pattern is just a consequence of different lines having
different station sequences. Let's prove it by building every common junction type.
Pattern 1: The Y-Split
Two lines share a trunk, then diverge at a junction station. This is the most common pattern — we saw it in
Chapter 4. Let's render it cleanly with proper station markers:
Live Editor — Y-Split: two lines diverge
At Junction, both lines arrive together (offset ±3px), then their next stations are in different directions. L1
heads up to Hillside, L2 heads down to Lakefront. The bezier smoothing creates the fork curves automatically —
no junction geometry code at all.
Pattern 2: The Merge
The opposite of a split — two lines arriving from different directions and joining into a shared corridor.
Structurally identical to a split, just read in the other direction:
Live Editor — Merge: two branches join a trunk
L1 arrives from the north, L2 from the south. At Grand Central they merge into a shared trunk heading east.
Again — no merge-specific code. The offset system sees that Hub→Mid has 2 lines, so it spaces them. North→Hub
has only L1, so it centers it. The bezier transition handles the rest.
Pattern 3: The Through-Branch
Three lines enter a shared section. Two continue together, one branches off. This is common in real networks
where an express line peels away from a local trunk:
Live Editor — Through-branch: one line peels off, two continue
Three lines share the trunk from West End to Exchange. At Exchange, L3 peels away toward the Airport while L1
and L2 continue east. Watch the visual: the three-line corridor narrows to two lines after Exchange, and L3
curves smoothly upward. The station pill at Exchange has three transfer dots; East Side only has two.
All emergent. All from the same rendering functions.
Pattern 4: The Cross
Two lines cross at a station but don't share any other track. This is an interchange, not a shared corridor. The
lines arrive from perpendicular directions:
Live Editor — Crossing lines at a transfer station
This is the one case where we do something slightly manual. When two lines cross at perpendicular angles, a
single pill can't span both directions. Instead, we draw a larger circular interchange marker with transfer dots
arranged along each line's axis.
Most transit networks have only a handful of true perpendicular crossings. The vast majority of interchanges are
shared-corridor patterns (types 1-3 above) where the standard pill works perfectly.
When to Use Manual Markers
The automatic drawStation() from Chapter 5 handles 90% of stations. For the remaining 10% —
perpendicular crossings, complex hubs with 5+ lines from multiple directions, unusually shaped interchanges —
it's fine to hand-draw the marker. Transit maps are a blend of systematic rendering and editorial judgment.
The system handles the regular cases; you polish the special ones.
Pattern 5: The Terminus Mid-Corridor
A line ends at a station where other lines continue. The terminating line's track simply stops:
Live Editor — One line terminates, others continue
L1 runs the full length. L2 starts at Market and ends at Garden. L3 starts at Central and runs to the end. The
corridor width expands and contracts: 1 line → 2 lines → 3 lines → 2 lines. The station markers adapt
automatically — Central has three transfer dots, Market has two, Start has just a dot.
The termini of L2 and L3 happen mid-corridor. Their tracks simply end with the
stroke-linecap="round" cap. No special terminus logic needed for the path rendering — the smooth
path just starts and stops at the right stations.
All Five Patterns, One System
Let's put them all in perspective. Every junction pattern we've seen is handled by the same three-step process:
1. For each line, look up its station sequence. 2. For each consecutive pair, compute the parallel offset based on how many lines share that
segment. 3. Draw a smooth path through the offset points.
Y-splits, merges, through-branches, mid-corridor termini — they're all just different arrangements of station
sequences. The rendering system doesn't know about "junctions." It only knows about stations and the lines that
connect them.
The only exception is the perpendicular crossing (Pattern 4), which benefits from a custom station marker.
Everything else is automatic.
A Reflection
When we started this project, junctions felt like the hardest problem. How do you draw a smooth fork? How do
you handle three lines merging from different angles? How do you render a line terminating in the middle of a
shared corridor?
The answer turned out to be: you don't. You don't handle junctions at all. You draw each line
independently through its station sequence, and the junction geometry emerges from the combination of offsets
and bezier smoothing. The "hard problem" dissolved when we stopped trying to solve it directly.
We've now covered every visual element of a transit map: lines, curves, parallel corridors, station markers, and
junctions. In the next chapter, we stop using toy examples and render a piece of a realistic network.