/** * Line and area path generators. Inputs are already in pixel space (the caller * maps data through scales first), so these are pure string builders. */ import type { AreaPoint, Curve, Point } from './types' import { coord } from './path' import { curveLinear } from './curve' export interface LineOptions { /** How to connect points. Default: straight segments. */ curve?: Curve } /** * An open polyline through `points`. Returns an empty string for no points (a * valid, invisible `d`), so binding it to a `` is always safe. */ export function line(points: readonly Point[], options: LineOptions = {}): string { if (points.length === 0) return '' const link = options.curve ?? curveLinear return `M${coord(points[0].x, points[0].y)}${link(points)}` } export interface AreaOptions { curve?: Curve } /** * A filled band between the `y0` (baseline) and `y1` (value) edges at each `x`. * The outline runs along the top edge left→right, then back along the baseline * right→left, and closes — so the same curve shapes both edges. */ export function area(points: readonly AreaPoint[], options: AreaOptions = {}): string { if (points.length === 0) return '' const link = options.curve ?? curveLinear const tops: Point[] = points.map((p) => ({ x: p.x, y: p.y1 })) const baseline: Point[] = points.map((p) => ({ x: p.x, y: p.y0 })).reverse() return ( `M${coord(tops[0].x, tops[0].y)}${link(tops)}` + `L${coord(baseline[0].x, baseline[0].y)}${link(baseline)}Z` ) }