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 };