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

双Y轴散点图表tooltip异常:仅X轴前两点触发正确提示

解决Chart.js双Y轴散点图Tooltip触发异常问题

问题现象

配置双Y轴图表后,第二个Y轴(右侧)的散点位置显示正确,但仅当鼠标悬停在X轴前两个点位置时,才会触发对应数据的Tooltip。当前第二个数据集只传递了非零值的点:

[{x: 7, y: 4}, {x: 18, y: 1}]

且已设置showLine: false实现散点图效果。但如果保留所有包含y=0的点(如下),Tooltip就能正常触发对应数据点:

[{x: 1, y: 0}, {x: 2, y: 0}, {x: 3, y: 0}, {x: 4, y: 0}, {x: 5, y: 0}, {x: 6, y: 0}, {x: 7, y: 4}]

问题原因

Chart.js默认Tooltip的匹配模式为'index',该模式会尝试按数据索引匹配不同数据集的点。当第二个数据集过滤掉y=0的点后,数据量远少于第一个数据集(或labels数组为空),此时只有前N个索引(N为第二个数据集的点数)的位置能触发Tooltip,而实际散点的x值对应的索引并不在这个范围内,导致Tooltip无法正常触发。

解决方案

方案1:修改Tooltip匹配模式

将Tooltip的mode设置为'nearest'或'point',让Tooltip根据鼠标位置匹配最近的点,而非按索引匹配。在图表配置中添加以下Tooltip设置:

options: {
  tooltip: {
    mode: 'nearest',
    intersect: true // 可选,仅当鼠标精确悬停在点上时触发
  },
  // 其他配置(如y轴、数据集)...
}

方案2:保留全量数据,隐藏零值点

不通过filter()移除y=0的点,而是通过pointRadius和pointHoverRadius的回调函数隐藏这些点,同时保留数据索引的完整性,确保Tooltip正常触发。

修改第二个数据集的配置:

{
  label: 'Incorrect Characters',
  data: incorrectCharsHistory.map(item => ({x: item.idx +1, y: item.totalIncorrectCharacters})), // 不做filter
  fill: true,
  borderColor: function (context) {
    const dataPoint = context.dataset.data[context.dataIndex];
    return dataPoint?.y === 0 ? 'transparent' : errorColor;
  },
  backgroundColor: function (context) {
    const dataPoint = context.dataset.data[context.dataIndex];
    return dataPoint?.y === 0 ? 'transparent' : errorColor;
  },
  borderWidth: 3,
  // 新增:根据y值设置点半径,y=0时隐藏点
  pointRadius: function(context) {
    const dataPoint = context.dataset.data[context.dataIndex];
    return dataPoint?.y === 0 ? 0 : 4;
  },
  pointHoverRadius: function(context) {
    const dataPoint = context.dataset.data[context.dataIndex];
    return dataPoint?.y === 0 ? 0 : 8;
  },
  showLine: false,
  yAxisID: 'y1',
  pointStyle: 'cross'
}

同时,生成数据集的方法去掉filter():

const calculateIncorrectChars = (incorrectCharsHistory) => {
  return incorrectCharsHistory.map(item => ({
    x: item.idx + 1,
    y: item.totalIncorrectCharacters
  }));
};

完整配置示例

以下是整合方案2后的完整图表配置示例:

const data = {
  labels: [] as number[],
  datasets: [
    {
      label: 'Words Per Minute',
      data: [] as number[],
      fill: true,
      backgroundColor: mainColor,
      borderColor: mainColor,
      borderWidth: 3,
      lineTension: 0.4,
      yAxisID: 'y',
    },
    {
      label: 'Incorrect Characters',
      data: calculateIncorrectChars(incorrectCharsHistory),
      fill: true,
      borderColor: function (context: any) {
        const dataPoint = context.dataset.data[context.dataIndex];
        return dataPoint?.y === 0 ? 'transparent' : errorColor;
      },
      backgroundColor: function (context: any) {
        const dataPoint = context.dataset.data[context.dataIndex];
        return dataPoint?.y === 0 ? 'transparent' : errorColor;
      },
      borderWidth: 3,
      pointRadius: function(context: any) {
        const dataPoint = context.dataset.data[context.dataIndex];
        return dataPoint?.y === 0 ? 0 : 4;
      },
      pointHoverRadius: function(context: any) {
        const dataPoint = context.dataset.data[context.dataIndex];
        return dataPoint?.y === 0 ? 0 : 8;
      },
      showLine: false,
      yAxisID: 'y1',
      pointStyle: 'cross'
    },
  ],
};

const options = {
  scales: {
    y: { /* 左侧Y轴配置 */ },
    y1: {
      position: 'right',
      title: {
        display: true,
        text: 'Incorrect Characters',
        color: subColor,
        font: {
          size: 16,
          family: 'lexend, sans-serif',
        },
      },
      ticks: {
        autoSkip: true,
        maxTicksLimit: 5,
        precision: 0,
        font: {
          family: 'lexend, sans-serif',
        },
        color: subColor,
        callback: function (value: any, index: number, values: string | any[]) {
          if (incorrectChars.length !== 0) {
            return value;
          } else {
            if (index === values.length - 1) return 1;
            else if (index === 0) return 0;
            else return '';
          }
        }
      },
      grid: {
        color: "transparent"
      },
      border: {
        color: subColor
      }
    },
  },
  tooltip: {
    // 可选,若用方案1可添加此配置
    // mode: 'nearest',
    // intersect: true
  }
};

内容的提问来源于stack exchange,提问作者Rohith Nambiar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 11:07:12