news 2026/9/18 7:08:20

Chart.js 气泡图 Scriptable 选项实战:用回调函数驱动颜色、半径与悬浮态

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Chart.js 气泡图 Scriptable 选项实战:用回调函数驱动颜色、半径与悬浮态

Chart.js 气泡图 Scriptable 选项实战:用回调函数驱动颜色、半径与悬浮态

【免费下载链接】Chart.jsSimple HTML5 Charts using thetag项目地址: https://gitcode.com/gh_mirrors/ch/Chart.js

导读

本文以 Chart.js 官方示例 docs/samples/scriptable/bubble.md 为骨架,完整剖析如何通过 Scriptable(脚本化)选项让气泡图的每个数据点拥有独立的颜色、边框与半径。你将掌握context参数的使用方法、elements.point各属性回调的写法,以及 Chart.js 内部如何解析半径并驱动动画与悬浮交互,可直接复制示例代码到你的项目中运行验证。

示例全景:一个数据驱动的气泡图

原始示例生成 16 个数据点,每个点携带xyv三个维度,通过 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文件。

气泡图数据的三维结构

气泡图与传统散点图的核心区别在于用三个维度表达信息:xy决定气泡位置,第三个维度决定气泡大小。官方文档 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)。本示例为backgroundColorborderColorborderWidthhoverBorderColorhoverBorderWidthradius六个属性都使用了回调。

选项解析的分层作用域

Scriptable 回调解析遵循"自顶向下"的作用域链(docs/general/options.md#option-resolution):

  • 元素级选项依次查找:datasetoptions.datasets[type].elements[elementType]options.elements[elementType]optionsoverrides[...]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将平面按xy正负划分为四个象限,为每个象限分配不同的 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: 4hitRadius: 1hoverBorderWidth: 1等默认值。结合 test/specs/controller.bubble.tests.js 的测试用例可以验证:悬浮时radius会在基础半径上叠加hoverRadius(如测试中20 + 4),移出后恢复为20,且hoverBackgroundColorhoverBorderColorhoverBorderWidth均会被单独解析——这意味着 hover 系列同样支持完整的脚本化能力。

测试用例佐证

controller.bubble.tests.js 还验证了"数据集级 hover 属性"的优先级:当在chart.data.datasets[0]上定义hoverBackgroundColorhoverRadius等属性并chart.update()后,悬浮态样式立即生效;鼠标移出后恢复数据集与元素级配置。这印证了文档中"hover 属性未定义时回退到elements.point.*"的解析规则(docs/charts/bubble.md#interactions)。

运行与验证

在 Chart.js 的文档环境中,该示例以chart-editor代码块形式渲染,并导出actionsconfig;页面上的 "Randomize" 按钮会重新生成数据并调用chart.update()。若要在自己的项目中使用:

  1. 引入 Chart.js 并注册BubbleControllerPointElement
  2. config直接传给new Chart(ctx, config)
  3. 在本地复制示例时,用真实的Math.random()或固定数据替换Utils依赖,因为Utils仅为文档示例服务(见 docs/samples/utils.md 的免责声明)。

运行限制提示:气泡图默认需要线性坐标轴——controller.bubble.js 的overrides中自动为xy轴配置了type: 'linear',因此本示例无需显式声明scales

相关文档

  • 气泡图完整配置项(数据集属性、默认值、数据格式):docs/charts/bubble.md
  • 选项解析与 Scriptable/Indexable 机制:docs/general/options.md(Scriptable Options、Option Context)
  • 示例工具函数(srandrandcolor等):docs/samples/utils.md
  • 其余 scriptable 示例(bar、line、pie、polar、radar):docs/samples/scriptable

【免费下载链接】Chart.jsSimple HTML5 Charts using thetag项目地址: https://gitcode.com/gh_mirrors/ch/Chart.js

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 7:06:41

ClickHouse 数据备份与恢复:快照备份、增量备份与跨集群迁移方案

ClickHouse 数据备份与恢复&#xff1a;快照备份、增量备份与跨集群迁移方案 摘要&#xff1a;本文详细介绍了 ClickHouse 数据库的三种关键备份与恢复策略&#xff1a;快照备份、增量备份和跨集群迁移方案。通过实际操作示例和最佳实践&#xff0c;帮助读者理解如何高效备份和…

作者头像 李华
网站建设 2026/9/18 7:06:17

2025年十大潜力AI产品解析与市场影响评估

1. 2025年AI产品影响力评估框架在预测未来两年AI产品的市场影响力时&#xff0c;我们需要建立多维度的评估体系。根据技术成熟度曲线和产品落地周期&#xff0c;2025年真正具备影响力的产品往往已经在2023年进入原型验证阶段。以下是我们的核心评估维度&#xff1a;技术突破性&…

作者头像 李华
网站建设 2026/9/18 7:04:10

GitHub Desktop 完整教程:从可视化操作到真正理解 Git 核心概念

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 7:03:20

FPGA采集卡为何不可替代:时序确定性、多通道同步与高速接口

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 7:00:49

开放代码评审:把Code Review从形式变为团队技术基础设施

1. 先想清楚&#xff1a;open-code-review 到底在解决什么问题代码评审这事&#xff0c;几乎所有技术团队都在做&#xff0c;但真正做得好的少。大部分团队所谓的 code review&#xff0c;要么是走个过场在 PR 底下回个 LGTM&#xff0c;要么变成两个人坐在一起口述上下文&…

作者头像 李华