{
  "schemaVersion": 1,
  "id": "bar-chart-race",
  "version": "1.0.0",
  "status": "published",
  "kind": "text",
  "title": "Bar Chart Race",
  "summary": "The racing bar chart: your names and numbers over the years, bars overtaking each other until a winner emerges.",
  "description": "Type your entries and their values for each period (years, months, weeks) and watch them race: bars grow and slide past each other, values tick up and the period rolls over in the corner. Choose the title, the units, how many bars are on screen, a dark or light theme and a colour palette. The sample figures are made up; replace them with your own.",
  "role": "Data animation (Canvas 2D, full frame): a ranked bar chart that animates over time.",
  "collections": [
    "kinetic-text"
  ],
  "tags": [
    "bar chart race",
    "chart",
    "data",
    "ranking",
    "leaderboard",
    "statistics",
    "infographic",
    "animated chart",
    "top 10"
  ],
  "synonyms": [
    "racing bar chart",
    "bar race",
    "animated bar chart",
    "data race",
    "ranking animation",
    "leaderboard animation",
    "top ten over time",
    "chart race"
  ],
  "useCases": [
    "\"Most popular over the years\" videos for YouTube and shorts",
    "Sales, votes or scores changing month by month in a presentation",
    "A league table or leaderboard that updates week by week"
  ],
  "featured": null,
  "publishedAt": "2026-10-05",
  "implementation": {
    "target": "canvas2d",
    "runtime": "browser",
    "language": "javascript",
    "module": "esm",
    "entry": "src/bar-chart-race.js",
    "exports": [
      {
        "name": "createBarChartRace",
        "signature": "createBarChartRace({ entries, periods?, title?, subtitle?, prefix?, unit?, decimals?, visible?, palette?, theme?, badges?, background?, duration?, intro?, hold?, fontFamily? }) => { draw(ctx, { time }, width, height), valuesAt(time), ranksAt(time), periodAt(time), timing }",
        "description": "The race. entries are { name, values[] } with one value per period; draw paints the whole frame for a time in seconds."
      },
      {
        "name": "parseRaceData",
        "signature": "parseRaceData(text) => { name, values }[]",
        "description": "Reads compact text: 'Apples 42 48 55; Bananas 50 52 54' (rows split by ; or new lines)."
      },
      {
        "name": "parsePeriods",
        "signature": "parsePeriods(text, count) => string[]",
        "description": "Period labels from text; a single year counts up ('2019' gives 2019, 2020, …)."
      },
      {
        "name": "PALETTES",
        "signature": "PALETTES: { vivid, pastel, neon }",
        "description": "The built-in bar colours (or pass your own array, or a color per entry)."
      },
      {
        "name": "THEMES",
        "signature": "THEMES: { dark, light }",
        "description": "Background and text colours."
      }
    ],
    "reusable": [
      "src/bar-chart-race.js"
    ],
    "demo": "demo.js",
    "harness": "index.html"
  },
  "dependencies": [
    {
      "id": "apw-runtime",
      "version": "1.0.0",
      "scope": "demo",
      "reason": "Demo harness."
    }
  ],
  "params": [
    {
      "key": "title",
      "label": "Title",
      "type": "text",
      "default": "Fruit sold at the market",
      "maxLength": 60,
      "sensitive": true
    },
    {
      "key": "subtitle",
      "label": "Subtitle",
      "type": "text",
      "default": "Tonnes per year (sample figures)",
      "maxLength": 80,
      "sensitive": true
    },
    {
      "key": "data",
      "label": "Data",
      "help": "One entry per row, rows separated by ; — a name, then one number per period. Example: Apples 42 48 55; Pears 30 45 50",
      "type": "text",
      "default": "Apples 42 48 55 58 61 66; Bananas 50 52 54 60 70 72; Cherries 12 20 35 52 68 85; Mangoes 30 45 50 52 55 57; Grapes 35 38 40 44 50 62; Kiwis 20 22 30 41 45 49; Lemons 25 27 28 31 40 43",
      "maxLength": 200,
      "sensitive": true
    },
    {
      "key": "periods",
      "label": "Periods",
      "help": "One label per number: 2019 2020 2021, or Jan Feb Mar, or with commas when labels have spaces (Week 1, Week 2). A single year counts up.",
      "type": "text",
      "default": "2019",
      "maxLength": 120,
      "sensitive": true
    },
    {
      "key": "prefix",
      "label": "Before values",
      "help": "For example $ or €.",
      "type": "text",
      "default": "",
      "maxLength": 4
    },
    {
      "key": "unit",
      "label": "After values",
      "help": "For example k, % or  pts.",
      "type": "text",
      "default": " t",
      "maxLength": 8
    },
    {
      "key": "decimals",
      "label": "Decimal places",
      "type": "integer",
      "min": 0,
      "max": 2,
      "step": 1,
      "default": 0
    },
    {
      "key": "visible",
      "label": "Bars on screen",
      "type": "integer",
      "min": 3,
      "max": 12,
      "step": 1,
      "default": 7
    },
    {
      "key": "theme",
      "label": "Theme",
      "type": "select",
      "default": "dark",
      "options": [
        {
          "value": "dark",
          "label": "Dark"
        },
        {
          "value": "light",
          "label": "Light"
        }
      ]
    },
    {
      "key": "palette",
      "label": "Colours",
      "type": "select",
      "default": "vivid",
      "options": [
        {
          "value": "vivid",
          "label": "Vivid"
        },
        {
          "value": "pastel",
          "label": "Pastel"
        },
        {
          "value": "neon",
          "label": "Neon"
        }
      ]
    },
    {
      "key": "badges",
      "label": "Initials on bars",
      "type": "boolean",
      "default": true
    }
  ],
  "presets": [
    {
      "id": "default",
      "label": "Fruit market",
      "params": {}
    },
    {
      "id": "sales",
      "label": "Sales by region (light)",
      "params": {
        "title": "Sales by region",
        "subtitle": "Monthly, thousands of dollars (sample figures)",
        "data": "North 120 135 150 148 170 176; South 90 110 160 175 182 201; East 140 138 145 150 155 158; West 60 95 120 160 190 214; Central 100 104 118 121 140 150",
        "periods": "Jan Feb Mar Apr May Jun",
        "prefix": "$",
        "unit": "k",
        "theme": "light"
      }
    },
    {
      "id": "league",
      "label": "League table (neon)",
      "params": {
        "title": "Points after each week",
        "subtitle": "A made-up league",
        "data": "Falcons 3 6 7 10 13 16; Wolves 1 4 7 10 11 14; Otters 3 4 7 8 11 15; Bears 0 3 6 9 12 13; Hawks 1 2 5 6 9 12; Lynxes 3 3 4 7 8 11",
        "periods": "Week 1, Week 2, Week 3, Week 4, Week 5, Week 6",
        "unit": " pts",
        "palette": "neon",
        "visible": 6
      }
    },
    {
      "id": "votes",
      "label": "Class trip vote (pastel)",
      "params": {
        "title": "Where should the class trip go?",
        "subtitle": "Votes so far",
        "data": "Beach 12 25 31 40 52; Zoo 20 28 35 38 41; Museum 8 15 30 44 49; Theme park 25 30 37 42 50; Mountains 5 12 22 35 55",
        "periods": "Mon Tue Wed Thu Fri",
        "unit": " votes",
        "theme": "light",
        "palette": "pastel",
        "visible": 5
      }
    }
  ],
  "output": {
    "width": 1080,
    "height": 1080,
    "fps": 30,
    "durationSeconds": 12,
    "posterSeconds": 8.8,
    "alpha": false,
    "motion": "procedural",
    "loop": {
      "seamless": false,
      "method": "A race from the first period to the last, holding on the final standings."
    },
    "seed": null
  },
  "composition": {
    "inputs": "Entries with one value per period, period labels, the race duration. Time in seconds for each frame.",
    "outputs": "Draws the whole frame on a 2D canvas (or only the chart over your own background with background: false).",
    "coordinates": "Fills the canvas it is given, any aspect ratio; title top left, the period bottom right.",
    "lighting": "None.",
    "timing": "draw is a pure function of time: 0.8 s for the bars to grow in, the periods spread evenly, then 1.5 s on the final standings.",
    "compatibleWith": [
      "gradient-background",
      "kinetic-title",
      "rolling-counter"
    ],
    "notes": [
      "Data, names and titles are treated as private: not stored in shared packs unless you choose to include them.",
      "The panel's Data box holds up to 200 characters; in code, pass as many entries and periods as you like.",
      "System fonts differ between computers; pass fontFamily and load the font first for identical renders."
    ]
  },
  "reuse": [
    "\"Most popular over the years\": a dozen entries, a value per year, visible: 10, and a 30-second duration.",
    "Business update: the light theme with a $ prefix and k unit for monthly sales by region.",
    "Over your own background: draw a Gradient Background first, then the race with background: false."
  ],
  "integration": {
    "newProject": "Create the race once with your entries and periods, then each frame call race.draw(ctx, { time: frame / fps }, width, height).",
    "existingProject": "Copy src/bar-chart-race.js. Build entries from your data (or parseRaceData for quick text), and draw onto your 2D canvas or an overlay canvas above a WebGL scene."
  },
  "performance": {
    "notes": "Overtakes are found once when the race is created; each frame is a few dozen fills and text draws. Negligible.",
    "limits": "Best with up to about 20 entries and 12 bars on screen; names longer than the space after the bar are shortened with an ellipsis."
  },
  "limitations": [
    "Values glide smoothly between the numbers you give; the chart does not show real values in between periods.",
    "Negative values are drawn as zero."
  ],
  "license": {
    "spdx": "MIT",
    "notice": "Copyright (c) 2026 AI Pixel Wizard. Released under the MIT License. See LICENSE in this folder.",
    "thirdParty": []
  },
  "provenance": "Adapted from the owner's threejs-sketches/data-information/leaderboard-shuffle (MIT), rebuilt for AI Pixel Wizard in October 2026 as a 2D bar chart race with your own data: labelled bars, smooth overtakes, ticking values, an animated axis and period, and the frame/time contract.",
  "changelog": [
    {
      "version": "1.0.0",
      "date": "2026-10-05",
      "notes": "Initial release."
    }
  ]
}
