diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformers.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformers.ts index 6d167787d3da..29f135f3de56 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformers.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformers.ts @@ -467,9 +467,13 @@ export function transformSeries( return formatter(numericValue); } if (!onlyTotal) { + // A zero value occupies no space in a stacked series, so its label + // would render on top of the adjacent segment's label. Skip it + // regardless of the threshold. if ( + numericValue !== 0 && numericValue >= - (thresholdValues[dataIndex] || Number.MIN_SAFE_INTEGER) + (thresholdValues[dataIndex] || Number.MIN_SAFE_INTEGER) ) { return formatter(numericValue); } diff --git a/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/transformers.test.ts b/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/transformers.test.ts index 84022759a503..6e043faa2bc4 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/transformers.test.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/transformers.test.ts @@ -26,7 +26,10 @@ import { supersetTheme } from '@apache-superset/core/theme'; import type { SeriesOption } from 'echarts'; import type { ScatterSeriesOption } from 'echarts/charts'; import { EchartsTimeseriesSeriesType } from '../../src'; -import { TIMESERIES_CONSTANTS } from '../../src/constants'; +import { + TIMESERIES_CONSTANTS, + StackControlsValue, +} from '../../src/constants'; import { LegendOrientation, EchartsTimeseriesChartProps, @@ -87,6 +90,70 @@ describe('transformSeries', () => { ); }); + describe('stacked value labels', () => { + // Two series stacked on one category: A is 32, B is 0. The zero-height + // B segment must not render a label over A's label. + const buildFormatter = (thresholdValues: number[]) => { + const result = transformSeries(series, mockColorScale, 'test-key', { + showValue: true, + stack: StackControlsValue.Stack, + onlyTotal: false, + formatter: (v: any) => String(v), + totalStackedValues: [32], + thresholdValues, + }); + return (result as any).label.formatter; + }; + + test('hides the label for a zero value when the threshold is 0', () => { + const formatter = buildFormatter([0]); + expect( + formatter({ + value: [0, 0], + dataIndex: 0, + seriesIndex: 1, + seriesName: 'B', + }), + ).toBe(''); + }); + + test('still shows the label for a non-zero value when the threshold is 0', () => { + const formatter = buildFormatter([0]); + expect( + formatter({ + value: [0, 32], + dataIndex: 0, + seriesIndex: 0, + seriesName: 'A', + }), + ).toBe('32'); + }); + + test('still shows the label for a negative value when the threshold is 0', () => { + const formatter = buildFormatter([0]); + expect( + formatter({ + value: [0, -5], + dataIndex: 0, + seriesIndex: 0, + seriesName: 'A', + }), + ).toBe('-5'); + }); + + test('keeps hiding values below a non-zero threshold', () => { + const formatter = buildFormatter([16]); + expect( + formatter({ + value: [0, 4], + dataIndex: 0, + seriesIndex: 0, + seriesName: 'A', + }), + ).toBe(''); + }); + }); + test('should not apply border styles for non-bar series', () => { const opts = { seriesType: EchartsTimeseriesSeriesType.Line,