iT邦幫忙

2026 iThome 鐵人賽

DAY 16
0
Modern Web

30 天手把手學會 Chart.js v4:從圖表基礎到互動式資料視覺化實戰系列 第 22

Day 22 - 30 天手把手學會 Chart.js|外掛系統(Plugins)基礎

  • 分享至 

  • xImage
  •  

從今天開始,我們進入第四週:外掛開發與框架整合。前三週我們學會了畫各式各樣的圖表、串接真實資料、處理互動事件,也搞定了響應式設計,這些都是「使用 Chart.js 內建功能」就能完成的事。但實務上常常會遇到「內建功能做不到」的需求,例如:在圖表上疊加浮水印、在資料點旁邊直接顯示數字標籤、在圖表載入前顯示自訂的載入動畫……這些需求,都要透過 Chart.js 的**外掛系統(Plugin System)**來實現。今天要打好外掛系統的地基:了解 Plugin 的架構、idregister 的關係、如何安裝與設定官方外掛 chartjs-plugin-datalabels,並整理 Chart.js 生命週期(Lifecycle)中所有可以「插隊」動手腳的 Hook。

一、什麼是 Chart.js 的 Plugin?

先建立一個核心觀念:Chart.js 本身內建的功能(軸線、圖例、Tooltip 等),其實也是用「外掛」的方式實作的。也就是說,Plugin 系統不是一個附加的「外掛工具」,而是 Chart.js 整個渲染引擎的核心骨架

官方文件是這樣描述的:

Plugins are the most efficient way to customize or change the default behavior of a chart.

翻成白話文:Plugin 是客製化或修改圖表預設行為最有效率的方式。與其在自己的程式碼裡「用暴力手法」直接操作 Canvas、修改 Chart.js 的內部資料,不如透過官方提供的 Plugin 介面,在指定的時間點「掛入」自己的邏輯,讓客製化的行為跟著 Chart.js 的渲染流程走,穩定又好維護。

最簡單的 Plugin,長得像這樣(一個普通的 JavaScript 物件):

const backgroundColorPlugin = {
  id: 'customCanvasBackgroundColor', // 外掛的唯一識別碼
  beforeDraw: (chart, args, options) => {
    const { ctx } = chart;
    ctx.save();
    ctx.globalCompositeOperation = 'destination-over'; // 畫在圖表內容「後面」
    ctx.fillStyle = options.color || 'lightgreen';
    ctx.fillRect(0, 0, chart.width, chart.height);
    ctx.restore();
  }
};

這段程式碼定義了一個外掛:在 Chart.js 每次繪製圖表之前(beforeDraw,先把整張畫布(Canvas)填滿一層背景色。有了這樣的架構,我們就能在 Chart.js 原本「畫座標軸 => 畫資料 => 畫圖例 => 畫 Tooltip」的固定流程中,插入任何自己想要的客製化行為。

二、使用 Plugin 的方式

Per-chart Plugin(單一圖表外掛)

只想讓某一個圖表套用這個外掛,透過建立圖表時的 plugins 陣列傳入:

const plugin = { /* 外掛實作內容 */ };

// chart1 與 chart2 都會套用這個 plugin
const chart1 = new Chart(ctx1, {
  type: 'bar',
  data: data1,
  plugins: [plugin]
});

const chart2 = new Chart(ctx2, {
  type: 'line',
  data: data2,
  plugins: [plugin]
});

// chart3 沒有傳入 plugins,所以不會套用
const chart3 = new Chart(ctx3, { type: 'pie', data: data3 });

這種寫法的特點是:外掛可以在多個圖表之間共用(傳入同一個物件即可),但只有明確寫在 plugins 陣列裡的圖表才會套用

Global Plugin(全域外掛)

如果專案中每一張圖表都要套用同一個外掛(例如:所有圖表都要有浮水印),一個一個手動加入 plugins 陣列會很麻煩。這時可以用 Chart.register() 把外掛註冊成全域外掛:

Chart.register({
  id: 'customCanvasBackgroundColor',
  beforeDraw: (chart, args, options) => { /* ... */ }
});

// 之後建立的所有圖表都會自動套用這個外掛,不需要再傳 plugins
const chart = new Chart(ctx, { type: 'bar', data });

Inline Plugin(行內外掛)

如果只是想「臨時」加一段客製化邏輯,不打算重複使用,可以直接把外掛物件寫在 plugins 陣列裡,不用另外宣告變數:

const chart = new Chart(ctx, {
  type: 'bar',
  data,
  plugins: [{
    beforeInit: (chart, args, options) => {
      console.log('圖表即將初始化!');
    }
  }]
});

⚠️ 重要限制:Inline Plugin 不會被註冊(registered),也因此無法被全域註冊使用,也不能透過 options.plugins.{id} 設定選項(因為它通常連 id 都沒有)。有些外掛(例如需要被其他外掛引用、或需要支援 options 設定的外掛)就不能用 Inline 的方式使用,必須乖乖走 Per-chart 或 Global 註冊。

三、外掛的 idregisteroptions.plugins 三角關係

id:外掛的身分證字號

想要讓外掛「可以被設定(Configurable)」,就一定要給它一個獨一無二的 id

Chart.register():把外掛「掛」進 Chart.js

register 是把外掛(或控制器、座標軸等元件)正式登記到 Chart.js 內部的動作。只有註冊過的外掛,才能:

  • 成為 Global Plugin,被所有圖表套用。
  • options.plugins.{id} 設定選項(就算是 Per-chart 傳入,也需要先有 id 才能被設定,不一定要 register,但沒有 register 就無法「全域套用」)。
import { Chart, registerables } from 'chart.js';
import ChartDataLabels from 'chartjs-plugin-datalabels';

Chart.register(...registerables, ChartDataLabels); // 一次註冊完整功能 + datalabels 外掛

options.plugins:外掛專屬的設定區

每個外掛的設定選項,會放在 options.plugins.{外掛的 id} 底下,彼此互不干擾:

const chart = new Chart(ctx, {
  type: 'bar',
  data,
  options: {
    plugins: {
      legend: { display: true },        // 內建 legend 外掛的設定
      tooltip: { enabled: true },        // 內建 tooltip 外掛的設定
      datalabels: {                      // chartjs-plugin-datalabels 的設定(id 就是 'datalabels')
        color: '#36454f',
        anchor: 'end',
        align: 'top'
      }
    }
  }
});

停用外掛(單一或全部)

// 只停用 id 為 'datalabels' 的外掛(僅針對這個圖表實例)
options: {
  plugins: {
    datalabels: false
  }
}

// 停用「這個圖表」的所有外掛(包含內建的 legend、tooltip 等)
options: {
  plugins: false
}

這個機制非常實用:例如專案中大部分圖表都要顯示資料標籤,但某一張特殊的圖表(例如資料點太密集)不想顯示標籤,就可以只針對那一張圖表把 datalabels: false 關掉,不需要動到全域設定。

Plugin Defaults:外掛自己的預設值

外掛也可以在物件裡定義 defaults,讓使用者不設定時也有合理的預設行為:

const plugin = {
  id: 'custom_canvas_background_color',
  beforeDraw: (chart, args, options) => {
    const { ctx } = chart;
    ctx.save();
    ctx.globalCompositeOperation = 'destination-over';
    ctx.fillStyle = options.color;
    ctx.fillRect(0, 0, chart.width, chart.height);
    ctx.restore();
  },
  defaults: {
    color: 'lightGreen' // 使用者沒有設定 options.plugins.custom_canvas_background_color.color 時的預設值
  }
};

四、安裝官方外掛:chartjs-plugin-datalabels

chartjs-plugin-datalabels 是 Chart.js 生態系中最受歡迎的官方外掛之一,功能是直接在資料點(圓餅圖的扇形、長條圖的長條、折線圖的節點)旁邊顯示數值標籤,不需要滑鼠 Hover 就能看到具體數字,非常適合報表或簡報情境。

安裝方式

透過 CDN(適合單純用 <script> 引入的專案):

<script src="https://cdn.jsdelivr.net/npm/chart.js@4.5.1"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2.2.0"></script>

透過 CDN 引入時,chartjs-plugin-datalabels 會建立全域變數 window.ChartDataLabels,但不會自動註冊——官方從 1.x 版開始就取消了自動註冊的行為,無論是 CDN 或 npm 安裝,都必須手動呼叫 Chart.register(ChartDataLabels) 外掛才會真正生效。

透過 npm(適合搭配 Vite / Webpack 等打包工具的專案):

npm install chart.js@4.5.1 chartjs-plugin-datalabels@2.2.0
import { Chart, registerables } from 'chart.js';
import ChartDataLabels from 'chartjs-plugin-datalabels';

Chart.register(...registerables, ChartDataLabels); // 需要手動 register,CDN 引入方式也不例外

基本使用範例

const chart = new Chart(ctx, {
  type: 'bar',
  data: {
    labels: ['一月', '二月', '三月', '四月'],
    datasets: [{
      label: '銷售額(萬元)',
      data: [65, 59, 80, 81],
      backgroundColor: '#4bc0c0'
    }]
  },
  options: {
    plugins: {
      datalabels: {
        color: '#333',           // 標籤文字顏色
        anchor: 'end',           // 標籤錨點位置:資料點的哪個位置
        align: 'top',            // 標籤相對錨點的對齊方式
        font: { weight: 'bold' },
        formatter: (value) => `${value} 萬` // 自訂顯示格式
      }
    }
  }
});
  • anchor:決定標籤要「錨定」在資料元素(長條、扇形、節點)的哪個位置,常見值有 'center''start''end'
  • align:標籤相對於錨點「往哪個方向」對齊,例如 'top'(往上)、'bottom'(往下)。
  • formatter:一個函式,可以把原始數值轉換成任何顯示格式(例如加上千分位、百分比符號、幣別文字)。

只在特定資料集或圖表關閉標籤

options: {
  plugins: {
    datalabels: {
      display: (context) => context.dataset.data[context.dataIndex] > 50 // 只顯示大於 50 的標籤
    }
  }
}

或是針對「不需要標籤的那張圖」直接關閉:

options: {
  plugins: {
    datalabels: false
  }
}

五、常見誤區與注意事項

  1. 把 Inline Plugin 當成 Global Plugin 使用:Inline Plugin(直接寫在 plugins: [{...}] 裡的物件)沒有經過 Chart.register(),無法被其他圖表共用,也通常無法透過 options.plugins.{id} 設定選項。如果外掛需要被多張圖表重複使用,或需要支援選項設定,請改用 Per-chart 或 Global 的方式。
  2. id 重複或缺漏:如果外掛沒有設定 id,或是跟其他已註冊的外掛 id 撞名,會導致設定選項對應錯亂,甚至互相覆蓋彼此的邏輯。
  3. 在錯誤的 Hook 畫圖,導致畫面順序錯亂:例如把「背景色」畫在 afterDraw,會蓋住原本已經畫好的資料內容;反之把「浮水印」畫在 beforeDraw,則會被之後畫的資料內容蓋住。畫面的視覺順序,就是 Hook 被呼叫的順序。
  4. 忘記 ctx.save() / ctx.restore():外掛在 Hook 裡直接操作 Canvas 的 2D Context(例如改變 fillStyleglobalCompositeOperation)時,若沒有用 ctx.save() 保存狀態、事後用 ctx.restore() 還原,可能會影響到 Chart.js 接下來自己繪製圖表元素時使用的樣式設定。
  5. 搞混「全域註冊」跟「全域套用預設樣式」Chart.register(plugin) 只是讓外掛「可以被使用」(並且如果沒有特別停用,會被所有圖表套用),跟 Chart.defaults 全域樣式設定(Day 24 會介紹)是兩個不同的機制,不要混為一談。
  6. 忘記手動註冊 chartjs-plugin-datalabels 導致外掛沒生效:不論是透過 CDN(<script> 引入後得到全域變數 ChartDataLabels)還是 npm(import ChartDataLabels from 'chartjs-plugin-datalabels'),從 1.x 版開始都不會自動註冊,必須自己手動呼叫 Chart.register(ChartDataLabels),否則外掛不會生效,畫面上完全看不到資料標籤也不會有錯誤訊息,很容易花時間排查。

明天(Day 23)我們會延續今天建立的 Hook 觀念,正式動手撰寫屬於自己的外掛:從最簡單的自訂背景色開始,實作「浮水印」與「圖表正中央標籤」兩個實用小外掛,並深入了解外掛的 options 該如何設計,讓外掛在 Global(全域)與 Per-chart(單一圖表)兩種情境下,都能有各自獨立、互不干擾的設定。

參考資源


上一篇
Day 21 - 30 天手把手學會 Chart.js|第三週總複習與小專案
系列文
30 天手把手學會 Chart.js v4:從圖表基礎到互動式資料視覺化實戰22
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言