chart-animation
This skill should be used when the user asks to "make a bar chart race", "turn a CSV into a video", "build an animated data visualization", "create an animated statistics video", "animate a chart/graph", "make a number counter/ticker", "animate a ranking over time", or "batch-render one chart template across many datasets". Covers data-driven keyframing, value interpolation, rank transitions, counters, pacing, annotation, and template×data batch output.
1How It Works & Mechanism
The golden rule to prevent 90% of bugs: every displayed value must be derived from the current frame; wall-clock time and internal library animation loops are forbidden.
**Drive every value from the current frame — never from wall-clock time or a library's internal animation loop.**
Split the chart into three decoupled functions: domain mapping, frame interpolation, and display value formatting.
| Value scale | `scaleLinear` / `scaleBand` | data units → px (height, x-position) |
Two hard challenges of a bar chart race: sparse data must be interpolated, and rankings must slide smoothly (y-position derived from interpolated rank).
Real datasets are sparse (yearly/monthly rows); a smooth race needs **interpolated keyframes between data rows** plus bars that slide to new ranks.
Data must be validated before rendering: silent NaNs turn into height-0 bars that are only noticed three minutes later.
Treat the dataset as the single input. Parse, validate, and *fail loud* before rendering — a silent `NaN` becomes a bar of height 0 in the final MP4, which you only notice after a 3-minute render.
Checklist of cleaning rules: strict numerical coercion and validation, explicit gap filling, ascending chronological order, and a cap of ~12 bars.
- **Cap item count.** Beyond ~12 bars a race is unreadable; keep top-N per frame and drop the rest, or pre-aggregate the long tail into "Other".
Counter precision: interpolate numeric value → round → format, and use tabular-nums for fixed glyph width.
Interpolate the underlying number, then format on render. Two musts: **round before formatting** (no `1287.4013`), and **fix the digit box** so the layout doesn't jump as digits change.
Pacing budget calculated "per data event": 0.3–0.6s per row, decelerate at key points, rest for 2–3s on the final frame.
| Per data row (race) | 0.3–0.6s | fast enough to feel alive, slow enough to track the leader |
Annotations appear only at critical moments (crossovers, peaks), fading in and out via time windows.
const eventFrame = fps * 6.2; // when the crossover happens
Verification cannot be skipped: deliverables carry precise numbers; a 1-pixel layout bug is forgivable, but an off-by-one digit is not.
a one-pixel layout bug is forgivable, a wrong digit is not.
2Core Capabilities
3External Dependencies
| Kind | Dependency / Endpoint |
|---|---|
| package | remotion(frame/interpolate/useVideoConfig) |
| package | d3-scale / d3-shape(值域映射与路径生成,仅取其数学,不用其计时器) |
| package | csv-parse(CSV → records,配 skip_empty_lines/trim) |
| cli | ffmpeg / ffprobe(仓库共享 contact-sheet.sh、probe-mp4.sh) |
| network | iart.ai(reference 尾部推广链接,具 utm 参数;仅读者点击才请求) |
4Recommended Governance & Evidence
- scripts/: Local script or command execution: No built-in scripts inside the skill directory; SKILL.md/reference instructs the agent to run `node scripts/build-props.mjs`
- 网络端点: External network access declaration: No business API calls during execution; datasets are provided by the user via CSV/JSON.
- 本地工作目录: File I/O and artifact output: Writes `props/<name>.json`, `out/<name>.mp4`, `out/f-start.png`/`out/f-mid.p` inside user project
5Second-Pass Verification
- [discrepancy] SKILL.md 是否声明了 CSV 解析依赖 — SKILL.md 通篇未提 `csv-parse`(只把 CSV→props 指向 data-pipeline.md),而 reference 直接 `import { parse } from "csv-parse/sync"`;依赖清单在 SKILL.md 层面不完整。
- [discrepancy] race 伪代码是否与 reference 的 clamp 要求一致 — SKILL.md 片段 `const f = easeInOut(t - i);` 未把 f 夹到 [0,1],而 bar-chart-race.md 用 `Math.min(Math.max(tRow - i, 0), 1)` 并把它列入 pitfall 表(Last frame overshoots → Clamp f to [0,1]);直接照抄 SKILL.md 片段会末帧过冲。
- [discrepancy] 示例帧号与 reference 默认节奏是否对齐 — SKILL.md 验证示例给 `--frame=149 # last = durationInFrames - 1`(即 150 帧=5s 总长),而 reference 默认 SECONDS_PER_ROW=1.5 且示例 4 行 → 195 帧(末帧 194)。公式正确但示例数字与 reference 算例不一致。
- [ok] 数字格式化口径是否全文一致 — SKILL.md 与 data-pipeline/bar-chart-race 三处都用 Math.round() + Intl.NumberFormat(en-US) + font-variant-numeric: tabular-nums;percent 变体单独处理分数。
- [ok] 「D3 可用 scale 不可用 transition」的边界是否一致 — SKILL.md 明说 D3 仍适合 scales and shapes,只是不用它的 timers;bar-chart-race.md 实际只 import scaleLinear、无任何 .transition() 调用。
- [ok] 时长推导与 calculateMetadata 声明 — SKILL.md 要求数据相关时长在 calculateMetadata 计算,reference 用 (rows.length - 1) * SECONDS_PER_ROW * fps + hold * fps 推导总帧数,思路一致。
6Verdict & Scenarios
a97e1c50769273d4…