Area chart
Filled trend reveal for volume and cumulative change.
Customize
import {Scene, Video} from "odori";
import {AreaChart} from "../components/area-chart/area-chart";
<Video>
<Scene id="area-chart" duration="4s">
<AreaChart
values={[
2,
5,
9,
14,
22,
31,
44,
60
]}
title="Frames rendered"
detail="Cumulative, across every format."
/>
</Scene>
</Video>Installation
pnpm odori add component area-chartnpx odori add component area-chartbunx odori add component area-chartEdit the source in videos/components/area-chart/. Use odori diff to compare upstream changes and odori update to apply updates.
Copy these files into your project
videos/components/area-chart/area-chart.tsx
import {Fill, Easing, interpolate, useBrand, useFrame, useDesignScale} from "odori";
export type AreaChartProps = {
values: number[];
title?: string;
detail?: string;
};
/**
* A filled trend for volume and cumulative change. The fill wipes in with the
* path, so the area never appears ahead of the line that bounds it.
*/
export const AreaChart = ({values, title, detail}: AreaChartProps) => {
const frame = useFrame();
const brand = useBrand();
const scale = useDesignScale();
const max = Math.max(...values, 1);
const draw = interpolate(frame, [12, 62], [0, 1], {easing: Easing.standard});
const points = values.map((value, index) => ({
x: (index / Math.max(1, values.length - 1)) * 100,
y: 100 - (value / max) * 92,
}));
const line = points.map((p, i) => `${i === 0 ? "M" : "L"} ${p.x} ${p.y}`).join(" ");
const area = `${line} L 100 100 L 0 100 Z`;
return (
<Fill style={{gap: 30 * scale, justifyContent: "center", padding: `${110 * scale}px ${130 * scale}px`}}>
{title ? (
<div style={{fontSize: 58 * scale, fontWeight: 560, letterSpacing: "-0.04em", opacity: interpolate(frame, [0, 16], [0, 1])}}>
{title}
</div>
) : null}
<div style={{height: 460 * scale, width: "100%"}}>
<svg preserveAspectRatio="none" style={{clipPath: `inset(0 ${(1 - draw) * 100}% 0 0)`, height: "100%", width: "100%"}} viewBox="0 0 100 100">
<path d={area} fill={brand.colors.accent} opacity={0.18} />
<path d={line} fill="none" stroke={brand.colors.accent} strokeWidth={9 * scale} vectorEffect="non-scaling-stroke" />
</svg>
</div>
{detail ? (
<div style={{color: brand.colors.muted, fontSize: 32 * scale, opacity: interpolate(frame, [56, 74], [0, 1])}}>{detail}</div>
) : null}
</Fill>
);
};
videos/components/area-chart/area-chart.preview.tsx
import {defineComponentPreview} from "odori/preview";
import {AreaChart} from "./area-chart";
export default defineComponentPreview({
title: "Area chart",
category: "Data",
description: "Filled trend reveal for volume and cumulative change.",
component: AreaChart,
canvas: {width: 1920, height: 1080, duration: "4s"},
controls: {
title: {type: "text", defaultValue: "Frames rendered"},
detail: {type: "text", defaultValue: "Cumulative, across every format."},
},
examples: [{name: "Adoption", props: {values: [2, 5, 9, 14, 22, 31, 44, 60]}}],
});
Add it to a scene
import {Scene, Video} from "odori";
import {AreaChart} from "../components/area-chart/area-chart";
<Video>
<Scene id="area-chart" duration="4s">
<AreaChart
values={[
2,
5,
9,
14,
22,
31,
44,
60
]}
title="Frames rendered"
detail="Cumulative, across every format."
/>
</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
- 78 frames · 2.6s
- Entrance
- 62 frames
- Exit
- 0 frames
- Reduced motion
- the filled area appears at once
- Requires
- sans font
- Content limits
- points ≤ 16
Props
| Prop | Type | Default | Required |
|---|---|---|---|
values | number[] | — | Yes |
title | string | — | — |
detail | string | — | — |
Related components
All componentsBar chartRanked bars with labels, values, and staged growth.Data tableAnimated sorting and row selection.Donut chartPart-to-whole display with a stable center metric.HeatmapGrid intensity reveal for activity and distribution.Line chartTime-series path draw with optional focal annotation.Progress ringCircular completion display with restrained motion.