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 templa
By iart-ai · 395 installs
npx skills add iart-ai/data-animation-skills --skill chart-animation
Source repository · Upstream listing
Data Video
Turn a dataset into a chart that moves with intent: a bar chart race, a growing line, a count up stat. The craft is mapping numbers to pixels every frame, easing the value (not just the opacity), and pacing the data so a viewer can actually read it.
When to use
Bar chart race / ranking over time (the headline use case).
Animated line/area reveal, animated counters and number tickers.
One chart design rendered across many datasets (template × CSV → N videos).
The one rule that prevents 90% of bugs
Drive every value from the current frame — never from wall clock time or a library's internal animation loop. A video frame is rendered deterministically; if a bar's height comes from a CSS transition or a D3/GSAP/Chart.js animation, it flickers or desyncs because the renderer and the animation clock disagree. Compute the displayed value as a pure function of frame.
In Remotion, disable all third party chart animations ( animation: false in Chart.js, no D3 .transition() ) and let useCurrentFrame() be the only clock. D3 is still great for scales and shapes ( scaleLinear , scaleBand , line() ) — just not its timers.
Map data → pixels
A chart is two functions: a value scale and a frame interpolator. Keep them separate.
Layer Tool Job
Value scale scaleLinear / scaleBand data units → px (height, x position)
Frame interpolation interpolate / spring frame → eased progress 0→1
Display value round + Intl.NumberFormat progress → the number a human reads
Easing value changes
Ease the value the same way you'd ease motion. For a count up or a bar growing in, easeOutCubic reads as "settling on a number." For rank changes, prefer spring so a bar that overtakes another has a little weight.
Never linearly interpolate a value the viewer is reading unless it's a steady time axis (a clock, a year counter) — linear motion on a headline number feels robotic.
Counters / number tickers
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.
For currency/percent use Intl.NumberFormat("en US", { style: "currency", currency: "USD" }) . Tabular figures keep each digit the same width so the number doesn't wiggle.
Bar chart race — rank transitions
The signature move. Real datasets are sparse (yearly/monthly rows); a smooth race needs interpolated keyframes between data rows plus bars that slide to new ranks. The y position comes from a rank that is itself interpolated, so an overtake animates as a glide, not a jump.
Render with stable keys per item (so React tracks each bar across reorders), animate y and width off the per frame value, and show a moving year/date label. See references/bar chart race.md for a complete, runnable Remotion component.
Pacing data over time
Animation that's too fast is unreadable. Budget time per data event, not per second of polish.
Beat Budget Why
Intro / first frame hold 1–1.5s let the viewer read axes + units before motion
Per data row (race) 0.3–0.6s fast enough to feel alive, slow enough to track the leader
A highlighted moment +0.5–1s slow down dwell on the crossover / record / inflection
Final state hold 2–3s the takeaway needs to land and be screenshot able
Reveal progressively — one series, then the next — instead of all data at once; viewers can only track one moving thing at a time.
Labeling & annotation
Numbers without context are noise. Always render: an always visible value label on each bar/point (it animates with the value), a time indicator (year/date), and axis units once . Add a callout only at the moment it matters (a crossover, a peak) — annotate the inflection, then let it fade.
Template × data — batch output
The payoff of code driven charts: one design, many datasets. Make the dataset an input prop, never a hardcoded constant, then render once per file.
Keep colors/fonts/layout in a single theme object so 50 videos stay brand consistent and only the numbers change. See references/data pipeline.md for CSV→props parsing, validation, and the full batch script.
Output checklist
Every animated value is a pure function of useCurrentFrame() ; no library timers.
Numbers are rounded and formatted ( Intl.NumberFormat ), digits use tabular nums .
Race bars carry stable keys; rank changes glide via interpolated rank.
Axes/units labeled once; value labels animate with their value.
Intro hold, readable per row pace, final hold ≥2s.
Dataset is an input prop — one template renders every CSV in the folder.
Deliver & verify (rendered stills → MP4)
Packaged helper ( scripts/ ): tile your stills with scripts/contact sheet.sh sheet.png f hook.png f mid.png f end.png , then assert the encode with scripts/probe mp4.sh out.mp4 [WxH] [fps] . See scripts/README.md .
Remotion is frame deterministic — every bar height, counter value, and rank is a pure function of useCurrentFrame() , so you can render any exact frame headlessly with no seek harness. The deliverable is an MP4 carrying exact numbers , so still inspection is non negotiable here: a one pixel layout bug is forgivable, a wrong digit is not.
Output contract:
A Remotion project with the chart registered ( <Composition + zod schema + defaultProps ), all motion frame driven (no timers / Date.now() / Math.random() , no Chart.js/D3 .transition() clocks).
Deliverable = the rendered out/ .mp4 (plus the project, so the user can re render with new datasets).
Data dependent duration (N rows × frames/row)? compute it in calculateMetadata , not by hand.
Verify loop — render stills → inspect → encode. Render single frames first (cheap, no video encode), then encode only once the numbers and layout are right.
Use npx remotion compositions to read each chart's durationInFrames / fps and pick the end frame.
Data driven / batch (the headline case) : verify ONE representative dataset via stills before batch rendering all N files — a height scale or column mapping bug caught once beats finding it in 50 MP4s.
README demo GIF for free : npx remotion render Race out/demo.gif codec=gif .
Before you finish:
1. npx remotion still renders cleanly at frame 0, mid, and last — no errors, no missing fonts/assets.
2. Every number is exact (rounded, Intl.NumberFormat , tabular nums ) and inside the safe area at each frame.
3. Frame driven only — no Date.now() / Math.random() / library timers (determinism holds in CI).
4. The shipped dataset props render correctly (not just defaultProps ) — right column mapped, right scale.
5. Full MP4 encoded and plays; (optional) GIF rendered for the README.
Reference files
references/bar chart race.md — a complete runnable Remotion bar chart race component: sparse row keyframe interpolation, per frame ranking, gliding y positions, animated value labels, and a D3 scale axis. Plus a vanilla canvas variant.
references/data pipeline.md — CSV/JSON → typed props parsing and validation, the theme object pattern, animated counters/line reveal recipes, and the template×data batch render script for N videos.