/**
* 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