From 26e02b0cb48588dfa4b94189d2fe14300ad8caa4 Mon Sep 17 00:00:00 2001 From: "David S. Langlands" Date: Tue, 10 Mar 2026 18:10:35 -0400 Subject: [PATCH] fix(model): guard against null data in getDataParams and event handler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a series is disposed (component unmount, setOption with notMerge, toolbox restore), getData() can return null — as acknowledged by the comment in SeriesModel.getData(). However, getDataParams() and its overrides unconditionally access properties on the result, causing TypeErrors when mouse events fire on stale DOM elements after disposal. This fix adds null guards at three levels: 1. DataFormatMixin.getDataParams() base method — returns {} when getData() is null, protecting all callers (tooltips, labels, etc.) 2. Event handler in echarts.ts — wraps getDataParams() call in try/catch, protecting against crashes in any chart override 3. All 6 chart-specific getDataParams() overrides — each now checks getData() before accessing chart-specific properties (.tree, .mapDimension, .getGraph, etc.) Fixes #9402, #16998 --- src/chart/chord/ChordSeries.ts | 3 +++ src/chart/funnel/FunnelSeries.ts | 3 +++ src/chart/pie/PieSeries.ts | 3 +++ src/chart/sunburst/SunburstSeries.ts | 6 +++++- src/chart/tree/TreeSeries.ts | 6 +++++- src/chart/treemap/TreemapSeries.ts | 6 +++++- src/core/echarts.ts | 11 ++++++++--- src/model/mixin/dataFormat.ts | 3 +++ 8 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/chart/chord/ChordSeries.ts b/src/chart/chord/ChordSeries.ts index 39464d113a..88ebfd5a53 100644 --- a/src/chart/chord/ChordSeries.ts +++ b/src/chart/chord/ChordSeries.ts @@ -273,6 +273,9 @@ class ChordSeriesModel extends SeriesModel { getDataParams(dataIndex: number, dataType: 'node' | 'edge') { const params = super.getDataParams(dataIndex, dataType); + if (!this.getData()) { + return params; + } if (dataType === 'node') { const nodeData = this.getData(); const node = this.getGraph().getNodeByIndex(dataIndex); diff --git a/src/chart/funnel/FunnelSeries.ts b/src/chart/funnel/FunnelSeries.ts index c775b759a5..86fc1fcb4e 100644 --- a/src/chart/funnel/FunnelSeries.ts +++ b/src/chart/funnel/FunnelSeries.ts @@ -140,6 +140,9 @@ class FunnelSeriesModel extends SeriesModel { // Overwrite getDataParams(dataIndex: number): FunnelCallbackDataParams { const data = this.getData(); + if (!data) { + return {} as FunnelCallbackDataParams; + } const params = super.getDataParams(dataIndex) as FunnelCallbackDataParams; const valueDim = data.mapDimension('value'); const sum = data.getSum(valueDim); diff --git a/src/chart/pie/PieSeries.ts b/src/chart/pie/PieSeries.ts index 496d9a5e6a..45f6db40ed 100644 --- a/src/chart/pie/PieSeries.ts +++ b/src/chart/pie/PieSeries.ts @@ -186,6 +186,9 @@ class PieSeriesModel extends SeriesModel { */ getDataParams(dataIndex: number): PieCallbackDataParams { const data = this.getData(); + if (!data) { + return {} as PieCallbackDataParams; + } // update seats when data is changed const dataInner = innerData(data); let seats = dataInner.seats; diff --git a/src/chart/sunburst/SunburstSeries.ts b/src/chart/sunburst/SunburstSeries.ts index 795b9c5a53..01507aa12d 100644 --- a/src/chart/sunburst/SunburstSeries.ts +++ b/src/chart/sunburst/SunburstSeries.ts @@ -209,8 +209,12 @@ class SunburstSeriesModel extends SeriesModel { */ getDataParams(dataIndex: number) { const params = super.getDataParams.apply(this, arguments as any) as SunburstDataParams; + const data = this.getData(); + if (!data) { + return params; + } - const node = this.getData().tree.getNodeByDataIndex(dataIndex); + const node = data.tree.getNodeByDataIndex(dataIndex); params.treePathInfo = wrapTreePathInfo(node, this); return params; diff --git a/src/chart/tree/TreeSeries.ts b/src/chart/tree/TreeSeries.ts index 6c8826f74b..ff3be809d2 100644 --- a/src/chart/tree/TreeSeries.ts +++ b/src/chart/tree/TreeSeries.ts @@ -242,8 +242,12 @@ class TreeSeriesModel extends SeriesModel { // Add tree path to tooltip param getDataParams(dataIndex: number) { const params = super.getDataParams.apply(this, arguments as any) as TreeSeriesCallbackDataParams; + const data = this.getData(); + if (!data) { + return params; + } - const node = this.getData().tree.getNodeByDataIndex(dataIndex); + const node = data.tree.getNodeByDataIndex(dataIndex); params.treeAncestors = wrapTreePathInfo(node, this); params.collapsed = !node.isExpand; diff --git a/src/chart/treemap/TreemapSeries.ts b/src/chart/treemap/TreemapSeries.ts index 4618ec464a..bbe427a241 100644 --- a/src/chart/treemap/TreemapSeries.ts +++ b/src/chart/treemap/TreemapSeries.ts @@ -446,8 +446,12 @@ class TreemapSeriesModel extends SeriesModel { */ getDataParams(dataIndex: number) { const params = super.getDataParams.apply(this, arguments as any) as TreemapSeriesCallbackDataParams; + const data = this.getData(); + if (!data) { + return params; + } - const node = this.getData().tree.getNodeByDataIndex(dataIndex); + const node = data.tree.getNodeByDataIndex(dataIndex); params.treeAncestors = wrapTreePathInfo(node, this); // compatitable the previous code. params.treePathInfo = params.treeAncestors; diff --git a/src/core/echarts.ts b/src/core/echarts.ts index 89f810088b..21fab73b7f 100644 --- a/src/core/echarts.ts +++ b/src/core/echarts.ts @@ -1208,9 +1208,14 @@ class ECharts extends Eventful { const ecData = getECData(parent); if (ecData && ecData.dataIndex != null) { const dataModel = ecData.dataModel || ecModel.getSeriesByIndex(ecData.seriesIndex); - params = ( - dataModel && dataModel.getDataParams(ecData.dataIndex, ecData.dataType, el) || {} - ) as ECElementEvent; + try { + params = ( + dataModel && dataModel.getDataParams(ecData.dataIndex, ecData.dataType, el) || {} + ) as ECElementEvent; + } + catch (e) { + params = {} as ECElementEvent; + } return true; } // If element has custom eventData of components diff --git a/src/model/mixin/dataFormat.ts b/src/model/mixin/dataFormat.ts index 2c39816235..4635ce5f9f 100644 --- a/src/model/mixin/dataFormat.ts +++ b/src/model/mixin/dataFormat.ts @@ -61,6 +61,9 @@ export class DataFormatMixin { ): CallbackDataParams { const data = this.getData(dataType); + if (!data) { + return {} as CallbackDataParams; + } const rawValue = this.getRawValue(dataIndex, dataType); const rawDataIndex = data.getRawIndex(dataIndex); const name = data.getName(dataIndex);