Functional Weave
Code in TypeScript

charts.sparkline@1.0.0

impl/typescript.ts

1,930 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 { extent } from "./charts_extent.ts";  ← from charts.extent ^1.0.0 · built alongside by fune
import { linearScale } from "./charts_scale_linear_scale.ts";
import { linePath } from "./charts_shape_line_path.ts";
import type { Point } from "./charts_shape_types.ts";
import type { Sparkline } from "./charts_sparkline_types.ts";
import { roundFloat } from "./math_round_float.ts";  ← from math.round-float ^1.0.0 · built alongside by fune

function checkSize(value: number, what: string): void {
  if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
    throw new RangeError(`${what} must be a finite number of zero or more, received ${value}`);
  }
}

/**
 * A sparkline in a box: values evenly spaced from left to right, the lowest
 * at the bottom and the highest at the top of the padded box.
 *
 * A flat series is drawn along the middle rather than divided by a zero
 * range; a single value sits at the right-hand end, where the latest value of
 * a sparkline belongs.
 */
export function sparkline(values: readonly (number | null)[], width: number, height: number, padding: number): Sparkline {
  checkSize(width, "width");
  checkSize(height, "height");
  checkSize(padding, "padding");
  if (width - 2 * padding <= 0 || height - 2 * padding <= 0) {
    throw new RangeError(`the box is too small for its padding: ${width} by ${height} with ${padding} on each side`);
  }
  const range = extent(values);
  if (range === null) return { path: "", end: null };
  const [lo, hi] = range;
  const n = values.length;
  const points: Point[] = values.map((v, i) => {
    const x = n === 1 ? width - padding : linearScale([0, n - 1], [padding, width - padding], i, false);
    if (v === null) return { x, y: null };
    const y = lo === hi ? height / 2 : linearScale([lo, hi], [height - padding, padding], v, false);
    return { x, y };
  });
  let end = null;
  for (const p of points) {
    if (p.y !== null) end = { x: roundFloat(p.x, 2), y: roundFloat(p.y, 2) };
  }
  return { path: linePath(points), end };
}