📊 ¿Por qué SVG puro en lugar de Chart.js?
Las bibliotecas de gráficos como Chart.js y D3 están diseñadas para el navegador: dependen del DOM, los eventos y el estado interno. En Remotion, cada frame se calcula de forma independiente y determinista. SVG puro con interpolate es el enfoque correcto: el valor de cada elemento visual es una función directa del frame.
🎯 El modelo mental de los charts en Remotion
Cada elemento visual (barra, punto, arco) tiene una dimensión que crece según el frame. La fórmula es siempre la misma:
const height = targetHeight * progress; // el SVG usa esta altura
✓ SVG puro: ventajas
- ✓100% determinista — mismo fotograma, mismo píxel.
- ✓Control total del timing: cada barra con su propio stagger.
- ✓Cero dependencias adicionales: el bundle del proyecto es más pequeño.
✗ Chart.js en Remotion: problemas
- ✗Las animaciones internas no se sincronizan con el frame de Remotion.
- ✗Canvas puede renderizarse de forma diferente en headless Chrome.
- ✗Es difícil personalizar el timing y el easing.
📊 chart-animation — barras SVG con stagger
O chart-animation.tsx es la plantilla de referencia para gráficos de barras. Cada barra crece desde la base con un delay proporcional a su índice; el stagger da ritmo visual y separa la información visualmente.
{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}
/>
);
})}
📊 ¿Por qué y crece de abajo hacia arriba en SVG?
En SVG, el eje Y está invertido: y=0 es la parte superior, y=altura es la base. Para que una barra crezca desde la base, defines y = chartHeight - padding - currentHeight e height = currentHeight. A medida que aumenta currentHeight, la barra sube visualmente.
Timing de las barras (delay = i × 3):
Barra 0: se anima del frame 0 al 15
inputRange [0, 15] — la primera en crecer.
Barra 1: se anima del frame 3 al 18
inputRange [3, 18] — empieza 3 fotogramas después.
Barra n: inputRange [n×3, n×3+15]
Patrón universal de stagger para cualquier cantidad N de barras.
📈 line-chart — strokeDashoffset
El gráfico de líneas usa la técnica de stroke-dashoffset para dar la ilusión de una línea que se dibuja. El truco: definir stroke-dasharray igual a la longitud total del path, después animar dashoffset de longitud total a cero.
// 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" />
💡 La técnica funciona con cualquier path SVG
No solo polyline: cualquier <path>, <circle> con stroke-dasharray puedes usar esta técnica. Así animas bordes circulares (circular-progress), firmas cursivas y cualquier forma dibujada a mano.
🥧 pie-chart y donut-chart — arcos SVG
Los gráficos circulares y de anillo son los más solicitados en videos de negocios. La técnica usa Comandos de arco SVG (A) calculados trigonométricamente a partir de los porcentajes de cada sector. El donut agrega un círculo blanco central para crear el hueco.
// 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`; };
📊 La bandera del arco (large-arc-flag)
El quinto parámetro del comando A é o large-arc-flag: 1 = usa el arco mayor (más de 180°), 0 = usa el menor. Olvidarlo hace que las porciones mayores al 50% se dibujen del lado equivocado.
flag = endAngle - startAngle > 180 ? 1 : 0
🔢 stat-counter — contador con coma
O stat-counter.tsx es uno de los componentes más solicitados en videos de marketing y resultados: un número grande que cuenta desde 0 hasta el valor objetivo, con separador de miles. Todo con 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>;
💡 Easing ease-out para contadores
Los contadores tienen más impacto con Easing.out(Easing.quad) — el conteo empieza rápido y se desacelera al llegar al valor final, dando énfasis al número de destino. Linear parece mecánico. ease-in hace lo contrario (lento-rápido) y rara vez funciona aquí.
⭕ circular-progress — anillo con dashoffset
O circular-progress.tsx usa la misma técnica del line-chart aplicada a un <circle> SVG. La circunferencia total es 2πr, y el dashoffset comienza allí y llega hasta cero a medida que aumenta el progreso. En el centro, una métrica numérica animada con 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 */ />
📊 ¿Por qué rotate(-90)?
El SVG empieza a dibujar círculos a las 3 en punto (0°). El transform="rotate(-90 cx cy)" gira el punto de partida hacia arriba (las 12 en punto), que es donde esperamos que comience un anillo de progreso. Sin esto, el anillo empieza por la derecha.
📚 Biblioteca completa — Charts & Datos
📌 Resumen del módulo
Próximo módulo:
2.3 — Animación de contenido: listas con stagger, card-flip 3D, countdown, partículas y carrusel giratorio.