昨天講的是海拔那條資料不穩定:兩條流差 8 公尺、逐秒抖動差 4.9 倍、同一個地點隔 75 分鐘再經過會低 4.8 公尺。
所以昨天的結論是:3D 圖的垂直方向只拿來看「這裡有起伏」,不在軸上標高度刻度,因為誤差大到「這裡比那裡高幾公尺」這些數字如果被呈現出來會失準。
今天要問的是另一件事:就算它完全準確,海拔真的是那個軸最好的選擇嗎?
用一筆田徑場的資料來回答。
素材是 8/13 的間歇課表:1600m×3,組間 180 秒,松山區田徑場。總共 5.22 公里、28 分鐘。
這種資料畫成 3D 路線,會出現一個地圖類視覺化不太處理的情況:軌跡完全重疊。
四百公尺的跑道繞十幾圈,經緯度上就是同一個橢圓畫十幾次。
跑道是平的。所以整條軌跡壓成一個扁環—所有圈數疊在一起,什麼都看不出來。

看不出跑了幾圈,更看不出哪幾圈是主課表、哪幾段是組間的恢復慢跑。
直覺的解法是加誇張倍率,讓起伏看得見。
結果更糟—那條線變成一根實心的綠柱,比扁環還難讀。

因為那筆資料的海拔,實測起伏是 14.8 公尺(−1.8 到 13.0)。
而那是一個四百公尺的田徑場 ,二十八分鐘之內,同一個平面上跑了十幾圈。
所以那 14.8 公尺全部是誤差—而且不是逐秒的雜訊(那筆資料逐秒只抖 0.014 公尺),是整段時間的漂移。跟昨天量到的「同一個地點隔 75 分鐘差 4.8 公尺」是同一件事,只是這裡二十八分鐘就飄了十幾公尺。
放大 14 倍,等於把那十幾公尺的誤差畫成兩百公尺的假山。
問題不在倍率,在選錯了維度。
跑道是平的,所以海拔在這筆資料裡沒有任何資訊量—它記到的全是誤差。而真正把每一圈區分開來的東西是時間—第一圈和第十圈是同一個地點的不同時刻。
所以垂直軸改用「起跑後幾秒」:
// 垂直座標:海拔(誇張倍率)或時間(把重疊的圈數展開成螺旋)
const yOf = (p: { ele: number; t: number }) =>
vert === 'ele' ? p.ele * ex : (p.t / maxT) * extent * (ex / 18);
extent 是路線在水平方向的最大跨度,ex 是介面上那個倍率滑桿;除以 18 是手調出來的常數,讓預設倍率 14 之下螺旋的總高度大約是路線寬度的八成,看起來不會太扁也不會太高。
一行三元運算,但畫出來完全是另一張圖:所有圈數展開成一條螺旋。

而且一眼可見的東西變多了:
同一筆資料、同一個渲染流程,只換了 yOf 那一行。
地圖類的 3D 視覺化幾乎都預設把海拔當高度,因為在地理的語境裡那是唯一合理的答案。
但一旦資料是「同一條路徑走很多次」—田徑場、折返跑、多圈賽事、通勤路線—海拔軸會把資訊疊掉,因為它表達的是「這裡是哪裡」,而不是「這是第幾次經過這裡」。
第三個維度是設計決策。它問的是:你想把什麼東西分開?
| 想分開的東西 | 垂直軸 |
|---|---|
| 地形起伏 | 海拔 |
| 同一地點的不同時刻 | 時間 |
| 一圈一圈本身 | 距離 |
時間軸有一個問題:它不是等距的。
螺旋的高度是 t / maxT,整場時間被均勻攤在垂直方向上。可是快的圈佔的時間少、慢的圈佔得多,所以螺距的疏密同時混合了「配速」和「時間佔比」兩件事。
而配速這件事,顏色已經在講了(colorMode 預設就是配速)。畫面上兩個通道在講同一件事,其中一個還講得不乾淨,看圖的人會不知道該信哪一個。
解法是讓兩個通道各講一件事:垂直軸改用累積距離。
const yOf = (p: { ele: number; t: number; dist: number }) =>
vert === 'ele' ? p.ele * ex
: vert === 'time' ? (p.t / maxT) * extent * (ex / 18)
: (p.dist / maxD) * extent * (ex / 18);
資料裡本來就有累積距離(Day 4 列 record 欄位時的 distance),所以只是多一個分支。

每 400 公尺一圈,所以每一圈等高,螺距是固定的。形狀只剩「跑了幾圈、在哪裡」,配速完全交給顏色。
代價也要說清楚:
所以三個軸各有用途,不是距離軸最好。要看休息,切回時間軸就好,三個按鈕都留著。
所以 dataset 的設定裡,垂直軸是跟著資料走的:
tokyo: {
vertical: 'ele',
// 這場高度誤差實測中位數 4.6m(同一地點兩次經過),倍率開太大會放大成假高低差
exaggeration: 5,
},
interval: {
// 不用時間軸:快的圈佔的時間少、慢的佔得多,螺距會同時混到配速與時間佔比,
// 跟顏色講的配速打架。距離軸每 400 m 一圈等高,形狀只剩圈數,配速全交給顏色。
vertical: 'dist',
exaggeration: 14,
},
東京馬用海拔—四十二公里幾乎不重複經過同一個地方,海拔軸表達的是真實地形。但倍率壓在 5,註解寫著原因:昨天量到的誤差中位數就是 4.6 公尺,倍率開大會把它放大成假的高低差。

這就是 Day 1 開頭放的那張圖。當時它只是一張截圖,現在它是儀表板裡的一個分頁,垂直軸和倍率都可以自己調。
間歇用距離—因為那條跑道是平的,海拔軸沒有東西可以表達;時間軸會跟顏色打架;距離軸把圈數和配速分給兩個通道。
兩個預設不一樣,不是因為偏好,是因為那兩筆資料要回答的問題不一樣。
3D 畫面、六場全馬圖有了,回放和計算機也準備好。
但這些畫面到今天為止,全部只跑在本地 localhost:5173—Day 20 盤點的第一項,到現在還是沒處理。
要上線,第一件事就撞到:打開總覽頁,瀏覽器會先把整個 Three.js 載下來,即使那個人根本不會點進 3D 那頁,明天處理這件事!