ChartJS堆叠柱状图如何在图例中按堆叠名称分组展示数据集标签
ChartJS 堆叠柱状图按堆叠分组图例实现方案
方案1:使用原生图例配置自定义标签(无需额外DOM)
这种方法利用ChartJS自带的图例生成钩子,直接在图例项中插入分组表头,无需额外编写HTML结构。
实现步骤:
- 先对所有数据集按
stack字段做分组,记录每个堆叠名称对应的数据集列表 - 配置
options.plugins.legend.labels.generateLabels自定义生成图例项:
首先遍历所有堆叠分组,每个分组先插入一个不可点击的表头项,样式可自定义区分;再遍历分组下的每个数据集,生成对应的数据标签项,前面可加缩进效果 - 同时配置
options.plugins.legend.onClick,点击时判断如果是分组表头就不触发隐藏/显示逻辑,普通标签正常切换对应数据集显隐
核心代码示例:
const options = { plugins: { legend: { labels: { generateLabels: (chart) => { const datasets = chart.data.datasets; // 按stack字段分组 const groups = datasets.reduce((res, ds) => { if (!res[ds.stack]) res[ds.stack] = []; res[ds.stack].push(ds); return res; }, {}); const labels = []; Object.keys(groups).forEach(stackName => { // 插入分组表头,设置不可点击、样式区分 labels.push({ text: stackName, font: { weight: 'bold' }, fillStyle: 'transparent', // 不显示左侧色块 strokeStyle: 'transparent', hidden: false, isGroupHeader: true // 自定义标记区分表头 }); // 插入分组下的数据集标签 groups[stackName].forEach(ds => { labels.push({ text: ` ${ds.label}`, // 加空格做缩进 fillStyle: ds.backgroundColor, strokeStyle: ds.borderColor, hidden: !chart.getDataVisibility(datasets.indexOf(ds)), datasetIndex: datasets.indexOf(ds), isGroupHeader: false }); }); }); return labels; } }, onClick: (e, legendItem, legend) => { // 点击表头不触发操作 if (legendItem.isGroupHeader) return; // 普通标签正常切换显隐 const chart = legend.chart; const index = legendItem.datasetIndex; chart.toggleDataVisibility(index); chart.update(); } } }, scales: { x: { stacked: true }, y: { stacked: true } } }
方案2:完全自定义HTML图例(自由度最高)
如果需要更复杂的分组样式(比如折叠展开分组、自定义间距等),可以关闭原生图例,自己渲染HTML结构:
实现步骤:
- 配置
options.plugins.legend.display = false关闭原生图例 - 在图表容器外准备一个自定义图例的DOM容器
- 监听图表的
render事件,每次图表渲染时按stack分组生成对应的HTML结构插入到容器中 - 给每个数据标签绑定点击事件,调用
chart.toggleDataVisibility切换对应数据集显隐,之后调用chart.update()更新图表
核心代码示例:
<div> <canvas id="myChart"></canvas> <div id="custom-legend"></div> </div>
const chart = new Chart(ctx, { type: 'bar', data: data, options: { plugins: { legend: { display: false }, }, scales: { x: { stacked: true }, y: { stacked: true } } }, plugins: [{ id: 'customLegend', afterRender: (chart) => { const legendContainer = document.getElementById('custom-legend'); // 按stack字段分组 const groups = chart.data.datasets.reduce((res, ds) => { if (!res[ds.stack]) res[ds.stack] = []; res[ds.stack].push({...ds, index: chart.data.datasets.indexOf(ds)}); return res; }, {}); // 生成图例HTML let html = ''; Object.keys(groups).forEach(stackName => { html += `<div style="font-weight: bold; margin: 8px 0 4px 0;">${stackName}</div>`; groups[stackName].forEach(ds => { const isHidden = !chart.getDataVisibility(ds.index); html += ` <div style="margin-left: 16px; cursor: pointer; ${isHidden ? 'opacity: 0.5;' : ''}" data-index="${ds.index}"> <span style="display: inline-block; width: 12px; height: 12px; background: ${ds.backgroundColor}; margin-right: 8px;"></span> ${ds.label} </div> `; }); }); legendContainer.innerHTML = html; // 绑定点击切换事件 legendContainer.querySelectorAll('[data-index]').forEach(el => { el.addEventListener('click', () => { const index = parseInt(el.getAttribute('data-index')); chart.toggleDataVisibility(index); chart.update(); }); }); } }] });
内容的提问来源于stack exchange,提问作者dickey
相关产品推荐
相关产品推荐

