/** * HeatmapChart — a calendar of bins, shaded by how much happened in each. * * The contribution grid: one column per period (usually a week), one row per * bin inside it (usually a weekday), and a colour ramp carrying the count. It * answers "when was this busy" at a glance, which no line can — a year of daily * numbers plotted as a series is a hairball, and as a grid it is a pattern. * * Composed rather than configured, so a chart that wants no axis simply does * not have one: * * ```tsx * * * * * * * * * ``` * * The parts sort themselves into a real layout rather than stacking over the * plot. That is the difference from a line chart, where the axis floats over * the drawing: here the labels and the legend sit *beside* and *below* the * grid, so they take up room, and the grid is sized with them accounted for. * Only the cells and the rules between them are SVG; every label is a React * Native view, because SVG text ignores the platform's text scaling and the * theme's font. * * The ramp is one colour at five opacities rather than five colours. A heatmap * reads as *more* and *less* of one thing, and five distinct hues read as five * different things — which is what the `--color-chart-*` tokens are for, and * why they are not used here. The base is `--color-chart-1`, so the ramp * follows the theme, and `levelColors` replaces it outright when a brand needs * its own. */ import { type ReactNode } from 'react'; import { View, type ViewProps } from 'react-native'; import { type ChartAccessibilityProps } from '../../primitives/chart-accessibility.js'; /** * Where a part belongs in the layout. Read off the component itself, so * composition stays a flat list of children instead of four nested slots the * caller has to remember the order of. */ type Slot = 'cells' | 'x-axis' | 'y-axis' | 'rules' | 'tooltip' | 'legend' | 'header'; export type HeatmapLayout = 'fluid' | 'fill'; /** One bin inside a column — usually a single day. */ export interface HeatmapBin { /** Row index within the column, `0` to `6`. */ bin: number; /** How much happened. The ramp is derived from these across the whole chart. */ count: number; /** The day this bin stands for. Used by the axis labels and the tooltip. */ date?: Date; } /** One column — usually a week. Missing bins are drawn as empty cells. */ export interface HeatmapColumn { /** Column index across the chart. */ bin: number; bins: HeatmapBin[]; } /** A cell resolved to its place in the grid, as the tooltip receives it. */ export interface HeatmapCell { column: number; row: number; count: number; level: number; date?: Date; } /** * The cell under the finger, for something rendered *inside* the chart. A * readout in the card's header is outside this provider — use * `onActiveCellChange` for that. */ export declare function useHeatmapChart(): { activeCell: HeatmapCell | null; }; /** * Build a year of columns from a flat list of dated counts. * * Every heatmap starts as "I have some dates and some numbers", and the * bucketing into weeks is the same arithmetic every time — including the two * parts that are easy to get wrong: the leading blanks before the first day of * the first week, and days with no entry at all, which must still be drawn as * empty cells or the calendar develops holes. */ export declare function buildHeatmapCalendar(entries: { date: Date; count: number; }[], options?: { start?: Date; end?: Date; weekStartDay?: number; }): HeatmapColumn[]; export interface HeatmapChartProps extends ViewProps, ChartAccessibilityProps { className?: string; /** One column per period, with its row bins inside. */ data: HeatmapColumn[]; /** * `fluid` draws cells at `binSize` and lets the grid be as wide as it needs * to be — put it in a horizontal `ScrollView` for a full year. `fill` * divides the available width between the columns instead. */ layout?: HeatmapLayout; /** Side of one cell in `fluid` layout, in pixels. */ binSize?: number; /** Space between cells, in pixels. */ gap?: number; /** Corner radius of a cell. */ cornerRadius?: number; /** Which weekday is the top row. `0` is Sunday. Labels follow it. */ weekStartDay?: number; /** * Rows per column. Seven for a calendar; use another number when the bins * are not weekdays — twenty-four for a grid of hours. */ rows?: number; /** * The four counts at which the ramp steps up. Derived from the data's own * quartiles when omitted, so a chart of single digits and a chart of * thousands both use the whole ramp. */ levels?: number[]; /** * Five colours — empty, then the four activity levels. Replaces the derived * ramp outright. Omit it and the ramp is `--color-chart-1` at five * opacities, which follows the theme. */ levelColors?: string[]; /** * Base colour for the derived ramp — the colour the busiest cells are drawn * in, with the quieter levels the same colour at lower opacity. * * Takes a theme token by name as well as a literal, so `"--color-chart-3"` * recolours the chart and keeps following the theme through light and dark. * Defaults to `--color-chart-1`. */ color?: string; /** * Colour of a cell with nothing in it. Takes a token name too. Defaults to * `--color-muted`, which is the right weight for "measured, and empty" — * override it for a chart that should read as denser or fainter than that. */ emptyColor?: string; /** * Opacity of the base colour at each of the five levels, quietest first. * The way to retune the ramp's contrast without having to name five colours. * Ignored when `levelColors` is given, which sets the colours outright. */ levelOpacity?: number[]; /** Milliseconds for the reveal on mount. */ animationDuration?: number; /** Opacity of every cell that is not the one under the finger. */ inactiveOpacity?: number; /** * The cell under the finger as it moves, and `null` when it lifts. This is * how a readout above the chart gets its value — that readout is outside the * chart, so it cannot use `useHeatmapChart`. */ onActiveCellChange?: (cell: HeatmapCell | null) => void; children?: ReactNode; } export interface HeatmapCellsProps { /** Corner radius of a cell. Falls back to the chart's. */ cornerRadius?: number; } /** * The grid itself. Every row of every column is drawn, including the ones with * nothing in them — a calendar with holes in it stops being a calendar. */ declare function HeatmapCells({ cornerRadius }: HeatmapCellsProps): import("react").JSX.Element; declare namespace HeatmapCells { var slot: Slot; } export interface HeatmapSeparatorProps { /** * `quarter` draws a rule every thirteen columns; a number draws one every * that many columns. */ every?: 'quarter' | number; color?: string; /** Dash pattern, e.g. `"2,4"`. Omit for a solid rule. */ dashArray?: string; } /** Vertical rules grouping the columns — quarters, months, sprints. */ declare function HeatmapSeparator({ every, color, dashArray }: HeatmapSeparatorProps): import("react").JSX.Element | null; declare namespace HeatmapSeparator { var slot: Slot; } export interface HeatmapXAxisProps { className?: string; /** * Label a column. Given the first dated bin in it, so a month name can be * derived. Return an empty string to leave the column unlabelled. */ formatLabel?: (date: Date, column: number) => string; /** * Column labels, left to right. Overrides the month names — for a grid whose * columns are not weeks, where there is no month to change and so nothing to * emit a label on. */ labels?: string[]; } /** * Month labels above the grid. * * A label is emitted where the month changes rather than at a fixed interval, * because months are not the same length — spacing them evenly puts "Mar" over * a week in February. A grid whose columns are not weeks has no such signal, so * it passes `labels` and gets one over every column. */ declare function HeatmapXAxis({ className, formatLabel, labels: given }: HeatmapXAxisProps): import("react").JSX.Element; declare namespace HeatmapXAxis { var slot: Slot; } export interface HeatmapYAxisProps { className?: string; /** Width reserved for the labels. The grid is sized around it. */ width?: number; /** Which rows get a label. Every other row is the usual choice. */ tickFilter?: 'all' | 'odd' | 'even'; /** `initial` is the single letter; `full` is the abbreviated name. */ labelFormat?: 'initial' | 'full'; /** * Row labels, top to bottom. Overrides the weekday names — for a grid whose * rows are not days. */ labels?: string[]; } /** Weekday labels down the left of the grid. */ declare function HeatmapYAxis({ className, width, tickFilter, labelFormat, labels, }: HeatmapYAxisProps): import("react").JSX.Element; declare namespace HeatmapYAxis { var slot: Slot; } export interface HeatmapTooltipProps { className?: string; /** The line shown for a cell. Defaults to the count and the date. */ formatLabel?: (cell: HeatmapCell) => string; /** * How long a press has to be held before the readout takes over, in * milliseconds. * * It is not zero, and cannot be: a full year of columns lives inside a * horizontal scroller, and a readout that claims the touch on the first pixel * of movement means the chart can never be scrolled. Holding first is what * separates "I am moving the chart" from "I am reading it". Set `0` only for * a chart that is not inside a scroll view at all. */ activateAfterLongPress?: number; } /** * A readout following the finger across the grid. * * It lives in the view layer, over the SVG: a gesture handler cannot be * attached to an SVG node, and SVG text ignores the platform's text scaling. * The cell under the finger is resolved on the UI thread and only crosses back * into JS when it changes, so a drag across a year costs a handful of * re-renders rather than one per frame. */ declare function HeatmapTooltip({ className, formatLabel, activateAfterLongPress, }: HeatmapTooltipProps): import("react").JSX.Element; declare namespace HeatmapTooltip { var slot: Slot; } export interface HeatmapLegendProps { className?: string; /** Text at the low end of the ramp. */ lessLabel?: string; /** Text at the high end. */ moreLabel?: string; /** Side of a swatch, in pixels. Defaults to the chart's cell size. */ swatchSize?: number; } /** The `Less ▢▢▢▢▢ More` key, under the grid. */ declare function HeatmapLegend({ className, lessLabel, moreLabel, swatchSize, }: HeatmapLegendProps): import("react").JSX.Element; declare namespace HeatmapLegend { var slot: Slot; } export interface HeatmapHeaderProps extends ViewProps { className?: string; /** Small line above the value — what the grid 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 total, the held cell. */ caption?: string; /** * Draw the ramp along the trailing edge, `Less ▢▢▢▢▢ More`. The key for a * grid that scrolls sideways, where `HeatmapChart.Legend` under the cells * would scroll away with them. */ legend?: boolean; /** Text at the low end of the ramp, when `legend` is set. */ lessLabel?: string; /** Text at the high end. */ moreLabel?: string; /** Trailing slot — a control, a badge, a range picker. Wins over `legend`. */ children?: ReactNode; } /** * The strip above the grid: what the chart is of, what it currently reads, and * what the shading means. * * It belongs to the chart rather than to the card around it because it is about * the *grid* — the number changes as a finger moves across the cells, and the * ramp is the scale the chart itself derived. 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. Take it from `onActiveCellChange` and pass the * formatted string down, so one header can show a total when nothing is held * and a day's own count when something is. */ declare function HeatmapHeader({ className, title, value, caption, legend, lessLabel, moreLabel, children, ...props }: HeatmapHeaderProps): import("react").JSX.Element; declare namespace HeatmapHeader { var displayName: string; var slot: Slot; } export declare const HeatmapChart: import("react").ForwardRefExoticComponent> & { Header: typeof HeatmapHeader; Cells: typeof HeatmapCells; Separator: typeof HeatmapSeparator; XAxis: typeof HeatmapXAxis; YAxis: typeof HeatmapYAxis; Tooltip: typeof HeatmapTooltip; Legend: typeof HeatmapLegend; }; export {}; //# sourceMappingURL=index.d.ts.map