/** * HexChart — a whole broken into parts, counted out in cells. * * ```tsx * * * * * * ``` * * ## What it is, against the pie beside it * * Both divide one total. The difference is what the reader has to do to read a * share off it. A pie asks them to compare angles, which is the hardest * quantity there is to judge by eye; this asks them to compare *counts*, and a * count is something anyone can check by looking. A series holding a tenth of * the cells looks like a tenth and can be confirmed as one, which is why this * is the better shape for a split someone is going to quote. * * What it gives up is precision at the small end. Every cell is a whole unit, * so a series worth half a cell either rounds up to a full one or vanishes. It * is a chart for shares of a few percent and up, not for a long tail. * * ## The field * * The cells are pointy-top hexagons on an offset grid, so each row nests half a * cell into the one above it. The unfilled cells are drawn too, in the muted * token: the field is the denominator made visible, and a honeycomb floating on * nothing gives the eye no total to read the coloured part against. * * `shape` decides how the filled cells are arranged. `grid` is reading order, * which is the arrangement a reader can actually count off. `blob` grows the * series out from the middle of the field instead — the smallest in the centre, * each larger one wrapped around it — which counts for nothing but shows the * shape of the split at a glance. The blob's edge is ragged by design, and * ragged the *same way* every time: the nudge that roughens it is a hash of * each cell's own coordinates rather than a random number, so a re-render is * not an animation and the same data screenshots twice. * * Cell counts are apportioned by largest remainder, so they add up to the * budget exactly. A honeycomb whose parts came to one less than the whole would * have a cell in it that nothing in the data accounts for. * * ## Drawing and animating * * Two hundred cells is two hundred nodes if each one is drawn on its own, which * is more than this needs to spend. Every cell belonging to a series is * concatenated into a *single* path instead, so the whole chart is one node per * series plus one for the unfilled field — six or seven, whatever the cell * count. * * That is also why the reveal is a clip rather than a per-cell stagger: the * cells are no longer separate things to stagger. An ellipse grows from the * centre of the field, in the field's own proportions, so the honeycomb is * uncovered in the order the blob grew in — which is the same effect a stagger * would have given, for one animated value instead of two hundred. A `grid` * wipes across instead, because that is the order its cells were filled in. * * Touch, not hover: a series is selected by pressing one of its cells, and * pressing it again clears the selection. There is no equivalent of a pointer * resting somewhere without committing, so a chart that only revealed its * numbers on hover would never reveal them at all. * * Colours come from the `--color-chart-*` tokens, so a chart follows the active * theme and is put on brand by overriding those five. Nothing here hardcodes a * hex. */ import { type ReactNode } from 'react'; import { type ViewProps } from 'react-native'; import { type HexShape } from '../../utils/chart.js'; /** Whether the chart is showing data or waiting for it. */ export type HexChartStatus = 'loading' | 'ready'; export type { HexShape }; /** One series. Its share is worked out from the others, so there is no maximum. */ export interface HexDatum { /** Name for the legend, the readout and the accessibility label. */ label: string; /** How much of the whole this series is. Negatives are treated as zero. */ value: number; /** Explicit colour, overriding the `--color-chart-*` token. */ color?: string; } /** The selected series and its share, for something rendered inside the chart. */ export declare function useHexChart(): { activeIndex: number; activeSeries: HexDatum | null; /** Its share of the whole, 0 to 1. */ activeFraction: number; /** How many cells it was given, which is what the reader can count. */ activeCells: number; }; export interface HexChartProps extends ViewProps { className?: string; /** One entry per series. */ data: HexDatum[]; /** * Cells across the field. The cell size follows from it and the measured * width, so this is the one knob for how fine the honeycomb is. * * More cells resolve a smaller share — twenty-one across a phone is around * two hundred and fifty in the field, so roughly a half a percent each — at * the cost of every cell getting smaller and harder to press. */ columns?: number; /** Width over height of the field. */ aspectRatio?: number; /** * How much of the field the series fill, 0 to 1. * * Only meaningful with `shape="blob"`, where the unfilled cells are the * margin the blob is read against; a `grid` fills every cell, because a * waffle with a ragged last row is a waffle that has stopped being countable. */ density?: number; /** How the filled cells are arranged. */ shape?: HexShape; /** * The gap between cells, as a share of the cell radius. Given as a share so * the field keeps its proportions at whatever size it is measured at. */ cellGap?: number; /** Milliseconds for the honeycomb to fill in. */ animationDuration?: number; /** `loading` draws the field with nothing divided up yet. */ status?: HexChartStatus; /** Selected series. Leave unset to let the chart track it. */ activeIndex?: number; /** Fires with the selected series, or `-1` when the selection is cleared. */ onActiveIndexChange?: (index: number) => void; children?: ReactNode; } /** Imperative handle: re-run the fill, for a "replay" control. */ export interface HexChartHandle { replay: () => void; } export interface HexChartCellsProps { /** Colour of the cells no series took. Defaults to the muted token. */ emptyColor?: string; /** Opacity of the series that are not selected, once one is. */ dimOpacity?: number; } /** * The honeycomb: the unfilled field, and one path per series over it. * * One part rather than one per series. Every cell shares a radius, a gap and a * grid by definition — a chart where one series' cells could be given a size of * their own would be a chart drawing a lie, since the whole claim of the shape * is that one cell means the same thing wherever it appears. * * The field is drawn outside the reveal's clip and the series inside it, so the * total is there from the first frame and what fills in against it is the * split. Uncovering both together would animate the denominator, which is not * something that changed. */ declare function HexChartCells({ emptyColor, dimOpacity }: HexChartCellsProps): import("react").JSX.Element | null; declare namespace HexChartCells { var displayName: string; var slot: "svg"; } export interface HexChartSkeletonProps { color?: string; } /** * The loading state: the field, with nothing divided up yet. * * Deliberately undivided. Placeholder shares would be a made-up split, and a * reader has no way to tell an invented one from a real one until it changes * under them — which is worse than showing nothing, because it is showing * something wrong. */ declare function HexChartSkeleton({ color }: HexChartSkeletonProps): import("react").JSX.Element | null; declare namespace HexChartSkeleton { var displayName: string; var slot: "svg"; } export interface HexChartTooltipProps { /** Format the selected series' value. Defaults to a compact number. */ formatValue?: (value: number, series: HexDatum) => string; /** Show the count of cells beside the share. */ showCells?: boolean; className?: string; } /** * The press target over the honeycomb, and the label that names what was * pressed. * * The label hangs *above* the selected series rather than on it: it is centred * across that series' cells and sits clear of the highest one, so the cells * being read are never underneath the thing reading them. Two other places it * could go are both worse — under the finger is the one part of the chart * nobody can see, and on the middle of the series covers the mass the reader * just asked about. * * A series reaching the top of the field pushes the label back inside it, which * is the one case it does overlap. That series is the largest one, and its * topmost cells are the part of it a reader is least likely to be counting. * * A press on an unfilled cell clears the selection, the same as pressing the * selected series again. Everything outside the honeycomb is "none of them", * and making that gesture do nothing would leave a chart you can select in but * not out of. */ declare function HexChartTooltip({ formatValue, showCells, className }: HexChartTooltipProps): import("react").JSX.Element | null; declare namespace HexChartTooltip { var displayName: string; var slot: "overlay"; } export interface HexChartLegendProps extends ViewProps { className?: string; /** Show each series' share of the whole beside its name. */ showValue?: boolean; } /** * A swatch, a name and a share per series, under the chart and across the width * of it. Pressable in the same way the cells are — the legend is usually the * easier target of the two, and a series worth a couple of percent is a handful * of cells that may not be adjacent. * * It wraps rather than stacking, so five or six entries take two lines instead * of six. A key is a lookup table, and a lookup table read down a column of one * word each is a column the eye has to walk. */ declare function HexChartLegend({ className, showValue, ...props }: HexChartLegendProps): import("react").JSX.Element | null; declare namespace HexChartLegend { var displayName: string; var slot: "footer"; } export interface HexChartHeaderProps extends ViewProps { className?: string; /** Small line above the value — what the chart is of. */ title?: string; /** The readout. The largest thing on the card, and the first thing read. */ value?: string; /** One muted line under the value — a period, a comparison, a caveat. */ caption?: string; /** Prettier names for the series, keyed by their `label`. */ labels?: Record; /** * Draw a swatch and a name per series along the trailing edge. * * For two or three short names. Past that use `HexChart.Legend`, which runs * under the chart across the full width: a key of five long names crammed * into the trailing corner of a header wraps to a column and leaves the title * beside it a few points wide. */ legend?: boolean; /** Trailing slot — a control, a badge, a range picker. Wins over `legend`. */ children?: ReactNode; } /** * The strip above the honeycomb: what the chart is of, what it reads, and what * the colours mean. * * It belongs to the chart rather than to the card around it because it is about * the *series* — the number changes as one is selected, and the legend is the * list the chart itself is holding. The card's header is a caption on the tray * the chart sits in; this is the chart introducing itself. * * The value is not derived here even though there is a total to derive it from, * because the formatting is not the chart's to guess: a total of 6750 is a * count, a currency or a percentage depending on what was counted, and only the * caller knows which. */ declare function HexChartHeader({ className, title, value, caption, labels, legend, children, ...props }: HexChartHeaderProps): import("react").JSX.Element; declare namespace HexChartHeader { var displayName: string; var slot: "header"; } export declare const HexChart: import("react").ForwardRefExoticComponent> & { Header: typeof HexChartHeader; Cells: typeof HexChartCells; Tooltip: typeof HexChartTooltip; Legend: typeof HexChartLegend; Skeleton: typeof HexChartSkeleton; }; //# sourceMappingURL=index.d.ts.map