chart-animation
Skill8 starsChanged 5 months agoMITView on GitHub
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.
1 other project uses a version of this.
Adopted by
Most recently taken up by
- willlee1995/skillsword for word, first seen Oct 13, 2026
Other versions1 project uses a version of this.
Word for word · 1
- willlee1995/skills★ 1100% same
Edited · 0
None found.
Wrote their own version · 0
None found.
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository2 other files it gives its agents.
- animated-infographicSkill
- presentation-videoSkill
The file
We split this skill into the rules it gives an agent
6 rules. About 0 are common, 2 are distinctive, and 4 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈2
Distinctive · estimate
This author's own choices. Worth a look.
4
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 55
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 …
- Very specific: line 121
Keep colors/fonts/layout in a single theme object so 50 videos stay brand-consistent and only the numbers change.
- Very specific: line 125
Every animated value is a pure function of useCurrentFrame().
- Very specific: line 159
Use npx remotion compositions to read each chart's durationInFrames/fps and pick the end frame.
- Distinctive: line 160
Data-driven / batch (the headline case): verify ONE representative dataset via stills *before* batch-rendering all N files — a height-scale …
- Very specific: line 165
Every number is exact (rounded, Intl.NumberFormat, tabular-nums) and inside the safe area at each frame.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
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.
import { useCurrentFrame, interpolate } from "remotion";
const frame = useCurrentFrame();
const value = interpolate(frame, [0, 60], [0, 1287], { extrapolateRight: "clamp" });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 |
import { scaleLinear } from "d3-scale";
const yScale = scaleLinear().domain([0, maxValue]).range([0, chartHeight]);
const barHeight = yScale(interpolate(frame, [0, 45], [0, datum.value], { extrapolateRight: "clamp" }));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.
const easeOutCubic = (t) => 1 - Math.pow(1 - t, 3);
const t = easeOutCubic(interpolate(frame, [start, end], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" }));
const shown = from + (to - from) * t;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.
const n = Math.round(interpolate(frame, [0, 50], [0, 1287], { extrapolateRight: "clamp" }));
const label = new Intl.NumberFormat("en-US").format(n); // "1,287"
// CSS: font-variant-numeric: tabular-nums; line-height: 1; → no width jitter, no clippingFor 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.
// frame → continuous time between sparse rows, then rank each item by its interpolated value
const t = frame / fps; // seconds
const i = Math.min(Math.floor(t), rows.length - 2);
const f = easeInOut(t - i); // 0→1 within the current row pair
const vals = items.map((it) => ({
it,
v: rows[i][it] + (rows[i + 1][it] - rows[i][it]) * f, // interpolated value
}));
vals.sort((a, b) => b.v - a.v); // rank this frame
vals.forEach((d, rank) => { d.y = rank * (barH + gap); }); // y from interpolated rankRender 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.
// Remotion: data is a prop; same composition, different CSV → different video
export const Race = ({ data }) => { /* …reads `data`, hardcodes nothing… */ };# render the same template for every dataset in /data
for f in data/*.json; do
name=$(basename "$f" .json)
npx remotion render Race "out/$name.mp4" --props="$f"
doneKeep 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 usetabular-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 withscripts/contact-sheet.sh sheet.png f-hook.png f-mid.png f-end.png, then assert the encode withscripts/probe-mp4.sh out.mp4 [WxH] [fps]. Seescripts/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>+ zodschema+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.
# 1. Frame-exact stills at start / mid / end (PNG, headless, fast) — with the SHIPPED props
npx remotion still Race out/f-start.png --frame=0 --props='{"data":[...]}'
npx remotion still Race out/f-mid.png --frame=90 --props='{"data":[...]}'
npx remotion still Race out/f-end.png --frame=149 --props='{"data":[...]}' # last = durationInFrames - 1
# 2. Inspect each PNG — FIDELITY (axis labels, value labels, year/date, ranks, units all EXACT —
# round + tabular-nums working, no 1287.4013) AND artifacts (bar overflow, off-canvas label,
# clipped safe-area, missing font, wrong data binding / wrong column mapped).
# 3. Only after the stills check out, encode:
npx remotion render Race out/race.mp4 --props='{"data":[...]}'- Use
npx remotion compositionsto read each chart'sdurationInFrames/fpsand 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:
npx remotion stillrenders cleanly at frame 0, mid, and last — no errors, no missing fonts/assets.- Every number is exact (rounded,
Intl.NumberFormat,tabular-nums) and inside the safe area at each frame. - Frame-driven only — no
Date.now()/Math.random()/ library timers (determinism holds in CI). - The shipped dataset props render correctly (not just
defaultProps) — right column mapped, right scale. - 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.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

