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

原發於知乎:  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
相關文章
相關標籤/搜索