Skip to content

Commit 7be1f63

Browse files
authored
fix: stack as percentage with 0 or null values (#618)
This commit fix a regression related to the rendering of stacked area/bar charts where Y values are 0 or null and the stack is configured as percentage fix #617
1 parent d3a691b commit 7be1f63

File tree

6 files changed

+405
-12
lines changed

6 files changed

+405
-12
lines changed

src/chart_types/xy_chart/rendering/rendering.areas.test.ts

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { computeSeriesDomains } from '../state/utils';
2626
import { renderArea } from './rendering';
2727
import { ChartTypes } from '../..';
2828
import { SpecTypes } from '../../../specs/settings';
29+
import { MockSeriesSpec } from '../../../mocks/specs';
2930

3031
const SPEC_ID = 'spec_1';
3132
const GROUP_ID = 'group_1';
@@ -1081,4 +1082,115 @@ describe('Rendering points - areas', () => {
10811082
expect((zeroValueIndexdGeometry[0] as PointGeometry).radius).toBe(0);
10821083
});
10831084
});
1085+
it('Stacked areas with 0 values', () => {
1086+
const pointSeriesSpec1: AreaSeriesSpec = MockSeriesSpec.area({
1087+
id: 'spec_1',
1088+
data: [
1089+
[1546300800000, 0],
1090+
[1546387200000, 5],
1091+
],
1092+
xAccessor: 0,
1093+
yAccessors: [1],
1094+
xScaleType: ScaleType.Time,
1095+
yScaleType: ScaleType.Linear,
1096+
stackAccessors: [0],
1097+
stackAsPercentage: true,
1098+
});
1099+
const pointSeriesSpec2: AreaSeriesSpec = MockSeriesSpec.area({
1100+
id: 'spec_2',
1101+
data: [
1102+
[1546300800000, 0],
1103+
[1546387200000, 2],
1104+
],
1105+
xAccessor: 0,
1106+
yAccessors: [1],
1107+
xScaleType: ScaleType.Time,
1108+
yScaleType: ScaleType.Linear,
1109+
stackAccessors: [0],
1110+
stackAsPercentage: true,
1111+
});
1112+
const pointSeriesDomains = computeSeriesDomains([pointSeriesSpec1, pointSeriesSpec2]);
1113+
expect(pointSeriesDomains.formattedDataSeries.stacked[0].dataSeries[0].data).toEqual([
1114+
{
1115+
datum: [1546300800000, 0],
1116+
initialY0: null,
1117+
initialY1: null,
1118+
x: 1546300800000,
1119+
y0: null,
1120+
y1: null,
1121+
},
1122+
{
1123+
datum: [1546387200000, 5],
1124+
initialY0: null,
1125+
initialY1: 0.7142857142857143,
1126+
x: 1546387200000,
1127+
y0: null,
1128+
y1: 0.7142857142857143,
1129+
},
1130+
]);
1131+
});
1132+
it('Stacked areas with null values', () => {
1133+
const pointSeriesSpec1: AreaSeriesSpec = MockSeriesSpec.area({
1134+
id: 'spec_1',
1135+
data: [
1136+
[1546300800000, null],
1137+
[1546387200000, 5],
1138+
],
1139+
xAccessor: 0,
1140+
yAccessors: [1],
1141+
xScaleType: ScaleType.Time,
1142+
yScaleType: ScaleType.Linear,
1143+
stackAccessors: [0],
1144+
});
1145+
const pointSeriesSpec2: AreaSeriesSpec = MockSeriesSpec.area({
1146+
id: 'spec_2',
1147+
data: [
1148+
[1546300800000, 3],
1149+
[1546387200000, null],
1150+
],
1151+
xAccessor: 0,
1152+
yAccessors: [1],
1153+
xScaleType: ScaleType.Time,
1154+
yScaleType: ScaleType.Linear,
1155+
stackAccessors: [0],
1156+
});
1157+
const pointSeriesDomains = computeSeriesDomains([pointSeriesSpec1, pointSeriesSpec2]);
1158+
expect(pointSeriesDomains.formattedDataSeries.stacked[0].dataSeries[0].data).toEqual([
1159+
{
1160+
datum: [1546300800000, null],
1161+
initialY0: null,
1162+
initialY1: null,
1163+
x: 1546300800000,
1164+
y0: null,
1165+
y1: null,
1166+
},
1167+
{
1168+
datum: [1546387200000, 5],
1169+
initialY0: null,
1170+
initialY1: 5,
1171+
x: 1546387200000,
1172+
y0: null,
1173+
y1: 5,
1174+
},
1175+
]);
1176+
1177+
expect(pointSeriesDomains.formattedDataSeries.stacked[0].dataSeries[1].data).toEqual([
1178+
{
1179+
datum: [1546300800000, 3],
1180+
initialY0: null,
1181+
initialY1: 3,
1182+
x: 1546300800000,
1183+
y0: null,
1184+
y1: 3,
1185+
},
1186+
{
1187+
datum: [1546387200000, null],
1188+
initialY0: null,
1189+
initialY1: null,
1190+
x: 1546387200000,
1191+
y0: null,
1192+
y1: null,
1193+
},
1194+
]);
1195+
});
10841196
});

src/chart_types/xy_chart/utils/stacked_series_utils.test.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,13 @@
1717
* under the License. */
1818

1919
import { RawDataSeries } from './series';
20-
import { computeYStackedMapValues, formatStackedDataSeriesValues, getYValueStackMap } from './stacked_series_utils';
20+
import {
21+
computeYStackedMapValues,
22+
formatStackedDataSeriesValues,
23+
getYValueStackMap,
24+
getStackedFormattedSeriesDatum,
25+
StackedValues,
26+
} from './stacked_series_utils';
2127
import { ScaleType } from '../../../scales';
2228

2329
describe('Stacked Series Utils', () => {
@@ -439,4 +445,21 @@ describe('Stacked Series Utils', () => {
439445
});
440446
});
441447
});
448+
test('Correctly handle 0 values on percentage stack', () => {
449+
const stackedValues: Map<any, StackedValues> = new Map();
450+
stackedValues.set(1, {
451+
values: [0, 0, 0],
452+
percent: [null, null, null],
453+
total: 0,
454+
});
455+
const formattedDatum = getStackedFormattedSeriesDatum({ x: 1, y1: 0 }, stackedValues, 0, false, true);
456+
expect(formattedDatum).toEqual({
457+
datum: undefined,
458+
initialY0: null,
459+
initialY1: null,
460+
x: 1,
461+
y0: null,
462+
y1: null,
463+
});
464+
});
442465
});

src/chart_types/xy_chart/utils/stacked_series_utils.ts

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@
1919
import { DataSeries, DataSeriesDatum, RawDataSeries, RawDataSeriesDatum, FilledValues } from './series';
2020
import { ScaleType } from '../../../scales';
2121

22-
interface StackedValues {
22+
/** @internal */
23+
export interface StackedValues {
2324
values: number[];
24-
percent: number[];
25+
percent: Array<number | null>;
2526
total: number;
2627
}
2728

@@ -103,6 +104,9 @@ export function computeYStackedMapValues(
103104
},
104105
);
105106
const percent = stackArray.values.map((value) => {
107+
if (stackArray.total === 0) {
108+
return null;
109+
}
106110
return value / stackArray.total;
107111
});
108112
stackedValues.set(xValue, {
@@ -124,7 +128,6 @@ export function formatStackedDataSeriesValues(
124128
): DataSeries[] {
125129
const yValueStackMap = getYValueStackMap(dataseries, xValues);
126130
const stackedValues = computeYStackedMapValues(yValueStackMap, scaleToExtent);
127-
128131
const stackedDataSeries: DataSeries[] = dataseries.map((ds, seriesIndex) => {
129132
const newData: DataSeriesDatum[] = [];
130133
const missingXValues = new Set([...xValues]);
@@ -169,11 +172,11 @@ export function formatStackedDataSeriesValues(
169172
data: newData,
170173
};
171174
});
172-
173175
return stackedDataSeries;
174176
}
175177

176-
function getStackedFormattedSeriesDatum(
178+
/** @internal */
179+
export function getStackedFormattedSeriesDatum(
177180
data: RawDataSeriesDatum,
178181
stackedValues: Map<any, StackedValues>,
179182
seriesIndex: number,
@@ -187,12 +190,19 @@ function getStackedFormattedSeriesDatum(
187190
return;
188191
}
189192
let y1: number | null = null;
193+
let y0: number | null | undefined = null;
190194
if (isPercentageMode) {
191-
y1 = data.y1 != null ? data.y1 / stack.total : null;
195+
if (data.y1 != null && stack.total !== 0) {
196+
y1 = data.y1 / stack.total;
197+
}
198+
if (data.y0 != null && stack.total !== 0) {
199+
y0 = data.y0 / stack.total;
200+
}
192201
} else {
193202
y1 = data.y1;
203+
y0 = data.y0;
194204
}
195-
const y0 = isPercentageMode && data.y0 != null ? data.y0 / stack.total : data.y0;
205+
196206
let computedY0: number | null;
197207
if (scaleToExtent) {
198208
computedY0 = y0 ? y0 : y1;
@@ -216,11 +226,16 @@ function getStackedFormattedSeriesDatum(
216226
let stackedY1: number | null = null;
217227
let stackedY0: number | null = null;
218228
if (isPercentageMode) {
219-
stackedY1 = y1 !== null ? stackY + y1 : null;
220-
stackedY0 = y0 != null ? stackY + y0 : stackY;
229+
stackedY1 = y1 !== null && stackY != null ? stackY + y1 : null;
230+
stackedY0 = y0 != null && stackY != null ? stackY + y0 : stackY;
221231
} else {
222-
stackedY1 = y1 !== null ? stackY + y1 : null;
223-
stackedY0 = y0 != null ? stackY + y0 : stackY;
232+
if (stackY == null) {
233+
stackedY1 = y1 !== null ? y1 : null;
234+
stackedY0 = y0 != null ? y0 : stackY;
235+
} else {
236+
stackedY1 = y1 !== null ? stackY + y1 : null;
237+
stackedY0 = y0 != null ? stackY + y0 : stackY;
238+
}
224239
// configure null y0 if y1 is null
225240
// it's semantically correct to say y0 is null if y1 is null
226241
if (stackedY1 === null) {

0 commit comments

Comments
 (0)