圖表庫源碼剖析 - Chart.js 最流行的 Canvas 圖表庫
引言, 為什麼想要研究 Chartjs
繼之前我們研究了SVG.js 和 Frappe Charts 後, 我們對於 svg 的圖表庫已經有了初步的了解, 但是對於可視化世界的 canvas, 我們更應該投入精力去了解學習.
在看到 chartist.js 講到自己的優勢的時候, 提到一些圖表庫使用了錯誤的技術 canvas, 那我們就更有興趣去了解, 為什麼會有這種說法. 首先讓我們一起來了解一下 Chartjs.
Chartjs 介紹
Chartjs 的官方介紹是一個簡單靈活的圖表庫, 相對而言 Chartjs 在圖表庫中的優勢, 主要是配置簡單, 動畫比較優雅, 而基於 canvas 的特性, 讓 Chartjs 性能會更有優勢. Chartjs 目前擁有 34.4 K的 star, 幾乎已經是 canvas 版本的圖表代名詞, 也是最流行的基於 canvas 的圖表庫. 在 GitHub 上搜索 chart
, 可以看到 Chartjs 的流行度排名僅次於 D3.
先來看一下 Chartjs 如何使用吧?
<canvas id="myChart"></canvas> n
var ctx = document.getElementById(myChart).getContext("2d");nvar myChart = new Chart(ctx, {n type: line,n data: {n labels: ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL"],n datasets: [{n label: "Data",n borderColor: "#80b6f4",n fill: false,n data: [50, 120, 150, 170, 180, 170, 160]n }]n }n});n
得到如圖的趨勢圖
Chart.js 代碼組織方式
Chart.js 圖表創建過程
從源文件的 core.controller.js
分析, 來看 Chartjs 初始化圖表的過程如下:
左側為觸發的插件機制的事件.
Chartjs 插件機制
Chartjs 的插件機制看起來很簡單, 但是也很有效. 插件直接註冊到 plugin 裡面, 擁有全部的執行的生命周期, 而且可以直接訪問 Chart 的全局變數, 擁有所有 API 的訪問許可權.
Chart.plugins.register({n beforeInit() {}n afterInit() {}n afterUpdate() {}n afterLayout() {}n afterDatasetsUpdate() {}n afterDatasetUpdate() {}n afterRender() {}n afterDraw() {}n afterDatasetsDraw() {}n afterDatasetDraw() {}n afterEvent() {}n resize() {}n destroy() {}n});n
Chartjs 滑鼠事件和動畫
對於 canvas 類型的圖表而言, 處理相應的滑鼠時間一直是件比較麻煩的事情. 讓我們來看下 Chartjs 是怎麼做的吧? 從源文件的core.controller.js
文件的 handleEvent
可以看出, Chartjs 在根元素位置, 監聽對應的滑鼠事件, 然後通過之前記錄的元素位置, 找到最近的對應元素, 響應對應的事件.
if (e.type === mouseout) {n me.active = [];n} else {n me.active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions);n}n...nme.updateHoverStyle(me.active, hoverOptions.mode, true);n
從代碼中可以看出, Chartjs 是通過 getElementsAtEventForMode 方法去獲取對應的元素. Chartjs 提供了 6 種模式, 來響應交互. 我們一起來看一下這六種模式.
- point: 找到 滑鼠位置相交的 對應元素
- nearest: 找到對應距離最近的元素
- index: 根據位置, 找到不同數據集中 對應 index 的數據
- dataset: 根據位置, 找到只在同一數據集的元素
- x: 只根據滑鼠位置的 x 軸值, 找到與 x 軸值相交的元素, 適應於垂直游標的場景
- y: 只根據滑鼠位置的 y 軸值, 找到與 y 軸值相交的元素, 適應於垂直游標的場景
而對於動畫, 在core.animation.js
文件的實現了對於 animation 的堆棧, 針對動畫依次使用 requestAnimationFrame 進行動畫的調用. 動畫中也內置了常見的各種緩動函數, 用於常見的動畫效果. 我們可以看一下動畫的核心實現, 裡面的 advance
方法.
while (i < animations.length) {n animation = animations[i];n chart = animation.chart;n animation.currentStep = (animation.currentStep || 0) + count;n animation.currentStep = Math.min(animation.currentStep, animation.numSteps);n helpers.callback(animation.render, [chart, animation], chart);n helpers.callback(animation.onAnimationProgress, [animation], chart);n if (animation.currentStep >= animation.numSteps) {n helpers.callback(animation.onAnimationComplete, [animation], chart);n chart.animating = false;n animations.splice(i, 1);n } else {n ++i;n }n}n
上述代碼中的, animation.render
根據動畫中的當前動畫的進度, 來繪製齣動畫所涉及元素的中間狀態.
Chartjs 浮點數問題
在閱讀 Chartjs 源碼的過程中, 發現源碼部分沒有針對浮點數問題做任何處理, 很多地方也都沒有考慮過浮點數問題. 所以 Chartjs 在使用過程中可能會有如下的問題:
One more thing
在使用很多通用圖表的時候, 相信大家都會遇到通用圖表的定製化困難這種問題, 下期我們將分析一下可視化圖形語法G2, 看下 G2 是怎麼實現對於圖表的高度的易用性和擴展性.
在看 Chartjs源碼的同時, 我們也動手用 canvas 實踐了一些基礎圖表, 具體可以參見 Taco.
如果想來和我們一起研究可視化,歡迎投遞簡歷 linhui.wlh@alibaba-inc.com
推薦閱讀: