Data Visualization Animations in Remotion — Bar Charts, Counters, and Racing Charts
Data Visualization Animations in Remotion — Bar Charts, Counters, and Racing Charts
Animated data visualizations are among the most effective tools in modern video content. A counter that ticks up to 10,000 customers, a bar chart growing to show market share, or a racing chart that ranks changing values over time — these convey information with an immediacy that static graphics cannot match.
In Remotion, all three are straightforward React animations driven by useCurrentFrame(). This guide walks through each pattern with complete, production-usable code.
Why Build Data Animations in Remotion?
In traditional video production, data animations are created in After Effects with expressions, or in specialised tools like Flourish or Data Wrapper. Remotion offers a different trade-off:
- Data is code: pass an array of numbers as props, change the data, re-render. No GUI required.
- Pixel-perfect control: use any CSS layout technique, SVG, or canvas inside your React component.
- Reproducible: the same data always produces the same video. Automated pipelines become trivial.
- Reusable: build the component once, use it for every quarterly report, every social post, every client deck video.
Animated Number Counter
The counter animation is the simplest data visualization: a number that increases from zero (or a start value) to a target over a given duration.
import { interpolate, useCurrentFrame } from "remotion";
interface CounterProps {
from?: number;
to: number;
durationInFrames?: number;
prefix?: string;
suffix?: string;
decimals?: number;
}
export const AnimatedCounter: React.FC<CounterProps> = ({
from = 0,
to,
durationInFrames = 60,
prefix = "",
suffix = "",
decimals = 0,
}) => {
const frame = useCurrentFrame();
const value = interpolate(frame, [0, durationInFrames], [from, to], {
extrapolateRight: "clamp",
easing: (t) => 1 - Math.pow(1 - t, 3), // ease-out cubic
});
return (
<div
style={{
fontFamily: "-apple-system, Segoe UI, Roboto, sans-serif",
fontSize: 120,
fontWeight: 800,
color: "#ffffff",
letterSpacing: -2,
fontVariantNumeric: "tabular-nums",
}}
>
{prefix}
{value.toFixed(decimals).replace(/\B(?=(\d{3})+(?!\d))/g, ",")}
{suffix}
</div>
);
};
Usage example
// "$10,000" counting up over 2 seconds at 30fps
<AnimatedCounter from={0} to={10000} durationInFrames={60} prefix="$" />
// "98.6%" counting up
<AnimatedCounter from={90} to={98.6} durationInFrames={45} suffix="%" decimals={1} />
The fontVariantNumeric: "tabular-nums" setting prevents the text from jumping left and right as digit widths change — a small but crucial detail for polish.
Animated Bar Chart
An animated bar chart grows from zero height to its final value, optionally with a stagger delay between bars.
import { interpolate, spring, useCurrentFrame, useVideoConfig } from "remotion";
interface BarData {
label: string;
value: number;
color?: string;
}
interface AnimatedBarChartProps {
data: BarData[];
maxValue?: number;
chartHeight?: number;
staggerFrames?: number;
barColor?: string;
}
export const AnimatedBarChart: React.FC<AnimatedBarChartProps> = ({
data,
maxValue,
chartHeight = 400,
staggerFrames = 8,
barColor = "#4f46e5",
}) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const computedMax = maxValue ?? Math.max(...data.map((d) => d.value));
return (
<div
style={{
display: "flex",
alignItems: "flex-end",
gap: 16,
height: chartHeight,
fontFamily: "-apple-system, Segoe UI, Roboto, sans-serif",
}}
>
{data.map((bar, i) => {
const barFrame = i * staggerFrames;
const progress = spring({
fps,
frame: Math.max(0, frame - barFrame),
config: { damping: 20, stiffness: 100 },
});
const barHeight = (bar.value / computedMax) * chartHeight * progress;
return (
<div
key={i}
style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 8 }}
>
{/* Value label above bar */}
<div
style={{
color: "#fff",
fontSize: 18,
fontWeight: 700,
opacity: progress,
}}
>
{bar.value.toLocaleString()}
</div>
{/* Bar itself */}
<div
style={{
width: 60,
height: barHeight,
background: bar.color ?? barColor,
borderRadius: "6px 6px 0 0",
}}
/>
{/* X-axis label */}
<div style={{ color: "rgba(255,255,255,0.7)", fontSize: 14 }}>
{bar.label}
</div>
</div>
);
})}
</div>
);
};
Sample data
const salesData: BarData[] = [
{ label: "Q1", value: 42000 },
{ label: "Q2", value: 67000 },
{ label: "Q3", value: 91000 },
{ label: "Q4", value: 138000 },
];
<AnimatedBarChart data={salesData} barColor="#6366f1" />
Racing Bar Chart
A racing bar chart shows ranked data that changes over time. It is one of the most engaging data visualization formats on social media. The implementation has two layers: the current-frame value (interpolated between two snapshots) and the current rank (used to position each bar vertically).
import { interpolate, useCurrentFrame } from "remotion";
interface DataPoint {
[key: string]: number;
}
interface RacingBarChartProps {
snapshots: DataPoint[]; // one object per keyframe
snapshotFrames: number[]; // frame numbers for each snapshot
colors: Record<string, string>;
barHeight?: number;
}
export const RacingBarChart: React.FC<RacingBarChartProps> = ({
snapshots,
snapshotFrames,
colors,
barHeight = 52,
}) => {
const frame = useCurrentFrame();
// Determine which interval we are in
const snapshotIndex = snapshotFrames.findIndex((f) => f > frame) - 1;
const clampedIndex = Math.max(0, Math.min(snapshotIndex, snapshots.length - 2));
const from = snapshots[clampedIndex];
const to = snapshots[clampedIndex + 1] ?? snapshots[clampedIndex];
const fromFrame = snapshotFrames[clampedIndex];
const toFrame = snapshotFrames[clampedIndex + 1] ?? fromFrame + 1;
const t = interpolate(frame, [fromFrame, toFrame], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
// Interpolate all values for this frame
const keys = Object.keys(from);
const currentValues: Record<string, number> = {};
for (const key of keys) {
currentValues[key] = interpolate(t, [0, 1], [from[key], to[key]]);
}
// Sort by value descending
const sorted = keys.sort((a, b) => currentValues[b] - currentValues[a]);
const maxValue = Math.max(...Object.values(currentValues));
return (
<div style={{ position: "relative", width: "100%", height: sorted.length * (barHeight + 12) }}>
{sorted.map((key, rank) => {
const value = currentValues[key];
const barWidth = (value / maxValue) * 80; // percentage of container
return (
<div
key={key}
style={{
position: "absolute",
top: rank * (barHeight + 12),
left: 0,
right: 0,
display: "flex",
alignItems: "center",
gap: 12,
transition: "top 0.1s", // Remotion ignores CSS transitions; rank is per-frame
fontFamily: "-apple-system, Segoe UI, Roboto, sans-serif",
}}
>
<div style={{ width: 100, textAlign: "right", color: "#fff", fontSize: 14, fontWeight: 600 }}>
{key}
</div>
<div
style={{
height: barHeight,
width: `${barWidth}%`,
background: colors[key] ?? "#4f46e5",
borderRadius: "0 6px 6px 0",
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
paddingRight: 12,
color: "#fff",
fontSize: 16,
fontWeight: 700,
}}
>
{Math.round(value).toLocaleString()}
</div>
</div>
);
})}
</div>
);
};
Note on racing charts: in Remotion, CSS transitions are not animated between frames — every frame is a fresh render. The smoothness comes entirely from interpolate driving values on every frame. The “transition” property above has no effect; it is left as a visual hint only.
Combining Elements: A Complete Data Story
These three components can be composed into a complete data story:
import { Sequence } from "remotion";
export const DataStory: React.FC = () => (
<>
{/* Counter reveals headline stat */}
<Sequence from={0} durationInFrames={60}>
<AnimatedCounter to={10000} suffix=" customers" />
</Sequence>
{/* Bar chart shows quarterly breakdown */}
<Sequence from={60} durationInFrames={90}>
<AnimatedBarChart data={salesData} />
</Sequence>
{/* Racing chart shows market ranking over 12 months */}
<Sequence from={150} durationInFrames={120}>
<RacingBarChart snapshots={monthlyData} snapshotFrames={[0,30,60,90,120]} colors={brandColors} />
</Sequence>
</>
);
Using Templates for Data Visualization
Building these components from scratch is achievable for developers, but polishing them to broadcast quality — smooth easing, correct label positioning, responsive layout at multiple aspect ratios — takes significant iteration.
RenderComp provides a range of data visualization templates among its 1,400+ Remotion components. If you are producing regular reporting videos, investor update videos, or social media data content, starting from a polished template that already handles edge cases (negative values, very long labels, ties in ranking) is the practical choice. You own the source code and can modify every detail.
FAQ
Q: Can Remotion animate SVG charts made with D3?
A: Yes. D3 is a JavaScript library for computing shapes and paths — it does not require the DOM. In Remotion, you can call D3’s layout functions inside your component and render the resulting <path> and <rect> elements as React SVG elements, driven by useCurrentFrame().
Q: How do I add a Y-axis with gridlines to my bar chart?
A: Render a set of horizontal <div> or SVG <line> elements at computed positions based on your max value. Calculate the label at each grid line from your data range and round to a readable interval (e.g., every 25,000 units).
Q: Can I import data from a CSV or API?
A: At render time, you can read JSON or CSV files using Node.js fs in a Remotion calculateMetadata function, or fetch from an API. The data is passed to the component as inputProps.
Q: How do I handle very long category labels in a bar chart?
A: Rotate labels with transform: "rotate(-45deg)" or truncate them. For horizontal bars (bar chart rotated 90°), long labels are easier to read and are generally preferred for data with many categories.
Q: What is the best easing for a counter animation?
A: Ease-out cubic (1 - (1-t)^3) works well for most counters — it feels fast at the start and decelerates before landing on the final value, which draws the eye to the number. For counters that represent growth milestones, a slight overshoot (spring with low damping) can feel energetic.
Q: Can I render a racing bar chart with 50+ categories? A: Technically yes, but the chart becomes unreadable. Limit visible bars to the top 10–12 at any frame. You can still compute all values but only render the top N sorted by current value.
Q: How do I export data visualization videos for LinkedIn?
A: LinkedIn accepts MP4 at up to 1920×1080, 30fps. Use npx remotion render --codec=h264 with width={1920} and height={1080} in your composition. For square format (better on mobile), use 1080×1080.
Conclusion
Remotion turns data visualization into a programming problem — which means it benefits from all the tools developers already know: version control, loops, functions, and typed data structures. The three patterns in this guide (counter, bar chart, racing bar chart) cover the vast majority of data storytelling needs in video content.
For teams producing data-driven video regularly, combining these patterns with a template library like RenderComp gives you a production-ready pipeline where the data changes but the animation quality stays consistent.
Ready to ship
Get 1,400+ Remotion Templates
Lifetime license. TypeScript-first. Ship polished video in minutes, not days.
Get RenderComp →