ChartJS饼图悬停彩色扇区时自定义图形绘制失效问题求助
Chart.js 饼图自定义Tooltip 悬停扇区失效问题解决思路
问题描述
为Chart.js饼图制作自定义Tooltip,在鼠标悬停图表空白区域时,能在光标位置绘制自定义图形,但悬停饼图彩色扇区时功能失效,怀疑与动画冲突有关,代码如下:
function tooltipPosition(chart, e) { const { ctx, chartArea: {top, bottom, left, right, width, height} } = chart; const coorX = e.nativeEvent.offsetX; const coorY = e.nativeEvent.offsetY; chart.update('none'); if (coorX >= left && coorX <= right && coorY >= top && coorY <= bottom) { ctx.beginPath(); ctx.fillStyle = 'white'; ctx.strokeStyle = 'white'; ctx.lineJoin = 'round'; ctx.shadowColor = 'rgba(0,0,0,0.1)'; ctx.shadowBlur = 30; ctx.shadowOffsetX = 0; ctx.shadowOffsetY = 2; ctx.fillRect(coorX, coorY, 150, 50); ctx.shadowColor = 'rgba(0,0,0,0)'; ctx.lineWidth = 5; ctx.strokeRect(coorX, coorY, 150, 50); ctx.closePath(); ctx.restore(); } } const doughnutChartRef = useRef(); return <Doughnut ref={doughnutChartRef} onMouseMove={(e) => {tooltipPosition(doughnutChartRef.current, e)}} data={data} options={options}/>
问题根源
onMouseMove中调用chart.update('none')会触发图表重绘,直接覆盖手动绘制的自定义图形;悬停扇区时,Chart.js自身会触发高亮状态重绘,进一步清除你的自定义绘制内容。- 手动操作Canvas上下文的时机错误,Chart.js的渲染周期会自动清理画布,
onMouseMove里的绘制会被后续重绘步骤清除。 - 代码中无对应
ctx.save()的ctx.restore()会破坏Canvas状态栈,导致绘制异常。
解决思路
1. 用Chart.js官方Tooltip自定义钩子(推荐)
利用tooltip.custom配置项,这是官方专门为自定义Tooltip提供的入口,能和图表交互状态同步:
const options = { tooltip: { enabled: false, // 关闭默认Tooltip custom: function(context) { const ctx = context.chart.ctx; const tooltipModel = context.tooltip; // 无Tooltip内容时直接返回 if (!tooltipModel.opacity) return; // 获取基于扇区的Tooltip位置(或改用鼠标坐标) const x = tooltipModel.caretX; const y = tooltipModel.caretY; // 绘制自定义Tooltip ctx.beginPath(); ctx.fillStyle = 'white'; ctx.strokeStyle = 'white'; ctx.lineJoin = 'round'; ctx.shadowColor = 'rgba(0,0,0,0.1)'; ctx.shadowBlur = 30; ctx.shadowOffsetX = 0; ctx.shadowOffsetY = 2; ctx.fillRect(x, y, 150, 50); ctx.shadowColor = 'rgba(0,0,0,0)'; ctx.lineWidth = 5; ctx.strokeRect(x, y, 150, 50); ctx.closePath(); } } };
配置后,悬停扇区时Chart.js会自动调用该函数渲染自定义Tooltip,不会被重绘覆盖。
2. 手动绘制需绑定afterDraw钩子
若坚持手动绘制逻辑,需将绘制代码放到Chart.js的afterDraw回调中,确保每次重绘后重新渲染自定义图形:
const options = { animation: { duration: 0 // 可选:关闭动画减少重绘冲突 }, plugins: { afterDraw: function(chart) { // 读取提前存储的鼠标位置 if (!chart._mousePos) return; const { ctx, chartArea } = chart; const { x, y } = chart._mousePos; if (x >= chartArea.left && x <= chartArea.right && y >= chartArea.top && y <= chartArea.bottom) { // 自定义绘制逻辑 ctx.beginPath(); ctx.fillStyle = 'white'; ctx.strokeStyle = 'white'; ctx.lineJoin = 'round'; ctx.shadowColor = 'rgba(0,0,0,0.1)'; ctx.shadowBlur = 30; ctx.shadowOffsetX = 0; ctx.shadowOffsetY = 2; ctx.fillRect(x, y, 150, 50); ctx.shadowColor = 'rgba(0,0,0,0)'; ctx.lineWidth = 5; ctx.strokeRect(x, y, 150, 50); ctx.closePath(); } } } }; // 鼠标移动时记录坐标,触发重绘 const handleMouseMove = (e) => { const chart = doughnutChartRef.current; const rect = chart.canvas.getBoundingClientRect(); // 计算画布相对坐标,避免offsetX兼容性问题 chart._mousePos = { x: e.clientX - rect.left, y: e.clientY - rect.top }; chart.update('none'); };
通过自定义属性chart._mousePos传递鼠标位置给afterDraw钩子,确保每次重绘都能同步绘制自定义图形。
3. 修复Canvas状态错误
移除代码中无对应ctx.save()的ctx.restore(),避免破坏Canvas状态栈导致绘制异常。
内容的提问来源于stack exchange,提问作者Théodore Conrad
相关产品推荐
相关产品推荐

