"use client"; import { motion } from "motion/react"; import { ChartFrame } from "./chart-frame"; import { linearScale, niceTicks } from "@/lib/chart-utils"; import { formatDelta } from "@/lib/format"; export type WaterfallStep = { label: string; value: number; /** "total" draws a full-height bar from zero; "delta" (default) is a floating change. */ type?: "total" | "delta"; }; type WaterfallChartProps = { data: WaterfallStep[]; width?: number; height?: number; className?: string; }; const M = { top: 24, right: 18, bottom: 36, left: 48 }; /** WaterfallChart — floating bars rise/fall from the running total; dotted connectors draw between them. */ export function WaterfallChart({ data, width = 640, height = 380, className }: WaterfallChartProps) { const bars = data.reduce>((arr, d) => { const running = arr.length ? arr[arr.length - 1].end : 0; const start = d.type === "total" ? 0 : running; const end = d.type === "total" ? d.value : running + d.value; return [...arr, { ...d, start, end }]; }, []); const lo = Math.min(0, ...bars.map((b) => Math.min(b.start, b.end))); const hi = Math.max(...bars.map((b) => Math.max(b.start, b.end))); const sy = linearScale([lo, hi], [height - M.bottom, M.top]); const ticks = niceTicks(lo, hi, 4); const plotW = width - M.left - M.right; const step = plotW / data.length; const bw = Math.min(step * 0.6, 64); return ( {({ inView, reduce }) => ( <> {ticks.map((t) => ( {t} ))} {bars.map((b, i) => { const cx = M.left + step * (i + 0.5); const yTop = sy(Math.max(b.start, b.end)); const yBot = sy(Math.min(b.start, b.end)); const up = b.end >= b.start; const fill = b.type === "total" ? "var(--accent-600)" : up ? "var(--accent-500)" : "var(--ink-400)"; const delay = 0.1 + i * 0.12; return ( {i < bars.length - 1 && ( )} {b.label} {b.type !== "total" && ( {formatDelta(b.value, 0, false)} )} ); })} )} ); }