# Heatmap

> Month, quarter or year heatmap from @tpe-oy/charts, with Finnish/English localization, numeric formats and hover/focus details.

Package: `@tpe-oy/charts` · Category: Charts · Interactive page: https://tpe-kit-eea478.gitlab.io/components/heatmap

Follow the general TPE Kit rules in llms.txt (setup, controlled state, accessibility, styling).

## Example

```tsx
import '@tpe-oy/ui/styles.css';
import '@tpe-oy/charts/styles.css';
import { Heatmap } from '@tpe-oy/charts';

<Heatmap
  year={2026}
  data={[{ date: '2026-06-01', value: 0.35 }]}
  valueFormat="percent"
  locale="fi-FI"
/>;
```

## Props

### Heatmap

Keyboard arrows move by day/week; Home/End move within a week, Ctrl+Home/End across the displayed period. Enter/Space request day selection.

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `year` | `number` | yes |  | Calendar year, 1–9999. Dates are civil days with no timezone conversion. |
| `period` | `HeatmapPeriod` | no | `year` | Calendar span to display. |
| `month` | `number` | no | `1` | Month number (1–12), used when period is month. |
| `quarter` | `1 \| 2 \| 3 \| 4` | no | `1` | Calendar quarter (1–4), used when period is quarter. |
| `data` | `readonly HeatmapDatum[]` | yes |  | Unique dated readings. Omitted dates and null values are missing, never zero. Readings outside the displayed period are validated but not displayed. |
| `title` | `string` | no |  | Visible and accessible heading. Defaults to the localized period plus activity label. |
| `description` | `ReactNode` | no |  | Optional instructions or context beside the heading. |
| `valueFormat` | `"integer" \| "decimal" \| "percent"` | no | `decimal` | Number presentation; percent uses ratios (0.35 = 35%). |
| `fractionDigits` | `number` | no |  | Fixed decimal places, 0–10. Defaults to 0 for integers, 2 for decimals and 1 for percentages. |
| `locale` | `string` | no | `fi-FI` | Intl locale for numbers, weekday/month labels and tooltips. Finnish and English chart text is built in; other locales use English text with localized dates/numbers. |
| `thresholds` | `readonly [number, number, number]` | no |  | Three ascending absolute breakpoints defining four color bands. Percent defaults to [0.2, 0.35, 0.5]; other formats use quarters of the displayed data range, including zero. |
| `colorRanges` | `readonly HeatmapColorRange[]` | no |  | Custom ordered numeric bands, replacing thresholds and default colors. Bounds are exclusive, finite and increasing; the final range must omit max. Missing/no-runtime colors stay separate. |
| `cellSize` | `number` | no | `12` | Cell side in pixels, 10–32. |
| `showLegend` | `boolean` | no | `true` | Show the value-band and missing-data legend. |
| `showWeekNumbers` | `boolean` | no | `true` | Show ISO week numbers below the calendar. |
| `selectedDate` | `string \| null` | no |  | Optional application-owned selected civil date. |
| `activeWeek` | `number \| HeatmapWeekId \| null` | no |  | Application-owned active ISO week. A number uses the displayed year; use { year, number } at year boundaries. Null clears the active week. |
| `onWeekSelect` | `(week: HeatmapWeek) => void` | no |  | Optional week-number action; receives ISO identity, full week dates and days within the displayed period. |
| `onDaySelect` | `(day: HeatmapDay) => void` | no |  | Optional day action; receives raw values and missing/no-runtime state. |
| `labels` | `HeatmapLabels` | no |  | Override non-numeric labels. |

Also accepts inherited native/Radix props; see the type definitions.

## Type definitions

```ts
import { ComponentProps, ReactNode, ReactElement } from 'react';

/** One band in an ordered, gap-free numeric color scale. */
interface HeatmapColorRange {
    /** Exclusive upper bound in raw units. Omit only on the last range to cover all remaining values. */
    max?: number;
    /** Any CSS color, including a theme variable such as var(--tpe-success). */
    color: string;
    /** Optional CSS border color. Defaults to color. */
    borderColor?: string;
    /** Optional legend text. Defaults to localized numeric bounds. */
    label?: string;
}

/** One civil day. Percentages use ratios: 0.35 means 35%. */
type HeatmapDatum = {
    /** Calendar date in YYYY-MM-DD form, independent of timezone. */ date: string;
    /** Finite reading; null means no reading. */ value: number | null;
    status?: never;
} | {
    /** Calendar date in YYYY-MM-DD form. */ date: string;
    /** Explicit absence of operating time, distinct from a zero reading. */ status: 'no-runtime';
    value?: never;
};
/** Normalized cell supplied to selection callbacks. */
interface HeatmapDay {
    /** Civil date, YYYY-MM-DD. */
    date: string;
    /** Raw reading, or null when unavailable. */
    value: number | null;
    /** Missing, measured, and non-operating days remain distinguishable. */
    status: 'value' | 'missing' | 'no-runtime';
}
/** Calendar span displayed by the heatmap. */
type HeatmapPeriod = 'month' | 'quarter' | 'year';

/** ISO week identity; the ISO week-year can differ from the calendar year. */
interface HeatmapWeekId {
    /** ISO week number, 1–53. */
    number: number;
    /** ISO week-year. */
    year: number;
}
/** Week context supplied when activating a week number. */
interface HeatmapWeek extends HeatmapWeekId {
    /** Monday of the full ISO week, YYYY-MM-DD. */
    startDate: string;
    /** Sunday of the full ISO week, YYYY-MM-DD. */
    endDate: string;
    /** Normalized days within the displayed period, in date order. */
    days: readonly HeatmapDay[];
}
/** Localization for non-numeric chart text. Dates, months and weekdays use locale. */
interface HeatmapLabels {
    /** Missing-reading text; defaults follow locale. */
    missing?: string;
    /** Non-operating-day text; defaults follow locale. */
    noRuntime?: string;
    /** Week-number heading; defaults follow locale. */
    week?: string;
    /** Accessible weekday-column heading; defaults follow locale. */
    day?: string;
    /** Default heading suffix; defaults follow locale. */
    activity?: string;
    /** Single unbounded color-range legend text; defaults follow locale. */
    allValues?: string;
}
/** Monday-first month, quarter or year calendar heatmap with square cells and accessible hover/focus details. */
interface HeatmapProps extends Omit<ComponentProps<'section'>, 'children' | 'title'> {
    /** Calendar year, 1–9999. Dates are civil days with no timezone conversion. */
    year: number;
    /** Calendar span to display. @default 'year' */
    period?: HeatmapPeriod;
    /** Month number (1–12), used when period is month. @default 1 */
    month?: number;
    /** Calendar quarter (1–4), used when period is quarter. @default 1 */
    quarter?: 1 | 2 | 3 | 4;
    /** Unique dated readings. Omitted dates and null values are missing, never zero. Readings outside the displayed period are validated but not displayed. */
    data: readonly HeatmapDatum[];
    /** Visible and accessible heading. Defaults to the localized period plus activity label. */
    title?: string;
    /** Optional instructions or context beside the heading. */
    description?: ReactNode;
    /** Number presentation; percent uses ratios (0.35 = 35%). @default 'decimal' */
    valueFormat?: 'integer' | 'decimal' | 'percent';
    /** Fixed decimal places, 0–10. Defaults to 0 for integers, 2 for decimals and 1 for percentages. */
    fractionDigits?: number;
    /** Intl locale for numbers, weekday/month labels and tooltips. Finnish and English chart text is built in; other locales use English text with localized dates/numbers. @default 'fi-FI' */
    locale?: string;
    /** Three ascending absolute breakpoints defining four color bands. Percent defaults to [0.2, 0.35, 0.5]; other formats use quarters of the displayed data range, including zero. */
    thresholds?: readonly [number, number, number];
    /** Custom ordered numeric bands, replacing thresholds and default colors. Bounds are exclusive, finite and increasing; the final range must omit max. Missing/no-runtime colors stay separate. */
    colorRanges?: readonly HeatmapColorRange[];
    /** Cell side in pixels, 10–32. @default 12 */
    cellSize?: number;
    /** Show the value-band and missing-data legend. @default true */
    showLegend?: boolean;
    /** Show ISO week numbers below the calendar. @default true */
    showWeekNumbers?: boolean;
    /** Optional application-owned selected civil date. */
    selectedDate?: string | null;
    /** Application-owned active ISO week. A number uses the displayed year; use { year, number } at year boundaries. Null clears the active week. */
    activeWeek?: number | HeatmapWeekId | null;
    /** Optional week-number action; receives ISO identity, full week dates and days within the displayed period. */
    onWeekSelect?: (week: HeatmapWeek) => void;
    /** Optional day action; receives raw values and missing/no-runtime state. */
    onDaySelect?: (day: HeatmapDay) => void;
    /** Override non-numeric labels. */
    labels?: HeatmapLabels;
}
/** Keyboard arrows move by day/week; Home/End move within a week, Ctrl+Home/End across the displayed period. Enter/Space request day selection. */
declare function Heatmap({ year, period, month: displayedMonth, quarter, data, title, description, valueFormat, fractionDigits, locale, thresholds, colorRanges, cellSize, showLegend, showWeekNumbers, selectedDate, onDaySelect, activeWeek, onWeekSelect, labels, className, style, ...props }: HeatmapProps): ReactElement;

export { Heatmap };
export type { HeatmapColorRange, HeatmapDatum, HeatmapDay, HeatmapLabels, HeatmapPeriod, HeatmapProps, HeatmapWeek, HeatmapWeekId };
```
