Chart.js 从零实战用 Step-by-step 指南掌握图表类型、数据集、定制、插件与 Tree-shaking【免费下载链接】Chart.jsSimple HTML5 Charts using thetag项目地址: https://gitcode.com/gh_mirrors/ch/Chart.js本篇技术指南以 Chart.js 官方《Step-by-step guide》为骨架带你从空目录开始搭建一个完整的 Chart.js 数据可视化应用先用手写数据渲染柱状图再接入真实世界数据集渲染气泡图逐步掌握图表类型与元素、数据集结构、options定制、aspectRatio与坐标轴配置、自定义 tick 格式、多数据集、插件系统以及面向生产环境的 Tree-shaking 按需注册。读完本文你将能独立用 Chart.js v4 构建可定制、可优化打包体积的现代化图表应用并能从仓库源码层面理解每个配置项背后的实现机制。本文对应仓库文档 docs/getting-started/usage.md所有源码佐证均来自当前 Chart.js v4.5.1 仓库。一、搭建项目创建 package.json 与安装依赖在一个全新的文件夹中首先创建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 } }这里的关键设计Parcel 作为零配置打包器现代前端应用通常依赖 JavaScript 模块打包器Parcel 无需任何配置文件即可工作非常适合教学场景。Chart.js v4当前仓库package.json中版本为4.5.1见 package.jsontype: module表明包以 ES Module 形式分发module: ./dist/chart.js与main: ./dist/chart.cjs分别对应 ESM 与 CommonJS 入口exports字段还暴露了./auto与./helpers子路径package.json。cubejs-client/coreCube 的 JavaScript 客户端库用于从公开数据 API 拉取真实数据让示例更接近生产环境。执行npm install、yarn install或pnpm install安装依赖然后创建src文件夹。二、最小 HTML 骨架一个 canvas 就够在src下创建极简的index.html!doctype html html langen head titleChart.js example/title /head body !-- div stylewidth: 500px;canvas iddimensions/canvas/divbr/ -- div stylewidth: 800px;canvas idacquisitions/canvas/div !-- script typemodule srcdimensions.js/script -- script typemodule srcacquisitions.js/script /body /html要点解读Chart.js 只要求极简标记一个带id的canvas标签后续通过该id引用图表。默认情况下 Chart.js 图表是响应式的会占满整个外层容器因此通过给div设置width来控制图表宽度——这是官方推荐的容器隔离做法同样见于 docs/getting-started/index.md 的最小示例。被注释掉的div iddimensions与dimensions.js是为后面的气泡图预留的。三、第一个柱状图Chart 类、labels 与 datasets创建src/acquisitions.jsimport 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) } ] } } ); })();逐段拆解import Chart from chart.js/auto从特殊路径导入Chart主类。查看 auto/auto.js 的源码这一行实际做的是import {Chart, registerables} from ../dist/chart.js; Chart.register(...registerables); export * from ../dist/chart.js; export default Chart;即它一次性注册了src/index.ts中导出的registerablessrc/index.ts包含controllers、elements、plugins、scales四大类组件。便利性极佳但代价是禁用 tree-shaking——因为 package.json 中将./auto/auto.js声明为sideEffects打包器不会移除它且注册表单例意味着所有组件都会被保留在包内。这个问题我们会在 Tree-shaking 一节解决。new Chart(canvas, config)两个参数——第一个是图表要渲染到的 canvas 元素document.getElementById(acquisitions)第二个是配置对象。type: bar声明图表类型。Chart.js 通过控制器Controller实现图表类型当前仓库的 src/controllers/index.js 导出 8 个控制器BarController、BubbleController、DoughnutController、LineController、PolarAreaController、PieController、RadarController、ScatterController。data.labels数据点的标签数组通常是数值或文本描述如年份。data.datasets数据集数组多数图表类型支持多个数据集。每个数据集以label命名并包含数据点数组data。此处用map从{year, count}对象中分别抽出标签与数值。运行npm run dev或yarn dev/pnpm dev浏览器打开 Parcel 提示的本地地址仅仅几行代码就得到了一个功能齐全的图表自带图例、网格线、刻度和悬停提示的 tooltip。刷新页面还能看到动画效果。试试点击图例中的 “Acquisitions by year” 标签——你可以切换数据集的可见性这在多数据集场景下尤为实用。源码佐证默认动画并非凭空而来。在 src/core/core.animations.defaults.js 中Chart.js 默认注册了animation配置duration: 1000毫秒、easing: easeOutQuart这正是刷新页面时观察到过渡动画的底层来源。四、简单定制通过 options 关闭动画、图例与 tooltipChart.js 的定制入口是第二个参数里的options属性。把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) } ] } } );三个关键点animation: false用一个布尔标志整体关闭动画图表将立即出现。相关文档见 animations.md#disabling-animation。等价地你也可以设置animation: { duration: 0 }见core.animations.defaults.js中默认值的注释。plugins下的布尔标志legend.display: false隐藏图例tooltip.enabled: false禁用提示框。注意二者都挂在plugins命名空间下。插件是 Chart.js 的架构基石部分功能被拆分为独立、自包含的插件随 Chart.js 发行版内置仓库中的 src/plugins 目录包含plugin.colors.ts、plugin.legend.js、plugin.tooltip.js、plugin.title.js、plugin.subtitle.js、plugin.decimation.js以及 plugin.filler 子目录另一些插件则由社区独立维护。多数图表级选项如响应式、设备像素比都以类似方式在options顶层配置。五、接入真实数据使用 Cube API硬编码的小数据集无法展示 Chart.js 的全部潜力。下面通过 Cube 的公开数据 API纽约现代艺术博物馆 MoMA 约 14 万条馆藏记录让示例接近生产形态。创建src/api.jsimport { 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]) })); }这段代码做了什么初始化客户端importCube 的 JS 客户端用apiUrlAPI 地址和cubeToken认证令牌实例化cubeApi。声明式查询acquisitionsByYearQuery是纯 JSON 查询——对每个yearAcquired维度求Artworks.count度量过滤条件为yearAcquired已设置非空结果按年份升序排列。异步取数函数getAquisitionsByYear返回按年份统计的藏品数getDimensions返回每个“宽 × 高”组合对应的藏品数供后续气泡图使用。两者都通过cubeApi.load(query)获取resultSet再用tablePivot()转表并映射为带目标字段的对象数组。然后改造src/acquisitions.js——添加 import 并替换data变量定义import { getAquisitionsByYear } from ./api // ... const data await getAquisitionsByYear();刷新页面柱状图就变成了真实数据。从图中可以看到 1964、1968、2008 年有明显的峰值六、第二种图表气泡图 Bubble chartBubble 气泡图可同时展示三维数据x、y轴坐标表示两个维度气泡半径表示第三维。首先停止正在运行的 dev 服务在src/index.html中取消注释两行div stylewidth: 500px;canvas iddimensions/canvas/divbr/ script typemodule srcdimensions.js/script再创建src/dimensions.jsimport 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 })) } ] } } ); })();思路与柱状图一致取数据、new Chart、指定type: bubble把三个维度映射为数据点的x、y、r半径属性。需要说明此处的labels映射来自原文档示例代码实际气泡图主要依赖x/y数值坐标对应LinearScale。执行rm -rf .parcel-cache清除缓存后重新npm run dev效果并不理想——问题有三图表不是正方形坐标轴范围不协调刻度单位不明确。下面逐一解决。七、进一步定制aspectRatio、坐标轴范围与自定义 tick 格式7.1 用 aspectRatio 控制宽高比藏品的宽高同等重要我们希望图表宽高相等。Chart.js 默认宽高比为 1所有径向图如环形图或 2其余所有图表因此给气泡图显式设置// ... new Chart( document.getElementById(dimensions), { type: bubble, options: { aspectRatio: 1, }, // ...源码佐证在 src/core/core.controller.js 的aspectRatiogetter 中若options.aspectRatio已定义则直接采用否则在maintainAspectRatio为真时沿用此前推算的_aspectRatio。也就是说显式传入的aspectRatio优先级最高。该值还会在布局阶段被传给平台层用于计算最大可用尺寸core.controller.js。修正后的图表7.2 用 scales 限定坐标轴范围水平轴范围 0–500垂直轴却只有 0–450——MoMA 馆藏中并没有 450–500cm 高的作品而默认情况下 Chart.js 会自动把坐标轴范围最小值/最大值贴合到数据集值上。修改坐标轴配置// ... new Chart( document.getElementById(dimensions), { type: bubble, options: { aspectRatio: 1, scales: { x: { max: 500 }, y: { max: 500 } } }, // ...源码佐证线性刻度在 src/scales/scale.linearbase.js 中会读取opts.min/opts.max并参与最终范围的计算因此通过scales.x.max、scales.y.max即可强制坐标轴上界。7.3 自定义 tick 格式callback 回调刻度上的数字含义不明单位是厘米通过tick 格式定制给两个坐标轴都加一个格式化回调// ... 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 } } } }, // ...每个刻度值都会被传入callback这里把厘米换算成米并拼接单位后缀八、多数据集独立渲染与差异化样式Chart.js 对每个数据集独立绘图并允许为它们应用各自的样式。仔细观察上图有一条x y的“对角线”气泡串正方形作品还有“更宽”和“更高”的两类作品。把它们拆成三个数据集并分别着色// ... 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 })) } ] // ..这里为每个数据集分配了不同的label用filter切分出各自的子集。三个数据集在视觉上相互区分并且前面提过可以独立切换可见性此例依赖默认调色板这正是内置Colors插件的作用见 src/plugins/plugin.colors.ts。请记住每种图表类型都支持丰富的数据集选项可自由定制。九、插件ad-hoc 插件给图表区域加边框插件是 Chart.js 生态中另一类且非常强大的定制手段。你可以使用现成插件也可以编写自己的临时ad-hoc插件——这是 Chart.js 生态中打磨图表的惯用方式。典型场景如定制画布背景或给图表区域加边框后者正是我们接下来的目标。插件拥有完善的 API但其本质很简单一个带name和若干扩展点extension point回调函数的对象。在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插件id插件唯一标识。beforeDraw(chart, args, options)在绘制阶段前触发的钩子。我们从chart中取出 canvas 上下文ctx和绘图区域chartArealeft、top、width、height。绘制流程ctx.save()保存画布状态 → 用options中的样式borderColor、borderWidth、borderDash、borderDashOffset设置描边 →ctx.strokeRect(...)沿图表区域画矩形 →ctx.restore()恢复画布状态避免污染后续绘制。插件选项通过options.plugins.chartAreaBorder传入这比把样式硬编码在插件源码里更可复用。插件在plugins: [ chartAreaBorder ]中被注册到当前图表实例因此只作用于这一个图表。源码佐证Chart.js 的注册机制在 src/core/core.registry.js 中。Registry内部维护controllers、elements、plugins、scales四个类型化注册表Chart.register(...)调用_each时会通过_getRegistryForType自动把传入对象分派到对应注册表插件是最终兜底并依次触发beforeRegister/afterRegister生命周期钩子core.registry.js。这也解释了为什么registerables这种“打包一大包组件”的导入也能被正确逐个注册。给气泡图加上红色虚线边框后的最终效果十、Tree-shaking按需注册组件大幅缩减打包体积生产环境中我们希望端到端交付尽量少的代码让用户更快加载。Tree-shaking摇树优化指从 JavaScript 产物中剔除未使用的代码。Chart.js 凭借其组件化设计完全支持 tree-shaking。先看基线体积。停止应用后运行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 的文件。要压缩体积只需把两个文件中的import Chart from chart.js/auto换成按需导入并用Chart.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主类外还需要图表类型对应的控制器BarController/BubbleController、坐标轴对应的比例尺CategoryScale处理分类标签LinearScale处理线性数值轴、图表元素柱条BarElement/ 数据点PointElement以及需要的插件Colors、Legend。完整的组件清单可查阅 integration.md#bundle-optimization仓库源码方面控制器清单见 src/controllers/index.js比例尺清单见 src/scales/index.jsCategory、Linear、Logarithmic、RadialLinear、Time、TimeSeries 共 6 种。源码佐证为什么chart.js/auto会破坏 tree-shakingauto/auto.js 执行Chart.register(...registerables)把 src/index.ts 导出的全部组件注册进全局单例Registry同时 package.json 把./auto/auto.js标记为sideEffects打包器不敢将其丢弃。因此生产代码中哪怕残留一个chart.js/auto导入整个图表库都会被保留。如果漏注册某个组件Chart.js 会在浏览器控制台给出明确提示。例如柱状图忘记导入BarController你会看到Unhandled Promise Rejection: Error: bar is not a registered controller.源码佐证这条错误信息正是 src/core/core.registry.js 中_get在注册表查找失败时抛出的 id is not a registered type .由getController在对控制器解析时触发。再次构建% 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 KBtree-shaking 帮助去掉了约 25% 的 Chart.js 代码。请务必在准备生产构建时仔细检查所有chart.js/auto导入——一个这样的导入就足以让 tree-shaking 失效。十一、下一步至此你已经熟悉 Chart.js 的全部核心概念图表类型与元素、数据集、定制options、aspectRatio、scales、tick 回调、插件、组件注册与 tree-shaking。继续深入可以参考文档中的大量图表示例各类图表的专属文档如气泡图的完整数据集属性本仓库的组件源码控制器在 src/controllers、比例尺在 src/scales、元素在 src/elements、内置插件在 src/plugins测试用例如 test/specs/controller.bar.tests.js、test/specs/controller.bubble.tests.js可以帮助你验证各配置项的实际行为。祝你在 Chart.js 的图表世界里玩得开心【免费下载链接】Chart.jsSimple HTML5 Charts using thetag项目地址: https://gitcode.com/gh_mirrors/ch/Chart.js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考