圖表庫原始碼剖析 - Chart.js 最流行的 Canvas 圖表庫

前端新能源發表於2018-01-08

原發於知乎: zhuanlan.zhihu.com/p/32740553

引言, 為什麼想要研究 Chartjs

繼之前我們研究了SVG.js 和 Frappe Charts 後, 我們對於 svg 的圖表庫已經有了初步的瞭解, 但是對於視覺化世界的 canvas, 我們更應該投入精力去了解學習.
圖表庫原始碼剖析 - Chart.js 最流行的 Canvas 圖表庫
在看到 chartist.js 講到自己的優勢的時候, 提到一些圖表庫使用了錯誤的技術 canvas, 那我們就更有興趣去了解, 為什麼會有這種說法. 首先讓我們一起來了解一下 Chartjs.

Chartjs 介紹

圖表庫原始碼剖析 - Chart.js 最流行的 Canvas 圖表庫
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 最流行的 Canvas 圖表庫

Chart.js 程式碼組織方式

圖表庫原始碼剖析 - Chart.js 最流行的 Canvas 圖表庫

Chart.js 圖表建立過程

從原始檔的 core.controller.js 分析, 來看 Chartjs 初始化圖表的過程如下:
左側為觸發的外掛機制的事件.
圖表庫原始碼剖析 - Chart.js 最流行的 Canvas 圖表庫

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 在使用過程中可能會有如下的問題:
圖表庫原始碼剖析 - Chart.js 最流行的 Canvas 圖表庫

One more thing

在使用很多通用圖表的時候, 相信大家都會遇到通用圖表的定製化困難這種問題, 下期我們將分析一下視覺化圖形語法G2, 看下 G2 是怎麼實現對於圖表的高度的易用性和擴充套件性.
在看 Chartjs原始碼的同時, 我們也動手用 canvas 實踐了一些基礎圖表, 具體可以參見 Taco.
如果想來和我們一起研究視覺化,歡迎投遞簡歷 linhui.wlh@alibaba-inc.com


相關文章