Trains as glowing dots, sliding between stations in real time.
A static map shows where trains could go. An animated map shows where they are. That's the difference between a diagram and a dashboard. In this chapter, we make dots move along our line paths — smoothly, continuously, with occupancy-based coloring.
SVG gives us a built-in tool for this: <animateMotion>. It moves an element along a path over a specified duration, and it works with the bezier curves we've been drawing. No JavaScript animation loop needed for the basic version.
A Single Train
The simplest case: one glowing dot traveling along a single line.
Live Editor — A train dot moving along a path
Two amber dots glide along the track — one outbound, one inbound. The <animateMotion> element references the track path via <mpath href="#track1"/>, and the browser handles interpolation along the bezier curves automatically.
The return trip uses keyPoints="1;0" to reverse the direction. The begin="-2s" offsets its start time so the two trains aren't at the same position.
Try changing dur="4s" to dur="8s" for a more realistic speed, or dur="1s" for a frantic rush hour.
Occupancy Coloring
Real-time transit data often includes occupancy levels — how full the train is. The visual language is universal: green for low, amber for medium, red for high. The dot's color communicates capacity at a glance.
Live Editor — Multiple trains with occupancy colors
Four trains, different colors based on how full they are. At a glance you can see: two green (plenty of room), one amber (getting busy), one pink (packed). The glow filter on the train dots makes them pop against the dimmed track.
In a real dashboard, you'd update the dot color based on live API data. The animation position would be computed from actual train ETAs rather than evenly spaced.
JavaScript-Driven Animation
<animateMotion> is great for demos, but a real-time dashboard needs more control. You need to place each train at a specific position based on live data — not just loop it endlessly. For this, we use JavaScript with getPointAtLength().
Live Editor — JavaScript-controlled train positions
This version uses JavaScript. After rendering the SVG, we grab the <path> element and call path.getTotalLength() to get the track's pixel length. Then in a requestAnimationFrame loop, we advance a parameter t from 0 to 1, call path.getPointAtLength(t * totalLength), and set the train dot's cx/cy.
The readout at the bottom shows each train's progress as a percentage. In a real dashboard, t wouldn't increment linearly — it would be computed from live ETA data: "this train left Station C 2 minutes ago and arrives at Station D in 3 minutes, so it's at 40% of that segment."
getPointAtLength() Is The Key
This single browser API — SVGPathElement.getPointAtLength(distance) — is what makes SVG train animation possible. Give it a distance along the path (in pixels), get back an {x, y} coordinate. It works with straight lines, bezier curves, arcs — any valid SVG path. The browser does all the curve math internally.
Combined with getTotalLength() (which returns the path's total pixel length), you can place anything at any percentage along any path. That's why we render lines as <path> elements instead of individual <line> segments — the path API needs a continuous path to work.
The Departure Board
A transit dashboard often includes a text-based departure display — the "next trains" board. This is pure DOM, no SVG, but it completes the picture. Think of the LED boards at metro stations:
Live Editor — Station departure board
Monospace font, dark background, colored line badges, occupancy bars, a pulsing "LIVE" indicator, and a scrolling alert ticker at the bottom. This isn't SVG — it's HTML/CSS styled to look like a station display. In a full dashboard, this would sit alongside the map as a sidebar or overlay.
The data structure is trivial: an array of departure objects with line, dest, eta, and occ. In production, this array would come from your real-time API, refreshing every 30 seconds. The ETA counter would tick down client-side between refreshes.
Connecting Animation to Data
In a real dashboard, the animation pipeline works like this:
1. Backend polls the transit API for real-time positions/ETAs. 2. Frontend receives: "Train X is between Station C and Station D, ETA to D: 3 minutes." 3. Frontend computes position: "segment C→D is from path length 340px to 480px. Train is 60% through this segment. Position = getPointAtLength(340 + 0.6 × 140) = getPointAtLength(424)." 4. Dot moves to that position with a CSS transition for smoothness between updates.
The key insight: you don't need GPS coordinates for the train. You only need to know which segment it's on and how far along. The SVG path handles the geometry.
Three Levels of Animation
Level 1: SVG animateMotion — Pure SVG, no JavaScript. Dots loop along paths forever. Good for demos and static showcases. Zero code complexity.
Level 2: JS + getPointAtLength — JavaScript controls position based on a percentage. Good for simulations and replay of historical data. Moderate complexity.
Level 3: Live data → position — Real-time API data drives the percentage. The train's position is computed from ETAs and departure times. This is what a production dashboard uses. The complexity is in the data pipeline, not the animation code.
All three use the same SVG paths. The rendering engine doesn't change — only the source of the t parameter.
We now have a complete, animated transit visualization. In the next chapter, we bring it all together — the full Metro Lumina network with line filtering, animated trains, and the neon treatment.