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); diff --git a/test/ut/spec/model/getDataParams.test.ts b/test/ut/spec/model/getDataParams.test.ts new file mode 100644 index 0000000000..280d0ec09b --- /dev/null +++ b/test/ut/spec/model/getDataParams.test.ts @@ -0,0 +1,179 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { EChartsType } from '@/src/echarts'; +import { createChart, getECModel } from '../../core/utHelper'; +import SeriesModel from '@/src/model/Series'; + + +describe('getDataParams_null_guard', function () { + + let chart: EChartsType; + beforeEach(function () { + chart = createChart(); + }); + + afterEach(function () { + chart.dispose(); + }); + + function getSeries(chart: EChartsType, seriesIndex: number): SeriesModel { + return getECModel(chart).getComponent('series', seriesIndex) as SeriesModel; + } + + it('should_not_throw_when_getData_returns_null_on_pie', function () { + chart.setOption({ + series: [{ + type: 'pie', + data: [ + { value: 100, name: 'A' }, + { value: 200, name: 'B' } + ] + }] + }); + + const seriesModel = getSeries(chart, 0); + expect(seriesModel).toBeTruthy(); + + // Simulate disposed state: replace getData to return null + const originalGetData = seriesModel.getData.bind(seriesModel); + seriesModel.getData = function () { + return null as any; + }; + + // Should not throw — returns empty params + expect(function () { + seriesModel.getDataParams(0); + }).not.toThrow(); + + const params = seriesModel.getDataParams(0); + expect(params).toBeDefined(); + + // Restore + seriesModel.getData = originalGetData; + }); + + it('should_not_throw_when_getData_returns_null_on_treemap', function () { + chart.setOption({ + series: [{ + type: 'treemap', + data: [ + { value: 100, name: 'A' }, + { value: 200, name: 'B' } + ] + }] + }); + + const seriesModel = getSeries(chart, 0); + expect(seriesModel).toBeTruthy(); + + const originalGetData = seriesModel.getData.bind(seriesModel); + seriesModel.getData = function () { + return null as any; + }; + + expect(function () { + seriesModel.getDataParams(0); + }).not.toThrow(); + + seriesModel.getData = originalGetData; + }); + + it('should_not_throw_when_getData_returns_null_on_funnel', function () { + chart.setOption({ + series: [{ + type: 'funnel', + data: [ + { value: 100, name: 'A' }, + { value: 200, name: 'B' } + ] + }] + }); + + const seriesModel = getSeries(chart, 0); + expect(seriesModel).toBeTruthy(); + + const originalGetData = seriesModel.getData.bind(seriesModel); + seriesModel.getData = function () { + return null as any; + }; + + expect(function () { + seriesModel.getDataParams(0); + }).not.toThrow(); + + seriesModel.getData = originalGetData; + }); + + it('should_not_throw_when_getData_returns_null_on_sunburst', function () { + chart.setOption({ + series: [{ + type: 'sunburst', + data: [ + { value: 100, name: 'A' }, + { value: 200, name: 'B' } + ] + }] + }); + + const seriesModel = getSeries(chart, 0); + expect(seriesModel).toBeTruthy(); + + const originalGetData = seriesModel.getData.bind(seriesModel); + seriesModel.getData = function () { + return null as any; + }; + + expect(function () { + seriesModel.getDataParams(0); + }).not.toThrow(); + + seriesModel.getData = originalGetData; + }); + + it('should_not_throw_when_getData_returns_null_on_tree', function () { + chart.setOption({ + series: [{ + type: 'tree', + data: [{ + name: 'root', + children: [ + { name: 'A', value: 100 }, + { name: 'B', value: 200 } + ] + }] + }] + }); + + const seriesModel = getSeries(chart, 0); + expect(seriesModel).toBeTruthy(); + + const originalGetData = seriesModel.getData.bind(seriesModel); + seriesModel.getData = function () { + return null as any; + }; + + expect(function () { + seriesModel.getDataParams(0); + }).not.toThrow(); + + seriesModel.getData = originalGetData; + }); + +});