Heatmap
Basic Configuration
The Heatmap component renders a flat array of values as a grid of colored cells — like the GitHub contributions
calendar. It works inside a Single Container and only needs a value accessor. The data can be an array of
objects or, as below, a plain array of numbers:
- React
- Angular
- Svelte
- Vue
- Solid
- TypeScript
import { VisSingleContainer, VisHeatmap } from '@unovis/react'
function Component(props) {
const data: number[] = props.data
const value = (d: number) => d
return (
<VisSingleContainer data={data}>
<VisHeatmap
value={value}
numRows={7}
cellSize={16}
cellPadding={3}
cellCornerRadius={3}
/>
</VisSingleContainer>
)
}
@Component({
templateUrl: 'template.html'
})
export class Component {
@Input data: number[];
value = (d: number) => d
}
<vis-single-container [data]="data">
<vis-heatmap
[value]="value"
[numRows]="7"
[cellSize]="16"
[cellPadding]="3"
[cellCornerRadius]="3"
></vis-heatmap>
</vis-single-container>
<script lang='ts'>
import { VisSingleContainer, VisHeatmap } from '@unovis/svelte'
export let data: number[]
const value = (d: number) => d
</script>
<VisSingleContainer {data}>
<VisHeatmap
{value}
numRows={7}
cellSize={16}
cellPadding={3}
cellCornerRadius={3}
/>
</VisSingleContainer>
<script setup lang="ts">
import { VisSingleContainer, VisHeatmap } from '@unovis/vue'
const props = defineProps<{ data: number[] }>()
const value = (d: number) => d
</script>
<template>
<VisSingleContainer :data="data">
<VisHeatmap
:value="value"
:numRows="7"
:cellSize="16"
:cellPadding="3"
:cellCornerRadius="3"
/>
</VisSingleContainer>
</template>
import { VisSingleContainer, VisHeatmap } from '@unovis/solid'
function Component(props) {
const data: number[] = () => props.data
const value = (d: number) => d
return (
<VisSingleContainer data={data()}>
<VisHeatmap
value={value}
numRows={7}
cellSize={16}
cellPadding={3}
cellCornerRadius={3}
/>
</VisSingleContainer>
)
}
import { SingleContainer, Heatmap } from '@unovis/ts'
import { data } from './data'
const container = new SingleContainer<number>(node, {
component: new Heatmap<number>({
value: (d: number) => d,
numRows: 7,
cellSize: 16,
cellPadding: 3,
cellCornerRadius: 3
})
}, data)
Grid Dimensions
The array is laid out into a grid whose size you control with numRows and/or numColumns. By default the grid has
7 rows and the data fills column by column (layout: HeatmapLayoutType.Column) — each column completes top-to-bottom before the
next begins, just like GitHub weeks. Set layout: HeatmapLayoutType.Row to fill row by row instead.
- React
- Angular
- Svelte
- Vue
- Solid
- TypeScript
<VisHeatmap
value={value}
numRows={7}
cellSize={16}
cellPadding={3}
cellCornerRadius={3}
/>
<vis-heatmap
[value]="value"
[numRows]="7"
[cellSize]="16"
[cellPadding]="3"
[cellCornerRadius]="3"
></vis-heatmap>
<VisHeatmap
{value}
numRows={7}
cellSize={16}
cellPadding={3}
cellCornerRadius={3}
/>
<VisHeatmap
:value="value"
:numRows="7"
:cellSize="16"
:cellPadding="3"
:cellCornerRadius="3"
/>
<VisHeatmap
value={value}
numRows={7}
cellSize={16}
cellPadding={3}
cellCornerRadius={3}
/>
const heatmap = new Heatmap<number>({
value,
numRows: 7,
cellSize: 16,
cellPadding: 3,
cellCornerRadius: 3
})
Empty Cells & Offset
When the value accessor returns null or undefined, the cell is rendered empty (using the
--vis-heatmap-cell-fill-color variable). Use offset to leave empty cells before the first datum — handy for aligning
the first value to a specific weekday:
- React
- Angular
- Svelte
- Vue
- Solid
- TypeScript
<VisHeatmap
value={value}
numRows={7}
cellSize={16}
cellPadding={3}
cellCornerRadius={3}
offset={3}
/>
<vis-heatmap
[value]="value"
[numRows]="7"
[cellSize]="16"
[cellPadding]="3"
[cellCornerRadius]="3"
[offset]="3"
></vis-heatmap>
<VisHeatmap
{value}
numRows={7}
cellSize={16}
cellPadding={3}
cellCornerRadius={3}
offset={3}
/>
<VisHeatmap
:value="value"
:numRows="7"
:cellSize="16"
:cellPadding="3"
:cellCornerRadius="3"
:offset="3"
/>
<VisHeatmap
value={value}
numRows={7}
cellSize={16}
cellPadding={3}
cellCornerRadius={3}
offset={3}
/>
const heatmap = new Heatmap<number>({
value,
numRows: 7,
cellSize: 16,
cellPadding: 3,
cellCornerRadius: 3,
offset: 3
})
Color
By default cells are colored with a quantized green scale (configurable via the --vis-heatmap-color-* CSS variables).
Pass colorRange to use your own bucket colors, colorDomain to fix the [min, max] mapping, or a per-cell color
accessor for full control:
- React
- Angular
- Svelte
- Vue
- Solid
- TypeScript
function Component(props) {
const data: number[] = props.data
const value = (d: number) => d
const colorRange = [`#e0e7ff`, `#a5b4fc`, `#6366f1`, `#4338ca`]
return (
<VisHeatmap
value={value}
numRows={7}
cellSize={16}
cellPadding={3}
cellCornerRadius={3}
colorRange={colorRange}
/>
)
}
@Component({
template: `
<vis-heatmap
[value]="value"
[numRows]="7"
[cellSize]="16"
[cellPadding]="3"
[cellCornerRadius]="3"
[colorRange]="colorRange"
></vis-heatmap>
`
})
export class Component {
@Input data: number[];
value = (d: number) => d
colorRange = [`#e0e7ff`, `#a5b4fc`, `#6366f1`, `#4338ca`]
}
<script lang='ts'>
import { VisSingleContainer, VisHeatmap } from '@unovis/svelte'
export let data: number[]
const value = (d: number) => d
const colorRange = [`#e0e7ff`, `#a5b4fc`, `#6366f1`, `#4338ca`]
</script>
<VisHeatmap
{value}
numRows={7}
cellSize={16}
cellPadding={3}
cellCornerRadius={3}
{colorRange}
/>
<script setup lang="ts">
import { VisSingleContainer, VisHeatmap } from '@unovis/vue'
const props = defineProps<{ data: number[] }>()
const value = (d: number) => d
const colorRange = [`#e0e7ff`, `#a5b4fc`, `#6366f1`, `#4338ca`]
</script>
<template>
<VisHeatmap
:value="value"
:numRows="7"
:cellSize="16"
:cellPadding="3"
:cellCornerRadius="3"
:colorRange="colorRange"
/>
</template>
function Component(props) {
const data: number[] = () => props.data
const value = (d: number) => d
const colorRange = [`#e0e7ff`, `#a5b4fc`, `#6366f1`, `#4338ca`]
return (
<VisHeatmap
value={value}
numRows={7}
cellSize={16}
cellPadding={3}
cellCornerRadius={3}
colorRange={colorRange}
/>
)
}
const heatmap = new Heatmap<number>({
value: (d: number) => d,
numRows: 7,
cellSize: 16,
cellPadding: 3,
cellCornerRadius: 3,
colorRange: [`#e0e7ff`, `#a5b4fc`, `#6366f1`, `#4338ca`]
})
// Quantized bucket colors (value → color)
colorRange={['#e0e7ff', '#a5b4fc', '#6366f1', '#4338ca']}
// Fix the domain so the scale doesn't depend on the data extent
colorDomain={[0, 20]}
// Or take full control per cell
color={d => d > 10 ? '#4338ca' : '#a5b4fc'}
Cell Appearance
Cell Size
By default cells stretch to fill the container. Set cellSize to render fixed-size square cells (the component then
reports its own size, which pairs with the container's Sizing.Extend).
- React
- Angular
- Svelte
- Vue
- Solid
- TypeScript
<VisHeatmap
value={value}
numRows={7}
cellSize={16}
cellPadding={3}
cellCornerRadius={3}
/>
<vis-heatmap
[value]="value"
[numRows]="7"
[cellSize]="16"
[cellPadding]="3"
[cellCornerRadius]="3"
></vis-heatmap>
<VisHeatmap
{value}
numRows={7}
cellSize={16}
cellPadding={3}
cellCornerRadius={3}
/>
<VisHeatmap
:value="value"
:numRows="7"
:cellSize="16"
:cellPadding="3"
:cellCornerRadius="3"
/>
<VisHeatmap
value={value}
numRows={7}
cellSize={16}
cellPadding={3}
cellCornerRadius={3}
/>
const heatmap = new Heatmap<number>({
value,
numRows: 7,
cellSize: 16,
cellPadding: 3,
cellCornerRadius: 3
})
Cell Padding
cellPadding is the gap between cells in pixels.
- React
- Angular
- Svelte
- Vue
- Solid
- TypeScript
<VisHeatmap
value={value}
numRows={7}
cellSize={16}
cellPadding={3}
cellCornerRadius={3}
/>
<vis-heatmap
[value]="value"
[numRows]="7"
[cellSize]="16"
[cellPadding]="3"
[cellCornerRadius]="3"
></vis-heatmap>
<VisHeatmap
{value}
numRows={7}
cellSize={16}
cellPadding={3}
cellCornerRadius={3}
/>
<VisHeatmap
:value="value"
:numRows="7"
:cellSize="16"
:cellPadding="3"
:cellCornerRadius="3"
/>
<VisHeatmap
value={value}
numRows={7}
cellSize={16}
cellPadding={3}
cellCornerRadius={3}
/>
const heatmap = new Heatmap<number>({
value,
numRows: 7,
cellSize: 16,
cellPadding: 3,
cellCornerRadius: 3
})
Corner Radius
cellCornerRadius rounds the cell corners (pass a number, or true for a default radius of 2).
- React
- Angular
- Svelte
- Vue
- Solid
- TypeScript
<VisHeatmap
value={value}
numRows={7}
cellSize={16}
cellPadding={3}
cellCornerRadius={3}
/>
<vis-heatmap
[value]="value"
[numRows]="7"
[cellSize]="16"
[cellPadding]="3"
[cellCornerRadius]="3"
></vis-heatmap>
<VisHeatmap
{value}
numRows={7}
cellSize={16}
cellPadding={3}
cellCornerRadius={3}
/>
<VisHeatmap
:value="value"
:numRows="7"
:cellSize="16"
:cellPadding="3"
:cellCornerRadius="3"
/>
<VisHeatmap
value={value}
numRows={7}
cellSize={16}
cellPadding={3}
cellCornerRadius={3}
/>
const heatmap = new Heatmap<number>({
value,
numRows: 7,
cellSize: 16,
cellPadding: 3,
cellCornerRadius: 3
})
Labels
Because the Heatmap isn't an XY component, labels are built in. Provide columnLabel and/or rowLabel accessors —
each is called per index and may return undefined to skip a label. Column labels render above the grid, row labels to
the left:
- React
- Angular
- Svelte
- Vue
- Solid
- TypeScript
<VisHeatmap
value={value}
numRows={7}
cellSize={16}
cellPadding={3}
cellCornerRadius={3}
rowLabel={rowLabel}
/>
<vis-heatmap
[value]="value"
[numRows]="7"
[cellSize]="16"
[cellPadding]="3"
[cellCornerRadius]="3"
[rowLabel]="rowLabel"
></vis-heatmap>
<VisHeatmap
{value}
numRows={7}
cellSize={16}
cellPadding={3}
cellCornerRadius={3}
{rowLabel}
/>
<VisHeatmap
:value="value"
:numRows="7"
:cellSize="16"
:cellPadding="3"
:cellCornerRadius="3"
:rowLabel="rowLabel"
/>
<VisHeatmap
value={value}
numRows={7}
cellSize={16}
cellPadding={3}
cellCornerRadius={3}
rowLabel={rowLabel}
/>
const heatmap = new Heatmap<number>({
value,
numRows: 7,
cellSize: 16,
cellPadding: 3,
cellCornerRadius: 3,
rowLabel
})
// GitHub-style: months across the top, alternating weekdays on the left
columnLabel={column => /* month name on the first column of each month */}
rowLabel={row => ['', 'Mon', '', 'Wed', '', 'Fri', ''][row]}
Handling Overlap
When many labels are packed into a small space their text can collide. By default (labelHideOverlapping: true) the
Heatmap hides overlapping labels, resolving each axis independently and keeping the top-most row label and left-most
column label of every colliding group. Toggle it off to always render every label:
- React
- Angular
- Svelte
- Vue
- Solid
- TypeScript
<VisHeatmap
value={value}
numRows={8}
cellSize={12}
cellPadding={2}
cellCornerRadius={3}
columnLabel={columnLabel}
rowLabel={rowLabel}
labelHideOverlapping={true}
/>
<vis-heatmap
[value]="value"
[numRows]="8"
[cellSize]="12"
[cellPadding]="2"
[cellCornerRadius]="3"
[columnLabel]="columnLabel"
[rowLabel]="rowLabel"
[labelHideOverlapping]="true"
></vis-heatmap>
<VisHeatmap
{value}
numRows={8}
cellSize={12}
cellPadding={2}
cellCornerRadius={3}
{columnLabel}
{rowLabel}
labelHideOverlapping={true}
/>
<VisHeatmap
:value="value"
:numRows="8"
:cellSize="12"
:cellPadding="2"
:cellCornerRadius="3"
:columnLabel="columnLabel"
:rowLabel="rowLabel"
:labelHideOverlapping="true"
/>
<VisHeatmap
value={value}
numRows={8}
cellSize={12}
cellPadding={2}
cellCornerRadius={3}
columnLabel={columnLabel}
rowLabel={rowLabel}
labelHideOverlapping={true}
/>
const heatmap = new Heatmap<number>({
value,
numRows: 8,
cellSize: 12,
cellPadding: 2,
cellCornerRadius: 3,
columnLabel,
rowLabel,
labelHideOverlapping: true
})
Events
The following selectors are available for events:
import { Heatmap } from '@unovis/ts'
...
events = {
[Heatmap.selectors.cell]: { },
}
- React
- Angular
- Svelte
- Vue
- Solid
- TypeScript
<VisHeatmap
value={value}
numRows={7}
cellSize={16}
cellPadding={3}
cellCornerRadius={3}
events={events}
/>
<vis-heatmap
[value]="value"
[numRows]="7"
[cellSize]="16"
[cellPadding]="3"
[cellCornerRadius]="3"
[events]="events"
></vis-heatmap>
<VisHeatmap
{value}
numRows={7}
cellSize={16}
cellPadding={3}
cellCornerRadius={3}
{events}
/>
<VisHeatmap
:value="value"
:numRows="7"
:cellSize="16"
:cellPadding="3"
:cellCornerRadius="3"
:events="events"
/>
<VisHeatmap
value={value}
numRows={7}
cellSize={16}
cellPadding={3}
cellCornerRadius={3}
events={events}
/>
const heatmap = new Heatmap<number>({
value,
numRows: 7,
cellSize: 16,
cellPadding: 3,
cellCornerRadius: 3,
events
})
CSS Variables
All supported CSS variables and their default values
--vis-heatmap-cell-fill-color: #ebedf0;
--vis-heatmap-cell-stroke-color: transparent;
--vis-heatmap-cell-stroke-width: 0px;
--vis-heatmap-cell-cursor: default;
--vis-heatmap-color-1: #9be9a8;
--vis-heatmap-color-2: #40c463;
--vis-heatmap-color-3: #30a14e;
--vis-heatmap-color-4: #216e39;
--vis-heatmap-label-color: #6e7781;
--vis-heatmap-label-font-size: 12px;
--vis-heatmap-label-font-family: undefined;
--vis-heatmap-label-font-weight: 400;
--vis-dark-heatmap-cell-fill-color: #161b22;
--vis-dark-heatmap-color-1: #0e4429;
--vis-dark-heatmap-color-2: #006d32;
--vis-dark-heatmap-color-3: #26a641;
--vis-dark-heatmap-color-4: #39d353;
--vis-dark-heatmap-label-color: #7d8590;
Component Props
| Name | Type | Description |
|---|---|---|
| * required property |