CodeFarmer 技術週報 #21 - 從 React charts 效能問題討論前端技術選型
這篇久違地來分享下偏向前端的技術內容,用一個在實務上遇到的 line chart 效能問題討論技術選型上可以多做考量的面相
嗨嗨大家端午連假愉快,歡迎閱讀不小心遲到兩天的第 21 期 CodeFarmer 技術週報。
進入正題前一樣來閒話家常一下。前陣子因為搬家各種忙碌加上在適應新工作,就不小心斷掉了年初維持的還不錯的運動習慣。上週四起床時看著窗外的好天氣突如其來的覺得好想運動一下,就這樣帶著運動鞋出門。
原本以為當天可能需要加班趕工一下當週的進度,結果意外的自己做的第一個後端服務在中午前就順利部署上測試機,下午也順手把隔天準備要內部分享的筆記給快速寫完,多虧了自己這幾年以來持續寫文章講廢話的習慣。
到了下班時間突然覺得好像做到一個段落,不如就真的去探索一下公司的健身房好了,裡面運動的人比我想像的還少,但器材也沒我想像的多,像是深蹲架只有一個,就這樣復健地做了一些引體向上後再跑個幾十分鐘,果然有運動會讓人精神狀態有能量一些,期待之後可以繼續維持下去看看年末統計總運動天數時能不能達到 100up (目前才少少的 36 天)。
BTW,原本年初是有打算在 IG 持續運動打卡,但後來又覺得當時經營帳號的初衷也比較不是分享影音向的生活,還是寫寫文字更適合我一些,IG/Threads 就之後找回分享的節奏後再漸漸回歸技術筆記好了,暫時就先持續更新週報分享學習筆記為主。
廢話不多說,就開始本週的週報吧,最近學 K8s 寫的有點厭倦,突然想到這是個全端技術週報,久違地來寫一些前端相關的內容好了。
這陣子在工作上遇到一個關於圖表效能這個蠻有趣的議題,以前做較多 To C 的專案比較沒有這方面的經驗,這次開發後才發現圖表技術選型上的眉角也是很多,趁著記憶還鮮明來分享下。
釐清基本需求
如果今天需要在 React 專案中根據資料畫出折線圖 (line chart),在套件的技術選型上需要注意什麼問題?
在比較資深的軟體工程師面試中,遇到這樣的情境題或系統設計問題起手式常常就會需要釐清需求。
如果從最基本的專案需求去確認,首要就是圖表上互動的功能會需要哪些?參考上圖的圖表基本與進階功能對照圖像是以下幾點:
Series:圖上每條線的資料序列,大多套件都能支援像是 Marker、Label 等功能,但如果需要有 highlight 某個異常點資料這種功能可能要提前確認套件支不支援或好不好客製化。
Legend (圖例):圖上底部每條 series 的標示說明,通常會期待可以透過 toggle 互動時可以暫時隱藏該 series。另外這次工作中也遇到關於這部份的客製化需求,此時圖表的渲染方式是走 SVG 或 Canvas 就會是一個關鍵,後面可以再多著墨。
Tooltip:hover 到 series 上點資料時顯示詳細資訊,此處也可能會需要顯示較多客製化資訊,要確認套件在這部份是否支援類似 renderer 這樣的屬性,可以另外傳入自訂 component 來調整。
Axis:軸線的屬性上會需要注意的像是能不能動態調整 Intervals (間距),Label 在過多資料時,若仍需要顯示每個 X 軸上資訊,是否有辦法自動旋轉避免字疊在一起或被省略。
進階功能:其他像是 Navigator、Zoom (像股票圖表一樣可以縮放圖表顯示時間區間)、Crosshairs (圖表上 hover 時的十字輔助線) 等進階功能,如果不是在開發金融軟體應該不太會碰到,有碰到的話也會需要確認套件有沒有支援,需不需要付費。
效能與進階問題確認
在釐清完需求後,覺得有幾個比較容易被忽視的問題也需要提前確認:
專案中是否會需要支援巨量數據的渲染?
圖表是 SVG 渲染還是 Canvas 渲染?
是否需要支援將圖表匯出成圖片的功能?
選擇的套件文件夠不夠齊全?是否還有在維護?
1. 是否會需要支援巨量數據的渲染?
第一個問題可能會是最大的坑,最好在選型前就先確認好所有搜尋條件組合下,是否可能會有上萬筆資料點。
如果是的話就需要提前壓測並用 devtool 中的 performance monitor 試試看你選擇的套件,是否在巨量資料的渲染上會造成 JS heap size (記憶體使用量) 飆高、DOM Nodes 產生過多等,導致網頁卡頓無法操作其他功能最後 crash。
但如果真的遇到這些效能問題,但因為既有功能都已經開發上去不太可能打掉重練的話該怎麼辦呢?可以嘗試以下幾種解法:
downsampling、data decimation:
解決提出問題的人,誒不是,是透過溝通與 stakeholders 討論是否可以接受從前後端中找一些可行的 workaround,像是:是否可以接受當資料量超過一定量時,從後端直接限縮 LIMIT 並做前端提示建議縮小搜尋資料範圍
直接從前端在選取較大範圍日期資料時擋掉或做警告提示
避免在同一頁上畫太多 chart,在前端使用分頁或套用像是 react-virtualized、TanStack Virtual 等這類 Virtualized List 呈現 chart
2. 圖表是 SVG 渲染還是 Canvas 渲染?
真的不行的話最後一招就只能硬著頭皮換效能比較好的套件了。一般來說支援 Canvas 渲染的套件會優於 SVG 渲染的套件,因為 Canvas 版的圖表實際渲染的 DOM node 較少,但相對的在靈活性上也沒有 SVG 來得好。
像前面提到的這次收到一個客製化需求是想讓每個 series 是可以在橫軸上並排畫出來,且要能在點擊圖表 legend 時,可以用 toggle 的方式動態隱藏與顯示,且隱藏時要能從圖表上消失。
實測後發現 Canvas 套件若沒提供相應 API 的話,就只能走強制用 React component key re-render 這招,而使用 SVG 套件的話則可以嘗試找到對應 id 的 DOM node 去做動態移除與回溯,只能說效能與靈活性有時真的存在一種 trade-off。
而 React 生態系上的圖表套件,在 LogRocket 這篇文章也整理的蠻好的,其中就有根據渲染方式、下載量、是否為開源套件等做成圖表,推薦有遇到需要做圖表技術選型的讀者可以參考看看:
補充:Largest Triangle Three Bucket (LTTB)
前面提到的降採樣演算法 LTTB 直翻的話叫最大三角形三桶演算法,因為這個名字太有趣所以也好奇做了點筆記:
主要概念是從原始巨量資料中挑選出代表性的資料點,以利後續網頁在渲染折線圖時不會因為資料量過多造成瀏覽器記憶體的壓力
能保留原始資料的「視覺特徵」,例如峰值、谷底、趨勢變化
白話版原理:
將資料切成數個 buckets,可以想像每個桶子裡面有一段資料
原始數據的開頭、結尾的資料點會被保留
剩下資料切成多個 bucket
每個 bucket 挑出一個代表點
將頭、尾、所有代表點組合起來會跟原始折線圖幾乎一致
更進階一點去看的話會知道要做到這個演算法的難點在於如何找到每個 bucket 的代表點,而這就是演算法名字中的三桶與三角形派上用場的時候了:
參考上圖可以當作折線圖中的 8 個點,假設這裡切成 3 個桶子,也就是以虛線區分有左、中、右三段
左桶:這裡只有一個點,取為 A 點,沒有深讀但猜測是指前一個 3 桶中所選好的代表點
右桶:取平均點,這個桶內的所有資料取平均最接近的點為 C 點
中桶:從中間這個桶子與 A、C 兩點去遍歷找到最大三角形面積的點為 B 點
為什麼是最大三角形面積,詳細原因也沒有深讀,但可以想像今天 B 是中間值的話,畫出來的三角形面積會小小的,如果是峰值、谷值的話就會是較大的,所以在視覺上會影響更大
參考資料
小結
為什麼可以知道這些使用 line chart 的眉角,因為這次在實務上做技術選型還是不小心忽略的一些應該注意的點而踩了一些坑,額外花了許多時間研究解決,才會如此熟悉的令人心疼。
如果未來有碰到類似的前端技術選型問題,應該也可以參考這個範例多想想進階的效能問題、套件渲染方式、好不好客製化、套件下載量與是否還在維護、是否為開源套件等面向多考量。
後記:技術選型踩雷筆記
發現忘了提到一些實務上的脈絡,也記錄一下。
我自己在專案中原本在開案前也進行了一番 survey,但當初不夠了解 SVG 渲染與 Cavas 渲染的差異,也在釐清需求時漏掉其實需要支援巨量資料,因此一開始在簡單試了幾套的基本 demo 後,選用了甚至不在上述這張表上的 AG Charts,原因是因為美觀且功能豐富、文件齊全,且最重要的是剛好需求都對得上。
結果實作到後期才發現因為只支援 Canvas 渲染,在做部份客製化需求時其實不太方便,雖然最後仍然是找到一些 context API 完成了,但最棘手的效能問題卻仍然是硬傷,在渲染十幾二十個折線圖、且每個 chart 約 7000 多筆的點資料時雖然 DOM nodes 因為 Canvas 渲染的關係不會飆升,但記憶體使用量卻會逐步升高直至瀏覽器 OOM 而 crash,嘗試過一些 React 優化手段後仍然體驗不佳,原本也考慮要從後端改限制 query 點資料的 SQL 來做 workaround,但想想畢竟效能問題並非發生在後端這樣解實在也是下下策。
因此最後在一個神清氣爽的早上一口作氣把原本做完的各種功能都遷移變成了 ECharts,原因是因為在這次週報研究後發現原來 EChart 同時支援 SVG 渲染、Canvas 渲染的彈性,又原生支援開啟各種降採樣的 options,實際拿巨量資料測試後發現效能問題直接無痛解決了,甚至也實測了下開 SVG 渲染 (理論上因為 DOM nodes 增加效能會降低) 也是可以順暢操作網頁上其他功能,不愧是如此多星星數的開源套件。
另外因為我是使用 React 開發,看到 NPM 上也有人實作 echarts-for-react 可以無痛使用,但因為看 github 上已經四年多沒維護所以就只有參考他的原始碼自己另外實作。
總之算是因為沒有全盤考量後就開發而踩了大坑,也只能摸摸鼻子多加幾天班解決,畢竟工作專案還是不能得過且過,以免交出去的品質不佳仍要有後續的更多溝通成本,還不如一勞永逸實在些。
以上就是這期週報的所有內容了。若內容有什麼問題與討論也都歡迎透過以下管道與我交流,或直接留言與回覆這封電子信我也能收到:
Email:codefarmer.tw@gmail.com