原發於知乎: zhuanlan.zhihu.com/p/32740553
引言, 為什麼想要研究 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> 複製程式碼
var ctx = document.getElementById('myChart').getContext("2d");
var myChart = new Chart(ctx, {
type: 'line',
data: {
labels: ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL"],
datasets: [{
label: "Data",
borderColor: "#80b6f4",
fill: false,
data: [50, 120, 150, 170, 180, 170, 160]
}]
}
});複製程式碼
得到如圖的趨勢圖
Chart.js 程式碼組織方式
Chart.js 圖表建立過程
從原始檔的 core.controller.js 分析, 來看 Chartjs 初始化圖表的過程如下:
左側為觸發的外掛機制的事件.
Chartjs 外掛機制
Chartjs 的外掛機制看起來很簡單, 但是也很有效. 外掛直接註冊到 plugin 裡面, 擁有全部的執行的生命週期, 而且可以直接訪問 Chart 的全域性變數, 擁有所有 API 的訪問許可權.
Chart.plugins.register({
beforeInit() {}
afterInit() {}
afterUpdate() {}
afterLayout() {}
afterDatasetsUpdate() {}
afterDatasetUpdate() {}
afterRender() {}
afterDraw() {}
afterDatasetsDraw() {}
afterDatasetDraw() {}
afterEvent() {}
resize() {}
destroy() {}
});複製程式碼
Chartjs 滑鼠事件和動畫
對於 canvas 型別的圖表而言, 處理相應的滑鼠時間一直是件比較麻煩的事情. 讓我們來看下 Chartjs 是怎麼做的吧? 從原始檔的core.controller.js 檔案的 handleEvent 可以看出, Chartjs 在根元素位置, 監聽對應的滑鼠事件, 然後通過之前記錄的元素位置, 找到最近的對應元素, 響應對應的事件.
if (e.type === 'mouseout') {
me.active = [];
} else {
me.active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions);
}
...
me.updateHoverStyle(me.active, hoverOptions.mode, true);複製程式碼
從程式碼中可以看出, 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) {
animation = animations[i];
chart = animation.chart;
animation.currentStep = (animation.currentStep || 0) + count;
animation.currentStep = Math.min(animation.currentStep, animation.numSteps);
helpers.callback(animation.render, [chart, animation], chart);
helpers.callback(animation.onAnimationProgress, [animation], chart);
if (animation.currentStep >= animation.numSteps) {
helpers.callback(animation.onAnimationComplete, [animation], chart);
chart.animating = false;
animations.splice(i, 1);
} else {
++i;
}
}複製程式碼
上述程式碼中的, animation.render 根據動畫中的當前動畫的進度, 來繪製出動畫所涉及元素的中間狀態.
Chartjs 浮點數問題
在閱讀 Chartjs 原始碼的過程中, 發現原始碼部分沒有針對浮點數問題做任何處理, 很多地方也都沒有考慮過浮點數問題. 所以 Chartjs 在使用過程中可能會有如下的問題:
One more thing
在使用很多通用圖表的時候, 相信大家都會遇到通用圖表的定製化困難這種問題, 下期我們將分析一下視覺化圖形語法G2, 看下 G2 是怎麼實現對於圖表的高度的易用性和擴充套件性.
在看 Chartjs原始碼的同時, 我們也動手用 canvas 實踐了一些基礎圖表, 具體可以參見 Taco.
如果想來和我們一起研究視覺化,歡迎投遞簡歷 linhui.wlh@alibaba-inc.com