# 逐步指南

遵循本指南,熟悉 Chart.js 的所有主要概念:圖表類型和元素、數據集、自定義、外掛程式、元件和 Tree-shaking。請隨時點擊文字中的連結。

我們將從頭開始構建一個包含多個圖表的 Chart.js 數據視覺化應用程式

result

# 使用 Chart.js 構建新應用程式

在一個新資料夾中,建立包含以下內容的 package.json 檔案

{
  "name": "chartjs-example",
  "version": "1.0.0",
  "license": "MIT",
  "scripts": {
    "dev": "parcel src/index.html",
    "build": "parcel build src/index.html"
  },
  "devDependencies": {
    "parcel": "^2.6.2"
  },
  "dependencies": {
    "@cubejs-client/core": "^0.31.0",
    "chart.js": "^4.0.0"
  }
}

現代前端應用程式通常使用 JavaScript 模組打包器,因此我們選擇了 Parcel (在新視窗中開啟) 作為一個不錯的零配置建置工具。我們也正在安裝 Chart.js v4 和 Cube (在新視窗中開啟) 的 JavaScript 客戶端,這是一個用於資料應用程式的開源 API,我們將使用它來獲取真實世界數據(稍後會詳細介紹)。

執行 npm installyarn installpnpm install 來安裝依賴項,然後建立 src 資料夾。在該資料夾內,我們需要一個非常簡單的 index.html 檔案

<!doctype html>
<html lang="en">
  <head>
    <title>Chart.js example</title>
  </head>
  <body>
    <!-- <div style="width: 500px;"><canvas id="dimensions"></canvas></div><br/> -->
    <div style="width: 800px;"><canvas id="acquisitions"></canvas></div>
    <!-- <script type="module" src="dimensions.js"></script> -->
    <script type="module" src="acquisitions.js"></script>
  </body>
</html>

如您所見,Chart.js 需要最少的標記:一個帶有 idcanvas 標籤,我們稍後將通過該標籤引用圖表。預設情況下,Chart.js 圖表是 響應式的,並佔據整個封閉容器。因此,我們設定 div 的寬度來控制圖表寬度。

最後,我們來建立包含以下內容的 src/acquisitions.js 檔案

import Chart from 'chart.js/auto'
(async function() {
  const data = [
    { year: 2010, count: 10 },
    { year: 2011, count: 20 },
    { year: 2012, count: 15 },
    { year: 2013, count: 25 },
    { year: 2014, count: 22 },
    { year: 2015, count: 30 },
    { year: 2016, count: 28 },
  ];
  new Chart(
    document.getElementById('acquisitions'),
    {
      type: 'bar',
      data: {
        labels: data.map(row => row.year),
        datasets: [
          {
            label: 'Acquisitions by year',
            data: data.map(row => row.count)
          }
        ]
      }
    }
  );
})();

讓我們來逐步了解這段程式碼

  • 我們從特殊的 chart.js/auto 路徑匯入 Chart,即主要的 Chart.js 類別。它載入 所有可用的 Chart.js 元件(非常方便),但不允許 Tree-shaking。我們稍後會解決這個問題。
  • 我們實例化一個新的 Chart 實例,並提供兩個參數:圖表將呈現的畫布元素和選項物件。
  • 我們只需要提供一個圖表類型(bar)並提供 data,其中包含 labels(通常是數據點的數值或文字描述)和一個 datasets 陣列(Chart.js 支援大多數圖表類型的多個數據集)。每個數據集都以 label 指定,並包含一個數據點陣列。
  • 目前,我們只有一些虛擬資料條目。因此,我們提取 yearcount 屬性,以在唯一的數據集內產生 labels 和數據點陣列。

現在可以執行範例了,使用 npm run devyarn devpnpm dev,然後導覽至您的網頁瀏覽器中的 localhost:1234 (在新視窗中開啟)

result

僅用幾行程式碼,我們就獲得了一個具有許多功能的圖表:圖例網格線刻度和懸停時顯示的 工具提示。多次重新整理網頁以查看圖表也具有動畫效果。試著點擊「按年份劃分的收購」標籤,以查看您也可以切換數據集可見性(當您有多個數據集時特別有用)。

# 簡單自訂

讓我們來看看如何自訂 Chart.js 圖表。首先,讓我們關閉動畫,以便圖表立即顯示。其次,由於我們只有一個數據集和非常簡單的數據,因此讓我們隱藏圖例和工具提示。

src/acquisitions.js 中的 new Chart(...); 呼叫替換為以下程式碼片段

  new Chart(
    document.getElementById('acquisitions'),
    {
      type: 'bar',
      options: {
        animation: false,
        plugins: {
          legend: {
            display: false
          },
          tooltip: {
            enabled: false
          }
        }
      },
      data: {
        labels: data.map(row => row.year),
        datasets: [
          {
            label: 'Acquisitions by year',
            data: data.map(row => row.count)
          }
        ]
      }
    }
  );

如您所見,我們已將 options 屬性新增至第二個參數,這就是您可以為 Chart.js 指定各種自訂選項的方式。動畫是透過透過 animation 提供的布林標誌來停用。大多數圖表範圍的選項(例如,響應性設備像素比)都是這樣設定的。

圖例和工具提示會透過 plugins 中各自區段下提供的布林標誌隱藏。請注意,Chart.js 的某些功能會提取到外掛程式中:獨立、單獨的程式碼片段。其中一些外掛程式可作為 Chart.js 發行版 (在新視窗中開啟)的一部分提供,其他外掛程式則是獨立維護,並且可以在外掛程式、框架整合和其他圖表類型的 精選清單 (在新視窗中開啟) 中找到。

您應該可以在瀏覽器中看到更新後的極簡圖表。

# 真實世界資料

使用硬式編碼、大小有限且不切實際的資料,很難顯示 Chart.js 的全部潛力。讓我們快速連線到數據 API,使我們的範例應用程式更接近實際的生產使用案例。

讓我們建立包含以下內容的 src/api.js 檔案

import { CubejsApi } from '@cubejs-client/core';
const apiUrl = 'https://heavy-lansford.gcp-us-central1.cubecloudapp.dev/cubejs-api/v1';
const cubeToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjEwMDAwMDAwMDAsImV4cCI6NTAwMDAwMDAwMH0.OHZOpOBVKr-sCwn8sbZ5UFsqI3uCs6e4omT7P6WVMFw';
const cubeApi = new CubejsApi(cubeToken, { apiUrl });
export async function getAquisitionsByYear() {
  const acquisitionsByYearQuery = {
    dimensions: [
      'Artworks.yearAcquired',
    ],
    measures: [
      'Artworks.count'
    ],
    filters: [ {
      member: 'Artworks.yearAcquired',
      operator: 'set'
    } ],
    order: {
      'Artworks.yearAcquired': 'asc'
    }
  };
  const resultSet = await cubeApi.load(acquisitionsByYearQuery);
  return resultSet.tablePivot().map(row => ({
    year: parseInt(row['Artworks.yearAcquired']),
    count: parseInt(row['Artworks.count'])
  }));
}
export async function getDimensions() {
  const dimensionsQuery = {
    dimensions: [
      'Artworks.widthCm',
      'Artworks.heightCm'
    ],
    measures: [
      'Artworks.count'
    ],
    filters: [
      {
        member: 'Artworks.classification',
        operator: 'equals',
        values: [ 'Painting' ]
      },
      {
        member: 'Artworks.widthCm',
        operator: 'set'
      },
      {
        member: 'Artworks.widthCm',
        operator: 'lt',
        values: [ '500' ]
      },
      {
        member: 'Artworks.heightCm',
        operator: 'set'
      },
      {
        member: 'Artworks.heightCm',
        operator: 'lt',
        values: [ '500' ]
      }
    ]
  };
  const resultSet = await cubeApi.load(dimensionsQuery);
  return resultSet.tablePivot().map(row => ({
    width: parseInt(row['Artworks.widthCm']),
    height: parseInt(row['Artworks.heightCm']),
    count: parseInt(row['Artworks.count'])
  }));
}

讓我們來看看那裡發生了什麼

  • 我們 import 用於資料應用程式的開源 API Cube (在新視窗中開啟) 的 JavaScript 客戶端程式庫,使用 API URL (apiUrl) 和驗證權杖 (cubeToken) 來配置它,最後實例化客戶端 (cubeApi)。
  • Cube API 託管於 Cube Cloud (開啟新視窗),並連接到一個具有 公開數據集 (開啟新視窗) 的資料庫,該數據集包含 ~140,000 筆記錄,代表 紐約現代藝術博物館 (開啟新視窗) 的所有藝術品。這絕對比我們現在擁有的數據集更真實。
  • 我們定義了幾個非同步函數來從 API 獲取資料:getAquisitionsByYeargetDimensions。第一個函數會回傳按購買年份分類的藝術品數量,另一個函數會回傳每個寬度-高度組合的藝術品數量(我們將在另一個圖表中使用)。
  • 讓我們看看 getAquisitionsByYear。首先,我們在 acquisitionsByYearQuery 變數中建立一個宣告式的 JSON 查詢。如您所見,我們指定針對每個 yearAcquired,我們想要取得藝術品的 countyearAcquired 必須設定(即,不得為未定義);結果集將依 yearAcquired 以遞增順序排序。
  • 第二,我們透過呼叫 cubeApi.load 擷取 resultSet,並將其對應到具有所需 yearcount 屬性的物件陣列。

現在,讓我們將真實世界的資料提供給我們的圖表。請對 src/acquisitions.js 進行一些變更:新增一個匯入,並取代 data 變數的定義。

import { getAquisitionsByYear } from './api'
// ...
const data = await getAquisitionsByYear();

完成!現在,我們使用真實世界資料的圖表看起來像這樣。看起來在 1964 年、1968 年和 2008 年發生了一些有趣的事情!

result

我們完成了長條圖。讓我們嘗試另一種 Chart.js 圖表類型。

# 進一步自訂

Chart.js 支援許多常見的圖表類型。

例如,泡泡圖 允許同時顯示三個維度的資料:xy 軸上的位置表示兩個維度,第三個維度則由個別泡泡的大小表示。

要建立圖表,請停止已在執行的應用程式,然後前往 src/index.html,並取消註解以下兩行

<div style="width: 500px;"><canvas id="dimensions"></canvas></div><br/>
<script type="module" src="dimensions.js"></script>

然後,建立具有以下內容的 src/dimensions.js 檔案

import Chart from 'chart.js/auto'
import { getDimensions } from './api'
(async function() {
  const data = await getDimensions();
  new Chart(
    document.getElementById('dimensions'),
    {
      type: 'bubble',
      data: {
        labels: data.map(x => x.year),
        datasets: [
          {
            label: 'Dimensions',
            data: data.map(row => ({
              x: row.width,
              y: row.height,
              r: row.count
            }))
          }
        ]
      }
    }
  );
})();

或許,那裡的一切都很簡單明瞭:我們從 API 取得資料,並使用 bubble 類型轉譯一個新的圖表,將三個維度的資料作為 xyr(半徑)屬性傳遞。

現在,使用 rm -rf .parcel-cache 重設快取,然後使用 npm run devyarn devpnpm dev 再次啟動應用程式。我們現在可以檢視新圖表

result

嗯,看起來不太漂亮。

首先,圖表不是正方形。藝術品的寬度和高度同等重要,因此我們也希望圖表的寬度等於其高度。依預設,Chart.js 圖表的長寬比為 1(適用於所有放射狀圖表,例如,圓環圖)或 2(適用於其餘圖表)。讓我們修改圖表的長寬比

// ...
	new Chart(
    document.getElementById('dimensions'),
    {
      type: 'bubble',
      options: {
        aspectRatio: 1,
      },
// ...

現在看起來好多了

result

但是,它仍然不理想。水平軸的範圍從 0 到 500,而垂直軸的範圍從 0 到 450。依預設,Chart.js 會自動調整軸的範圍(最小值和最大值),以符合資料集中提供的值,因此圖表會「符合」您的資料。顯然,MoMa 系列中沒有高度介於 450 到 500 公分之間的藝術品。讓我們修改圖表的軸組態以說明這一點

// ...
  new Chart(
    document.getElementById('dimensions'),
    {
      type: 'bubble',
      options: {
        aspectRatio: 1,
        scales: {
          x: {
            max: 500
          },
          y: {
            max: 500
          }
        }
      },
// ...

太棒了!看看更新後的圖表

result

但是,還有一個小問題:這些數字是什麼?單位是公分這件事不太明顯。讓我們為兩個軸套用自訂刻度格式,以釐清單位。我們會提供一個回呼函式,該函式將被呼叫以格式化每個刻度值。以下是更新後的軸組態

// ...
  new Chart(
    document.getElementById('dimensions'),
    {
      type: 'bubble',
      options: {
        aspectRatio: 1,
        scales: {
          x: {
            max: 500,
            ticks: {
              callback: value => `${value / 100} m`
            }
          },
          y: {
            max: 500,
            ticks: {
              callback: value => `${value / 100} m`
            }
          }
        }
      },
// ...

完美,現在我們在兩個軸上都有正確的單位

result

# 多個資料集

Chart.js 會獨立繪製每個資料集,並允許將自訂樣式套用到它們。

看看圖表:有明顯的泡泡「線」,其 xy 座標相等,代表正方形藝術品。將這些泡泡放入它們自己的資料集中並以不同方式繪製它們會很酷。此外,我們也可以將「較高」的藝術品與「較寬」的藝術品分開,並以不同的方式繪製它們。

以下是我們執行此操作的方式。將 datasets 取代為以下程式碼

// ...
        datasets: [
          {
            label: 'width = height',
            data: data
              .filter(row => row.width === row.height)
              .map(row => ({
                x: row.width,
                y: row.height,
                r: row.count
              }))
          },
          {
            label: 'width > height',
            data: data
              .filter(row => row.width > row.height)
              .map(row => ({
                x: row.width,
                y: row.height,
                r: row.count
              }))
          },
          {
            label: 'width < height',
            data: data
              .filter(row => row.width < row.height)
              .map(row => ({
                x: row.width,
                y: row.height,
                r: row.count
              }))
          }
        ]
// ..

如您所見,我們定義了三個具有不同標籤的資料集。每個資料集都會取得透過 filter 擷取的資料片段。現在它們在視覺上截然不同,而且如您已經知道的,您可以獨立切換它們的能見度。

result

在這裡,我們依賴預設的調色盤。但是,請記住,每個圖表類型都支援許多您可以自由自訂的資料集選項

# 外掛程式

自訂 Chart.js 圖表的另一種方式(而且非常強大!)是使用外掛程式。您可以在外掛程式目錄 (開啟新視窗)中找到一些,或建立您自己的、臨機操作的外掛程式。在 Chart.js 生態系統中,使用外掛程式微調圖表是慣用的,也是預期的。例如,您可以使用簡單的臨機操作外掛程式自訂畫布背景,或新增邊框。讓我們嘗試後者。

外掛程式具有廣泛的 API,但簡而言之,外掛程式定義為具有 name 和一個或多個在擴充點中定義的回呼函式的物件。將以下程式碼片段插入 src/dimensions.jsnew Chart(...); 叫用之前和取代其位置

// ...
  const chartAreaBorder = {
    id: 'chartAreaBorder',
    beforeDraw(chart, args, options) {
      const { ctx, chartArea: { left, top, width, height } } = chart;
      ctx.save();
      ctx.strokeStyle = options.borderColor;
      ctx.lineWidth = options.borderWidth;
      ctx.setLineDash(options.borderDash || []);
      ctx.lineDashOffset = options.borderDashOffset;
      ctx.strokeRect(left, top, width, height);
      ctx.restore();
    }
  };
  new Chart(
    document.getElementById('dimensions'),
    {
      type: 'bubble',
      plugins: [ chartAreaBorder ],
      options: {
        plugins: {
          chartAreaBorder: {
            borderColor: 'red',
            borderWidth: 2,
            borderDash: [ 5, 5 ],
            borderDashOffset: 2,
          }
        },
        aspectRatio: 1,
// ...

如您所見,在這個 chartAreaBorder 外掛程式中,我們取得畫布內容、儲存其目前狀態、套用樣式、在圖表區域周圍繪製矩形形狀,並還原畫布狀態。我們也會在 plugins 中傳遞外掛程式,因此它只會套用到這個特定的圖表。我們也會在 options.plugins.chartAreaBorder 中傳遞外掛程式選項;我們當然可以在外掛程式原始碼中硬式編碼它們,但這種方式更具可重複使用性。

現在,我們的泡泡圖看起來更精美了

result

# Tree-shaking

在生產環境中,我們力求盡可能減少程式碼量,以便最終使用者可以更快載入我們的資料應用程式並獲得更好的體驗。為此,我們需要套用 tree-shaking (開啟新視窗),這是一個從 JavaScript 套件中移除未使用程式碼的酷炫術語。

Chart.js 完全支援使用其元件設計進行 tree-shaking。您可以一次註冊所有 Chart.js 元件(在您進行原型設計時很方便),並讓它們與您的應用程式一起封裝。或者,您可以只註冊必要的元件並取得最小的套件,其大小會小得多。

讓我們檢查我們的範例應用程式。套件大小是多少?您可以停止應用程式並執行 npm run buildyarn buildpnpm build。幾分鐘後,您會得到類似這樣的東西

% yarn build
yarn run v1.22.17
$ parcel build src/index.html
✨ Built in 88ms
dist/index.html              381 B   164ms
dist/index.74a47636.js   265.48 KB   1.25s
dist/index.ba0c2e17.js       881 B    63ms
✨ Done in 0.51s.

我們可以看見 Chart.js 和其他相依性一起封裝在單個 265 KB 的檔案中。

為了減少套件大小,我們需要對 src/acquisitions.jssrc/dimensions.js 進行一些變更。首先,我們需要從兩個檔案中移除以下匯入陳述式:import Chart from 'chart.js/auto'

相反,讓我們僅載入必要的元件,並使用 Chart.register(...) 將它們「註冊」到 Chart.js。以下是我們在 src/acquisitions.js 中需要的內容

import {
  Chart,
  Colors,
  BarController,
  CategoryScale,
  LinearScale,
  BarElement,
  Legend
} from 'chart.js'
Chart.register(
  Colors,
  BarController,
  BarElement,
  CategoryScale,
  LinearScale,
  Legend
);

以下是 src/dimensions.js 的程式碼片段

import {
  Chart,
  Colors,
  BubbleController,
  CategoryScale,
  LinearScale,
  PointElement,
  Legend
} from 'chart.js'
Chart.register(
  Colors,
  BubbleController,
  PointElement,
  CategoryScale,
  LinearScale,
  Legend
);

您可以看到,除了 Chart 類別之外,我們還載入圖表類型的控制器、刻度和其他圖表元素(例如,長條或點)。您可以在文件中查找所有可用的元件。

或者,您可以遵循主控台中的 Chart.js 建議。例如,如果您忘記為您的長條圖匯入 BarController,您會在瀏覽器主控台中看到以下訊息

Unhandled Promise Rejection: Error: "bar" is not a registered controller.

當您為生產環境準備應用程式時,請記得仔細檢查是否有從 chart.js/auto 匯入的項目。只需要一個這樣的匯入,即可有效地停用 tree-shaking。

現在,讓我們再次檢查我們的應用程式。執行 yarn build,您會得到類似這樣的東西

% yarn build
yarn run v1.22.17
$ parcel build src/index.html
✨ Built in 88ms
dist/index.html              381 B   176ms
dist/index.5888047.js    208.66 KB   1.23s
dist/index.dcb2e865.js       932 B    58ms
✨ Done in 0.51s.

透過匯入並僅註冊選取的元件,我們移除了超過 56 KB 的不必要程式碼。假設其他相依性在套件中佔用約 50 KB,則 tree-shaking 有助於從我們範例應用程式的套件中移除約 25% 的 Chart.js 程式碼。

# 後續步驟

現在,您已熟悉 Chart.js 的所有主要概念:圖表類型和元素、資料集、自訂、外掛程式、元件和 tree-shaking。

歡迎參考文件中眾多的圖表範例,並查看 超棒列表 (開啟新視窗),其中包含 Chart.js 的外掛程式和其他圖表類型,以及框架整合 (開啟新視窗) (例如:React、Vue、Svelte 等)。此外,也歡迎加入 Chart.js Discord (開啟新視窗) 並在 Twitter 上追蹤 Chart.js (開啟新視窗)

祝您使用 Chart.js 開發愉快!

最後更新: 2024/5/17, 下午 12:33:38