/** * Plot — a chart you assemble, for the chart that is not in the box. * * The other charts in this library each answer one question and answer it * completely: a line chart knows it is drawing a series over time, and every * decision inside it follows from that. This one knows nothing. It measures a * box, resolves a scale, and hands both to whatever marks you put in it — so a * combination nothing here ships as its own component is still a chart you can * build rather than a chart you have to go without. * * ```tsx * * * * * * * * * * ``` * * ## One scale, however many marks * * Every mark reads the same plot box and the same y-domain, and the domain is * derived from all of them together. That is the whole reason to compose rather * than to stack two charts on top of each other: two scales drawn over each * other look like a comparison and are not one. * * ## Where your own marks go * * `Plot.Layer` drops its children into the SVG tree, and `usePlot()` gives them * the resolved geometry — the box, the tweening domain, the reveal, the palette. * The scale functions are worklets exported alongside this component, so a mark * of your own is rebuilt on the UI thread on the same frames these are: * * ```tsx * function Threshold({ value }: { value: number }) { * const { plot, domainMin, domainMax } = usePlot(); * const props = useAnimatedProps(() => { * const y = yOf(value, plot, domainMin.value, domainMax.value); * return { d: `M${plot.left},${y}H${plot.left + plot.width}` }; * }); * return ; * } * * * * * ``` * * Anything that is text or takes a touch goes in `Plot.Overlay` instead, which * is a React Native view over the drawing. That split is not a preference: SVG * text ignores the platform's text scaling and the theme's font, and a gesture * handler cannot be attached to an SVG node at all. * * ## What it will not do for you * * There is no `type` prop and no set of defaults that guess at one. A chart * assembled here is exactly the marks you wrote, in the order you wrote them — * which is also the order they are drawn in, so a line over bars is a line * written after them. */ import { type ReactNode } from 'react'; import { type ViewProps } from 'react-native'; import { type SharedValue } from 'react-native-reanimated'; import { type ChartAccessibilityProps } from '../../primitives/chart-accessibility.js'; import { type Plot as PlotBox } from '../../utils/chart.js'; export { areaPath, bandOf, barPath, compactNumber, linePath, segment, xAt, xOf, yOf, } from '../../utils/chart.js'; export type { Plot as PlotBox, ChartPoint } from '../../utils/chart.js'; /** * Which layer a part belongs to. Read off the component itself, so composition * stays a flat list of children instead of three nested slots whose order the * caller has to remember. */ type Layer = 'svg' | 'series' | 'overlay' | 'header'; export type PlotStatus = 'loading' | 'ready'; export type PlotCurve = 'monotone' | 'linear'; export type PlotDatum = Record; /** * How the index of a row becomes an x. * * `point` puts the first and last rows on the plot's own edges, which is what a * series wants — the line should reach the frame. `band` gives every row an * equal slice and centres it in the middle of that, which is what anything with * width wants: a bar sitting on the edge of the plot is a bar half of which is * outside it. * * Resolved from the marks by default, so a chart with bars in it is banded * without being told. */ export type PlotScale = 'point' | 'band'; /** One end of the y-domain: a number to pin it at, or `auto` to derive it. */ export type PlotBound = number | 'auto'; /** Everything a mark needs to draw itself. Read it with `usePlot()`. */ export interface PlotGeometry { /** The rows, in order. */ data: PlotDatum[]; /** Key holding the x label. */ xDataKey: string; /** The drawable box, after the padding and any axis gutter are taken off. */ plot: PlotBox; /** How an index becomes an x. */ xScale: PlotScale; /** `loading` draws nothing but the frame. */ status: PlotStatus; /** * The y-domain, tweened. Read these inside worklets — they are what makes a * chart whose numbers changed redraw against a moving axis rather than jump. */ domainMin: SharedValue; domainMax: SharedValue; /** * The domain the tween is heading for. Labels read this rather than the shared * values: a number re-rendered on every frame of a tween is thirty renders of * a label that lands on the string it started on. */ extent: [number, number]; /** `0` to `1` as the plot is uncovered on mount. */ reveal: SharedValue; /** The five theme series colours, in order of prominence. */ palette: string[]; /** Every mark that registered itself, as `[dataKey, colour]`. */ series: [string, string][]; registerSeries: (key: string, color: string) => void; unregisterSeries: (key: string, color?: string) => void; /** Row under the cursor, or `-1`. On the UI thread. */ activeIndex: SharedValue; /** The same index on the JS thread, for anything that has to re-render. */ activeIndexJS: number; setActiveIndexJS: (index: number) => void; } /** * The resolved geometry, for a mark of your own. * * Everything in it is either a plain number that changes on layout or a shared * value that changes every frame, so a mark built from it animates with the * rest of the chart rather than beside it. */ export declare function usePlot(): PlotGeometry; /** The row under the cursor, for a readout drawn inside the plot. */ export declare function usePlotCursor(): { activeIndex: number; activePoint: PlotDatum | null; }; export interface PlotProps extends ViewProps, ChartAccessibilityProps { className?: string; /** The rows. Each one is a position along the x-axis. */ data: PlotDatum[]; /** Key holding the x label. Used by the axis and the readout. */ xDataKey?: string; /** * `loading` draws the frame and nothing in it, and reveals the marks when it * turns `ready`. One component throughout rather than a spinner swapped for a * chart — swapping loses the transition. */ status?: PlotStatus; /** Width ÷ height. `2` is the wide card shape; `1.6` suits a narrow column. */ aspectRatio?: number; /** Milliseconds for the plot to be uncovered on mount. */ animationDuration?: number; /** Milliseconds for the y-axis to settle after the data changes. */ domainDuration?: number; /** * The y-domain, as `[low, high]`. Either end may be a number to pin it there * or `auto` to take it from the data. * * Pinning one end is the case this exists for: `[0, 'auto']` keeps the * baseline at zero, which a chart of lengths needs — a bar cropped at the * bottom is a length that lies — while still letting the top follow whatever * arrives. */ yDomain?: [PlotBound, PlotBound]; /** * Round the derived ends of the y-domain out to whole numbers. * * Left off, an axis ends a tenth of the span past the largest value, so it * gets labelled 34,650 — true, and not a number anybody was looking for. On, * the ends move out to a step of 1, 2 or 5 times a power of ten, and the * labels become values a reader can measure against. * * It only ever widens the axis, and a pinned end is left alone. */ nice?: boolean; /** * How an index becomes an x. Derived from the marks when left out: a plot * with bars in it is banded, and anything else is on points. */ xScale?: PlotScale; /** How series are joined between points, unless a mark overrides it. */ curve?: PlotCurve; /** * The row under the cursor as it moves, and `-1`/`null` when the finger * lifts. This is how a readout *outside* the plot gets its value — that * header is not inside this provider, so it cannot use `usePlotCursor`. */ onActiveIndexChange?: (index: number, datum: PlotDatum | null) => void; /** * Drop the padding so the marks reach the edges — for a plot with no axis, * grid or cursor, where the shape is the whole point. */ compact?: boolean; children?: ReactNode; } /** Imperative handle: re-run the reveal, for a "replay" control. */ export interface PlotHandle { replay: () => void; } export interface PlotGridProps { /** Horizontal rules across the plot. */ rows?: number; color?: string; /** Dash pattern, e.g. `"4,6"`. Omit for a solid rule. */ dashArray?: string; opacity?: number; } /** * Horizontal reference lines. * * Outside the reveal clip on purpose: the grid is the frame the chart arrives * into, so it is already there when the marks start being uncovered. */ declare function PlotGrid({ rows, color, dashArray, opacity }: PlotGridProps): import("react").JSX.Element; declare namespace PlotGrid { var displayName: string; var layer: Layer; } export interface PlotSeriesProps { /** Column of `data` this mark draws. */ dataKey: string; /** Overrides the theme token. */ color?: string; /** Which `--color-chart-*` token to take, `1` to `5`. */ colorIndex?: number; } export interface PlotLineProps extends PlotSeriesProps { strokeWidth?: number; /** `monotone` never overshoots between points; `linear` joins them straight. */ curve?: PlotCurve; /** Dash pattern, e.g. `"6,4"` — for a forecast, or a series that is not real. */ dashArray?: string; } /** A series as a stroked line. */ declare function PlotLine({ dataKey, color, colorIndex, strokeWidth, curve, dashArray, }: PlotLineProps): import("react").JSX.Element | null; declare namespace PlotLine { var displayName: string; var layer: Layer; } export interface PlotAreaProps extends PlotSeriesProps { opacity?: number; curve?: PlotCurve; } /** * A series as a fill down to the baseline. * * Written before the line it belongs under, since the order the marks are * written is the order they are drawn. */ declare function PlotArea({ dataKey, color, colorIndex, opacity, curve, }: PlotAreaProps): import("react").JSX.Element | null; declare namespace PlotArea { var displayName: string; var layer: Layer; } export interface PlotBarsProps extends PlotSeriesProps { /** Fraction of each slice left empty, `0` to `1`. */ gap?: number; /** Rounds the end the bar grows towards, in points. */ radius?: number; opacity?: number; /** * The value the columns grow from. Zero by default, and zero is nearly always * right — a bar is a length, and a length has to start where the quantity * does. * * Set it for the case where the reader is being shown movement rather than * size: temperatures against a seasonal average, a score against a pass mark. * Columns then run up and down from that line instead of all standing on the * floor. It is clamped into the axis, so a baseline the domain does not cover * falls back to the nearer edge. */ baseline?: number; } /** * A series as columns. * * One path for all of them rather than one node per bar: a plot of two hundred * periods is the same single animated prop a frame as a plot of twenty. * * A bar has width, so its presence puts the whole plot on a band scale unless * the root was told otherwise — see `xScale`. */ declare function PlotBars({ dataKey, color, colorIndex, gap, radius, opacity, baseline, }: PlotBarsProps): import("react").JSX.Element | null; declare namespace PlotBars { var displayName: string; var layer: Layer; var mark: "band"; } export interface PlotDotsProps extends PlotSeriesProps { /** Radius, in points. */ size?: number; /** Ring around each dot, so it reads on top of the line rather than in it. */ ringWidth?: number; } /** A dot per row — for a series short enough that its points are worth marking. */ declare function PlotDots({ dataKey, color, colorIndex, size, ringWidth, }: PlotDotsProps): import("react").JSX.Element; declare namespace PlotDots { var displayName: string; var layer: Layer; } export interface PlotLayerProps { children?: ReactNode; } /** * Marks of your own, in the SVG tree. * * Whatever is inside is drawn where it is written — before the marks written * after it, after the ones before it — and reaches the geometry through * `usePlot()`. It is not given the geometry as an argument, because a mark that * animates has to hold hooks of its own and a render prop is not a component. */ declare function PlotLayer({ children }: PlotLayerProps): import("react").JSX.Element; declare namespace PlotLayer { var displayName: string; var layer: Layer; } export interface PlotOverlayProps { className?: string; children?: ReactNode; } /** * Anything of your own that is text or takes a touch, laid over the drawing. * * SVG text ignores the platform's text scaling and the theme's font, and a * gesture handler cannot be attached to an SVG node at all — so those two go * here rather than in `Plot.Layer`. */ declare function PlotOverlay({ className, children }: PlotOverlayProps): import("react").JSX.Element; declare namespace PlotOverlay { var displayName: string; var layer: Layer; } export interface PlotRuleProps { /** * Where to draw it, in the data's own units. Omit it and pass `x` instead for * a rule down the plot rather than across it. */ y?: number; /** * A row to draw a vertical rule at, by index — the release the numbers are * read against, the day a change landed. * * The x axis here carries positions rather than quantities, so this is which * row rather than what value. Exactly one of `y` and `x` is drawn; `y` wins * if both are given. */ x?: number; /** A name for what the line means. Nothing is drawn without one. */ label?: string; /** Overrides the line *and* its caption, so the two cannot drift apart. */ color?: string; /** Thickness in points. */ strokeWidth?: number; /** * Break the line into dashes, for a rule that should read as an annotation * rather than as a series the chart is drawing. */ dashed?: boolean; /** Fades the line and its caption together. */ opacity?: number; /** Which end of the rule the caption sits at. */ labelPlacement?: 'start' | 'end'; labelClassName?: string; className?: string; } /** * A reference line across the plot — a target, a limit, an average. * * A view rather than an SVG line, so its label is real text and follows the * theme. Its thickness is a border and its position is a transform, so it costs * no more than the SVG line would while gaining a legible caption. * * It is drawn at full strength in the foreground colour, and it is meant to be. * A reference line is the number the series is being judged against — a target * nobody can read is a target the chart is not actually stating — so what keeps * it from being mistaken for a series is that it is neutral and optionally * dashed, not that it is faint. */ declare function PlotRule({ y, x, label, color, strokeWidth, dashed, opacity, labelPlacement, labelClassName, className, }: PlotRuleProps): import("react").JSX.Element | null; declare namespace PlotRule { var displayName: string; var layer: Layer; } export interface PlotXAxisProps { /** How many labels to show. The rest are dropped, evenly. */ ticks?: number; /** Turn a row into its label. Defaults to the value at `xDataKey`. */ format?: (datum: PlotDatum, index: number) => string; className?: string; } /** * The x labels. Real text rather than SVG text, so they follow the theme's font * and the platform's text scaling — SVG text does neither. */ declare function PlotXAxis({ ticks, format, className }: PlotXAxisProps): import("react").JSX.Element; declare namespace PlotXAxis { var displayName: string; var layer: Layer; } export interface PlotYAxisProps { /** How many intervals to divide the axis into. Yields `ticks + 1` labels. */ ticks?: number; /** Turn a value into its label. Defaults to a compact number. */ format?: (value: number) => string; className?: string; } /** * Value labels down the side, one per grid line. * * Give it the same `ticks` as the grid, or the numbers name lines that are not * there. Four is the default on both for that reason. * * The labels are the domain the data settles at, not the tweening one — a * number counting through every intermediate value while the axis animates is * noise, and the axis is the part of a chart that has to hold still enough to * be read. */ declare function PlotYAxis({ ticks, format, className }: PlotYAxisProps): import("react").JSX.Element; declare namespace PlotYAxis { var displayName: string; var layer: Layer; var axis: "y"; } export interface PlotCursorProps { color?: string; /** Hide the vertical line and keep only the touch handling. */ showLine?: boolean; } /** * The touch handling, and the line that follows it. * * Split from the readout next door because they are separately useful: a plot * whose value is shown in its own header wants this and no label, and a plot * that highlights a bar wants this and nothing else at all. Both read the same * index. * * The hit area is the whole plot. A cursor you have to land on the line to * summon is a cursor nobody finds. */ declare function PlotCursor({ color, showLine }: PlotCursorProps): import("react").JSX.Element | null; declare namespace PlotCursor { var displayName: string; var layer: Layer; } export interface PlotTooltipProps { /** Format one series' value. Defaults to a compact number. */ formatValue?: (value: number, key: string) => string; /** Format the heading from the row. Defaults to the value at `xDataKey`. */ formatX?: (datum: PlotDatum) => string; /** Draw the readout yourself, given the row under the cursor. */ children?: (datum: PlotDatum, index: number) => ReactNode; className?: string; } /** * The readout that rides the cursor. * * Needs a `Plot.Cursor` beside it — the cursor owns the gesture and this only * reads the index it resolves. On its own it never appears, which is the right * failure: a label with no way to move is worse than no label. */ declare function PlotTooltip({ formatValue, formatX, children, className }: PlotTooltipProps): import("react").JSX.Element | null; declare namespace PlotTooltip { var displayName: string; var layer: Layer; } export interface PlotHeaderProps extends ViewProps { className?: string; /** A word for what the plot is of. */ title?: string; /** The figure, large. Usually the total, or the row under the cursor. */ value?: string; /** A line under the value. */ caption?: string; /** Replaces the whole header, keeping only its place above the drawing. */ children?: ReactNode; } /** The row above the drawing: what it is, and the one number worth reading. */ declare function PlotHeader({ className, title, value, caption, children, ...props }: PlotHeaderProps): import("react").JSX.Element; declare namespace PlotHeader { var displayName: string; var layer: Layer; } export interface PlotLegendProps extends ViewProps { className?: string; /** Names for the columns, keyed by `dataKey`. Falls back to the key itself. */ labels?: Record; } /** * A swatch and a name per mark, taken from the marks that registered. * * In the header rather than over the drawing, because a key laid inside the * plot either covers a mark or is squeezed to one word a line. */ declare function PlotLegend({ className, labels, ...props }: PlotLegendProps): import("react").JSX.Element | null; declare namespace PlotLegend { var displayName: string; var layer: Layer; } export declare const Plot: import("react").ForwardRefExoticComponent> & { Header: typeof PlotHeader; Legend: typeof PlotLegend; Grid: typeof PlotGrid; Area: typeof PlotArea; Bars: typeof PlotBars; Line: typeof PlotLine; Dots: typeof PlotDots; Rule: typeof PlotRule; Layer: typeof PlotLayer; Overlay: typeof PlotOverlay; XAxis: typeof PlotXAxis; YAxis: typeof PlotYAxis; Cursor: typeof PlotCursor; Tooltip: typeof PlotTooltip; }; //# sourceMappingURL=index.d.ts.map