fix(echarts): fix stacked horizontal bar chart clipping and duplicate x-axis labels (#39012)

(cherry picked from commit 022342839a)
This commit is contained in:
Michael S. Molina
2026-04-01 15:50:08 -03:00
committed by Michael S. Molina
parent b1eb6ac7c9
commit bf399b9f97
3 changed files with 117 additions and 5 deletions

View File

@@ -24,6 +24,7 @@ import {
} from '@superset-ui/core';
import { GenericDataType } from '@apache-superset/core/common';
import { supersetTheme } from '@apache-superset/core/theme';
import { StackControlsValue } from '../../../src/constants';
import type {
GridComponentOption,
LegendComponentOption,
@@ -727,6 +728,97 @@ describe('Bar Chart X-axis Time Formatting', () => {
});
});
describe('Horizontal stacked bar chart axis bounds', () => {
// Dataset where each series max = 4 but stacked total max = 8
const stackedData: ChartDataResponseResult[] = [
createTestQueryData(
[
{ team: 'Team A', High: 2, Low: 2, Medium: 4 },
{ team: 'Team B', High: null, Low: null, Medium: 3 },
{ team: 'Team C', High: null, Low: null, Medium: 1 },
],
{
colnames: ['team', 'High', 'Low', 'Medium'],
coltypes: [
GenericDataType.String,
GenericDataType.Numeric,
GenericDataType.Numeric,
GenericDataType.Numeric,
],
},
),
];
const horizontalStackedFormData: EchartsTimeseriesFormData = {
...(baseFormData as EchartsTimeseriesFormData),
x_axis: 'team',
metric: ['High', 'Low', 'Medium'],
groupby: [],
orientation: OrientationType.Horizontal,
seriesType: EchartsTimeseriesSeriesType.Bar,
stack: StackControlsValue.Stack,
truncateYAxis: true,
};
test('xAxis.max uses stacked total, not individual series max', () => {
// Individual series max = 4 (Medium), stacked total for Team A = 8
// Without the fix, xAxis.max would be 4, clipping bars and duplicating labels
const chartProps = createEchartsTimeseriesTestChartProps<
EchartsTimeseriesFormData,
EchartsTimeseriesChartProps
>({
defaultFormData: horizontalStackedFormData,
defaultVizType: 'echarts_timeseries_bar',
defaultQueriesData: stackedData,
});
const { echartOptions } = transformProps(chartProps);
const xAxis = echartOptions.xAxis as any;
// xAxis.max must be >= stacked total (8), not capped at individual series max (4)
expect(xAxis.max).toBeGreaterThanOrEqual(8);
});
test('xAxis.max is not set to individual series max when stacking', () => {
const chartProps = createEchartsTimeseriesTestChartProps<
EchartsTimeseriesFormData,
EchartsTimeseriesChartProps
>({
defaultFormData: horizontalStackedFormData,
defaultVizType: 'echarts_timeseries_bar',
defaultQueriesData: stackedData,
});
const { echartOptions } = transformProps(chartProps);
const xAxis = echartOptions.xAxis as any;
// 4 is the individual series max — the axis should not be clipped there
expect(xAxis.max).not.toBe(4);
});
test('non-stacked horizontal bar chart still uses individual series max', () => {
const nonStackedFormData: EchartsTimeseriesFormData = {
...horizontalStackedFormData,
stack: null,
};
const chartProps = createEchartsTimeseriesTestChartProps<
EchartsTimeseriesFormData,
EchartsTimeseriesChartProps
>({
defaultFormData: nonStackedFormData,
defaultVizType: 'echarts_timeseries_bar',
defaultQueriesData: stackedData,
});
const { echartOptions } = transformProps(chartProps);
const xAxis = echartOptions.xAxis as any;
// Without stacking, xAxis.max should be based on individual series values
expect(xAxis.max).toBe(4);
});
});
describe('Legend layout regressions', () => {
const getBottomLegendLayout = (
chartWidth: number,