Bar chart
Ranked bars with labels, values, and staged growth.
Customize
import {Scene, Video} from "odori";
import {BarChart} from "../components/bar-chart/bar-chart";
<Video>
<Scene id="bar-chart" duration="4s">
<BarChart
data={[
{
label: "Manual edit",
value: 42
},
{
label: "Template tool",
value: 18
},
{
label: "Odori",
value: 4
}
]}
title="Time to first preview"
unit="s"
highlight={2}
/>
</Scene>
</Video>Installation
pnpm odori add component bar-chartnpx odori add component bar-chartbunx odori add component bar-chartEdit the source in videos/components/bar-chart/. Use odori diff to compare upstream changes and odori update to apply updates.
Copy these files into your project
videos/components/bar-chart/bar-chart.tsx
import {Fill, Easing, interpolate, useBrand, useFrame, useDesignScale} from "odori";
export type BarDatum = {label: string; value: number};
export type BarChartProps = {
data: BarDatum[];
title?: string;
/** Appended to every printed value. */
unit?: string;
/** Index drawn in the accent color. */
highlight?: number;
};
/**
* Ranked bars that grow from a shared baseline. The scale comes from the data,
* never from a rounded guess, so two charts in one video stay comparable.
*/
export const BarChart = ({data, title, unit = "", highlight}: BarChartProps) => {
const frame = useFrame();
const brand = useBrand();
const scale = useDesignScale();
const max = Math.max(...data.map((item) => item.value), 1);
return (
<Fill style={{gap: 40 * 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], {easing: Easing.standard}),
}}
>
{title}
</div>
) : null}
<div style={{display: "grid", gap: 22 * scale}}>
{data.map((item, index) => {
const delay = 10 + index * 6;
const grow = interpolate(frame, [delay, delay + 30], [0, 1], {easing: Easing.standard});
const accent = index === highlight;
return (
<div key={item.label} style={{alignItems: "center", display: "flex", gap: 24 * scale}}>
<span style={{color: brand.colors.muted, fontSize: 30 * scale, width: 320 * scale}}>{item.label}</span>
<div style={{background: brand.colors.border, borderRadius: 999, flex: 1, height: 34 * scale}}>
<div
style={{
background: accent ? brand.colors.accent : brand.colors.foreground,
borderRadius: 999,
height: "100%",
width: `${(item.value / max) * 100 * grow}%`,
}}
/>
</div>
<span
style={{
fontSize: 30 * scale,
fontVariantNumeric: "tabular-nums",
textAlign: "right",
width: 180 * scale,
}}
>
{(item.value * grow).toFixed(item.value % 1 === 0 ? 0 : 1)}
{unit}
</span>
</div>
);
})}
</div>
</Fill>
);
};
videos/components/bar-chart/bar-chart.preview.tsx
import {defineComponentPreview} from "odori/preview";
import {BarChart} from "./bar-chart";
export default defineComponentPreview({
title: "Bar chart",
category: "Data",
description: "Ranked bars with labels, values, and staged growth.",
component: BarChart,
canvas: {width: 1920, height: 1080, duration: "4s"},
controls: {
title: {type: "text", defaultValue: "Time to first preview"},
unit: {type: "text", defaultValue: "s"},
highlight: {type: "number", defaultValue: 2, min: 0, max: 5},
},
examples: [
{
name: "Comparison",
props: {
data: [
{label: "Manual edit", value: 42},
{label: "Template tool", value: 18},
{label: "Odori", value: 4},
],
highlight: 2,
},
},
],
});
Add it to a scene
import {Scene, Video} from "odori";
import {BarChart} from "../components/bar-chart/bar-chart";
<Video>
<Scene id="bar-chart" duration="4s">
<BarChart
data={[
{
label: "Manual edit",
value: 42
},
{
label: "Template tool",
value: 18
},
{
label: "Odori",
value: 4
}
]}
title="Time to first preview"
unit="s"
highlight={2}
/>
</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
- bars appear at full length
- Requires
- sans font
- Content limits
- bars ≤ 6, labelLength ≤ 28
Props
| Prop | Type | Default | Required |
|---|---|---|---|
data | BarDatum[] | — | Yes |
title | string | — | — |
unitAppended to every printed value. | string | "" | — |
highlightIndex drawn in the accent color. | number | — | — |
Related components
All componentsArea chartFilled trend reveal for volume and cumulative change.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.