import type { HTMLAttributes } from "react"; /** * TrendChart — multi-series time-trend chart in pure SVG (no chart lib — * keeps the registry copy-pasteable). Promoted from the theo-cloud dashboard * (production since M51, with the M76 sparse-marker and M90 ragged-series * lessons pinned by tests). * * Input contract: `y` values are expected finite and >= 0 (latency, counts); * non-finite points are skipped in the line and read as "—" in the table; * gaps are modeled by OMITTING the point (the a11y table reads each value * under its own period — never positionally). Points are connected in the * order given; sort by `x` upstream. Series `color` is any CSS color — prefer * theme tokens (e.g. `var(--primary)`). * * `${v}ms`} * series={[{ name: "p50", color: "var(--primary)", points }]} /> */ export interface TrendPoint { /** X in the series' own units (bucket index or epoch-ms); scaled linearly. */ x: number; /** Y value for this point. */ y: number; } export interface TrendSeries { name: string; /** CSS color for the line + legend swatch (prefer theme tokens). */ color: string; points: TrendPoint[]; } /** Linear scale mapping a value in `domain` to `range`. A zero-width domain maps everything to r0. */ export declare function linScale(domain: [number, number], range: [number, number]): (v: number) => number; /** A clean upper Y bound ≥ max(values). Returns 0 when there is no positive value. */ export declare function niceMax(values: number[]): number; /** * SVG path `d` for a polyline over the given scales. A non-finite point is a GAP: the path BREAKS * there and resumes with a new `M` subpath. * * M144 — this used to `filter(Number.isFinite)` and then join everything with `L`, which drew a * continuous line straight over the hole. The filter is what made it wrong: it erased the * information of WHERE the gap was, so the line could not break even in principle. * * Breaking is what every serious charting library does by default — `spanGaps` (Chart.js), * `connectNulls` (Highcharts, ECharts, Recharts) and `connectgaps` (Plotly) all default to `false`, * and Grafana ships "Connect null values: Never". It is also the only honest reading: connecting * asserts continuity the data does not have, and the rest of THIS file already treats a non-finite * `y` as absent (`niceMax` filters it, `hasData` filters it, and the accessible table renders `—` * for a period a series has no point for). * * A run of one point emits a lone `M` and no `L` — a one-point line has no length, and the sparse * marker is what makes it visible. */ export declare function seriesPath(points: TrendPoint[], xScale: (x: number) => number, yScale: (y: number) => number): string; /** * Which axis positions receive a label. * * Labelling 31 points in ~450px is illegible; labelling only the edges does not answer "when was * the spike". The density comes from the REAL width (`viewW`, which the ResizeObserver already * tracks), with the first and the last ALWAYS present: they are the window's edges, and without * them the reader does not know from when to when the chart speaks. * * Outside the JSX because it is arithmetic, and isolated arithmetic is arithmetic mutation reaches. */ export declare function xTicks(axisXs: number[], viewW: number): number[]; /** * Which points receive a marker. * * Two distinct reasons to mark, and the second only appeared once series with gaps came to exist * (M144): * * 1. **Sparse series** (< `SPARSE_MARKER_MAX` FINITE points) — 1-4 points read as a dramatic * cliff; the marker anchors the reader on the real data. M76's rule — which now counts data, * not slots, because M144's densification broke the equivalence between the two. * 2. **Isolated point** — a finite point surrounded by gaps on both sides becomes a lone `M` in * the `path`, and a lone `M` **draws nothing**: zero bounding box, invisible. The data exists, * it is correct, and the operator does not see it. Measured in M144's e2e * (`d="M419.86,92.00 M448.00,36.00"`). * * The old rule covered only (1), keying on the TOTAL number of points instead of asking whether * the point is actually drawn — even though its own comment already stated the intent *"a dot * keeps real data from rendering as an invisible line"*. After the densification, a 30-day series * with two finite days separated by a gap falls outside (1) and disappears. Chart.js and Plotly * mark the isolated point for exactly this reason. * * Outside the JSX on purpose: it is the decision's arithmetic, and isolated arithmetic is the kind * a mutation reaches. */ export declare function markedPoints(points: TrendPoint[]): TrendPoint[]; export interface TrendChartProps extends HTMLAttributes { title: string; series: TrendSeries[]; height?: number; /** Formats y-axis labels and table cells (tremor vocabulary). */ valueFormatter?: (v: number) => string; /** * Pin the top of the y-axis. Pass a fixed bound (e.g. `1` for a 0–1 score) so the * scale is stable and comparable across renders instead of auto-fitting to the data * max via {@link niceMax} (which makes 0.68 look near the top of a [0, 0.7] axis). * Ignored when ≤ 0. Default: auto (`niceMax`). */ yMax?: number; /** * Formats the `x` for the axis and for the accessible table. **Optional on purpose.** * * The `x` contract is ambiguous — "bucket index or epoch-ms" — and in practice nearly unanimous: * of the consumer's six series factories, five pass an INDEX and one passes epoch-ms. Inferring by * magnitude would work, and would be the exact shape of the defect this component has already paid * for (M76's marker rule keyed on `points.length` instead of asking what it wanted to know, and * M144's densification switched it off silently). The one who knows what `x` means is the caller. * * Without this prop nothing changes: no X-axis label is drawn and the table keeps numbering. */ xFormatter?: (x: number) => string; /** Header of the accessible table's first column. "Point" over dates would be dishonest. */ xLabel?: string; } declare const TrendChart: import("react").ForwardRefExoticComponent>; export { TrendChart };