关于 remotion-data-viz
用于将动态数据可视化构建为 Remotion composition 的技能,面向制作 React 视频内容的开发者。它提供基于帧的确定性动画模式,涵盖计数器、图表揭示、recharts 集成和按数据确定时长。
主要功能
- 01根据当前帧驱动数值动画
- 02构建计数器和图表揭示效果
- 03以确定性数值使用 recharts
- 04根据数据长度设置时长
适用场景
- 01在视频中制作动态计数器
- 02揭示柱状图、折线图或圆环图
- 03关闭内部动画以使用 recharts
- 04根据数据集大小设置 composition 时长
安装
复制下面的命令,在终端中运行。
$
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 中的数据可视化会从第 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 测量。将spring()和interpolate()用于计算图表数据值,然后再传给 recharts——不要依赖 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 计算(比例尺、路径) | references/d3-in-remotion.md |
| 使用 calculateMetadata 设置动态时长 | remotion-core/references/calculate-metadata.md |
| 使用 spring/interpolate 实现数据显现 | remotion-keyframes |
评论
支持 Markdown