Chart.js 气泡图 Scriptable 选项实战:用回调函数驱动颜色、半径与悬浮态
【免费下载链接】Chart.jsSimple HTML5 Charts using the项目地址: https://gitcode.com/gh_mirrors/ch/Chart.js
导读
本文以 Chart.js 官方示例 docs/samples/scriptable/bubble.md 为骨架,完整剖析如何通过 Scriptable(脚本化)选项让气泡图的每个数据点拥有独立的颜色、边框与半径。你将掌握context参数的使用方法、elements.point各属性回调的写法,以及 Chart.js 内部如何解析半径并驱动动画与悬浮交互,可直接复制示例代码到你的项目中运行验证。
示例全景:一个数据驱动的气泡图
原始示例生成 16 个数据点,每个点携带x、y、v三个维度,通过 scriptable 选项将v映射为颜色透明度与半径大小,最终形成一个数据密度可视化的散点气泡图:
const DATA_COUNT = 16; const MIN_XY = -150; const MAX_XY = 100; Utils.srand(110); const actions = [ { name: 'Randomize', handler(chart) { chart.data.datasets.forEach(dataset => { dataset.data = generateData(); }); chart.update(); } }, ]; function generateData() { const data = []; let i; for (i = 0; i < DATA_COUNT; ++i) { data.push({ x: Utils.rand(MIN_XY, MAX_XY), y: Utils.rand(MIN_XY, MAX_XY), v: Utils.rand(0, 1000) }); } return data; } const data = { datasets: [{ data: generateData() }, { data: generateData() }] }; function channelValue(x, y, values) { return x < 0 && y < 0 ? values[0] : x < 0 ? values[1] : y < 0 ? values[2] : values[3]; } function colorize(opaque, context) { const value = context.raw; const x = value.x / 100; const y = value.y / 100; const r = channelValue(x, y, [250, 150, 50, 0]); const g = channelValue(x, y, [0, 50, 150, 250]); const b = channelValue(x, y, [0, 150, 150, 250]); const a = opaque ? 1 : 0.5 * value.v / 1000; return 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')'; } const config = { type: 'bubble', data: data, options: { aspectRatio: 1, plugins: { legend: false, tooltip: false, }, elements: { point: { backgroundColor: colorize.bind(null, false), borderColor: colorize.bind(null, true), borderWidth: function(context) { return Math.min(Math.max(1, context.datasetIndex + 1), 8); }, hoverBackgroundColor: 'transparent', hoverBorderColor: function(context) { return Utils.color(context.datasetIndex); }, hoverBorderWidth: function(context) { return Math.round(8 * context.raw.v / 1000); }, radius: function(context) { const size = context.chart.width; const base = Math.abs(context.raw.v) / 1000; return (size / 24) * base; } } } } }; module.exports = { actions, config, };这段代码在chart-editor代码块内运行,依赖文档示例专用的Utils工具(见 docs/samples/utils.md)。Utils.srand(110)固定随机种子,保证每次刷新图表数据一致;Utils.rand(min, max)生成区间内的随机数——这两个函数只用于示例与测试环境,官方明确提示生产环境不要依赖Utils文件。
气泡图数据的三维结构
气泡图与传统散点图的核心区别在于用三个维度表达信息:x、y决定气泡位置,第三个维度决定气泡大小。官方文档 docs/charts/bubble.md 给出的标准数据结构为:
{ x: number, // X 值 y: number, // Y 值 r: number // 气泡半径(像素,不随坐标缩放) }其中r是未缩放的原始像素半径,由 Chart.js 直接绘制在画布上。而本示例中的v字段是一个自定义维度:它不直接参与渲染,而是通过 scriptable 回调在运行时计算透明度(0.5 * value.v / 1000)和半径(Math.abs(context.raw.v) / 1000),这正是 scriptable 选项的典型用法——原始数据中任何字段都可以在回调中读取并参与样式计算。
内部数据格式与半径解析
数据在进入渲染管线后会被解析为内部格式{x, y, _custom},其中_custom就是半径。从 src/controllers/controller.bubble.js 的parseObjectData可以看到:
parseObjectData(meta, data, start, count) { const parsed = super.parseObjectData(meta, data, start, count); for (let i = 0; i < parsed.length; i++) { const item = data[start + i]; parsed[i]._custom = valueOrDefault(item && item.r && +item.r, this.resolveDataElementOptions(i + start).radius); } return parsed; }即:如果数据对象提供了r,直接采用;否则回退到elements.point.radius的解析值。随后在 resolveDataElementOptions 中,_custom与解析出的radius相加得到最终渲染半径;非激活(非悬浮)状态下radius会被临时置为 0,保证普通状态只用_custom的半径值。这解释了为什么脚本化radius回调的返回值是"基准半径",而数据里的r是"叠加半径"。
Scriptable 选项机制:回调如何驱动样式
Scriptable 选项的核心是:选项值可以是一个函数,Chart.js 会为每个数据点调用该函数,并传入context上下文对象(详见 docs/general/options.md#scriptable-options)。本示例为backgroundColor、borderColor、borderWidth、hoverBorderColor、hoverBorderWidth、radius六个属性都使用了回调。
选项解析的分层作用域
Scriptable 回调解析遵循"自顶向下"的作用域链(docs/general/options.md#option-resolution):
- 元素级选项依次查找:
dataset→options.datasets[type].elements[elementType]→options.elements[elementType]→options→overrides[...]→defaults[...]; - 气泡图的
dataElementType为'point',因此options.elements.point是气泡样式的标准挂载点(见 controller.bubble.js 的defaults定义)。
示例中所有回调都写在options.elements.point下,作用于全部数据集的所有气泡。
context 上下文:回调的数据来源
context是 scriptable 回调唯一的参数,包含以下常用字段(docs/general/options.md#data):
| 字段 | 含义 |
|---|---|
chart | 当前 Chart 实例,可读取chart.width等布局信息 |
dataIndex | 当前数据点的索引 |
datasetIndex | 当前数据集的索引 |
raw | 当前数据点的原始数据对象,如{x, y, v} |
parsed | 解析后的数据,如{x, y, _custom} |
active | 元素是否处于激活(悬浮)状态 |
type | 上下文类型,如'data'、'dataset' |
颜色回调:按象限配色
colorize函数利用channelValue将平面按x、y正负划分为四个象限,为每个象限分配不同的 RGB 通道值,再用value.v控制透明度:
function channelValue(x, y, values) { return x < 0 && y < 0 ? values[0] : x < 0 ? values[1] : y < 0 ? values[2] : values[3]; } function colorize(opaque, context) { const value = context.raw; const x = value.x / 100; const y = value.y / 100; const r = channelValue(x, y, [250, 150, 50, 0]); const g = channelValue(x, y, [0, 50, 150, 250]); const b = channelValue(x, y, [0, 150, 150, 250]); const a = opaque ? 1 : 0.5 * value.v / 1000; return 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')'; }这里用colorize.bind(null, false)/colorize.bind(null, true)预绑定opaque参数,让同一个函数同时服务填充色(半透明)和边框色(不透明)。v越大透明度越高,颜色越"实"。
半径回调:随图表宽度自适应
radius回调将气泡半径与画布宽度挂钩,使图表在任意尺寸下都能保持协调的比例:
radius: function(context) { const size = context.chart.width; const base = Math.abs(context.raw.v) / 1000; return (size / 24) * base; }v的取值范围(0–1000)被归一化到0–1,再乘以width / 24。注意这里使用了Math.abs,即使用户传入负的v值也能得到合法半径。
边框宽度回调:数据集序号的间接映射
borderWidth: function(context) { return Math.min(Math.max(1, context.datasetIndex + 1), 8); }该回调把datasetIndex映射到 1–8 像素:第一个数据集边框 1px,第二个 2px,依此类推,用Math.min/Math.max做了上下限钳制。气泡图通常最多十几个数据集,这种写法保证了边框粗细始终处于合理区间。
悬浮态:hover 系列选项的脚本化
示例为悬浮态定义了三个脚本化选项与一个静态选项:
hoverBackgroundColor: 'transparent', // 悬浮时填充变为全透明 hoverBorderColor: function(context) { return Utils.color(context.datasetIndex); // 按数据集序号取预设调色板颜色 }, hoverBorderWidth: function(context) { return Math.round(8 * context.raw.v / 1000); // 按 v 值 0-8 取整 },悬浮交互的底层逻辑可以参考 src/elements/element.point.ts:point元素默认定义了hoverRadius: 4、hitRadius: 1、hoverBorderWidth: 1等默认值。结合 test/specs/controller.bubble.tests.js 的测试用例可以验证:悬浮时radius会在基础半径上叠加hoverRadius(如测试中20 + 4),移出后恢复为20,且hoverBackgroundColor、hoverBorderColor、hoverBorderWidth均会被单独解析——这意味着 hover 系列同样支持完整的脚本化能力。
测试用例佐证
controller.bubble.tests.js 还验证了"数据集级 hover 属性"的优先级:当在chart.data.datasets[0]上定义hoverBackgroundColor、hoverRadius等属性并chart.update()后,悬浮态样式立即生效;鼠标移出后恢复数据集与元素级配置。这印证了文档中"hover 属性未定义时回退到elements.point.*"的解析规则(docs/charts/bubble.md#interactions)。
运行与验证
在 Chart.js 的文档环境中,该示例以chart-editor代码块形式渲染,并导出actions与config;页面上的 "Randomize" 按钮会重新生成数据并调用chart.update()。若要在自己的项目中使用:
- 引入 Chart.js 并注册
BubbleController与PointElement; - 将
config直接传给new Chart(ctx, config); - 在本地复制示例时,用真实的
Math.random()或固定数据替换Utils依赖,因为Utils仅为文档示例服务(见 docs/samples/utils.md 的免责声明)。
运行限制提示:气泡图默认需要线性坐标轴——controller.bubble.js 的overrides中自动为x、y轴配置了type: 'linear',因此本示例无需显式声明scales。
相关文档
- 气泡图完整配置项(数据集属性、默认值、数据格式):docs/charts/bubble.md
- 选项解析与 Scriptable/Indexable 机制:docs/general/options.md(Scriptable Options、Option Context)
- 示例工具函数(
srand、rand、color等):docs/samples/utils.md - 其余 scriptable 示例(bar、line、pie、polar、radar):docs/samples/scriptable
【免费下载链接】Chart.jsSimple HTML5 Charts using the项目地址: https://gitcode.com/gh_mirrors/ch/Chart.js
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考