/** * The maths every chart shares. * * Scales, monotone tangents and path building, in one place because four * charts need them and a copy per chart is four chances for two charts to * disagree about where a point goes. A line and an area drawn from different * code do not lie on top of each other, and that is exactly the pair most * often drawn together. * * Everything here is a worklet. The paths are rebuilt on the UI thread on * every frame a domain is tweening, so none of it can be allowed to need the * JS thread — which also rules out reaching for a charting dependency, since * that is the one thing none of them are written to survive. */ /** Which of the five chart tokens a series takes. */ export type SeriesColorIndex = 1 | 2 | 3 | 4 | 5; /** * A series' colour: an explicit one, else the `--color-chart-*` token. * * Shared so every chart draws its first series in the same colour. Two charts * on one screen disagreeing about what "series one" looks like is the kind of * thing nobody reports and everybody notices. */ export declare function useSeriesColor(explicit: string | undefined, index: SeriesColorIndex): string; /** Text laid over a fill: the solid colour and the softer one under it. */ export interface ChartInk { color: string; muted: string; } /** * The colour for the `index`th thing in a chart that draws an unbounded number * of them. * * There are five series tokens, and a chart is routinely asked to draw six * things. Taking the palette modulo its length hands two of them the identical * colour, which in a chart whose whole job is telling parts apart is a bug the * theme decides the severity of — on a theme whose first token is green, the * sixth wedge is a second green one. * * So each further lap through the palette is drawn a step further along a tone * run, keeping the theme's own hues rather than inventing new ones. * * The run goes *away* from whichever end the colour already sits near, and * always in that one direction. Alternating lighter and darker would be the * obvious thing and it reintroduces the bug on the themes that matter: a token * of `#fafafa` lightened is `#fafafa` back again, so the second lap of a * near-white palette entry is the same colour it was trying not to be. */ export declare function seriesColorAt(palette: string[], index: number): string; export declare function inkOn(fill: string, behind?: string, opacity?: number): ChartInk; /** * A value's radius on a bubble scale, in points. * * The value maps to *area* and the radius is taken from it, so a bubble holding * twice the value carries twice the ink. Mapping it to the radius instead * quadruples the ink for a doubled value, and the reader believes the larger * number by four times. * * `extent` is the smallest and largest value in the whole set, so one bubble's * size means the same thing as another's. A degenerate extent — one row, or * every value identical — puts everything at the top of the range rather than * dividing by zero. */ export declare function bubbleRadius(value: number, extent: readonly [number, number], range: readonly [number, number]): number; /** `12k` rather than `12400` — a readout has one line to say it in. */ export declare function compactNumber(value: number): string; /** The drawable box inside a chart, after its padding is taken off. */ export interface Plot { width: number; height: number; left: number; top: number; } /** A point's x, spreading `total` of them evenly across the plot. */ export declare function xOf(index: number, total: number, plot: Plot): number; /** A value's y, with `min` at the bottom of the plot and `max` at the top. */ export declare function yOf(value: number, plot: Plot, min: number, max: number): number; /** * Widens a domain out to round numbers. * * An axis derived from the data ends wherever the data happened to end, so it * gets labelled 34,650 — a number that is true and that nobody was looking for. * Rounding the ends out to a step of 1, 2 or 5 times a power of ten gives the * axis labels somebody can actually read a value off, and it can only ever add * room, never crop. * * Zero survives: any step divides it, so a baseline pinned there stays there. */ export declare function niceDomain(min: number, max: number, ticks?: number): [number, number]; /** * A value's x on a *measured* axis, with `min` at the left edge and `max` at * the right. * * The counterpart to `xOf`, and not a replacement for it. `xOf` spreads points * evenly by position, which is what a time series wants — twelve months are * twelve equal steps whatever the gaps between the dates behind them. This one * places a point at the value it holds, which is what a scatter plot needs: the * whole claim of a scatter plot is that both coordinates are quantities, and * spacing its points evenly would throw away the one axis the reader is being * asked to look for a relationship along. */ export declare function xAt(value: number, plot: Plot, min: number, max: number): number; /** The centre of the `index`th of `total` bands, as used for bars and columns. */ export declare function bandOf(index: number, total: number, plot: Plot): number; /** * Monotone cubic tangents. * * Written out rather than taken from a charting dependency, because it is forty * lines and it has to run inside a worklet. * * Monotone is the right default for a time series. A plain cubic spline * overshoots between points, so a series that never goes below zero draws a dip * under the axis between two low values — a shape that is not in the data. This * one cannot, because the tangent at each point is clamped against the slopes * either side of it, and a local peak or trough is given a flat one. */ export declare function tangents(xs: number[], ys: number[]): number[]; /** How a series is joined between its points. */ export type ChartCurve = 'monotone' | 'linear'; export interface ChartPoint { x: number; y: number; } /** Points, split at the gaps — a null breaks the series rather than crossing it. */ export declare function runsOf(values: (number | null)[], plot: Plot, min: number, max: number, /** Baselines to stack each point on top of, for a stacked area. */ baselines?: number[], /** * Place points at band centres rather than spreading them edge to edge. * * A line drawn over columns has to agree with them about where a row sits, * and a column owns a slice of the axis rather than a position on it. Spread * evenly, the first point lands half a slice left of the bar it describes and * every point after it is wrong by the same amount. */ banded?: boolean): ChartPoint[][]; /** One unbroken run of points as a path, curved or straight. */ export declare function segment(points: ChartPoint[], curve: ChartCurve): string; /** A series as a stroked line. Flat down the middle while it is still loading. */ export declare function linePath(values: (number | null)[], plot: Plot, min: number, max: number, curve: ChartCurve, loading: boolean, baselines?: number[], /** Place points at band centres, for a line drawn over columns. */ banded?: boolean): string; /** * A series as a filled area. * * `floors` is what makes a stack: without it every run is closed against the * bottom of the plot, and with it each run is closed against the series below, * so the bands sit on each other rather than overlapping. The floor is walked * backwards because the fill has to trace its underside right to left to close. */ export declare function areaPath(values: (number | null)[], plot: Plot, min: number, max: number, curve: ChartCurve, loading: boolean, baselines?: number[], /** Place points at band centres, for an area drawn against columns. */ banded?: boolean): string; /** * A colour as a gradient stop: the colour without its alpha, and the alpha as * the stop's opacity. A `` given `rgba(255,255,255,0.13)` as its colour * draws it opaque on native, so a translucent token such as `--color-skeleton` * comes out solid white in a dark theme. */ export declare function colorStop(color: string): { stopColor: string; stopOpacity: number; }; /** * One unbroken run of points joined by S-curves, for a chart of positions. * * Each join leaves one point level and arrives at the next one level, with * both control points at the midpoint between them. A line that holds its * position stays flat, and a line that changes position crosses over in the * middle of the gap. The monotone curve suits values, not places: it bends * through every point, so a series holding its rank would still look like it * was moving. */ export declare function bumpSegment(points: ChartPoint[]): string; /** A dot at every point, as one path, so a series' markers cost one node. */ export declare function dotsPath(points: ChartPoint[], radius: number): string; /** * A rounded rectangle, built as a path rather than drawn as a `Rect`. * * A bar is rounded on the end it grows towards and square on the end it grows * from, so it reads as sitting on the axis rather than floating above it — * which `rx` cannot express, since it rounds all four corners or none. */ export declare function barPath(x: number, y: number, width: number, height: number, radius: number, /** Which end is the growing one. */ towards: 'up' | 'down' | 'left' | 'right'): string; /** * An arc of a circle, as a path. * * Angles are in turns from twelve o'clock, clockwise, because that is how a * ring chart is described — "sixty percent of the way round" — and converting * at every call site is where the sign errors live. */ export declare function arcPath(cx: number, cy: number, radius: number, from: number, to: number): string; /** * A filled slice of an annulus, as a path. * * `arcPath` above cannot express this: it draws a line to be stroked, and a * stroke is a band of even thickness with no ends of its own. A slice is a * region — bounded by two arcs and two radial edges — and only a closed path * can be filled as one. * * `inner` of zero gives the pie's wedge, closing on the centre rather than on a * second arc. Anything above it gives the donut's, and the two are worth being * one function: a donut is not a pie with a circle painted over the middle, * because a slice pushed out of a donut has to be hollow along its whole length. * * `corner` rounds the four turns of a slice, and rounds them with a quadratic * through the sharp corner rather than with a true fillet arc. The two are * indistinguishable at the radii a slice is drawn at, and the quadratic cannot * degenerate at a narrow slice the way solving for tangent points does — which * matters here, because the narrow slices are exactly the ones a reader is * least able to check. */ export declare function wedgePath(cx: number, cy: number, outer: number, inner: number, from: number, to: number, corner: number): string; /** * A point at `radius` from the centre, `turn` of the way round. * * Turns from twelve o'clock, clockwise, matching `arcPath` — a radar's axes * and a ring's arcs are described the same way, and two polar conventions in * one file is how a spoke ends up a quarter turn from its own label. */ export declare function polarPoint(cx: number, cy: number, radius: number, turn: number): { x: number; y: number; }; /** * A closed polygon through evenly spaced spokes. * * `values` are already scaled to 0…1 — the fraction of the full radius each * axis reaches. A `null` is a missing reading rather than a zero: a radar with * a hole punched through to its centre says "none of this" when what happened * was "we did not measure this", so the gap is bridged by its neighbours and * the shape stays a shape. */ export declare function radarPath(values: (number | null)[], cx: number, cy: number, radius: number): string; /** * A pointy-top hexagon's metrics, from the radius of the circle through its * corners. * * Pointy-top rather than flat-top because it is the rows that have to tile: a * row of pointy-top cells has a straight top and bottom edge, so the next row * nests into it by half a cell and the field reads as a honeycomb. Flat-top * cells tile by column instead, which gives the same shape turned a quarter * turn and a field that is taller than it is wide for the same cell count. */ export interface HexMetrics { /** Centre to corner. */ radius: number; /** Across the flats — the full width of one cell. */ width: number; /** Point to point — the full height of one cell. */ height: number; /** Centre to centre along a row. */ stepX: number; /** * Centre to centre between rows. Three quarters of the height, not all of * it: consecutive rows interlock, and each one only costs the height of the * cell less the point it slots into. */ stepY: number; } export declare function hexMetrics(radius: number): HexMetrics; /** The cell radius that fits `columns` of them across `width`. */ export declare function hexRadiusFor(width: number, columns: number): number; /** How many rows of cells fit in `height`. */ export declare function hexRowsFor(height: number, metrics: HexMetrics): number; /** The centre of the cell at `column`, `row`, with odd rows nested half a cell right. */ export declare function hexCenter(column: number, row: number, metrics: HexMetrics, left: number, top: number): ChartPoint; /** * One hexagon as a closed path. * * Corners are turns from twelve o'clock like everything else polar in this * file, which puts the first one straight up — the definition of pointy-top. * * The coordinates are rounded to two decimals because these paths are * concatenated by the hundred: a field is one path string per series rather * than one node per cell, and full float precision would make each of those * strings several times longer for a difference no display can resolve. */ export declare function hexPath(cx: number, cy: number, radius: number): string; /** * A number in 0…1 from a pair of coordinates, the same one every time. * * `Math.random` would give the honeycomb a different edge on every render, * which turns a re-render into an animation nobody asked for and means the * same data never screenshots twice. */ export declare function hashUnit(a: number, b: number): number; /** * How many cells each value gets out of `budget`, by largest remainder. * * Rounding each share on its own does not add up — three equal parts of a * hundred round to 33 each and leave one over — and a spare cell in a * honeycomb is not a rounding error the reader can shrug off, it is a cell of * some colour that nothing in the data accounts for. Flooring every share and * then handing the leftovers to the largest fractions spends the budget * exactly, and spends it on the series with the strongest claim to each one. */ export declare function shareCounts(values: number[], budget: number): number[]; /** How the filled cells are arranged in the field. */ export type HexShape = 'blob' | 'grid'; export interface HexCell { column: number; row: number; } /** * Every cell of the field, in the order the series fill it. * * `grid` is reading order, which is the honest arrangement: counting cells off * a row is something a reader can actually do. `blob` grows out from the middle * instead, which counts for nothing but shows the shape of the split at a * glance — the smallest series in the centre with each larger one wrapped * around it. * * Two things make the blob look grown rather than stamped. Distance is measured * in the field's own proportions, so a wide field grows a wide blob instead of * a circle with empty shoulders either side of it; and each cell's distance is * nudged by a hash of its own coordinates, so the boundary between two series * comes out ragged instead of as a clean arc. The nudge is a hash and not a * random number, so the same data draws the same honeycomb every time. * * Computed once per layout rather than per frame, so it is a plain function. */ export declare function hexFillOrder(columns: number, rows: number, shape: HexShape): HexCell[]; /** * The cell under a point, or null when the point is between cells or outside * the field. * * Only three rows can be near any y, and one column near any x within a row, so * this checks six candidates rather than solving the cube-rounding — the same * answer, and short enough to read. */ export declare function hexAt(x: number, y: number, metrics: HexMetrics, left: number, top: number, columns: number, rows: number): HexCell | null; /** The numbers of one column, with anything unusable left as a gap. */ export declare function columnValues(data: Record[], key: string): (number | null)[]; /** * One length of a funnel: a band symmetrical about a centre line, `head` wide * at one end and `tail` wide at the other. * * Given as two half-extents about a middle rather than as four corners, because * that is the shape of the data — a stage knows what it is worth and what the * next one is worth, and the taper between them is the drop. * * `curve` is how far along the band the control points reach, as a fraction of * its length. Past `0.5` the two reach beyond each other, which is what turns * the join from a diagonal into the S the eye reads as a single continuous * funnel rather than a stack of separate trapezoids; `0` gives the straight * diagonal. The sides are the only curved part — the ends stay square, so a * band's end lines up exactly with the next band's start. * * The run goes across, so `offset` and `length` are horizontal and the band is * `head` and `tail` tall about `middle`. */ export declare function ribbonPath(offset: number, length: number, head: number, tail: number, middle: number, curve: number): string; /** * One ribbon of a flow diagram: a band of constant thickness running from a * centre at one end to a different centre at the other. * * Given by centres rather than edges because that is what the layout produces * — a ribbon is stacked against its node's edge and then recorded by its * middle, so that growing it in place thickens it about its own centre line * instead of sliding it down the node. * * The ends stay vertical whatever the two centres are, so a ribbon meets the * flat side of a node exactly rather than at the angle it happened to arrive. * `curve` is how far the control points reach towards the middle, as a * fraction of the horizontal run: `0.5` puts both on the halfway line, which * is the S that reads as one continuous flow, and `0` gives a straight * diagonal. */ export declare function flowPath(x0: number, cy0: number, x1: number, cy1: number, thickness: number, curve: number): string; //# sourceMappingURL=chart.d.ts.map