Skip to content

Commit 343185a

Browse files
rusackasclaude
andcommitted
fix(chart): guard empty data array and fix X-axis cross-filter visual feedback
- Add null check on `props.data[0]` in click and context menu handlers to prevent undefined access when data array is empty (Copilot #2, #3) - Add `hasDimensions` option to `transformSeries` so series-level opacity dimming is skipped when cross-filtering by X-axis value, since `selectedValues` contains X-axis values rather than series names in that case (Copilot #4) - Add tests for both dimension-based and X-axis-based filtering opacity Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 09c44cf commit 343185a

File tree

4 files changed

+38
-5
lines changed

4 files changed

+38
-5
lines changed

superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/EchartsTimeseries.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -231,10 +231,9 @@ export default function EchartsTimeseries({
231231
// Cross-filter by dimension (original behavior)
232232
const { seriesName: name } = props;
233233
handleChange(name);
234-
} else if (canCrossFilterByXAxis && props.data) {
234+
} else if (canCrossFilterByXAxis && props.data?.[0] != null) {
235235
// Cross-filter by X-axis value when no dimensions (issue #25334)
236-
const xAxisValue = props.data[0];
237-
handleXAxisChange(xAxisValue);
236+
handleXAxisChange(props.data[0]);
238237
}
239238
}, TIMER_DURATION);
240239
},
@@ -316,7 +315,7 @@ export default function EchartsTimeseries({
316315
let crossFilter;
317316
if (hasDimensions) {
318317
crossFilter = getCrossFilterDataMask(seriesName);
319-
} else if (canCrossFilterByXAxis && data) {
318+
} else if (canCrossFilterByXAxis && data?.[0] != null) {
320319
crossFilter = getXAxisCrossFilterDataMask(data[0]);
321320
}
322321

superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,7 @@ export default function transformProps(
419419
timeCompare: array,
420420
timeShiftColor,
421421
theme,
422+
hasDimensions: groupBy.length > 0,
422423
},
423424
);
424425
if (transformedSeries) {

superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformers.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ export function transformSeries(
196196
timeCompare?: string[];
197197
timeShiftColor?: boolean;
198198
theme?: SupersetTheme;
199+
hasDimensions?: boolean;
199200
},
200201
): SeriesOption | undefined {
201202
const { name, data } = series;
@@ -237,8 +238,12 @@ export function transformSeries(
237238
const isConfidenceBand =
238239
forecastSeries.type === ForecastSeriesEnum.ForecastLower ||
239240
forecastSeries.type === ForecastSeriesEnum.ForecastUpper;
241+
// When cross-filtering by X-axis (no dimensions), selectedValues contains
242+
// X-axis values rather than series names, so skip series-level dimming.
240243
const isFiltered =
241-
filterState?.selectedValues && !filterState?.selectedValues.includes(name);
244+
opts.hasDimensions !== false &&
245+
filterState?.selectedValues &&
246+
!filterState?.selectedValues.includes(name);
242247
const opacity = isFiltered
243248
? OpacityEnum.SemiTransparent
244249
: opts.lineStyle?.opacity || OpacityEnum.NonTransparent;

superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/transformers.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,34 @@ describe('transformSeries', () => {
8989
expect((result as any).itemStyle.borderType).toBeUndefined();
9090
expect((result as any).itemStyle.borderColor).toBeUndefined();
9191
});
92+
93+
it('should dim series when selectedValues does not include series name (dimension-based filtering)', () => {
94+
const opts = {
95+
filterState: { selectedValues: ['other-series'] },
96+
hasDimensions: true,
97+
seriesType: EchartsTimeseriesSeriesType.Bar,
98+
timeShiftColor: false,
99+
};
100+
101+
const result = transformSeries(series, mockColorScale, 'test-key', opts);
102+
103+
// OpacityEnum.SemiTransparent = 0.3
104+
expect((result as any).itemStyle.opacity).toBe(0.3);
105+
});
106+
107+
it('should not dim series when hasDimensions is false (X-axis cross-filtering)', () => {
108+
const opts = {
109+
filterState: { selectedValues: ['Product A'] },
110+
hasDimensions: false,
111+
seriesType: EchartsTimeseriesSeriesType.Bar,
112+
timeShiftColor: false,
113+
};
114+
115+
const result = transformSeries(series, mockColorScale, 'test-key', opts);
116+
117+
// OpacityEnum.NonTransparent = 1 (not dimmed)
118+
expect((result as any).itemStyle.opacity).toBe(1);
119+
});
92120
});
93121

94122
describe('transformNegativeLabelsPosition', () => {

0 commit comments

Comments
 (0)