如何在SPFx React的Chart.js饼图中给数据标签、图例加图片与边框
在SPFx React项目的Chart.js饼图中添加图标到数据标签和图例(含图例图标边框)
我已创建SPFx React项目并集成了chart.js@3.3.2,现需实现以下需求:
- 在饼图的数据标签和图例中添加图片/图标
- 给图例中的图片添加外边框
以下是当前代码片段:
<Chart type="pie" data={chartData} options={chartOptions} plugins={pluginsForShowDataOnPieChart} /> const iconImageArray = [ 'https://primefaces.org/cdn/primereact/images/galleria/galleria1.jpg', 'https://primefaces.org/cdn/primereact/images/galleria/galleria2.jpg', 'https://primefaces.org/cdn/primereact/images/galleria/galleria3.jpg', ]; const labelsNameArray = ["Label 1","Label 2","Label 3"] const pieChartData = () => { const documentStyle = getComputedStyle(document.documentElement); const data = { labels: labelsNameArray, datasets: [ { data: percentageDataArray, backgroundColor: [ '#5066BB', '#6FC66E', '#ECAD68', '#EF876C', documentStyle.getPropertyValue('--cyan-500'), documentStyle.getPropertyValue('--teal-500'), documentStyle.getPropertyValue('--pink-500'), ], hoverBackgroundColor: [ '#5066BB', '#6FC66E', '#ECAD68', '#EF876C', documentStyle.getPropertyValue('--cyan-500'), documentStyle.getPropertyValue('--teal-500'), documentStyle.getPropertyValue('--pink-500'), ], toggledVisibility: Array(productArray.length).fill(true) } ] }; const options = { aspectRatio: 2, plugins: { legend: { position: 'right', labels: { usePointStyle: true, padding: 20, generateLabels: (chart) => { const dataset = chart.data.datasets[0]; return dataset.data.map((data, i) => { const img = new Image(); img.src = iconImageArray[i]; img.height = 10; img.width = 10; img.style.border = '1px solid'; img.style.borderColor = legendColor[i]; return { text: chart.data.labels[i], fillStyle: dataset.backgroundColor[i], hidden: chart.getDatasetMeta(0).data[i].hidden, index: i, fontColor: dataset.toggledVisibility[i] ? '#495057' : '#888888', textDecoration: dataset.toggledVisibility[i] ? 'none' : 'line-through', pointStyle: img }; }); } }, onClick: function (event, legendItem, legend) { const ci = legend.chart; const index = legendItem.index; const meta = ci.getDatasetMeta(0); meta.data[index].hidden = !meta.data[index].hidden; const dataset = ci.data.datasets[0]; dataset.toggledVisibility[index] = !dataset.toggledVisibility[index]; ci.update(); } }, tooltip: { callbacks: { label: (tooltipItem) => { const countValue = productArray[tooltipItem.dataIndex]?.count || 0; const renewCountValue = productArray[tooltipItem.dataIndex]?.renewCount || 0; return `${tooltipItem.label}: ${countValue} | ${renewCountValue}`; }, }, }, datalabels: { formatter: (value) => `${value}%`, color: '#fff', anchor: 'end', align: 'start', offset: -10, font: { weight: 'bold', }, }, }, }; setChartData(data); setChartOptions(options); }; // Plugin to display percentage inside the Pie Chart const pluginsForShowDataOnPieChart = [ { afterDraw: (chart) => { const { ctx } = chart; chart.data.datasets.forEach((dataset, i) => { const numericData = dataset.data.map(value => parseFloat(value) || 0); const total = numericData.reduce((sum, value) => sum + value, 0); chart.getDatasetMeta(i).data.forEach((datapoint, index) => { if (dataset.toggledVisibility[index] && dataset.data[index] != 0) { if (numericData[index] !== 0) { const { x, y } = datapoint.tooltipPosition(); const percentage = ((numericData[index] / total) * 100).toFixed(1); ctx.fillStyle = '#495057'; ctx.font = 'bold 12px Open Sans'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(`${percentage}%`, x, y + 7); } } }); }); }, beforeDraw: (chart) => { const { ctx } = chart; ctx.restore(); ctx.fillStyle = '#495057'; ctx.font = 'bold 12px Open Sans'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; let total = 0; let isTotalVisible = false; if (chart.data.datasets[0] && chart.data.datasets[0].data) { total = chart.data.datasets[0].data.reduce((sum, value) => sum + value, 0); chart.data.datasets.forEach((dataset, i) => { chart.getDatasetMeta(i).data.forEach((datapoint, index) => { if (dataset.data[index] !== 0) { isTotalVisible = true; return; } }); }); } ctx.save(); } } ];
解决方案
1. 修复图例图标边框(Canvas绘制适配)
原代码中直接给Image元素设置style.border不会生效,因为Chart.js通过Canvas绘制内容而非DOM元素。需通过自定义pointStyle函数手动绘制带边框的图标:
修改options.plugins.legend.labels.generateLabels部分:
generateLabels: (chart) => { const dataset = chart.data.datasets[0]; return dataset.data.map((data, i) => { const img = new Image(); img.src = iconImageArray[i]; img.width = 16; img.height = 16; return { text: chart.data.labels[i], fillStyle: dataset.backgroundColor[i], hidden: chart.getDatasetMeta(0).data[i].hidden, index: i, fontColor: dataset.toggledVisibility[i] ? '#495057' : '#888888', textDecoration: dataset.toggledVisibility[i] ? 'none' : 'line-through', // 自定义绘制带边框的图标 pointStyle: (context) => { const { ctx, x, y } = context; // 绘制边框 ctx.strokeStyle = legendColor[i] || '#000'; ctx.lineWidth = 1; ctx.strokeRect(x - 8, y - 8, 16, 16); // 绘制图标 if (img.complete) { ctx.drawImage(img, x - 8, y - 8, 16, 16); } else { img.onload = () => { ctx.drawImage(img, x - 8, y - 8, 16, 16); chart.draw(); }; } } }; }); }
2. 数据标签添加图标
在饼图内部的百分比标签旁添加图标,需修改pluginsForShowDataOnPieChart的afterDraw方法:
afterDraw: (chart) => { const { ctx } = chart; chart.data.datasets.forEach((dataset, i) => { const numericData = dataset.data.map(value => parseFloat(value) || 0); const total = numericData.reduce((sum, value) => sum + value, 0); chart.getDatasetMeta(i).data.forEach((datapoint, index) => { if (dataset.toggledVisibility[index] && dataset.data[index] != 0 && numericData[index] !== 0) { const { x, y } = datapoint.tooltipPosition(); const percentage = ((numericData[index] / total) * 100).toFixed(1); const img = new Image(); img.src = iconImageArray[index]; img.width = 12; img.height = 12; // 绘制图标(放在百分比左侧) if (img.complete) { ctx.drawImage(img, x - 20, y + 7, 12, 12); } else { img.onload = () => { ctx.drawImage(img, x - 20, y + 7, 12, 12); chart.draw(); }; } // 绘制百分比文本 ctx.fillStyle = '#495057'; ctx.font = 'bold 12px Open Sans'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(`${percentage}%`, x, y + 7); } }); }); }
额外优化建议
- 预加载图标:避免图片加载延迟导致绘制异常,在组件挂载时提前加载所有图标:
useEffect(() => { iconImageArray.forEach(src => { const img = new Image(); img.src = src; }); }, []);
- 使用本地图标:SPFx项目建议将图标放在
src/assets目录下,使用相对路径避免跨域问题。
内容的提问来源于stack exchange,提问作者Bhoomesh Joshi
相关产品推荐
相关产品推荐

