Skip to content

fix(dashboard): ECharts tooltip crashes during auto-refresh — Cannot read properties of undefined (reading 'getItemModel'/'getRawIndex') #39247

@richardfogaca

Description

@richardfogaca

Bug description

Hovering over an ECharts-backed chart (heatmap, bar, pie, timeseries, …) on a dashboard with auto-refresh enabled intermittently throws one of:

TypeError: Cannot read properties of undefined (reading 'getItemModel')
TypeError: Cannot read properties of undefined (reading 'getRawIndex')

The error fires from inside ECharts' TooltipView, triggered by a zrender mousemove hit-test landing on a stale series element during an auto-refresh tick.

Stack trace (captured on a clean master reproduction):

TypeError: Cannot read properties of undefined (reading 'getItemModel')
  at TooltipView._showSeriesItemTooltip (echarts/lib/component/tooltip/TooltipView.js:477)
  at TooltipView._tryShow               (echarts/lib/component/tooltip/TooltipView.js:358)
  at TooltipView.eval                   (echarts/lib/component/tooltip/TooltipView.js:159)   ← _initGlobalListener closure
  at doEnter                            (echarts/lib/component/axisPointer/globalListener.js:117)
  at Handler.dispatchToElement          (zrender/lib/Handler.js:166)

The exact failing line inside ECharts TooltipView._showSeriesItemTooltip:

var seriesModel = ecModel.getSeriesByIndex(seriesIndex);
var dataModel   = ecData.dataModel || seriesModel;
var data        = dataModel.getData(dataType);                       // ← returns undefined
var tooltipModel = buildTooltipModel([data.getItemModel(dataIndex)]); // ← crash

data is undefined because dataModel.getData(dataType) is being called with a dataType that no longer exists on the freshly-merged series model, while the old rendered zrender shape (and its ecData.dataType) is still hit-testable.

Steps to reproduce

  1. Open any dashboard with ECharts-backed charts. The bundled FCC New Coder Survey 2018 dashboard reproduces reliably — tab 🚀 Aspiring Developers, the Preferred Employment Style vs Degree heatmap plus the other echarts viz on that tab.
  2. Dashboard "..." menu → Set auto-refresh10 secondsSave for this session.
  3. Hover the cursor continuously over chart data points while auto-refresh ticks fire. A single error surfaces within 30–90 seconds of continuous hover during normal use; under a programmatic hover stress test (~90k synthetic mousemoves over 90 s crossing ~9 refresh ticks) the error reliably reproduces at least once per run.
  4. Observe the JavaScript console / React error boundary / unhandled-error overlay.

The error is intermittent because it is a race: the hit-test has to land on a stale zrender element during the ~milliseconds between setOption({ replaceMerge: ['series'] }) swapping the series models and zrender re-rendering the shapes.

Root cause analysis

The auto-refresh setOption path in superset-frontend/plugins/plugin-chart-echarts/src/components/Echart.tsx uses:

const notMerge = !isDashboardRefreshing;  // false during refresh tick
if (!notMerge) {
  chartRef.current?.dispatchAction({ type: 'hideTip' });
}
chartRef.current?.setOption(themedEchartOptions, {
  notMerge,                                        // false
  replaceMerge: notMerge ? undefined : ['series'], // ['series']
  lazyUpdate: isDashboardRefreshing,               // true
});

This strategy was introduced in 8818fcf99e ("fix: charts flickering") and shipped with #37459 ("feat: auto refresh dashboard"). The goal was to preserve legend selection, zoom, and tooltip state across ticks on real-time dashboards and eliminate a flicker — so notMerge: false / replaceMerge: ['series'] / lazyUpdate: true are all deliberate and load-bearing.

The race is between two things ECharts does on different timelines when this combination is passed:

  1. replaceMerge: ['series'] synchronously swaps the series models on the ecModel.
  2. lazyUpdate: true defers the zrender render of the new shapes to the next requestAnimationFrame.

In the window between (1) and (2), the old rendered shapes are still mounted in the zrender scene and still hit-testable, but their backing SeriesModel in the new ecModel may have a different (or missing) List/dataType. A mousemove that lands inside one of those stale shapes walks the tooltip view's hit-test path (_tryShowfindEventDispatcher_showSeriesItemTooltip), then dereferences undefined.

The existing if (!notMerge) dispatchAction({ type: 'hideTip' }) guard attempts to handle a related concern (stale visible tooltip content across a merge) but does not protect against the stale hit-test path:

  • It runs inside the setOption React effect, i.e. after the React re-render has already been committed.
  • hideTip clears _lastX/_lastY on TooltipView but does not clear zrender's hit-test cache or prevent a fresh mousemove from immediately re-entering _tryShow during the lazyUpdate window.

Proposed fix (to be validated)

Not blocking this issue on a specific fix, but a few avenues that look promising:

  1. Refresh-time pre-teardown. Dispatch hideTip plus an axis-pointer reset on every echarts chart synchronously the moment isRefreshing flips to true in Redux — i.e. before the new data arrives and before the setOption effect fires. That eliminates the race window entirely since the zrender pointer state is cleared before the stale shapes can be hit-tested.
  2. Hit-test guard in Superset's tooltip formatter layer. Wrap getData/getItemModel calls in the plugin-chart-echarts formatter so a mid-refresh stale dispatch becomes a no-op instead of a crash. Defensive, doesn't fix the underlying race, but cheap.
  3. Drop lazyUpdate: true during replaceMerge ticks — would likely reintroduce the flicker that feat: auto refresh dashboard #37459 fixed, so probably off the table.
  4. Upstream fix in ECharts: guard TooltipView._showSeriesItemTooltip against data === undefined. Worth filing upstream regardless.

Option 1 looks like the cleanest fix and would live in the auto-refresh hook rather than Echart.tsx.

Screenshots/recordings

N/A — JavaScript console error only. Happy to attach a screen recording of the reproduction if helpful.

Environment

Superset version master / latest-dev
Python version 3.11
Node version 18 or greater
Browser Chrome

Additional context

  • No Python stacktrace — frontend-only crash.
  • Reproduces with the default sample dashboard (FCC New Coder Survey 2018) — no custom data or feature flags required.
  • Feature-flag dependency: none (auto-refresh is GA since feat: auto refresh dashboard #37459).

Checklist

  • I have searched Superset docs and Slack and didn't find a solution to my problem.
  • I have searched the GitHub issue tracker and didn't find a similar bug report.
  • I have checked Superset's logs for errors and if I found a relevant Python stacktrace, I included it here as text in the "additional context" section.

Metadata

Metadata

Assignees

Labels

#bugBug report#bug:regressionBugs that are identified as regessionsdashboard:refreshRelated to the refresh frequency of the Dashboardvalidation:requiredA committer should validate the issueviz:charts:echartsRelated to Echarts

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions