Heatmap
Grid intensity reveal for activity and distribution.
Customize
import {Scene, Video} from "odori";
import {Heatmap} from "../components/heatmap/heatmap";
<Video>
<Scene id="heatmap" duration="4s">
<Heatmap
rows={[
[
0.1,
0.3,
0.2,
0.6,
0.4,
0.1,
0.05
],
[
0.2,
0.5,
0.7,
0.8,
0.6,
0.2,
0.1
],
[
0.4,
0.7,
0.9,
1,
0.8,
0.3,
0.15
]
]}
title="Renders per day"
rowLabels={[
"week 1",
"week 2",
"week 3"
]}
/>
</Scene>
</Video>Installation
pnpm odori add component heatmapnpx odori add component heatmapbunx odori add component heatmapEdit the source in videos/components/heatmap/. Use odori diff to compare upstream changes and odori update to apply updates.
Copy these files into your project
videos/components/heatmap/heatmap.tsx
import {Fill, Easing, interpolate, useBrand, useFrame, useDesignScale} from "odori";
export type HeatmapProps = {
/** Rows of intensity values, 0 to 1. */
rows: number[][];
title?: string;
rowLabels?: string[];
};
/**
* Grid intensity revealed cell by cell, in reading order. Intensity is carried
* by opacity of one accent color rather than a rainbow, so the scale stays
* legible at video distance.
*/
export const Heatmap = ({rows, title, rowLabels}: HeatmapProps) => {
const frame = useFrame();
const brand = useBrand();
const scale = useDesignScale();
const columns = Math.max(...rows.map((row) => row.length), 1);
let cellIndex = 0;
return (
<Fill style={{gap: 34 * scale, justifyContent: "center", padding: `${110 * scale}px ${130 * scale}px`}}>
{title ? (
<div style={{fontSize: 56 * scale, fontWeight: 560, letterSpacing: "-0.04em", opacity: interpolate(frame, [0, 16], [0, 1])}}>
{title}
</div>
) : null}
<div style={{display: "grid", gap: 12 * scale}}>
{rows.map((row, rowNumber) => (
<div key={rowNumber} style={{alignItems: "center", display: "flex", gap: 18 * scale}}>
{rowLabels ? (
<span style={{color: brand.colors.muted, fontSize: 26 * scale, width: 200 * scale}}>
{rowLabels[rowNumber]}
</span>
) : null}
<div style={{display: "grid", flex: 1, gap: 12 * scale, gridTemplateColumns: `repeat(${columns}, 1fr)`}}>
{row.map((intensity, columnNumber) => {
const delay = 10 + cellIndex * 1.5;
cellIndex += 1;
const enter = interpolate(frame, [delay, delay + 14], [0, 1], {easing: Easing.standard});
return (
<span
key={columnNumber}
style={{
background: `color-mix(in srgb, ${brand.colors.accent} ${Math.round(intensity * 100)}%, ${brand.colors.surface})`,
borderRadius: 10 * scale,
height: 62 * scale,
opacity: enter,
transform: `scale(${0.86 + enter * 0.14})`,
}}
/>
);
})}
</div>
</div>
))}
</div>
</Fill>
);
};
videos/components/heatmap/heatmap.preview.tsx
import {defineComponentPreview} from "odori/preview";
import {Heatmap} from "./heatmap";
export default defineComponentPreview({
title: "Heatmap",
category: "Data",
description: "Grid intensity reveal for activity and distribution.",
component: Heatmap,
canvas: {width: 1920, height: 1080, duration: "4s"},
controls: {title: {type: "text", defaultValue: "Renders per day"}},
examples: [
{
name: "Activity",
props: {
rowLabels: ["week 1", "week 2", "week 3"],
rows: [
[0.1, 0.3, 0.2, 0.6, 0.4, 0.1, 0.05],
[0.2, 0.5, 0.7, 0.8, 0.6, 0.2, 0.1],
[0.4, 0.7, 0.9, 1, 0.8, 0.3, 0.15],
],
},
},
],
});
Add it to a scene
import {Scene, Video} from "odori";
import {Heatmap} from "../components/heatmap/heatmap";
<Video>
<Scene id="heatmap" duration="4s">
<Heatmap
rows={[
[
0.1,
0.3,
0.2,
0.6,
0.4,
0.1,
0.05
],
[
0.2,
0.5,
0.7,
0.8,
0.6,
0.2,
0.1
],
[
0.4,
0.7,
0.9,
1,
0.8,
0.3,
0.15
]
]}
title="Renders per day"
rowLabels={[
"week 1",
"week 2",
"week 3"
]}
/>
</Scene>
</Video>Timing contract
Use these durations and content limits when composing a scene. Run odori test to check your video.
- Family
- Data
- Recommended duration
- 120 frames · 4s
- Minimum duration
- 60 frames · 2s
- Entrance
- 50 frames
- Exit
- 0 frames
- Reduced motion
- cells appear together
- Requires
- sans font
- Content limits
- rows ≤ 6, columns ≤ 10
Props
| Prop | Type | Default | Required |
|---|---|---|---|
rowsRows of intensity values, 0 to 1. | number[][] | — | Yes |
title | string | — | — |
rowLabels | string[] | — | — |
Related components
All componentsArea chartFilled trend reveal for volume and cumulative change.Bar chartRanked bars with labels, values, and staged growth.Data tableAnimated sorting and row selection.Donut chartPart-to-whole display with a stable center metric.Line chartTime-series path draw with optional focal annotation.Progress ringCircular completion display with restrained motion.