Tooltip throws "Cannot read properties of null (reading 'getParsed')" after a responsive chart's canvas is detached
维护者通常 1 天内回复
还没有人认领这个 Issue。
评估
- 难度
- 2/5
- 预计耗时
- 1-3 小时
- 新手友好度
- 82/100
- Issue 类型
- 缺陷
- 描述清晰度
- 描述清楚
- 活跃度
- 活跃
- 技术栈
- javascript
调研方向
从 src/plugins/plugin.tooltip.js 开始,检查 Tooltip._getActiveElements;issue 指出它是失效的过滤器。将现有的 dataset 检查与针对已分离图表所描述的 controller 生命周期进行比较。使用提供的页面以及一个在分离期间排队的指针事件重现问题。分离后事件不再抛出错误,并且 tooltip 在已连接的图表上仍能正常工作时,即为完成。
由索引模型根据 Issue 内容生成。
描述
Expected behavior
A pointer event that reaches a chart after its canvas leaves the DOM does nothing.
Current behavior
The tooltip throws:
TypeError: Cannot read properties of null (reading 'getParsed')
at Tooltip._getActiveElements (plugin.tooltip.js)
at Tooltip.handleEvent
at Object.afterEvent
at Chart._eventHandler
at listener
at platform.dom.js (throttled proxy, requestAnimationFrame)
When a responsive chart's canvas is detached, the detached handler in bindResponsiveEvents calls this._stop(), which destroys every dataset meta, and then _resize(0, 0). The user event listeners stay bound and chart.ctx stays set, so the chart.ctx !== null check in createProxyAndListen lets a pointer event that was queued for the next frame through.
The tooltip still holds its last active elements. The chart is now 0×0, so the event is outside the chart area, and _getActiveElements filters lastActive:
return lastActive.filter(i =>
this.chart.data.datasets[i.datasetIndex] &&
this.chart.getDatasetMeta(i.datasetIndex).controller.getParsed(i.index) !== undefined
);
The dataset is still in chart.data.datasets, so getDatasetMeta() builds a new meta with controller: null, and .getParsed() throws.
#11596 added the datasets[i.datasetIndex] check for a dataset that was removed. This is a case where the dataset is still there but its controller is gone.
Reproducible sample
Self-contained page:
<div id="wrap" style="width:400px;height:300px"><canvas id="c"></canvas></div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js"></script>
<script>
const chart = new Chart(document.getElementById('c'), {
type: 'bar',
data: { labels: ['a', 'b', 'c'], datasets: [{ label: 'x', data: [3, 5, 2] }] },
options: { animation: false },
});
const pointAtBar = () => {
const rect = chart.canvas.getBoundingClientRect();
const bar = chart.getDatasetMeta(0).data[1];
chart.canvas.dispatchEvent(new MouseEvent('mousemove', {
clientX: rect.left + bar.x,
clientY: rect.top + bar.y + 5,
}));
};
const frame = () => new Promise((resolve) => requestAnimationFrame(resolve));
(async () => {
await frame();
await frame();
pointAtBar(); // the tooltip becomes active
await frame();
await frame();
pointAtBar(); // throttled: runs on the next frame
document.getElementById('wrap').remove(); // detach: chart._stop()
})();
</script>
The second event runs after the detach and throws.
Optional extra steps/info to reproduce
In an app, this happens when a framework removes a chart from the page while the pointer is over it and destroys the chart a little later. Examples are a route transition, or vue-chartjs's destroyDelay, which keeps the chart alive for a short time after its component unmounts. We see it in production from Chrome 156 on Windows.
Possible solution
Skip a meta that has no controller in the filter:
return lastActive.filter(i => {
const { controller } = this.chart.getDatasetMeta(i.datasetIndex);
return this.chart.data.datasets[i.datasetIndex]
&& controller
&& controller.getParsed(i.index) !== undefined;
});
Another option is to clear the active elements of the chart and the tooltip in _stop(), so nothing refers to a destroyed controller.
Context
No response
chart.js version
v4.6.1 (the same code is on master, src/plugins/plugin.tooltip.js)
Browser name and version
Chrome 156; also reproduced in headless Chromium
Link to your project
No response
- 主要语言
- JavaScript
- 星标
- 67.8k
- 派生
- 12k
- 平均合并
- 6 小时 15 分钟
- 30 天内合并 PR
- 3
环境准备
- 没有 Dockerfile 或 Docker Compose 文件
- 有 Pull Request 模板
- 没有贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
chartjs/Chart.js 的其他 Issue
-
Numeric tick formatter gives repeated labels in scientific notation and with bounds: 'data'可能已有人在做 @RClaudiuM 于 5 天前认领。 未关闭
难度 2/5 1-3 小时 新手友好度 76/100
维护者通常 1 天内回复
-
linear axis: "reverse" breaks tick align 'inner'可能已有人在做 @SrkianFardin25 于 6 天前认领。 未关闭type: bug
难度 2/5 1-3 小时 新手友好度 77/100
维护者通常 1 天内回复
-
fill: 'stack': a dataset's whole fill disappears when its x differs from the line below by ≤ 1e-6 px (happens in animations)可能已有人在做 @ump45nose 于 42 天前认领。 未关闭type: bug
难度 2/5 1-3 小时 新手友好度 88/100
维护者通常 1 天内回复
-
Legend pointStyle: 'line' does not apply dataset borderDash pattern可能已有人在做 @HuzaifaChaudary 于 75 天前认领。 未关闭type: bug
难度 2/5 1-3 小时 新手友好度 84/100
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 70/100
维护者通常 1 天内回复
相似的 Issue
-
难度 2/5 1-3 小时 新手友好度 78/100
-
good first issue help wanted javascript up-for-grabs
难度 2/5 1-3 小时 新手友好度 78/100
andrewsiah/agentswarm-data-tasks#307 ·
维护者通常 1 天内回复
-
needs-triage
难度 2/5 1-3 小时 新手友好度 78/100
coder/claudecode.nvim#588 ·
维护者通常 1 天内回复
-
area: web tech debt
难度 2/5 1-3 小时 新手友好度 82/100
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 82/100
digitalbazaar/http-client#60 ·