mcpField
R
remotion-data-viz

remotion-data-viz

github.com/phamthanhnghiaクリエイティブ・メディア360 回インストール14 週間前に更新
リポジトリ

remotion-data-viz について

Remotion のコンポジションとして、アニメーション付きデータ可視化を作るスキルです。React ベースの動画制作者向けに、フレーム決定的なチャートのパターンを示し、カウンター、チャートの描画、recharts、データ連動の尺に対応します。

主な機能

  1. 01現在のフレームに基づく値のアニメーション
  2. 02カウンターやチャートの表示演出
  3. 03決定的な値による recharts の利用
  4. 04データ長に応じた尺の設定

活用シーン

  1. 01動画内にアニメーションカウンターを作成
  2. 02棒・線・ドーナツチャートを描画
  3. 03内部アニメーションを止めて recharts を使用
  4. 04データセットの大きさに応じて尺を設定

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/phamthanhnghia/remotion-agent-skills --skill remotion-data-viz

インストール後、Claude Code で/remotion-data-vizを入力して読み込まれたか確認します。

同梱ファイルreferences/chart-patterns.md

詳細

Remotion データビジュアライゼーション

Remotion のコンポジションとしてのアニメーション付きデータビジュアライゼーションです。フレーム単位で決定論的で、React ベースで、シークできます。

コンポジションの仕様については remotion-core を参照してください。アニメーションのプリミティブについては remotion-animation を参照してください。

基本原則

Remotion のデータビジュアライゼーションは、frame 0 から durationInFrames までアニメーションします。すべてのチャート値は useCurrentFrame() から導出されます。リサイズやデータ変更による再レンダリングはなく、フレームからビジュアル状態への決定論的なマッピングのみが行われます。

よく使うパターン

アニメーション付きカウンター
import {useCurrentFrame, useVideoConfig, interpolate} from 'remotion';

type CounterProps = {
  from: number;
  to: number;
  durationInFrames: number;
  prefix?: string;
  suffix?: string;
};

const AnimatedCounter: React.FC<CounterProps> = ({from, to, durationInFrames, prefix = '', suffix = ''}) => {
  const frame = useCurrentFrame();
  const value = interpolate(frame, [0, durationInFrames], [from, to], {
    extrapolateRight: 'clamp',
    easing: (t) => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t, // ease-in-out-quad
  });

  return (
    <div style={{fontSize: 120, fontWeight: 700, color: '#fff', fontVariantNumeric: 'tabular-nums'}}>
      {prefix}{Math.round(value).toLocaleString()}{suffix}
    </div>
  );
};
バーチャートの表示
import {interpolate, useCurrentFrame} from 'remotion';

type BarProps = {label: string; value: number; maxValue: number; index: number; color: string};

const AnimatedBar: React.FC<BarProps> = ({label, value, maxValue, index, color}) => {
  const frame = useCurrentFrame();
  const STAGGER = 5;
  const DURATION = 40;

  const progress = interpolate(
    frame,
    [index * STAGGER, index * STAGGER + DURATION],
    [0, 1],
    {extrapolateLeft: 'clamp', extrapolateRight: 'clamp'}
  );

  const barWidth = (value / maxValue) * 600 * progress;

  return (
    <div style={{marginBottom: 20}}>
      <div style={{color: '#ccc', fontSize: 24, marginBottom: 8}}>{label}</div>
      <div style={{height: 48, backgroundColor: '#1e1e2e', borderRadius: 8, overflow: 'hidden'}}>
        <div
          style={{
            height: '100%',
            width: barWidth,
            backgroundColor: color,
            borderRadius: 8,
            display: 'flex',
            alignItems: 'center',
            paddingLeft: 16,
          }}
        >
          <span style={{color: 'white', fontWeight: 700}}>{Math.round(value * progress).toLocaleString()}</span>
        </div>
      </div>
    </div>
  );
};
折れ線グラフの描画(SVG)
import {interpolate, useCurrentFrame} from 'remotion';

const LineChart: React.FC<{data: number[]; width: number; height: number}> = ({data, width, height}) => {
  const frame = useCurrentFrame();
  const {durationInFrames} = useVideoConfig();

  const progress = interpolate(frame, [0, durationInFrames * 0.8], [0, 1], {extrapolateRight: 'clamp'});

  const maxVal = Math.max(...data);
  const points = data
    .slice(0, Math.ceil(data.length * progress))
    .map((v, i) => `${(i / (data.length - 1)) * width},${height - (v / maxVal) * height}`)
    .join(' ');

  return (
    <svg width={width} height={height}>
      <polyline
        points={points}
        fill="none"
        stroke="#6366f1"
        strokeWidth={4}
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
};
円グラフ/ドーナツグラフの表示
const DonutChart: React.FC<{segments: {value: number; color: string}[]; size: number}> = ({segments, size}) => {
  const frame = useCurrentFrame();
  const {durationInFrames} = useVideoConfig();

  const progress = interpolate(frame, [0, durationInFrames * 0.7], [0, 1], {extrapolateRight: 'clamp'});
  const radius = size / 2 - 20;
  const cx = size / 2;
  const cy = size / 2;
  const total = segments.reduce((s, seg) => s + seg.value, 0);

  let cumulative = 0;
  const paths = segments.map((seg, i) => {
    const startAngle = (cumulative / total) * 2 * Math.PI - Math.PI / 2;
    cumulative += seg.value;
    const endAngle = (cumulative / total) * 2 * Math.PI * progress - Math.PI / 2;

    const x1 = cx + radius * Math.cos(startAngle);
    const y1 = cy + radius * Math.sin(startAngle);
    const x2 = cx + radius * Math.cos(endAngle);
    const y2 = cy + radius * Math.sin(endAngle);
    const largeArc = endAngle - startAngle > Math.PI ? 1 : 0;

    return (
      <path
        key={i}
        d={`M ${cx} ${cy} L ${x1} ${y1} A ${radius} ${radius} 0 ${largeArc} 1 ${x2} ${y2} Z`}
        fill={seg.color}
      />
    );
  });

  return <svg width={size} height={size}>{paths}</svg>;
};

recharts の使用

npm install recharts

重要: recharts は内部で requestAnimationFrame と DOM の測定を使用します。recharts に渡す前に spring() と interpolate() を使ってチャートのデータ値を計算してください。recharts の内部アニメーションには依存しないでください(フレーム単位で決定論的ではありません)。

import {BarChart, Bar, XAxis, YAxis} from 'recharts';

const MyChart: React.FC = () => {
  const frame = useCurrentFrame();
  const progress = interpolate(frame, [0, 60], [0, 1], {extrapolateRight: 'clamp'});

  const data = [
    {name: 'A', value: Math.round(1200 * progress)},
    {name: 'B', value: Math.round(800 * progress)},
    {name: 'C', value: Math.round(1600 * progress)},
  ];

  return (
    <BarChart width={600} height={400} data={data}>
      <XAxis dataKey="name" />
      <YAxis />
      <Bar dataKey="value" fill="#6366f1" isAnimationActive={false} />
    </BarChart>
  );
};

recharts コンポーネントでは、常に isAnimationActive={false} を設定してください。内部アニメーションはフレーム単位で決定論的ではありません。

データ駆動型コンポジションでの calculateMetadata

コンポジションの長さがデータの長さに依存する場合は、calculateMetadata を使用します。

import {Composition} from 'remotion';

type DataVizProps = {data: {label: string; value: number}[]};

const dataViz = {
  component: DataVizScene,
  calculateMetadata: async ({props}: {props: DataVizProps}) => {
    const fps = 30;
    const framesPerBar = 20;
    const durationInFrames = props.data.length * framesPerBar + 60; // padding
    return {durationInFrames, fps};
  },
};

参考資料

タスク参照先
アニメーション付きカウンター、バーチャート、折れ線グラフ、円グラフのパターンreferences/chart-patterns.md
recharts との統合(isAnimationActive、データバインディング)references/recharts.md
React レンダリングを使った D3 の計算(scale、path)references/d3-in-remotion.md
動的な長さに対応する calculateMetadataremotion-core/references/calculate-metadata.md
データの表示に使う spring/interpolateremotion-keyframes

コメント