Chart.js 极区图径向渐变实战:用 Scriptable 背景色与 Canvas 渐变打造立体 Polar Area 图表
2026/9/18 19:30:58 网站建设 项目流程

Chart.js 极区图径向渐变实战:用 Scriptable 背景色与 Canvas 渐变打造立体 Polar Area 图表

【免费下载链接】Chart.jsSimple HTML5 Charts using thetag项目地址: 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 驱动,其defaultsdataElementType: '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 属性(borderAlignborderDashhoverBorderWidthspacingcircular等)可参考 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层级,它继承datasetchart两层信息,包含:

  • chart:关联的图表实例(用于访问chart.chartAreachart.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/colorColor对象,并支持链式变换:

  • 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: falsetooltip: 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按钮可观察数据随机化后渐变与扇区角度的同步更新。

关键要点回顾

  1. 渐变即合法颜色CanvasGradient可被isPatternOrGradient识别并直接作为backgroundColor返回(helpers.color.ts)。
  2. Scriptable 上下文backgroundColor函数会在每个数据点上被调用,context.dataIndex定位扇区、context.active反映悬停状态(docs/general/options.md)。
  3. 必须防御 chartArea 为空:初始渲染时chart.chartArea可能为undefined,务必提前返回。
  4. 缓存渐变对象:以c1 + c2 + c3为键缓存渐变,并在绘图区尺寸变化时cache.clear(),兼顾重绘性能与响应式正确性。
  5. 颜色工程链helpers.color(...).lighten()/darken()/desaturate()/rotate()/rgbString()可快速派生和谐的多级渐变端点色;悬停色由getHoverColor(饱和 + 加深)统一处理。

延伸阅读

  • 极区图完整文档:数据集属性、样式、borderAlign、动画与默认覆盖(Chart.overrides.polarArea
  • Options 与 Scriptable Options:选项解析层级、Option Context 全字段说明
  • Arc 元素配置:elements.arc下的borderRadiusoffsetcircular等更多弧元素样式
  • 示例工具脚本:Utils.srandUtils.numbersUtils.monthsCHART_COLORS等实现

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

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询