📊 Why use pure SVG instead of Chart.js?
Chart libraries like Chart.js and D3 are designed for the browser — they depend on the DOM, events, and internal state. In Remotion, each frame is calculated independently and deterministically. Pure SVG with interpolate is the right approach: each visual element's value is a direct function of the frame.
🎯 The mental model for charts in Remotion
Each visual element (bar, dot, arc) has a dimension that grows according to the frame. The formula is always the same:
const height = targetHeight * progress; // the SVG uses this height
✓ Pure SVG: advantages
- ✓100% deterministic — same frame, same pixel.
- ✓Full timing control: each bar has its own stagger.
- ✓Zero extra dependencies — the project’s smallest bundle.
✗ Chart.js in Remotion: problems
- ✗Internal animations don’t sync with the Remotion frame.
- ✗Canvas may render differently in headless Chrome.
- ✗Hard to customize timing and easing.
📊 chart-animation — staggered SVG bars
O chart-animation.tsx is the reference template for bar charts. Each bar grows from the bottom with a delay proportional to its index—the stagger adds visual rhythm and visually separates the information.
{data.map((point, i) => {
const barHeight = (point.y / 100) * (chartHeight - padding * 2);
const barProgress = interpolate(
frame,
[i * 3, 15 + i * 3], // stagger
[0, 1],
{ extrapolateRight: "clamp" }
);
const currentHeight = barHeight * barProgress;
return (
<rect
key={i}
x={barX}
y={chartHeight - padding - currentHeight} /* crescer de baixo */
width={barWidth}
height={currentHeight}
fill={colors[i % colors.length]}
rx={4}
/>
);
})}
📊 Why does y increase from bottom to top in SVG?
In SVG, the Y axis is inverted: y=0 is the top, y=height is the bottom. For a bar to grow from the bottom, you define y = chartHeight - padding - currentHeight e height = currentHeight. As currentHeight increases, the bar rises visually.
Bar timing (delay = i × 3):
Bar 0: animates from frame 0 to 15
inputRange [0, 15] — the first to grow.
Bar 1: animates from frame 3 to 18
inputRange [3, 18] — starts 3 frames later.
Bar n: inputRange [n×3, n×3+15]
Universal stagger pattern for any N bars.
📈 line-chart — strokeDashoffset
The line chart uses the technique of stroke-dashoffset to give the illusion of a line being drawn. The trick: set stroke-dasharray equal to the total path length, then animate dashoffset of total length to zero.
// 1. Calcular pontos do polyline const points = data.map((d, i) => `${x0 + i * xStep},${y0 - d.value * scale}` ).join(" "); // 2. Comprimento total (estimado ou via ref) const totalLength = 600; // ou getTotalLength() em useRef // 3. Animar o offset de comprimento a 0 const dashOffset = interpolate( frame, [0, 60], [totalLength, 0], { extrapolateRight: "clamp" } ); return <polyline points={points} strokeDasharray={totalLength} strokeDashoffset={dashOffset} stroke="#60a5fa" strokeWidth=3 fill="none" />
💡 The technique works with any SVG path
Not just polyline — any <path>, <circle> with stroke-dasharray you can use this technique. It’s how you animate circular borders (circular-progress), cursive signatures, and any hand-drawn shape.
🥧 pie-chart & donut-chart — SVG arcs
Pie and doughnut charts are the most requested charts in business videos. The technique uses SVG arc commands (A) calculated trigonometrically from each slice's percentage. The donut adds a white circle in the center to create the hole.
// converte ângulo → ponto no círculo const polarToCartesian = (cx, cy, r, angle) => ({ x: cx + r * Math.cos((angle - 90) * Math.PI / 180), y: cy + r * Math.sin((angle - 90) * Math.PI / 180), }); // gera o "d" de uma fatia const describeArc = (cx, cy, r, start, end) => { const s = polarToCartesian(cx, cy, r, start); const e = polarToCartesian(cx, cy, r, end); const flag = end - start > 180 ? 1 : 0; return `M ${cx} ${cy} L ${s.x} ${s.y} A ${r} ${r} 0 ${flag} 1 ${e.x} ${e.y} Z`; };
📊 The arc flag (large-arc-flag)
The fifth parameter of the command A é o large-arc-flag: 1 = uses the larger arc (more than 180°), 0 = uses the smaller one. Forgetting this makes slices larger than 50% draw on the wrong side.
flag = endAngle - startAngle > 180 ? 1 : 0
🔢 stat-counter — counter with a comma
O stat-counter.tsx is one of the most requested components in marketing and results videos: a large number that counts from 0 to the target value, formatted with a thousands separator. All with interpolate + Math.floor + toLocaleString.
const frame = useCurrentFrame(); const targetValue = 1234567; const currentValue = interpolate( frame, [0, 60], [0, targetValue], { extrapolateRight: "clamp", easing: Easing.out(Easing.quad), } ); // formatar com separador de milhar const display = Math.floor(currentValue) .toLocaleString("pt-BR"); return <div>{display}</div>;
💡 Ease-out easing for counters
Counters become more impactful with Easing.out(Easing.quad) — The count starts fast and slows as it approaches the final value, emphasizing the target number. Linear feels mechanical. ease-in does the opposite (slow-fast) and rarely works here.
⭕ circular-progress — ring with dashoffset
O circular-progress.tsx uses the same technique as line-chart applied to a <circle> SVG. The total circumference is 2πr, and the dashoffset starts there and goes to zero as progress increases. In the center, an animated numerical metric with stat-counter.
const radius = 80; const circumference = 2 * Math.PI * radius; // ~502px const progress = interpolate( frame, [0, 60], [0, targetPercent / 100], { extrapolateRight: "clamp" } ); const dashOffset = circumference * (1 - progress); return <circle r={radius} cx={100} cy={100} fill="none" stroke="#3b82f6" strokeWidth={12} strokeDasharray={circumference} strokeDashoffset={dashOffset} strokeLinecap="round" transform="rotate(-90 100 100)" /* começa no topo */ />
📊 Why rotate(-90)?
The SVG starts drawing circles at 3 o’clock (0°). The transform="rotate(-90 cx cy)" rotates the starting point to the top (12 o'clock), where we expect a progress ring to start. Without this, the ring starts on the right side.
📚 Complete Library — Charts & Data
📌 Module summary
Next module:
2.3 — Content Animation: staggered lists, 3D card flips, countdown, particles, and rotating carousel.