in packages/charts/src/chart_types/xy_chart/utils/stacked_series_utils.ts [38:121]
export function formatStackedDataSeriesValues(
dataSeries: DataSeries[],
xValues: Set<string | number>,
seriesType: SeriesType,
stackMode?: StackMode,
): DataSeries[] {
const dataSeriesMap = dataSeries.reduce<Map<SeriesKey, DataSeries>>((acc, curr) => {
return acc.set(curr.key, curr);
}, new Map());
let hasNegative = false;
let hasPositive = false;
// group data series by x values
const xMap: XValueMap = new Map();
[...xValues].forEach((xValue) => {
const seriesMap = new Map<SeriesKey, DataSeriesDatum & { isFiltered: boolean }>();
dataSeries.forEach(({ key, data, isFiltered }) => {
const datum = data.find(({ x }) => x === xValue);
if (!datum) return;
const y1 = datum.y1 ?? 0;
if (hasPositive || y1 > 0) hasPositive = true;
if (hasNegative || y1 < 0) hasNegative = true;
const newDatum = Object.assign(datum, { isFiltered });
seriesMap.set(`${key}-y0`, newDatum);
seriesMap.set(key, newDatum);
});
xMap.set(xValue, seriesMap);
});
if (hasNegative && hasPositive && seriesType === SeriesType.Area) {
Logger.warn(
`Area series should be avoided with dataset containing positive and negative values. Use a bar series instead.`,
);
}
const keys = [...dataSeriesMap.keys()].reduce<string[]>((acc, key) => [...acc, `${key}-y0`, key], []);
const stackOffset = getOffsetBasedOnStackMode(stackMode, hasNegative && !hasPositive);
const stack = D3Stack<XValueSeriesDatum>()
.keys(keys)
.value(([, indexMap], key) => {
const datum = indexMap.get(key);
if (!datum || datum.isFiltered) return 0; // hides filtered series while maintaining their existence
return key.endsWith('-y0') ? datum.y0 ?? 0 : datum.y1 ?? 0;
})
.order(stackOrderNone)
.offset(stackOffset)(xMap)
.filter(({ key }) => !key.endsWith('-y0'));
return stack
.map<DataSeries | null>((stackedSeries) => {
const dataSeriesProps = dataSeriesMap.get(stackedSeries.key);
if (!dataSeriesProps) return null;
const data = stackedSeries
.map<DataSeriesDatum | null>((row) => {
const d = row.data[1].get(stackedSeries.key);
if (!d || d.x === undefined || d.x === null) return null;
const { initialY0, initialY1, mark, datum, filled, x } = d;
const [y0, y1] = row;
return {
x,
/**
* Due to floating point errors, values computed on a stack
* could falls out of the current defined domain boundaries.
* This in particular cause issues with percent stack, where the domain
* is hardcoded to [0,1] and some value can fall outside that domain.
*/
y1: clampIfStackedAsPercentage(y1, stackMode),
y0: clampIfStackedAsPercentage(y0, stackMode),
initialY0,
initialY1,
mark,
datum,
filled,
};
})
.filter(isDefined);
return {
...dataSeriesProps,
data,
};
})
.filter(isDefined);
}