# 逐步指南
按照本指南熟悉 Chart.js 的所有主要概念: 图表类型和元素、数据集、自定义、插件、组件和 tree-shaking。 不要犹豫,点击文本中的链接。
我们将从头开始使用几个图表构建一个 Chart.js 数据可视化:
# 使用 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 (opens new window) 作为一个不错的零配置构建工具。 我们还为 立方体 (opens new window) 安装了 Chart.js v4 和一个 JavaScript 客户端,这是一个用于数据应用的开源 API,我们将使用它来获取真实世界的数据(稍后会详细介绍)。
运行 npm install
、yarn install
或 pnpm 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 需要最少的标记: 带有 id
的 canvas
标签,稍后我们将通过它来引用图表。 默认情况下,Chart.js 图表是 responsive 并占用整个封闭容器。 所以,我们设置 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
) 并提供由labels
(通常是数据点的数字或文本描述)和一个datasets
数组(Chart.js 支持大多数图表类型的多个数据集)组成的data
。 每个数据集都指定有label
并包含一个数据点数组。 - 目前,我们只有一些虚拟数据条目。 因此,我们提取
year
和count
属性以生成labels
的数组和唯一数据集中的数据点。
是时候使用 npm run dev
、yarn dev
或 pnpm dev
运行示例并在 Web 浏览器中导航到 localhost:1234 (opens new window):
只需几行代码,我们就得到了一个具有很多功能的图表: a 悬停时显示 legend、网格线、刻度 和 tooltips。 刷新几次网页,看到图表也是 animated。 尝试单击 “Acquisitions by year” 标签以查看你还可以切换数据集可见性(当你有多个数据集时尤其有用)。
# 简单的定制
让我们看看如何自定义 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
提供的布尔标志。 大多数图表范围的选项(例如,responsiveness 或 设备像素比)都是这样配置的。
图例和工具提示隐藏在 plugins
中相应部分下提供的布尔标志中。 请注意,一些 Chart.js 功能被提取到插件中: 独立的、独立的代码片段。 其中一些作为 Chart.js 分布 (opens new window) 的一部分提供,其他插件是独立维护的,可以位于插件、框架集成和其他图表类型的 很棒的清单 (opens new window) 中。
你应该能够在浏览器中看到更新后的简约图表。
# 真实世界的数据
使用硬编码、大小有限、不切实际的数据,很难展示 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
立方体 (opens new window) 的 JavaScript 客户端库,一个用于数据应用的开源 API,使用 API URL (apiUrl
) 和身份验证令牌 (cubeToken
) 配置它,最后实例化客户端 (cubeApi
)。 - Cube API 托管在 立方云 (opens new window) 中,并连接到一个数据库,其中包含约 140,000 条记录的 公共数据集 (opens new window),代表美国纽约 现代艺术博物馆 (opens new window) 收藏的所有艺术品。 当然,这是一个比我们现在拥有的更真实的数据集。
- 我们定义了几个异步函数来从 API 中获取数据:
getAquisitionsByYear
和getDimensions
。 第一个返回按收购年份划分的艺术品数量,另一个返回每个宽高对的艺术品数量(我们将需要它用于另一个图表)。 - 让我们来看看
getAquisitionsByYear
。 首先,我们在acquisitionsByYearQuery
变量中创建一个声明性的、基于 JSON 的查询。 如你所见,我们指定对于每个yearAcquired
,我们希望获得艺术品的count
;yearAcquired
必须被设置(即不是未定义的); 结果集将按yearAcquired
升序排序。 - 其次,我们通过调用
cubeApi.load
获取resultSet
,并将其映射到具有所需year
和count
属性的对象数组。
现在,让我们将真实世界的数据传送到我们的图表。 请对 src/acquisitions.js
应用一些更改: 添加导入并替换 data
变量的定义。
import { getAquisitionsByYear } from './api'
// ...
const data = await getAquisitionsByYear();
完毕! 现在,我们的真实世界数据图表如下所示。 看来 1964 年、1968 年、2008 年都发生了一些有趣的事情!
我们完成了柱状图。 让我们尝试另一种 Chart.js 图表类型。
# 进一步定制
Chart.js 支持许多常见的图表类型。
例如,气泡图 允许同时展示三个维度的数据: x
和 y
轴上的位置表示两个维度,第三个维度由单个气泡的大小表示。
要创建图表,请停止已在运行的应用,然后转至 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
类型的新图表,将数据的三个维度作为 x
、y
和 r
(半径)属性传递。
现在,使用 rm -rf .parcel-cache
重置缓存并使用 npm run dev
、yarn dev
或 pnpm dev
再次启动应用。 我们现在可以查看新图表:
好吧,它看起来并不漂亮。
首先,图表不是正方形的。 图稿的宽度和高度同样重要,因此我们希望图表的宽度也等于它的高度。 默认情况下,Chart.js 图表的 纵横比 为 1(对于所有径向图表,例如圆环图)或 2(对于所有其他图表)。 让我们修改图表的纵横比:
// ...
new Chart(
document.getElementById('dimensions'),
{
type: 'bubble',
options: {
aspectRatio: 1,
},
// ...
现在看起来好多了:
然而,它仍然不理想。 水平轴的范围从 0 到 500,而垂直轴的范围从 0 到 450。 默认情况下,Chart.js 会自动将轴的范围(最小值和最大值)调整为数据集中提供的值,因此图表 “fits” 是你的数据。 显然,MoMa 收藏中没有 450 至 500 厘米高度范围内的艺术品。 让我们修改图表的 轴配置 以说明这一点:
// ...
new Chart(
document.getElementById('dimensions'),
{
type: 'bubble',
options: {
aspectRatio: 1,
scales: {
x: {
max: 500
},
y: {
max: 500
}
}
},
// ...
很好! 看看更新后的图表:
然而,还有一个挑剔的地方: 这些数字是多少? 单位是厘米不是很明显。 让我们将 自定义刻度格式 应用于两个轴以使事情变得清楚。 我们将提供一个回调函数,调用它来格式化每个刻度值。 这是更新的轴配置:
// ...
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`
}
}
}
},
// ...
完美,现在我们在两个轴上都有正确的单位:
# 多个数据集
Chart.js 独立绘制每个数据集并允许对它们应用自定义样式。
看一下图表: 有一个可见的 “line” 气泡,x
和 y
坐标相等,代表方形艺术品。 将这些气泡放入它们自己的数据集中并以不同方式绘制它们会很酷。 此外,我们可以将 “taller” 作品与 “wider” 作品分开,并以不同的方式绘制它们。
以下是我们如何做到这一点。 将 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
提取的数据切片。 现在它们在视觉上是截然不同的,而且正如你所知,你可以独立切换它们的可见性。
这里我们依赖于默认的调色板。 但是,请记住,每种图表类型都支持大量你可以随意自定义的 数据集选项。
# 插件
其他—而且非常强大!—自定义 Chart.js 图表的方法是使用插件。 你可以在 插件目录 (opens new window) 中找到一些或创建你自己的临时文件。 在 Chart.js 生态系统中,它是惯用的,并且有望使用插件微调图表。 例如,你可以使用简单的临时插件为其自定义 canvas 背景 或 添加边框。 让我们试试后者。
插件具有 广泛的 API,但简而言之,插件被定义为具有 name
的对象以及在扩展点中定义的一个或多个回调函数。 在 src/dimensions.js
中的 new 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
中传递了插件选项; 我们当然可以在插件源代码中对它们进行硬编码,但这种方式的可重用性要高得多。
我们的气泡图现在看起来更好看了:
# Tree-shaking
在生产中,我们力求发布尽可能少的代码,以便终端用户可以更快地加载我们的数据应用并获得更好的体验。 为此,我们需要应用 tree-shaking (opens new window),这是一个用于从 JavaScript 包中删除未使用代码的奇特术语。
Chart.js 完全支持 tree-shaking 及其组件设计。 你可以一次注册所有 Chart.js 组件(这在你制作原型时很方便)并将它们与你的应用打包在一起。 或者,你可以只注册必要的组件并获得最小的包,大小要小得多。
让我们检查一下我们的示例应用。 打包包大小是多少? 你可以停止应用并运行 npm run build
、yarn build
或 pnpm 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.js
和 src/dimensions.js
进行一些更改。 首先,我们需要从两个文件中删除以下导入语句: import Chart from 'chart.js/auto'
.
相反,让我们只加载必要的组件,并使用 Chart.register(...)
使用 Chart.js 对它们进行 “register”。 这是我们在 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
类之外,我们还为图表类型、刻度和其他图表元素(例如,条形或点)加载了一个控制器。 你可以在 documentation 中查找所有可用组件。
或者,你可以在控制台中遵循 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 插件的 很棒的清单 (opens new window) 和其他图表类型以及 框架整合 (opens new window)(例如,React、Vue、Svelte 等)。 另外,不要犹豫加入 Chart.js Discord (opens new window) 并关注 Twitter 上的 Chart.js (opens new window)。
祝你在使用 Chart.js 的过程中玩得开心,祝你好运!