PTENES
MODULE 2.2

📊 Charts & Data

9 templates that turn numbers into animated visuals using pure SVG and interpolate—with no Chart.js or D3. From bars growing in a stagger to a progress ring and a numeric counter with commas.

9
Templates
45
Minutes
Inter
Level
SVG
Type
data[ ] [45, 72, 58, 90] interpolate() frame → height Jan Feb Sea Abr stagger: each bar grows with delay = i × 3 frames video animated chart
1

📊 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 progress = interpolate(frame, [startFrame, endFrame], [0, 1], {'{'}extrapolateRight: "clamp"{'}'});
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.
Paradigm
frame → dimension
Tool
SVG + interpolate
Stagger
i × delay offset
No dependencies
Zero libraries
2

📊 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.

chart-animation.tsx — staggered bars templates/chart-animation.tsx
{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):

B0

Bar 0: animates from frame 0 to 15

inputRange [0, 15] — the first to grow.

B1

Bar 1: animates from frame 3 to 18

inputRange [3, 18] — starts 3 frames later.

Bn

Bar n: inputRange [n×3, n×3+15]

Universal stagger pattern for any N bars.

Stagger
i × 3 frames
SVG Y
Inverted (0=top)
Extrapolate
clamp required
Colors
i % colors.length
3

📈 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.

line-chart — dashoffset technique templates/line-chart.tsx
// 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.

Technique
dashoffset → 0
Works in
any SVG stroke
Length
getTotalLength()
Easing
easeInOut recommended
4

🥧 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.

slice calculation — helper function pie-chart helper
// 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
SVG command
M L A Z
Arc flag
>180° = 1
Donut
circle at the top
Animation
final angle × progress
5

🔢 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.

stat-counter — formatted number templates/stat-counter.tsx
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.

Formula
interpolate → floor → locale
Easing
ease-out quad
Format
toLocaleString()
Suffix
Optional K, M, %
6

⭕ 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.

circular-progress — animated ring templates/circular-progress.tsx
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.

Formula
2πr = circumference
Offset
circum × (1 - progress)
Start
rotate(-90) → top
Center
overlaid text
7

📚 Complete Library — Charts & Data

TemplateDescriptionSVG technique
chart-animation.tsx SVG bars growing with stagger rect height
line-chart.tsx Line drawn from the left dashoffset
pie-chart.tsx Pie chart with animated slices arc path M L A Z
donut-chart.tsx Donut chart with a central metric arc + circle overlay
area-chart.tsx Area filled with a gradient closed path + linearGradient
progress-bars.tsx Horizontal progress bars interpolated width%
stat-counter.tsx Counter with Thousands Separator interpolate + toLocaleString
comparison-chart.tsx Two groups side by side stagger by group
circular-progress.tsx Progress ring with % circle dashoffset 2πr

📌 Module summary

✓
Pure SVG with interpolate — no Chart.js, deterministic and controllable frame by frame.
✓
Stagger = i × delay in inputRange — each bar/dot with independent timing.
✓
dashoffset for drawn lines — works on any SVG stroke.
✓
Arc commands for pie and donut charts — large-arc-flag is critical for slices >180°.
✓
stat-counter with ease-out — interpolate + floor + toLocaleString + easing quad.

Next module:

2.3 — Content Animation: staggered lists, 3D card flips, countdown, particles, and rotating carousel.