Back to Remotion

Chart & Data Visualization

packages/template-prompt-to-motion-graphics/src/skills/charts.md

4.0.4572.2 KB
Original Source

Bar Chart Animations

Stagger bar entrances with 3-5 frame delays and use spring() for organic motion.

Incorrect (all bars animate together):

tsx
const bars = data.map((item, i) => {
  const height = spring({ frame, fps, config: { damping: 18 } });
  return <div style={{ height: height * item.value }} />;
});

Correct (staggered entrances):

tsx
const STAGGER_DELAY = 5;

const bars = data.map((item, i) => {
  const delay = i * STAGGER_DELAY;
  const height = spring({
    frame: frame - delay,
    fps,
    config: { damping: 18, stiffness: 80 },
  });
  return <div style={{ height: height * item.value }} />;
});

Always Include Y-Axis Labels

Charts without axis labels are hard to read. Always add labeled tick marks.

Incorrect (no axis):

tsx
<div style={{ display: "flex", alignItems: "flex-end", gap: 8 }}>{bars}</div>

Correct (with Y-axis):

tsx
const yAxisSteps = [0, 25, 50, 75, 100];

<div style={{ display: "flex" }}>
  <div
    style={{
      display: "flex",
      flexDirection: "column",
      justifyContent: "space-between",
    }}
  >
    {yAxisSteps.reverse().map((step) => (
      <span style={{ fontSize: 12, color: "#888" }}>{step}</span>
    ))}
  </div>
  <div
    style={{
      display: "flex",
      alignItems: "flex-end",
      gap: 8,
      borderLeft: "1px solid #333",
    }}
  >
    {bars}
  </div>
</div>;

Value Labels Inside Bars

Position value labels inside bars when height is sufficient, fade in after bar animates.

tsx
const barHeight = normalizedHeight * progress;

<div style={{ height: barHeight, backgroundColor: COLOR_BAR }}>
  {barHeight > 30 && (
    <span style={{ opacity: progress, fontSize: 11 }}>
      {item.value.toLocaleString()}
    </span>
  )}
</div>;

Pie Chart Animation

Animate segments using stroke-dashoffset, starting from 12 o'clock.

tsx
const circumference = 2 * Math.PI * radius;
const segmentLength = (value / total) * circumference;
const offset = interpolate(progress, [0, 1], [segmentLength, 0]);

<circle
  r={radius}
  cx={center}
  cy={center}
  fill="none"
  stroke={color}
  strokeWidth={strokeWidth}
  strokeDasharray={`${segmentLength} ${circumference}`}
  strokeDashoffset={offset}
  transform={`rotate(-90 ${center} ${center})`}
/>;