You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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}/>

问题根源

  1. onMouseMove中调用chart.update('none')会触发图表重绘,直接覆盖手动绘制的自定义图形;悬停扇区时,Chart.js自身会触发高亮状态重绘,进一步清除你的自定义绘制内容。
  2. 手动操作Canvas上下文的时机错误,Chart.js的渲染周期会自动清理画布,onMouseMove里的绘制会被后续重绘步骤清除。
  3. 代码中无对应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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 04:50:00