Functional Weave
Code in TypeScript

charts.stack@1.0.0

impl/typescript.ts

2,364 bytes · the TypeScript implementation · view raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

import { roundFloat } from "./math_round_float.ts";  ← from math.round-float ^1.0.0 · built alongside by fune
import type { StackedValue, StackOffset } from "./charts_stack_types.ts";

/**
 * Stack series position by position, as d3.stack does with the series in the
 * given order: stackOffsetNone (zero), stackOffsetExpand and
 * stackOffsetDiverging. Running totals are kept unrounded; only the returned
 * edges are rounded, so rounding never accumulates up the stack.
 */
export function stack(series: readonly (readonly number[])[], offset: StackOffset): readonly (readonly StackedValue[])[] {
  if (offset !== "zero" && offset !== "expand" && offset !== "diverging") {
    throw new RangeError(`stack offset must be zero, expand or diverging, received ${offset}`);
  }
  const n = series.length;
  if (n === 0) return [];
  const m = series[0].length;
  for (const s of series) {
    if (s.length !== m) throw new RangeError("every series must have the same number of values");
    for (const v of s) {
      if (typeof v !== "number" || !Number.isFinite(v)) throw new TypeError(`stack values must be finite numbers, received ${v}`);
      if (offset === "expand" && v < 0) throw new RangeError(`expand needs values of zero or more, received ${v}`);
    }
  }
  const lower: number[][] = series.map(() => new Array<number>(m));
  const upper: number[][] = series.map(() => new Array<number>(m));
  for (let j = 0; j < m; j++) {
    if (offset === "diverging") {
      let up = 0;
      let down = 0;
      for (let i = 0; i < n; i++) {
        const v = series[i][j];
        if (v > 0) {
          lower[i][j] = up;
          up += v;
          upper[i][j] = up;
        } else if (v < 0) {
          upper[i][j] = down;
          down += v;
          lower[i][j] = down;
        } else {
          lower[i][j] = 0;
          upper[i][j] = 0;
        }
      }
    } else {
      let total = 0;
      if (offset === "expand") for (let i = 0; i < n; i++) total += series[i][j];
      let top = 0;
      for (let i = 0; i < n; i++) {
        // Each share is divided by the total first, then summed, as d3 does.
        const v = offset === "expand" ? (total > 0 ? series[i][j] / total : 0) : series[i][j];
        lower[i][j] = top;
        top += v;
        upper[i][j] = top;
      }
    }
  }
  return series.map((_, i) => lower[i].map((y0, j) => ({ y0: roundFloat(y0, 6), y1: roundFloat(upper[i][j], 6) })));
}