Chart.js 极区图径向渐变实战:用 Scriptable 背景色与 Canvas 渐变打造立体 Polar Area 图表
【免费下载链接】Chart.jsSimple HTML5 Charts using the项目地址: https://gitcode.com/gh_mirrors/ch/Chart.js
本文基于仓库 docs/samples/advanced/radial-gradient.md 提供的官方示例,完整讲解如何在 Chart.js 极区图(Polar Area Chart)中通过elements.arc.backgroundColor的 Scriptable 配置,为每个扇区注入基于 Canvas APIcreateRadialGradient生成的三色径向渐变,并配合悬停(hover)状态与颜色缓存机制,让图表在渲染性能与视觉效果上同时达标。读完本文,你将掌握 Scriptable Options 的完整上下文结构、Canvas 渐变与 Chart.js 颜色工具链(helpers.color/getHoverColor)的组合用法,以及如何规避"初始渲染时 chartArea 尚不可用"这一经典坑点。
示例概览:一个带随机化的渐变极区图
该示例位于docs/samples/advanced/radial-gradient.md,其核心是type: 'polarArea'的图表:共 5 个数据点,标签取自月份(Utils.months({count: 5})),每个扇区根据dataIndex从一组 Chart.js 调色板颜色中取色,再通过createRadialGradient3函数为每个扇区生成"外圈亮色 → 中间过渡色 → 内圈深色"的径向渐变背景。
示例还注册了一个名为Randomize的 action,点击后重新生成数据并调用chart.update():
const actions = [ { name: 'Randomize', handler(chart) { chart.data.datasets.forEach(dataset => { dataset.data = generateData(); }); chart.update(); } }, ];为什么极区图能接受渐变:Arc 元素的 backgroundColor 解析链路
要理解本例,先要明白极区图与弧元素的关系。在 Chart.js 中,极区图由 controller.polarArea.js 驱动,其defaults中dataElementType: 'arc',即每个扇区是一个 Arc 元素,具体渲染逻辑在 element.arc.ts。
Arc 元素绘制扇区填充时直接使用options.backgroundColor:
ctx.fillStyle = options.backgroundColor; ctx.strokeStyle = options.borderColor;关键在于ArcElement的声明:
static defaultRoutes = { backgroundColor: 'backgroundColor' }; static descriptors = { _scriptable: true, _indexable: (name) => name !== 'borderDash' };defaultRoutes.backgroundColor表示当 dataset 上未显式给出backgroundColor时,会回退到元素级配置(这正是示例使用options.elements.arc.backgroundColor的原因);_scriptable: true表示backgroundColor是脚本化(Scriptable)选项,可以传入函数,函数在每次取值时为每个数据点执行一次并接收一个上下文对象context。
因此,示例中的写法:
elements: { arc: { backgroundColor: function(context) { // context.dataIndex 定位当前扇区 // 返回 string、CanvasGradient 或 CanvasPattern 均可 }, } }是完全合法的。Chart.js 的颜色解析工具 helpers.color.ts 中通过isPatternOrGradient检测值是否为CanvasGradient/CanvasPattern,若是则原样透传而不做字符串解析:
export function color(value) { return isPatternOrGradient(value) ? value : new Color(value); }也就是说,Canvas 渐变对象本身就是一个合法的颜色值,可以直接作为backgroundColor返回——这正是本例的技术基石。更完整的极区图 dataset 属性(borderAlign、borderDash、hoverBorderWidth、spacing、circular等)可参考 docs/charts/polar.md。
Scriptable 上下文:context.dataIndex 与 context.active
示例的渐变函数依赖 Scriptable 上下文中的两个关键字段:
backgroundColor: function(context) { let c = colors[context.dataIndex]; if (!c) { return; } if (context.active) { c = helpers.getHoverColor(c); } // ... }根据 docs/general/options.md 的 Option Context 定义,元素级 Scriptable 函数收到的context属于data层级,它继承dataset、chart两层信息,包含:
chart:关联的图表实例(用于访问chart.chartArea、chart.ctx);dataset/datasetIndex:当前数据集及其索引;dataIndex:当前数据点(扇区)的索引;active:该元素当前是否处于激活(悬停)状态;parsed/raw:解析后的数值与原始数据。
示例利用context.dataIndex从预设颜色数组中按索引取色(颜色数量少于数据时会得到undefined,此时直接return不返回颜色,让浏览器/画布保持默认),并利用context.active在悬停时切换为悬停色。
悬停色的底层实现
helpers.getHoverColor定义于 helpers.color.ts:
export function getHoverColor(value) { return isPatternOrGradient(value) ? value : new Color(value).saturate(0.5).darken(0.1).hexString(); }它把传入颜色提高饱和度0.5、加深0.1,再转回十六进制字符串。若传入的本来就是渐变/图案对象,则原样返回。注意示例中传入的是字符串颜色(chartColors.red等),所以悬停时会得到一个加深后的新色值,而createRadialGradient3的三个端点色都基于该悬停色重新计算,悬停时整个扇区的渐变会同步变化。
createRadialGradient3 逐行拆解:构建三色径向渐变
示例的核心函数createRadialGradient3(context, c1, c2, c3)接收 Scriptable 上下文与三个颜色值,返回一个以图表绘图区中心为圆心、覆盖整个绘图区的径向渐变:
function createRadialGradient3(context, c1, c2, c3) { const chartArea = context.chart.chartArea; if (!chartArea) { // This case happens on initial chart load return; } const chartWidth = chartArea.right - chartArea.left; const chartHeight = chartArea.bottom - chartArea.top; if (width !== chartWidth || height !== chartHeight) { cache.clear(); } let gradient = cache.get(c1 + c2 + c3); if (!gradient) { width = chartWidth; height = chartHeight; const centerX = (chartArea.left + chartArea.right) / 2; const centerY = (chartArea.top + chartArea.bottom) / 2; const r = Math.min( (chartArea.right - chartArea.left) / 2, (chartArea.bottom - chartArea.top) / 2 ); const ctx = context.chart.ctx; gradient = ctx.createRadialGradient(centerX, centerY, 0, centerX, centerY, r); gradient.addColorStop(0, c1); gradient.addColorStop(0.5, c2); gradient.addColorStop(1, c3); cache.set(c1 + c2 + c3, gradient); } return gradient; }1. 初始渲染的 chartArea 判空
const chartArea = context.chart.chartArea; if (!chartArea) { return; }chart.chartArea是图表绘图区(去除了标题、图例、坐标轴等外围布局后的区域),其left/right/top/bottom是布局完成后的像素坐标。在图表首次加载、布局尚未完成时chartArea可能为undefined,此时无法计算尺寸,函数直接返回undefined(即不设置背景色)。这是 Scriptable 函数必须做的防御性校验——docs/general/options.md 也明确提示:"context参数应在 Scriptable 函数中做校验,因为函数会在不同上下文中被调用"。
2. 尺寸变化检测与渐变缓存
if (width !== chartWidth || height !== chartHeight) { cache.clear(); }渐变依赖于绘图区尺寸(圆心与半径随尺寸变化)。模块级变量width/height记录上次生成渐变时的绘图区尺寸,一旦检测到尺寸变化(如窗口缩放、responsive触发重排),就清空整个缓存,强制下次重建所有渐变。
3. 计算圆心与半径
const centerX = (chartArea.left + chartArea.right) / 2; const centerY = (chartArea.top + chartArea.bottom) / 2; const r = Math.min( (chartArea.right - chartArea.left) / 2, (chartArea.bottom - chartArea.top) / 2 );圆心取绘图区的几何中心,半径取宽、高一半的较小值,保证渐变圆始终内切于绘图区。
4. 创建 Canvas 径向渐变
const ctx = context.chart.ctx; gradient = ctx.createRadialGradient(centerX, centerY, 0, centerX, centerY, r); gradient.addColorStop(0, c1); gradient.addColorStop(0.5, c2); gradient.addColorStop(1, c3);context.chart.ctx是图表使用的 2D 画布上下文。createRadialGradient(x0, y0, r0, x1, y1, r1)定义两个同心圆:起点圆半径为 0(圆心点),终点圆半径为r。随后通过三个addColorStop设置颜色断点:
0(圆心):c1—— 示例中为"提亮 0.2 并色相旋转 270°"的起始色;0.5(中点):c2—— 示例中为"去饱和 0.2 并加深 0.2"的过渡色;1(外缘):c3—— 示例中为"提亮 0.1"的结束色。
5. 缓存命中与键设计
cache.set(c1 + c2 + c3, gradient);渐变对象以c1 + c2 + c3拼接字符串为键存入Map。由于同一个扇区在重绘(update())时会反复调用 Scriptable 函数,而渐变的创建是相对昂贵的 Canvas 操作,缓存可避免重复创建;键中携带三个颜色值,保证任一颜色变化(例如悬停换色)时能生成新的渐变条目。而尺寸变化时通过第 2 步的cache.clear()兜底清理。
三色端点的颜色工程:helpers.color 链式运算
示例为每个基础色计算出三个渐变端点色:
const mid = helpers.color(c).desaturate(0.2).darken(0.2).rgbString(); const start = helpers.color(c).lighten(0.2).rotate(270).rgbString(); const end = helpers.color(c).lighten(0.1).rgbString(); return createRadialGradient3(context, start, mid, end);这里的helpers.color是 Chart.js 暴露的颜色解析工具(见 helpers.color.ts 的export function color(value)重载),它将任意合法颜色字符串解析为@kurkle/color的Color对象,并支持链式变换:
lighten(0.2)/darken(0.2):按比例提亮 / 加深;desaturate(0.2):按比例降低饱和度;rotate(270):对色相做 270° 旋转(即把基准色偏移到色环的另一侧,产生互补感);rgbString():输出rgb(r, g, b)字符串作为渐变端点色。
设计意图很清晰:start(圆心)用提亮 + 色相旋转的亮色,mid(中点)用去饱和 + 加深的中间调,end(外缘)用轻度提亮的基色。三者在同一个径向渐变中从圆心到外缘平滑过渡,让扇区呈现类似"高光 + 环境阴影"的立体观感,同时整套色系仍源自同一基准色,整体协调统一。
数据生成与工具函数
示例的数据部分依赖文档站示例脚本 docs/scripts/utils.js 提供的工具:
Utils.srand(110); // 固定随机种子,保证每次渲染数据一致 Utils.numbers({count: 5, min: 0, max: 100}); // 生成 5 个 0~100 的随机数 Utils.months({count: 5}); // 生成 5 个月份标签 Utils.CHART_COLORS // red/orange/yellow/green/blue 等预设色其中srand(seed)基于线性同余算法(_seed = (_seed * 9301 + 49297) % 233280)实现可复现的伪随机序列:固定种子110后,每次刷新页面生成的随机数据都相同,便于示例的视觉一致性。numbers支持min/max/count/decimals/continuity等参数,continuity小于 1 时还会在数据中插入null(极区图会将对应扇区跳过)。
极区图的数据结构本身要求:datasets[].data为数值数组,Chart.js 会自动求和并按比例分配角度(见 docs/charts/polar.md 的 Data Structure 一节);labels数组则用于图例与提示框。
完整配置与运行方式
把上述片段组合,即可得到完整的可运行配置:
const config = { type: 'polarArea', data: { labels: Utils.months({count: DATA_COUNT}), datasets: [{ data: generateData() }] }, options: { plugins: { legend: false, tooltip: false, }, elements: { arc: { backgroundColor: function(context) { let c = colors[context.dataIndex]; if (!c) { return; } if (context.active) { c = helpers.getHoverColor(c); } const mid = helpers.color(c).desaturate(0.2).darken(0.2).rgbString(); const start = helpers.color(c).lighten(0.2).rotate(270).rgbString(); const end = helpers.color(c).lighten(0.1).rgbString(); return createRadialGradient3(context, start, mid, end); }, } } } };示例关闭了图例与提示框(legend: false、tooltip: false),把视觉焦点完全放在渐变扇区上。在实际项目中,你可以:
- 保留图例,此时图例色块取自
meta.controller.getStyle(i).backgroundColor(见 controller.polarArea.js 的 legendgenerateLabels覆写),渐变颜色会被直接用作图例色块; - 将渐变半径从
Math.min(宽, 高) / 2调整为outerRadius,让渐变严格贴合扇区外缘而非整个绘图区; - 配合极区图的
animation.animateRotate/animation.animateScale(均默认true,定义于 controller.polarArea.js 的defaults)体验入场动画。
运行该示例:在仓库根目录安装依赖后启动文档站点(docs/package.json提供文档构建脚本),打开 "Advanced → Radial Gradient" 页面即可看到实时效果,点击Randomize按钮可观察数据随机化后渐变与扇区角度的同步更新。
关键要点回顾
- 渐变即合法颜色:
CanvasGradient可被isPatternOrGradient识别并直接作为backgroundColor返回(helpers.color.ts)。 - Scriptable 上下文:
backgroundColor函数会在每个数据点上被调用,context.dataIndex定位扇区、context.active反映悬停状态(docs/general/options.md)。 - 必须防御 chartArea 为空:初始渲染时
chart.chartArea可能为undefined,务必提前返回。 - 缓存渐变对象:以
c1 + c2 + c3为键缓存渐变,并在绘图区尺寸变化时cache.clear(),兼顾重绘性能与响应式正确性。 - 颜色工程链:
helpers.color(...).lighten()/darken()/desaturate()/rotate()/rgbString()可快速派生和谐的多级渐变端点色;悬停色由getHoverColor(饱和 + 加深)统一处理。
延伸阅读
- 极区图完整文档:数据集属性、样式、
borderAlign、动画与默认覆盖(Chart.overrides.polarArea) - Options 与 Scriptable Options:选项解析层级、Option Context 全字段说明
- Arc 元素配置:
elements.arc下的borderRadius、offset、circular等更多弧元素样式 - 示例工具脚本:
Utils.srand、Utils.numbers、Utils.months、CHART_COLORS等实现
【免费下载链接】Chart.jsSimple HTML5 Charts using the项目地址: https://gitcode.com/gh_mirrors/ch/Chart.js
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考