双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
相关产品推荐
相关产品推荐

