View raw

Pattern card: data-viz#

Proven idioms for stat reveals, dashboards, and KPI rows — count-up flipbooks, growing bars, progress tracks. Load this alongside the core agent card when the brief is about numbers (clipkit docs pattern-data-viz, or MCP read_docs topic pattern-data-viz). Mirrors the repo's patterns/data-viz.md.

When to use: stat reveals, year-in-review dashboards, KPI/progress rows — numbers and bars ARE the story. Loads alongside the core card; idioms only.

Count-up flipbook (the data-viz idiom)#

An animating number is N short-lived copies of ONE text element. Rows are the display strings; element duration == repeat_stagger, so exactly one copy is on screen at a time. The last row holds with "duration": "end".

{ "type": "text", "id": "value-1", "layer": 16, "time": 1.0, "duration": 0.14,
  "x": 1760, "y": 356, "x_anchor": "100%", "font_size": 44,
  "font_family": "Inter, sans-serif", "font_weight": 800, "fill_color": "#67e8f9",
  "text": "{v}", "repeat_stagger": 0.14,
  "animations": [ { "type": "fade-in", "time": "start", "duration": 0.05 } ],
  "repeat_data": [
    { "v": "0" }, { "v": "1.4M" }, { "v": "2.4M" }, { "v": "3.1M" },
    { "v": "3.6M" }, { "v": "3.9M" }, { "v": "4.1M" }, { "v": "4.2M", "duration": "end" } ] }

Copies are UNIFORM in time, so bake easing into the values: shrinking deltas toward the target read as ease-out. Format in the data ("4.2M", "1,240", "4.8/5") — the renderer never formats numbers.

Exact-sync variant: rows can instead each patch time + duration (element-local seconds) when a counter must hit landmarks with a bar or beat:

"repeat_data": [
  { "v": "0K",   "time": 1.1,  "duration": 0.07 },
  { "v": "61K",  "time": 1.17, "duration": 0.07 },
  { "v": "114K", "time": 1.24, "duration": 0.07 },
  { "v": "318K", "time": 1.31, "duration": 5.69 } ]

Bar rows: one element per role#

A 4-row dashboard is 3 elements (tracks, bars, labels) plus one flipbook per value. i spaces the grid, row data drives width/colors/text, repeat_stagger cascades the rows.

{ "type": "shape", "id": "tracks", "layer": 4, "time": 0, "duration": "end",
  "x": 160, "y": { "expr": "402 + i * 170" }, "width": 1600, "height": 50,
  "border_radius": 25, "fill_color": "#242a4a", "opacity": 0.55, "repeat": 4 },
{ "type": "shape", "id": "bars", "layer": 8, "time": 1.0, "duration": "end",
  "x": 160, "y": { "expr": "402 + i * 170" }, "height": 50, "border_radius": 25,
  "width": { "expr": "ease(t, 0, 1.1, 0, w)" }, "repeat_stagger": 0.4,
  "gradient": { "type": "linear", "angle": 90,
    "stops": [ { "offset": 0, "color": "{c0}" }, { "offset": 1, "color": "{c1}" } ] },
  "effects": [ { "type": "glow", "radius": 18, "intensity": 0.55, "color": "{c0}" } ],
  "repeat_data": [
    { "w": 1472, "c0": "#22d3ee", "c1": "#6366f1" },
    { "w": 1088, "c0": "#6366f1", "c1": "#a855f7" },
    { "w": 1248, "c0": "#a855f7", "c1": "#ec4899" },
    { "w": 1536, "c0": "#ec4899", "c1": "#f59e0b" } ] },
{ "type": "text", "id": "labels", "layer": 12, "time": 1.0, "duration": "end",
  "x": 160, "y": { "expr": "360 + i * 170" }, "font_size": 28, "letter_spacing": 2,
  "font_family": "Inter, sans-serif", "font_weight": 600, "fill_color": "#aab4d9",
  "text": "{label}", "repeat_stagger": 0.4,
  "animations": [ { "type": "fade-in", "time": "start", "duration": 0.4 } ],
  "repeat_data": [ { "label": "HOURS WATCHED" }, { "label": "NEW SUBSCRIBERS" },
    { "label": "TITLES ADDED" }, { "label": "AVG. RATING" } ] }

Track and bar share x/y/height/border_radius; bar target w = fraction of track width (1472 = 92% of 1600). Bare w/c0 bind inside exprs; {c0} substitutes in strings — even inside nested gradient stops.

Gotchas#

  • One flipbook element per counter: an element can't repeat in two dimensions, so counters never merge into the bar/label sets.
  • Right-anchor counters ("x_anchor": "100%"): row width varies, anchoring pins the number to the column edge instead of jittering.
  • Forget the final "duration": "end" row and the number vanishes mid-video.
  • Copy k starts at element time + k * stagger. Use the SAME stagger on bars and labels (0.4 here) so each row arrives as a unit; start values when their bar starts, not when it lands.
  • Land everything well before root duration so the end frame holds calm.