export function formatStackedDataSeriesValues()

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);
}