Chart.js环形图如何实现分组内展示子分组及图例联动
Chart.js 分组联动环形图实现方案
核心实现逻辑是绕开默认的单数据项图例生成规则,自定义图例仅保留3个顶层分组,同时改写图例点击事件,实现点击一个分组批量控制组内12个子项的显隐;同组子项用同色系做视觉区分,保证用户能直观识别子项归属。
实现步骤
1. 准备容器与依赖
页面中添加canvas容器作为图表挂载点:
<canvas id="groupDoughnut" width="600" height="500"></canvas>
引入Chart.js依赖,本地安装或者CDN引入都可以:
# 本地npm安装 npm install chart.js
2. 定义分层数据结构
把3个顶层分组和36个子项做关联映射,每个子项绑定所属分组ID,同组子项配置同色系不同透明度/深浅的填充色,视觉上自动归组:
// 顶层分组配置,仅这3项会显示在图例区 const topGroups = [ { id: 'g1', name: '分类1', baseColor: 'rgb(59, 130, 246)' }, { id: 'g2', name: '分类2', baseColor: 'rgb(239, 68, 68)' }, { id: 'g3', name: '分类3', baseColor: 'rgb(16, 185, 129)' } ] // 组装36个子项数据,每个分组12条,可直接替换为实际业务数据 const chartItems = [] topGroups.forEach(group => { for (let i = 0; i < 12; i++) { // 同组子项透明度从0.25到1渐变,既区分不同子项,又能识别同组归属 const alpha = 0.25 + (i / 12) * 0.75 chartItems.push({ groupId: group.id, label: `${group.name}-子项${i+1}`, value: Math.round(Math.random() * 80 + 20), // 模拟数值,替换为实际业务值即可 bgColor: group.baseColor.replace('rgb', 'rgba').replace(')', `, ${alpha})`) }) } })
3. 初始化图表,自定义图例交互
核心是重写legend插件的generateLabels方法控制图例仅显示顶层分组,重写onClick方法实现分组批量显隐:
const ctx = document.getElementById('groupDoughnut').getContext('2d') new Chart(ctx, { type: 'doughnut', data: { labels: chartItems.map(i => i.label), datasets: [{ data: chartItems.map(i => i.value), backgroundColor: chartItems.map(i => i.bgColor), borderWidth: 1, borderColor: '#ffffff', }] }, options: { responsive: true, // 可调整环形厚度,数值越大环越细 cutout: '60%', plugins: { legend: { position: 'top', // 自定义图例项,仅生成3个顶层分组 generateLabels: (chart) => { return topGroups.map(group => { // 找出当前分组下所有子项的索引 const itemIndexes = chartItems .map((item, idx) => item.groupId === group.id ? idx : -1) .filter(idx => idx !== -1) // 判断当前分组是否全部隐藏,控制图例选中态 const isHidden = itemIndexes.every(idx => !chart.getDataVisibility(idx)) return { text: group.name, fillStyle: group.baseColor, strokeStyle: group.baseColor, hidden: isHidden, itemIndexes // 存下子项索引供点击事件调用 } }) }, // 重写点击逻辑,批量切换组内子项显隐 onClick: (e, legendItem, legend) => { const chart = legend.chart const toggleToShow = legendItem.hidden legendItem.itemIndexes.forEach(idx => { toggleToShow ? chart.show(idx) : chart.hide(idx) }) chart.update() } }, tooltip: { // 提示框正常展示子项名称、数值、占比 callbacks: { label: (ctx) => { const total = ctx.dataset.data.reduce((sum, v) => sum + v, 0) const percent = ((ctx.parsed / total) * 100).toFixed(1) return `${ctx.label}: ${ctx.parsed}(占比${percent}%)` } } } } } })
自定义调整说明
- 同组子项配色如果不想用透明度区分,可替换为同色系不同明度的纯色值,视觉归类效果一致
- 如果需要默认隐藏某个分组,初始化完成后遍历对应分组的子项索引调用
chart.hide(idx)后执行chart.update()即可 - 图例位置、环形厚度、边框样式都可以通过Chart.js原生配置项修改,不会影响分组联动逻辑
内容的提问来源于stack exchange,提问作者Pernic Ryuusei Huy
相关产品推荐
相关产品推荐

