📖 Estrutura narrativa — o arco do dado
Um Data-Story eficaz não começa com o gráfico — começa com o problema. O espectador precisa entender o que está em jogo antes de ver o número. A sequência de cenas cria esse arco narrativo automaticamente quando você ordena bem o Series.
📈 O arco de 5 cenas
💡 Regra dos 5–8 segundos por cena
Cada cena de dados deve ter entre 5 e 8 segundos. Menos que 5 s: o espectador não processa o dado. Mais que 8 s: o ritmo cai e parece apresentação de PowerPoint. As transições cross-dissolve de 10–15 frames cobrem a mudança sem interromper o fluxo.
📊 Gráfico de barras animado — chart-animation
O chart-animation.tsx usa uma array de valores e anima cada barra crescendo de 0 até sua altura proporcional. O stagger entre barras faz o gráfico parecer estar "se preenchendo" — o olho do espectador acompanha a barra maior, que é normalmente o dado mais importante.
const data = [42, 68, 55, 91, 37]; // valores % const maxH = 200; // px de altura máxima {data.map((val, i) => { const delay = i * 6; // stagger de 6 frames const barHeight = interpolate( frame, [delay, delay + 45], [0, (val / 100) * maxH], { extrapolateRight: "clamp", easing: Easing.out(Easing.quad) } ); // rótulo aparece quando barra chega ao topo const labelOpacity = interpolate( frame, [delay + 40, delay + 55], [0, 1], { extrapolateRight: "clamp" } ); return <div key={i} style={{ height: `${barHeight}px`, opacity: 1 }}> <span style={{ opacity: labelOpacity }}>{val}%</span> </div>; })}
✓ Barras que funcionam
- ✓Stagger de 4–8 frames entre barras — perceptível sem ser arrastado.
- ✓Rótulo aparece quando a barra termina — 15 frames depois do delay da barra.
- ✓Easing.out para crescimento — começa rápido e desacelera ao chegar ao topo.
✗ Erros de gráfico
- ✗Todas as barras crescendo ao mesmo tempo — perde o efeito de revelação.
- ✗Rótulo aparece antes da barra terminar — parece um bug.
- ✗Easing.in para crescimento — começa lento demais, parece travado.
🍩 Donut chart de proporção
O donut-chart.tsx usa SVG com stroke-dasharray e stroke-dashoffset animados. Cada fatia é um <circle> com comprimento de arco calculado a partir da porcentagem. O reveal progressivo segue a mesma lógica das barras: Easing.out, revelação de cada fatia com pequeno stagger.
Calcular circunferência e offsets
const c = 2 * Math.PI * r — circunferência do círculo. Para uma fatia de 42%, o comprimento de arco é c * 0.42. O strokeDasharray é "arco restante" e o offset é rotacionado para posicionar corretamente.
Animação do strokeDashoffset
Começa com dashoffset = c (fatia invisível) e anima até dashoffset = c - arco (fatia completa). O resultado é a fatia "desenhando" no sentido horário.
Legenda aparece após reveal
Cada cor do donut tem uma legenda que entra com fade-in quando a fatia correspondente termina de aparecer. Legenda e fatia ficam visualmente associadas.
🔢 Stat-counter e text-highlight combinados
A cena de dado central combina dois templates: stat-counter.tsx com o número animando de 0 até o valor, e text-highlight.tsx que destaca uma frase contextual com background colorido crescendo de 0% até 100% de width. Os dois entram em stagger — primeiro o número, depois o texto.
const frame = useCurrentFrame(); // número conta de 0 → 68% em 60 frames const pct = Math.round(interpolate( frame, [0, 60], [0, 68], { easing: Easing.out(Easing.cubic), extrapolateRight: "clamp" } )); // highlight cresce de 0 → 100% a partir do frame 50 const hlWidth = interpolate( frame, [50, 80], [0, 100], { extrapolateLeft: "clamp", extrapolateRight: "clamp" } ); return <AbsoluteFill style={{ display:"flex", flexDirection:"column", alignItems:"center", justifyContent:"center" }}> <span style={{ fontSize: 120, fontWeight: 800, color: "#fbbf24" }}>{pct}%</span> <div style={{ position: "relative" }}> <div style={{ position:"absolute", inset:0, background:"rgba(251,191,36,0.25)", width:`${hlWidth}%` }}/> <span>dos vídeos são assistidos sem som</span> </div> </AbsoluteFill>;
⚔️ Gráfico de comparação — comparison-chart
O comparison-chart.tsx mostra duas barras lado a lado que crescem de forma diferente — o argumento visual mais poderoso para mostrar diferença. A regra de ouro: a barra "ruim" aparece primeiro, cresce um pouco, e então a barra "boa" ultrapassa ela dramaticamente.
✓ Comparação impactante
- ✓Barra negativa (vermelha) aparece primeiro — âncora baixa.
- ✓Barra positiva (âmbar/verde) cresce depois e ultrapassa — drama visual.
- ✓Rótulo de diferença percentual aparece com spring no topo da barra maior.
✗ Comparação fraca
- ✗Ambas as barras crescendo juntas — perde o efeito de "ultrapassagem".
- ✗Cores parecidas para os dois lados — comparação não fica evidente.
- ✗Sem rótulo de diferença — o espectador precisa calcular mentalmente.
🔀 Transições cross-dissolve entre cenas
O cross-dissolve é a transição ideal para Data-Story: fluida o suficiente para não quebrar o raciocínio, mas marcando claramente a mudança de cena. Implementa-se sobrepondo dois Sequence com overlap e animando as opacidades em sentidos opostos nos frames de sobreposição.
// Cena A: frames 0–90 | Cena B: frames 78–168 | overlap: 78–90 const DISSOLVE = 12; // frames de sobreposição return ( <AbsoluteFill> <Sequence from={0} durationInFrames={90}> <div style={{ opacity: interpolate(frame, [90 - DISSOLVE, 90], [1, 0], { extrapolateLeft: "clamp", extrapolateRight: "clamp" }) }}> <CenaA /> </div> </Sequence> <Sequence from={90 - DISSOLVE}> <div style={{ opacity: interpolate(frame, [90 - DISSOLVE, 90], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" }) }}> <CenaB /> </div> </Sequence> </AbsoluteFill> );
💡 10–15 frames de dissolve
Dissolve de 10 frames (0.33 s a 30fps) é quase imperceptível mas suaviza o corte. Dissolve de 15 frames (0.5 s) é notável e mais elegante. Acima de 20 frames começa a parecer propositalmente dramático — só use se quiser esse efeito.
🏁 Resumo do Módulo
Próximo Módulo:
4.4 — Reels / Shorts: vídeo vertical 9:16, typewriter, glitch, sound-wave, bokeh e subscribe reminder.