{
  "name": "insightcharts",
  "generated": true,
  "components": [
    {
      "level": "atom",
      "component": "ArcDiagram",
      "name": "arc-diagram",
      "title": "Arc diagram",
      "category": "poster",
      "description": "Places entities in order on a line and joins each related pair with a semicircular arc whose thickness is the value of the connection; dots are sized by each entity's total.",
      "whenToUse": "Connections among up to about 20 entities that have a meaningful order (stops on a line, chapters, age groups, teams by size): trips between stations, trade between neighbours, characters sharing scenes.",
      "avoid": "Flows with a direction through stages (use sankey-chart); one source splitting into parts (use fan-flow); a full matrix of every pair where exact values matter (use heatmap); more than about 20 entities or 60 connections (use heatmap).",
      "props": [
        {
          "name": "from",
          "type": "string",
          "required": true,
          "description": "Field naming one end of each connection. With `select: true` a click selects the entity under this field name, so inside `<InsightData>` other charts are filtered on `from`; pass `select: \"<field>\"` to link on the field that holds the entity in the shared rows."
        },
        {
          "name": "to",
          "type": "string",
          "required": true,
          "description": "Field naming the other end."
        },
        {
          "name": "value",
          "type": "string",
          "description": "Value field: arc thickness. Omit to count rows. Rows joining the same pair are summed, in either direction."
        },
        {
          "name": "order",
          "type": "string[]",
          "description": "Entity names in the order they sit on the line. Entities not listed follow in data order."
        },
        {
          "name": "orientation",
          "type": "\"auto\" | \"horizontal\" | \"vertical\"",
          "default": "\"auto\"",
          "description": "Line along the bottom with arcs above, or down the left with arcs to the right. Auto turns vertical on narrow screens."
        },
        {
          "name": "colorBy",
          "type": "\"series\" | \"none\"",
          "description": "`series`: each arc in the colour of its first entity; past the size of the theme's palette the extra colours are derived from the theme's own. `none`: every arc in the accent. Default: series while the entities fit the palette (at most 8), none beyond."
        },
        {
          "name": "arcLabels",
          "type": "boolean",
          "default": "true",
          "description": "Print values at the top of the arcs, largest first, wherever they do not collide. The values that do not fit are printed while their entity is hovered or selected, and every value is in the tooltip."
        },
        {
          "name": "names",
          "type": "Record<string, string>",
          "description": "Display names for entity keys."
        }
      ],
      "example": {
        "type": "arc-diagram",
        "data": [
          {
            "a": "Harbour",
            "b": "Market",
            "trips": 4200
          },
          {
            "a": "Harbour",
            "b": "Quay",
            "trips": 900
          },
          {
            "a": "Quay",
            "b": "Market",
            "trips": 1300
          },
          {
            "a": "Market",
            "b": "Town Hall",
            "trips": 2600
          },
          {
            "a": "Market",
            "b": "University",
            "trips": 3100
          },
          {
            "a": "Town Hall",
            "b": "University",
            "trips": 1500
          },
          {
            "a": "Town Hall",
            "b": "Hospital",
            "trips": 1100
          },
          {
            "a": "University",
            "b": "Park",
            "trips": 800
          },
          {
            "a": "University",
            "b": "Airport",
            "trips": 1900
          },
          {
            "a": "Park",
            "b": "Hospital",
            "trips": 600
          },
          {
            "a": "Hospital",
            "b": "Stadium",
            "trips": 700
          },
          {
            "a": "Stadium",
            "b": "Airport",
            "trips": 1200
          },
          {
            "a": "Harbour",
            "b": "Airport",
            "trips": 2300
          },
          {
            "a": "Market",
            "b": "Stadium",
            "trips": 1700
          },
          {
            "a": "Quay",
            "b": "University",
            "trips": 500
          }
        ],
        "from": "a",
        "to": "b",
        "value": "trips",
        "order": [
          "Harbour",
          "Quay",
          "Market",
          "Town Hall",
          "University",
          "Park",
          "Hospital",
          "Stadium",
          "Airport"
        ],
        "colorBy": "none",
        "format": "integer",
        "title": "Harbour and Market stations exchanged 4,200 trips a day",
        "subtitle": "Daily trips between pairs of stations on one tram line, both directions; dots show each station's total",
        "note": "Illustrative figures for an invented line."
      }
    },
    {
      "level": "atom",
      "component": "AreaChart",
      "name": "area-chart",
      "title": "Area chart",
      "category": "trend",
      "description": "Shows a quantity over time as a filled area from zero: one series, stacked series, shares of 100%, or a streamgraph.",
      "whenToUse": "A volume or cumulative quantity over time where the filled magnitude matters; stacked when the total and its composition both matter; percent when only the mix matters; stream for the changing mix of many series over a long period.",
      "avoid": "Comparing the trajectories of several series against each other (use line-chart: only the bottom band of a stack has a flat baseline); a few time points (use bar-chart with stack); more than about 6 bands.",
      "props": [
        {
          "name": "x",
          "type": "string",
          "required": true,
          "description": "Time or ordered category field. ISO dates are placed on a true time axis."
        },
        {
          "name": "y",
          "type": "string | string[]",
          "required": true,
          "description": "Value field, or several (one area each)."
        },
        {
          "name": "series",
          "type": "string",
          "description": "Field that splits rows into areas (long data)."
        },
        {
          "name": "stack",
          "type": "boolean | \"percent\"",
          "default": "false",
          "description": "Stack the areas; `percent` normalises every x position to 100%."
        },
        {
          "name": "stream",
          "type": "boolean",
          "default": "false",
          "description": "Streamgraph: stack around a centred baseline. The value axis is dropped because only band thickness is readable."
        },
        {
          "name": "curve",
          "type": "\"linear\" | \"smooth\" | \"step\"",
          "default": "\"linear\"",
          "description": "Edge shape. A streamgraph defaults to smooth."
        },
        {
          "name": "labels",
          "type": "boolean",
          "description": "Name each band inside itself where it fits; the rest go to the legend. Default: on when stacked."
        },
        {
          "name": "names",
          "type": "Record<string, string>",
          "description": "Display names for series keys."
        }
      ],
      "example": {
        "type": "area-chart",
        "data": [
          {
            "year": "2014",
            "gw": 180
          },
          {
            "year": "2015",
            "gw": 229
          },
          {
            "year": "2016",
            "gw": 306
          },
          {
            "year": "2017",
            "gw": 405
          },
          {
            "year": "2018",
            "gw": 512
          },
          {
            "year": "2019",
            "gw": 633
          },
          {
            "year": "2020",
            "gw": 774
          },
          {
            "year": "2021",
            "gw": 945
          },
          {
            "year": "2022",
            "gw": 1177
          },
          {
            "year": "2023",
            "gw": 1610
          }
        ],
        "x": "year",
        "y": "gw",
        "names": {
          "gw": "Installed solar capacity"
        },
        "format": "{value} GW",
        "title": "World solar capacity rose from 180 GW in 2014 to 1,610 GW in 2023",
        "subtitle": "Cumulative installed solar PV capacity, gigawatts",
        "source": "IRENA, Renewable Capacity Statistics (rounded)",
        "note": "Figures are approximate, for illustration"
      }
    },
    {
      "level": "atom",
      "component": "ArrowChart",
      "name": "arrow-chart",
      "title": "Arrow chart",
      "category": "comparison",
      "description": "Draws an arrow per category from a start value to an end value, showing the direction and size of each shift.",
      "whenToUse": "Shifts between two moments across up to about 25 categories when some rise and some fall: swings between elections, before / after a policy.",
      "avoid": "When both levels matter more than the direction (use dumbbell-chart); more than two points in time (use slope-chart or line-chart); a single value per category (use bar-chart).",
      "props": [
        {
          "name": "category",
          "type": "string",
          "required": true,
          "description": "Category field: one arrow each."
        },
        {
          "name": "from",
          "type": "string",
          "required": true,
          "description": "Field with the start value, or (long data) the `series` value that marks it."
        },
        {
          "name": "to",
          "type": "string",
          "required": true,
          "description": "Field with the end value, or (long data) the `series` value that marks it."
        },
        {
          "name": "series",
          "type": "string",
          "description": "Long data: the field whose values are `from` and `to`."
        },
        {
          "name": "value",
          "type": "string",
          "default": "\"value\"",
          "description": "Long data: the measure field."
        },
        {
          "name": "good",
          "type": "\"up\" | \"down\" | \"none\"",
          "default": "\"up\"",
          "description": "Direction drawn in the positive colour. `none` uses the accent for every arrow."
        },
        {
          "name": "sort",
          "type": "\"change\" | \"from\" | \"to\" | \"none\"",
          "default": "\"change\"",
          "description": "Row order."
        },
        {
          "name": "order",
          "type": "\"desc\" | \"asc\"",
          "default": "\"desc\"",
          "description": "Sort direction."
        },
        {
          "name": "labels",
          "type": "boolean",
          "description": "Start and end values at the tail and head. Default: on for up to 14 rows; otherwise a value axis is drawn."
        },
        {
          "name": "changeLabel",
          "type": "boolean | string",
          "default": "false",
          "description": "Print the signed change in a right-hand column; a string sets the column header."
        },
        {
          "name": "zero",
          "type": "boolean",
          "default": "false",
          "description": "Start the value axis at zero."
        },
        {
          "name": "names",
          "type": "Record<string, string>",
          "description": "Display names for the `from` and `to` keys."
        }
      ],
      "example": {
        "type": "arrow-chart",
        "data": [
          {
            "group": "Under 30",
            "y2019": 62,
            "y2024": 71
          },
          {
            "group": "30 to 44",
            "y2019": 58,
            "y2024": 63
          },
          {
            "group": "45 to 64",
            "y2019": 49,
            "y2024": 47
          },
          {
            "group": "65 and over",
            "y2019": 41,
            "y2024": 34
          }
        ],
        "category": "group",
        "from": "y2019",
        "to": "y2024",
        "names": {
          "y2019": "2019",
          "y2024": "2024"
        },
        "format": "{value}%",
        "changeLabel": "Change",
        "height": 220,
        "title": "Support among under-30s rose from 62% in 2019 to 71% in 2024",
        "subtitle": "Share in favour of the congestion charge, by age group, 2019 to 2024",
        "source": "City residents survey",
        "note": "Illustrative figures."
      }
    },
    {
      "level": "atom",
      "component": "BarChart",
      "name": "bar-chart",
      "title": "Bar chart",
      "category": "comparison",
      "description": "Compares a value across categories with bars from a zero baseline.",
      "whenToUse": "Ranking or comparing up to about 20 categories; grouped for a few series, stacked when the total and its composition both matter.",
      "avoid": "Change over many time points (use line-chart); two values per category (use dumbbell-chart); more than about 5 stacked segments.",
      "props": [
        {
          "name": "x",
          "type": "string",
          "required": true,
          "description": "Category field."
        },
        {
          "name": "y",
          "type": "string | string[]",
          "required": true,
          "description": "Value field, or several for grouped / stacked bars."
        },
        {
          "name": "series",
          "type": "string",
          "description": "Field that splits rows into series (long data)."
        },
        {
          "name": "orientation",
          "type": "\"auto\" | \"horizontal\" | \"vertical\"",
          "default": "\"auto\"",
          "description": "Horizontal for rankings and long labels, vertical for time."
        },
        {
          "name": "sort",
          "type": "\"none\" | \"asc\" | \"desc\"",
          "description": "Bar order. Default: descending for rankings, data order for time."
        },
        {
          "name": "stack",
          "type": "boolean | \"percent\"",
          "default": "false",
          "description": "Stack series; `percent` normalises to 100%."
        },
        {
          "name": "labels",
          "type": "boolean",
          "description": "Value labels at bar ends. Default: on for one series of up to 12 bars."
        },
        {
          "name": "names",
          "type": "Record<string, string>",
          "description": "Display names for series keys."
        }
      ],
      "example": {
        "type": "bar-chart",
        "data": [
          {
            "country": "Norway",
            "share": 98
          },
          {
            "country": "Brazil",
            "share": 89
          },
          {
            "country": "Canada",
            "share": 68
          }
        ],
        "x": "country",
        "y": "share",
        "format": "{value}%",
        "title": "Renewables supplied 98% of Norway's electricity in 2023"
      }
    },
    {
      "level": "atom",
      "component": "BeeswarmChart",
      "name": "beeswarm-chart",
      "title": "Beeswarm chart",
      "category": "distribution",
      "description": "Shows a distribution as one dot per row along a value axis, with dots stacked sideways where they would collide.",
      "whenToUse": "Showing a distribution while keeping every individual visible: 30 to about 2,000 named things (countries, players, schools), optionally in a few rows by group, with one or two highlighted.",
      "avoid": "More than about 2,000 rows or when only the summary matters (use histogram or box-plot); many groups with few rows each (use strip-plot); two numeric measures (use scatter-chart).",
      "props": [
        {
          "name": "value",
          "type": "string",
          "required": true,
          "description": "Numeric field that positions each dot along the axis."
        },
        {
          "name": "group",
          "type": "string",
          "description": "Field that splits the dots into rows, one swarm per group."
        },
        {
          "name": "series",
          "type": "string",
          "description": "Field that colours dots by category. Beyond 6 categories the rest turn grey."
        },
        {
          "name": "label",
          "type": "string",
          "description": "Field naming each dot, for tooltips, `highlight` and labels on highlighted dots."
        },
        {
          "name": "radius",
          "type": "number",
          "description": "Dot radius in px. Default 5 down to 3 by row count; shrinks if a swarm would overflow its row."
        },
        {
          "name": "median",
          "type": "boolean",
          "default": "false",
          "description": "Mark each row's median with a vertical tick."
        },
        {
          "name": "names",
          "type": "Record<string, string>",
          "description": "Display names for series keys."
        }
      ],
      "example": {
        "type": "beeswarm-chart",
        "data": [
          {
            "country": "Qatar",
            "rate": 0.4
          },
          {
            "country": "Kuwait",
            "rate": 0.6
          },
          {
            "country": "UAE",
            "rate": 0.9
          },
          {
            "country": "Saudi Arabia",
            "rate": 1.2
          },
          {
            "country": "Japan",
            "rate": 2.5
          },
          {
            "country": "Italy",
            "rate": 2.9
          },
          {
            "country": "Germany",
            "rate": 3
          },
          {
            "country": "United States",
            "rate": 3.6
          },
          {
            "country": "France",
            "rate": 3.1
          },
          {
            "country": "Spain",
            "rate": 3.3
          },
          {
            "country": "United Kingdom",
            "rate": 3.4
          },
          {
            "country": "Canada",
            "rate": 3.2
          },
          {
            "country": "Australia",
            "rate": 3.1
          },
          {
            "country": "South Korea",
            "rate": 2.4
          },
          {
            "country": "Netherlands",
            "rate": 2.8
          },
          {
            "country": "Sweden",
            "rate": 2.6
          },
          {
            "country": "Norway",
            "rate": 2.5
          },
          {
            "country": "Switzerland",
            "rate": 2.3
          },
          {
            "country": "Poland",
            "rate": 4.2
          },
          {
            "country": "Portugal",
            "rate": 3.1
          },
          {
            "country": "Greece",
            "rate": 3.9
          },
          {
            "country": "Ireland",
            "rate": 2.7
          },
          {
            "country": "Denmark",
            "rate": 2.4
          },
          {
            "country": "Finland",
            "rate": 2.9
          },
          {
            "country": "Austria",
            "rate": 2.7
          },
          {
            "country": "Belgium",
            "rate": 2.9
          },
          {
            "country": "Czechia",
            "rate": 3.4
          },
          {
            "country": "Hungary",
            "rate": 4.4
          },
          {
            "country": "Romania",
            "rate": 5.6
          },
          {
            "country": "Bulgaria",
            "rate": 5.9
          },
          {
            "country": "Mexico",
            "rate": 12.4
          },
          {
            "country": "Brazil",
            "rate": 14.2
          },
          {
            "country": "Argentina",
            "rate": 9.8
          },
          {
            "country": "Chile",
            "rate": 7.9
          },
          {
            "country": "Colombia",
            "rate": 13.1
          },
          {
            "country": "Peru",
            "rate": 11.6
          },
          {
            "country": "China",
            "rate": 6.3
          },
          {
            "country": "India",
            "rate": 26.6
          },
          {
            "country": "Indonesia",
            "rate": 21
          },
          {
            "country": "Vietnam",
            "rate": 19.8
          },
          {
            "country": "Thailand",
            "rate": 8.1
          },
          {
            "country": "Malaysia",
            "rate": 7.4
          },
          {
            "country": "Philippines",
            "rate": 24.3
          },
          {
            "country": "Pakistan",
            "rate": 55.8
          },
          {
            "country": "Bangladesh",
            "rate": 28.9
          },
          {
            "country": "Turkey",
            "rate": 9.1
          },
          {
            "country": "Iran",
            "rate": 12
          },
          {
            "country": "Egypt",
            "rate": 19.2
          },
          {
            "country": "South Africa",
            "rate": 33.5
          },
          {
            "country": "Kenya",
            "rate": 37.2
          },
          {
            "country": "Ghana",
            "rate": 42.1
          },
          {
            "country": "Ethiopia",
            "rate": 46.8
          },
          {
            "country": "Tanzania",
            "rate": 44.3
          },
          {
            "country": "Uganda",
            "rate": 41
          },
          {
            "country": "Nigeria",
            "rate": 107.2
          },
          {
            "country": "Niger",
            "rate": 115.2
          },
          {
            "country": "Chad",
            "rate": 101.1
          },
          {
            "country": "Mali",
            "rate": 94
          },
          {
            "country": "Somalia",
            "rate": 106.1
          },
          {
            "country": "Sierra Leone",
            "rate": 100.8
          },
          {
            "country": "DR Congo",
            "rate": 76
          },
          {
            "country": "Angola",
            "rate": 66.5
          },
          {
            "country": "Mozambique",
            "rate": 67.2
          },
          {
            "country": "Cameroon",
            "rate": 69.8
          },
          {
            "country": "Senegal",
            "rate": 37.4
          },
          {
            "country": "Rwanda",
            "rate": 38.3
          },
          {
            "country": "Zambia",
            "rate": 56.2
          },
          {
            "country": "Sudan",
            "rate": 52.9
          },
          {
            "country": "Afghanistan",
            "rate": 55.5
          },
          {
            "country": "Yemen",
            "rate": 39.9
          },
          {
            "country": "Haiti",
            "rate": 56.3
          },
          {
            "country": "Bolivia",
            "rate": 23.7
          },
          {
            "country": "Guatemala",
            "rate": 22.1
          },
          {
            "country": "Nepal",
            "rate": 26.2
          },
          {
            "country": "Myanmar",
            "rate": 40.1
          },
          {
            "country": "Cambodia",
            "rate": 23.6
          },
          {
            "country": "Morocco",
            "rate": 16.7
          },
          {
            "country": "Algeria",
            "rate": 21.5
          },
          {
            "country": "Iraq",
            "rate": 23.4
          },
          {
            "country": "Ukraine",
            "rate": 7.8
          },
          {
            "country": "Russia",
            "rate": 4.8
          },
          {
            "country": "Kazakhstan",
            "rate": 9.7
          },
          {
            "country": "Uzbekistan",
            "rate": 13.6
          },
          {
            "country": "Sri Lanka",
            "rate": 6.4
          }
        ],
        "value": "rate",
        "label": "country",
        "highlight": [
          "Nigeria",
          "India",
          "United States"
        ],
        "title": "Nigeria had 107.2 under-five deaths per 1,000 live births in 2022",
        "subtitle": "Deaths before age five per 1,000 live births, 84 countries, 2022",
        "source": "UN IGME (rounded)",
        "note": "Figures are approximate, for illustration"
      }
    },
    {
      "name": "big-number",
      "component": "BigNumber",
      "title": "Big number",
      "level": "atom",
      "category": "figure",
      "description": "A featured statistic with its label, change and the context that gives it meaning.",
      "whenToUse": "A single headline figure, or a row of three or four key numbers. Always give it context: a comparison, a denominator or a period.",
      "avoid": "Showing a number with no comparison; using a gauge or a one-slice donut for the same job.",
      "props": [
        {
          "name": "value",
          "type": "number | string",
          "required": true,
          "description": "The figure. Numbers are formatted with `format`."
        },
        {
          "name": "label",
          "type": "string",
          "required": true,
          "description": "What it measures, sentence case."
        },
        {
          "name": "context",
          "type": "string",
          "description": "The comparison, denominator or period that gives it meaning."
        },
        {
          "name": "delta",
          "type": "number | string",
          "description": "Change against a baseline. Numbers render as signed percentages (0.12 is +12%)."
        },
        {
          "name": "deltaLabel",
          "type": "string",
          "description": "What the delta is against, e.g. \"vs last quarter\"."
        },
        {
          "name": "upIsGood",
          "type": "boolean",
          "default": "true",
          "description": "Whether an increase is good news; decides the delta colour."
        },
        {
          "name": "format",
          "type": "FormatSpec",
          "default": "\"auto\"",
          "description": "Number format."
        },
        {
          "name": "size",
          "type": "\"hero\" | \"stat\"",
          "default": "\"stat\"",
          "description": "`hero` for the one number a piece leads with."
        },
        {
          "name": "align",
          "type": "\"left\" | \"center\"",
          "default": "\"left\"",
          "description": "Text alignment."
        }
      ],
      "example": {
        "type": "big-number",
        "value": 0.317,
        "format": "percent",
        "label": "Share of global electricity from renewables",
        "delta": 0.021,
        "deltaLabel": "vs 2023",
        "context": "Up from 19% in 2010"
      }
    },
    {
      "level": "atom",
      "component": "BlockBar",
      "name": "block-bar",
      "title": "Block bars",
      "category": "poster",
      "description": "Compares 3 to 12 values as thick rounded bars from a zero baseline, with each value printed inside the end of its bar and the category beneath.",
      "whenToUse": "A short series or ranking that should read as a bold graphic with no axis: ticket sales in each of six years, two paired measures in five regions. Add `detail` for a second small figure above each bar and `marker` for an event between two bars.",
      "avoid": "More than 12 bars, more than two series or stacked parts (use bar-chart); negative values (use diverging-bar-chart); a long time series (use line-chart); values that differ by more than about 50 times, where the short bars vanish (use proportional-area).",
      "props": [
        {
          "name": "name",
          "type": "string",
          "required": true,
          "description": "Category field: one bar (or one pair of bars) per value."
        },
        {
          "name": "value",
          "type": "string | string[]",
          "required": true,
          "description": "Value field, or two fields for paired bars."
        },
        {
          "name": "series",
          "type": "string",
          "description": "Field that splits long-format rows into paired series."
        },
        {
          "name": "detail",
          "type": "string",
          "description": "Field holding a short secondary label printed above each bar, e.g. \"3.5% of GDP\"."
        },
        {
          "name": "marker",
          "type": "{ after: string; label?: string }",
          "description": "A dotted line after the named category, marking an event, with an optional label."
        },
        {
          "name": "orientation",
          "type": "\"auto\" | \"vertical\" | \"horizontal\"",
          "default": "\"auto\"",
          "description": "`auto` draws columns and switches to horizontal bars on narrow widths."
        },
        {
          "name": "names",
          "type": "Record<string, string>",
          "description": "Display names for series keys."
        }
      ],
      "example": {
        "type": "block-bar",
        "data": [
          {
            "year": "2020",
            "tickets": 18400,
            "seats": "41% of seats"
          },
          {
            "year": "2021",
            "tickets": 22900,
            "seats": "49% of seats"
          },
          {
            "year": "2022",
            "tickets": 31200,
            "seats": "58% of seats"
          },
          {
            "year": "2023",
            "tickets": 36800,
            "seats": "64% of seats"
          },
          {
            "year": "2024",
            "tickets": 41500,
            "seats": "69% of seats"
          },
          {
            "year": "2025",
            "tickets": 48200,
            "seats": "74% of seats"
          }
        ],
        "name": "year",
        "value": "tickets",
        "detail": "seats",
        "format": "integer",
        "marker": {
          "after": "2021",
          "label": "Second screen opened"
        },
        "title": "The Portholm Film Festival sold 48,200 tickets in 2025",
        "subtitle": "Tickets sold per year and share of available seats filled, 2020 to 2025",
        "source": "Illustrative data",
        "note": "Invented figures for a fictional festival"
      }
    },
    {
      "level": "atom",
      "component": "BoxPlot",
      "name": "box-plot",
      "title": "Box plot",
      "category": "distribution",
      "description": "Summarises a numeric field per group as median, quartile box, whiskers and outlier dots, computed from raw rows.",
      "whenToUse": "Comparing the centre and spread of a numeric value across several groups, especially with many rows per group and an audience comfortable with quartiles.",
      "avoid": "A general audience or fewer than about 20 rows per group (use strip-plot or beeswarm-chart, which show the observations themselves); one group whose shape matters, such as two peaks, which a box hides (use histogram).",
      "props": [
        {
          "name": "value",
          "type": "string",
          "required": true,
          "description": "Numeric field to summarise. Pass raw rows; quartiles are computed in the chart."
        },
        {
          "name": "group",
          "type": "string",
          "description": "Category field: one box per distinct value. Omit for a single box."
        },
        {
          "name": "label",
          "type": "string",
          "description": "Field naming each row, shown when hovering an outlier."
        },
        {
          "name": "orientation",
          "type": "\"horizontal\" | \"vertical\"",
          "default": "\"horizontal\"",
          "description": "Horizontal leaves room for long names; vertical suits ordered categories."
        },
        {
          "name": "whiskers",
          "type": "\"tukey\" | \"minmax\"",
          "default": "\"tukey\"",
          "description": "`tukey`: furthest point within 1.5 × IQR, the rest drawn as outliers. `minmax`: the extremes."
        },
        {
          "name": "sort",
          "type": "\"none\" | \"asc\" | \"desc\"",
          "default": "\"none\"",
          "description": "Order boxes by median."
        }
      ],
      "example": {
        "type": "box-plot",
        "data": [
          {
            "department": "Emergency",
            "minutes": 12
          },
          {
            "department": "Emergency",
            "minutes": 18
          },
          {
            "department": "Emergency",
            "minutes": 22
          },
          {
            "department": "Emergency",
            "minutes": 25
          },
          {
            "department": "Emergency",
            "minutes": 27
          },
          {
            "department": "Emergency",
            "minutes": 31
          },
          {
            "department": "Emergency",
            "minutes": 34
          },
          {
            "department": "Emergency",
            "minutes": 38
          },
          {
            "department": "Emergency",
            "minutes": 41
          },
          {
            "department": "Emergency",
            "minutes": 44
          },
          {
            "department": "Emergency",
            "minutes": 47
          },
          {
            "department": "Emergency",
            "minutes": 52
          },
          {
            "department": "Emergency",
            "minutes": 58
          },
          {
            "department": "Emergency",
            "minutes": 63
          },
          {
            "department": "Emergency",
            "minutes": 71
          },
          {
            "department": "Emergency",
            "minutes": 84
          },
          {
            "department": "Emergency",
            "minutes": 96
          },
          {
            "department": "Emergency",
            "minutes": 131
          },
          {
            "department": "Emergency",
            "minutes": 188
          },
          {
            "department": "Orthopaedics",
            "minutes": 9
          },
          {
            "department": "Orthopaedics",
            "minutes": 11
          },
          {
            "department": "Orthopaedics",
            "minutes": 14
          },
          {
            "department": "Orthopaedics",
            "minutes": 15
          },
          {
            "department": "Orthopaedics",
            "minutes": 17
          },
          {
            "department": "Orthopaedics",
            "minutes": 19
          },
          {
            "department": "Orthopaedics",
            "minutes": 20
          },
          {
            "department": "Orthopaedics",
            "minutes": 22
          },
          {
            "department": "Orthopaedics",
            "minutes": 24
          },
          {
            "department": "Orthopaedics",
            "minutes": 25
          },
          {
            "department": "Orthopaedics",
            "minutes": 27
          },
          {
            "department": "Orthopaedics",
            "minutes": 29
          },
          {
            "department": "Orthopaedics",
            "minutes": 31
          },
          {
            "department": "Orthopaedics",
            "minutes": 34
          },
          {
            "department": "Orthopaedics",
            "minutes": 38
          },
          {
            "department": "Orthopaedics",
            "minutes": 43
          },
          {
            "department": "Orthopaedics",
            "minutes": 55
          },
          {
            "department": "Cardiology",
            "minutes": 6
          },
          {
            "department": "Cardiology",
            "minutes": 8
          },
          {
            "department": "Cardiology",
            "minutes": 9
          },
          {
            "department": "Cardiology",
            "minutes": 11
          },
          {
            "department": "Cardiology",
            "minutes": 12
          },
          {
            "department": "Cardiology",
            "minutes": 13
          },
          {
            "department": "Cardiology",
            "minutes": 15
          },
          {
            "department": "Cardiology",
            "minutes": 16
          },
          {
            "department": "Cardiology",
            "minutes": 17
          },
          {
            "department": "Cardiology",
            "minutes": 18
          },
          {
            "department": "Cardiology",
            "minutes": 20
          },
          {
            "department": "Cardiology",
            "minutes": 21
          },
          {
            "department": "Cardiology",
            "minutes": 23
          },
          {
            "department": "Cardiology",
            "minutes": 26
          },
          {
            "department": "Cardiology",
            "minutes": 28
          },
          {
            "department": "Cardiology",
            "minutes": 33
          },
          {
            "department": "Cardiology",
            "minutes": 61
          },
          {
            "department": "Paediatrics",
            "minutes": 4
          },
          {
            "department": "Paediatrics",
            "minutes": 5
          },
          {
            "department": "Paediatrics",
            "minutes": 7
          },
          {
            "department": "Paediatrics",
            "minutes": 8
          },
          {
            "department": "Paediatrics",
            "minutes": 9
          },
          {
            "department": "Paediatrics",
            "minutes": 10
          },
          {
            "department": "Paediatrics",
            "minutes": 11
          },
          {
            "department": "Paediatrics",
            "minutes": 12
          },
          {
            "department": "Paediatrics",
            "minutes": 12
          },
          {
            "department": "Paediatrics",
            "minutes": 13
          },
          {
            "department": "Paediatrics",
            "minutes": 14
          },
          {
            "department": "Paediatrics",
            "minutes": 16
          },
          {
            "department": "Paediatrics",
            "minutes": 17
          },
          {
            "department": "Paediatrics",
            "minutes": 19
          },
          {
            "department": "Paediatrics",
            "minutes": 22
          },
          {
            "department": "Paediatrics",
            "minutes": 24
          },
          {
            "department": "Dermatology",
            "minutes": 3
          },
          {
            "department": "Dermatology",
            "minutes": 4
          },
          {
            "department": "Dermatology",
            "minutes": 5
          },
          {
            "department": "Dermatology",
            "minutes": 6
          },
          {
            "department": "Dermatology",
            "minutes": 6
          },
          {
            "department": "Dermatology",
            "minutes": 7
          },
          {
            "department": "Dermatology",
            "minutes": 8
          },
          {
            "department": "Dermatology",
            "minutes": 9
          },
          {
            "department": "Dermatology",
            "minutes": 9
          },
          {
            "department": "Dermatology",
            "minutes": 10
          },
          {
            "department": "Dermatology",
            "minutes": 11
          },
          {
            "department": "Dermatology",
            "minutes": 12
          },
          {
            "department": "Dermatology",
            "minutes": 13
          },
          {
            "department": "Dermatology",
            "minutes": 15
          },
          {
            "department": "Dermatology",
            "minutes": 18
          },
          {
            "department": "Dermatology",
            "minutes": 35
          }
        ],
        "value": "minutes",
        "group": "department",
        "highlight": "Emergency",
        "format": "{value} min",
        "title": "The median emergency wait was 44 minutes",
        "subtitle": "Minutes from arrival to being seen, sampled visits by department",
        "note": "Illustrative data. Box: middle half of visits; line: median; whiskers: within 1.5 × IQR; dots: outliers."
      }
    },
    {
      "level": "atom",
      "component": "BubbleNetwork",
      "name": "bubble-network",
      "title": "Bubble network",
      "category": "poster",
      "description": "Draws a small set of circles sized by value and joined by thin lines, with one circle highlighted and tagged.",
      "whenToUse": "Showing which of up to about 12 named things are connected, and how big each is: trade blocs, route maps between hubs, partnerships, shared ownership. Give `x` and `y` to place the circles by hand, for example roughly geographically.",
      "avoid": "Flows with magnitudes between stages (use sankey-chart); more than about 12 circles or a dense web of links (use heatmap as a matrix, or data-table); comparison of the sizes alone (use bar-chart or circle-pack).",
      "props": [
        {
          "name": "name",
          "type": "string",
          "required": true,
          "description": "Name field: one circle per row."
        },
        {
          "name": "value",
          "type": "string",
          "required": true,
          "description": "Value field: circle area."
        },
        {
          "name": "x",
          "type": "string",
          "description": "Horizontal position field, 0 (left) to 100 (right). With `y`, places the circles by hand."
        },
        {
          "name": "y",
          "type": "string",
          "description": "Vertical position field, 0 (top) to 100 (bottom)."
        },
        {
          "name": "links",
          "type": "{ from: string; to: string; value?: number }[]",
          "description": "Lines between circles, by name. `value` sets the line width and is printed on the line."
        },
        {
          "name": "tag",
          "type": "string",
          "description": "Short tag attached to the highlighted circle, e.g. \"Joined 2024\"."
        },
        {
          "name": "linkFormat",
          "type": "FormatSpec",
          "description": "How to print link values."
        },
        {
          "name": "linkLabels",
          "type": "boolean",
          "description": "Print link values on the lines. Default: on when any link has a value."
        },
        {
          "name": "layout",
          "type": "\"ring\" | \"hub\"",
          "description": "Without `x` and `y`: \"ring\" spaces the circles round an ellipse in data order, \"hub\" also moves the most-connected circle to the middle. Default: \"hub\" when one circle links to most of the others."
        }
      ],
      "example": {
        "type": "bubble-network",
        "data": [
          {
            "airport": "Lisbon",
            "m": 34
          },
          {
            "airport": "Madrid",
            "m": 60
          },
          {
            "airport": "Paris",
            "m": 67
          },
          {
            "airport": "Frankfurt",
            "m": 59
          },
          {
            "airport": "Rome",
            "m": 41
          },
          {
            "airport": "Athens",
            "m": 28
          },
          {
            "airport": "Istanbul",
            "m": 76
          }
        ],
        "name": "airport",
        "value": "m",
        "links": [
          {
            "from": "Istanbul",
            "to": "Frankfurt"
          },
          {
            "from": "Istanbul",
            "to": "Paris"
          },
          {
            "from": "Istanbul",
            "to": "Rome"
          },
          {
            "from": "Istanbul",
            "to": "Athens"
          },
          {
            "from": "Istanbul",
            "to": "Madrid"
          },
          {
            "from": "Istanbul",
            "to": "Lisbon"
          },
          {
            "from": "Madrid",
            "to": "Lisbon"
          },
          {
            "from": "Paris",
            "to": "Frankfurt"
          },
          {
            "from": "Rome",
            "to": "Athens"
          }
        ],
        "highlight": "Istanbul",
        "tag": "Busiest",
        "format": "{value}m",
        "title": "Istanbul handled 76 million passengers in 2023",
        "subtitle": "Passengers at seven airports, millions, 2023. Lines show the routes in this example.",
        "note": "Rounded passenger figures; the routes drawn are illustrative"
      }
    },
    {
      "level": "atom",
      "component": "BubbleScale",
      "name": "bubble-scale",
      "title": "Bubble scale",
      "category": "poster",
      "description": "Places circles sized by one measure along a single value scale set by another, spread sideways so none overlap, with boxed scale marks instead of an axis.",
      "whenToUse": "A poster answer to \"where does each one sit on this scale, and how big is it\": 5 to about 40 named things with a rate or score (the scale) and a magnitude (the area), such as import dependency against share of demand.",
      "avoid": "Two measures that should be read against each other on two axes (use scatter-chart); one measure only (use beeswarm-chart or strip-plot); precise comparison of the sizes (use bar-chart); more than about 60 rows (use beeswarm-chart).",
      "props": [
        {
          "name": "name",
          "type": "string",
          "required": true,
          "description": "Name field: one circle per row."
        },
        {
          "name": "value",
          "type": "string",
          "required": true,
          "description": "Numeric field that positions each circle along the scale. Printed with `format`."
        },
        {
          "name": "size",
          "type": "string",
          "required": true,
          "description": "Numeric field for circle area. Printed inside or beside each circle with `sizeFormat`."
        },
        {
          "name": "series",
          "type": "string",
          "description": "Field that colours circles by category. Default: circles are shaded along the scale with the sequential ramp."
        },
        {
          "name": "orientation",
          "type": "\"vertical\" | \"horizontal\"",
          "default": "\"vertical\"",
          "description": "Direction of the scale. Narrow containers always use vertical."
        },
        {
          "name": "domain",
          "type": "[number, number]",
          "description": "Scale range. Default: the data extent rounded out to the tick step."
        },
        {
          "name": "ticks",
          "type": "number[]",
          "description": "Values to mark on the scale. Default: about five round values."
        },
        {
          "name": "scaleLabel",
          "type": "string",
          "description": "Short name of the scale measure, printed at the head of the scale."
        },
        {
          "name": "sizeFormat",
          "type": "FormatSpec",
          "description": "How to print the size measure."
        },
        {
          "name": "guides",
          "type": "boolean",
          "default": "true",
          "description": "Thin dotted guide line at each scale mark."
        },
        {
          "name": "names",
          "type": "Record<string, string>",
          "description": "Display names for series keys."
        }
      ],
      "example": {
        "type": "bubble-scale",
        "data": [
          {
            "material": "Lithium",
            "imported": 100,
            "kt": 38
          },
          {
            "material": "Cobalt",
            "imported": 86,
            "kt": 21
          },
          {
            "material": "Graphite",
            "imported": 98,
            "kt": 96
          },
          {
            "material": "Nickel",
            "imported": 71,
            "kt": 164
          },
          {
            "material": "Manganese",
            "imported": 96,
            "kt": 52
          },
          {
            "material": "Copper",
            "imported": 48,
            "kt": 410
          },
          {
            "material": "Aluminium",
            "imported": 59,
            "kt": 290
          },
          {
            "material": "Rare earths",
            "imported": 100,
            "kt": 9
          },
          {
            "material": "Silicon metal",
            "imported": 64,
            "kt": 73
          },
          {
            "material": "Zinc",
            "imported": 22,
            "kt": 118
          },
          {
            "material": "Steel scrap",
            "imported": 4,
            "kt": 520
          },
          {
            "material": "Tin",
            "imported": 78,
            "kt": 14
          }
        ],
        "name": "material",
        "value": "imported",
        "size": "kt",
        "format": "{value}%",
        "sizeFormat": "{value} kt",
        "domain": [
          0,
          100
        ],
        "ticks": [
          0,
          20,
          40,
          60,
          80,
          100
        ],
        "scaleLabel": "Imported",
        "title": "Imports supply between 4% and 100% of the region's demand for 12 materials",
        "subtitle": "Share of each material imported (position) and annual demand in thousand tonnes (area), 12 battery and grid materials",
        "note": "Illustrative figures for an invented region"
      }
    },
    {
      "level": "atom",
      "component": "BulletChart",
      "name": "bullet-chart",
      "title": "Bullet chart",
      "category": "comparison",
      "description": "Compares one measure per row with its target, optionally against qualitative bands such as poor / fair / good.",
      "whenToUse": "Progress against a target or budget for one to about 10 measures, including measures in different units (`scale: \"independent\"`); a compact scorecard instead of a row of gauges.",
      "avoid": "No target or thresholds to compare with (use bar-chart); a single share of a whole (use progress-meter); change over time (use line-chart).",
      "props": [
        {
          "name": "category",
          "type": "string",
          "required": true,
          "description": "Field naming each row."
        },
        {
          "name": "value",
          "type": "string",
          "required": true,
          "description": "Field with the measured value. Values below zero are not supported."
        },
        {
          "name": "target",
          "type": "string",
          "description": "Field with the target, drawn as a tick across the bar."
        },
        {
          "name": "bands",
          "type": "number[] | string[]",
          "description": "Ascending upper bounds of qualitative bands, or the fields holding them per row. Omit unless the thresholds are real."
        },
        {
          "name": "bandNames",
          "type": "string[]",
          "description": "Band names, lowest first, for the legend and tooltip."
        },
        {
          "name": "max",
          "type": "number | string",
          "description": "End of the scale: a number or a per-row field. Default: just past the largest value, target or band."
        },
        {
          "name": "scale",
          "type": "\"shared\" | \"independent\"",
          "default": "\"shared\"",
          "description": "One axis for all rows, or each row on its own scale (different units)."
        },
        {
          "name": "formatField",
          "type": "string",
          "description": "Field holding a format spec per row, for mixed units."
        },
        {
          "name": "names",
          "type": "Record<string, string>",
          "description": "Display names for the value and target fields."
        }
      ],
      "example": {
        "type": "bullet-chart",
        "data": [
          {
            "region": "North America",
            "sales": 94,
            "target": 80
          },
          {
            "region": "Europe",
            "sales": 71,
            "target": 85
          },
          {
            "region": "Asia Pacific",
            "sales": 63,
            "target": 60
          },
          {
            "region": "Latin America",
            "sales": 38,
            "target": 55
          }
        ],
        "category": "region",
        "value": "sales",
        "target": "target",
        "bands": [
          50,
          75,
          110
        ],
        "bandNames": [
          "Below plan",
          "On plan",
          "Stretch"
        ],
        "names": {
          "sales": "Sales",
          "target": "Target"
        },
        "format": "currency:USD",
        "height": 240,
        "title": "Europe sold $71 million against a target of $85 million in 2024",
        "subtitle": "Sales against target by region, $ millions, 2024",
        "source": "Company accounts",
        "note": "Illustrative figures."
      }
    },
    {
      "level": "atom",
      "component": "BumpChart",
      "name": "bump-chart",
      "title": "Bump chart",
      "category": "trend",
      "description": "Shows how the ranking of entities changes over time, with rank 1 at the top and each line named at its end.",
      "whenToUse": "League tables over 3 to about 15 periods for up to about 12 entities, when the order and the changes in rank are what matter and the underlying values differ too much (or too little) to share an axis.",
      "avoid": "When the size of the values matters (use line-chart); only two periods (use slope-chart); a single ranking (use bar-chart or lollipop-chart).",
      "props": [
        {
          "name": "x",
          "type": "string",
          "required": true,
          "description": "Time or ordered category field."
        },
        {
          "name": "y",
          "type": "string | string[]",
          "required": true,
          "description": "Value field to rank by, or several (one line each)."
        },
        {
          "name": "series",
          "type": "string",
          "description": "Field that splits rows into entities (long data)."
        },
        {
          "name": "ranked",
          "type": "boolean",
          "default": "false",
          "description": "The values are already ranks (1 = best) and are plotted as given."
        },
        {
          "name": "order",
          "type": "\"desc\" | \"asc\"",
          "default": "\"desc\"",
          "description": "Which values rank first when ranks are computed: largest (`desc`) or smallest (`asc`)."
        },
        {
          "name": "startLabels",
          "type": "boolean",
          "description": "Name lines at the start as well as the end. Default: on when the chart is wide enough."
        },
        {
          "name": "names",
          "type": "Record<string, string>",
          "description": "Display names for series keys."
        }
      ],
      "example": {
        "type": "bump-chart",
        "data": [
          {
            "year": "2019",
            "Tesla": 0.37,
            "BYD": 0.23,
            "SAIC": 0.16,
            "Volkswagen": 0.14,
            "Hyundai": 0.13,
            "Geely": 0.12
          },
          {
            "year": "2020",
            "Tesla": 0.5,
            "BYD": 0.18,
            "SAIC": 0.27,
            "Volkswagen": 0.42,
            "Hyundai": 0.2,
            "Geely": 0.1
          },
          {
            "year": "2021",
            "Tesla": 0.94,
            "BYD": 0.6,
            "SAIC": 0.68,
            "Volkswagen": 0.76,
            "Hyundai": 0.35,
            "Geely": 0.2
          },
          {
            "year": "2022",
            "Tesla": 1.31,
            "BYD": 1.86,
            "SAIC": 0.72,
            "Volkswagen": 0.83,
            "Hyundai": 0.51,
            "Geely": 0.65
          },
          {
            "year": "2023",
            "Tesla": 1.81,
            "BYD": 3.02,
            "SAIC": 0.79,
            "Volkswagen": 1,
            "Hyundai": 0.57,
            "Geely": 0.93
          },
          {
            "year": "2024",
            "Tesla": 1.79,
            "BYD": 4.27,
            "SAIC": 0.9,
            "Volkswagen": 1.04,
            "Hyundai": 0.56,
            "Geely": 1.39
          }
        ],
        "x": "year",
        "y": [
          "Tesla",
          "BYD",
          "SAIC",
          "Volkswagen",
          "Hyundai",
          "Geely"
        ],
        "highlight": "BYD",
        "format": "{value}m",
        "height": 300,
        "title": "BYD rose from fifth in 2020 to first in 2022",
        "subtitle": "Rank among six carmakers by plug-in vehicles sold worldwide, 2019 to 2024",
        "source": "Company reports",
        "note": "Approximate figures."
      }
    },
    {
      "level": "atom",
      "component": "CalendarHeatmap",
      "name": "calendar-heatmap",
      "title": "Calendar heatmap",
      "category": "trend",
      "description": "Colours every day of one or more years by a value, laid out as a grid of weeks.",
      "whenToUse": "Daily data over months or years where weekly rhythm, seasonality, streaks and gaps matter: activity, sales, incidents, habits.",
      "avoid": "Reading the trend or exact values (use line-chart); fewer than about three months of days (use bar-chart); a non-daily grain such as hour by weekday (use heatmap).",
      "props": [
        {
          "name": "date",
          "type": "string",
          "required": true,
          "description": "Date field: ISO strings, Dates or epoch milliseconds. One cell per calendar day."
        },
        {
          "name": "value",
          "type": "string",
          "required": true,
          "description": "Numeric field that colours each day. Rows sharing a day are summed."
        },
        {
          "name": "orientation",
          "type": "\"auto\" | \"horizontal\" | \"vertical\"",
          "default": "\"auto\"",
          "description": "Weeks left to right, or top to bottom. `auto` goes vertical under 480px."
        },
        {
          "name": "weekStart",
          "type": "\"monday\" | \"sunday\"",
          "default": "\"monday\"",
          "description": "First day of the week."
        },
        {
          "name": "domain",
          "type": "[number, number]",
          "description": "Fix the ends of the colour scale. Default: 0 to the maximum."
        },
        {
          "name": "height",
          "type": "number | string",
          "description": "Common prop, but note the default: when omitted the height follows the cell size, which follows the width, so cells stay square."
        }
      ],
      "example": {
        "type": "calendar-heatmap",
        "data": [
          {
            "date": "2024-01-01",
            "commits": 16
          },
          {
            "date": "2024-01-02",
            "commits": 9
          },
          {
            "date": "2024-01-03",
            "commits": 8
          },
          {
            "date": "2024-01-04",
            "commits": 6
          },
          {
            "date": "2024-01-05",
            "commits": 8
          },
          {
            "date": "2024-01-06",
            "commits": 1
          },
          {
            "date": "2024-01-07",
            "commits": 1
          },
          {
            "date": "2024-01-08",
            "commits": 17
          },
          {
            "date": "2024-01-09",
            "commits": 12
          },
          {
            "date": "2024-01-10",
            "commits": 13
          },
          {
            "date": "2024-01-11",
            "commits": 14
          },
          {
            "date": "2024-01-12",
            "commits": 9
          },
          {
            "date": "2024-01-13",
            "commits": 2
          },
          {
            "date": "2024-01-14",
            "commits": 2
          },
          {
            "date": "2024-01-15",
            "commits": 11
          },
          {
            "date": "2024-01-16",
            "commits": 14
          },
          {
            "date": "2024-01-17",
            "commits": 10
          },
          {
            "date": "2024-01-18",
            "commits": 11
          },
          {
            "date": "2024-01-19",
            "commits": 14
          },
          {
            "date": "2024-01-20",
            "commits": 2
          },
          {
            "date": "2024-01-21",
            "commits": 2
          },
          {
            "date": "2024-01-22",
            "commits": 11
          },
          {
            "date": "2024-01-23",
            "commits": 9
          },
          {
            "date": "2024-01-24",
            "commits": 14
          },
          {
            "date": "2024-01-25",
            "commits": 8
          },
          {
            "date": "2024-01-26",
            "commits": 14
          },
          {
            "date": "2024-01-27",
            "commits": 1
          },
          {
            "date": "2024-01-28",
            "commits": 1
          },
          {
            "date": "2024-01-29",
            "commits": 10
          },
          {
            "date": "2024-01-30",
            "commits": 5
          },
          {
            "date": "2024-01-31",
            "commits": 11
          },
          {
            "date": "2024-02-01",
            "commits": 12
          },
          {
            "date": "2024-02-02",
            "commits": 15
          },
          {
            "date": "2024-02-03",
            "commits": 1
          },
          {
            "date": "2024-02-04",
            "commits": 2
          },
          {
            "date": "2024-02-05",
            "commits": 10
          },
          {
            "date": "2024-02-06",
            "commits": 9
          },
          {
            "date": "2024-02-07",
            "commits": 12
          },
          {
            "date": "2024-02-08",
            "commits": 6
          },
          {
            "date": "2024-02-09",
            "commits": 13
          },
          {
            "date": "2024-02-10",
            "commits": 1
          },
          {
            "date": "2024-02-11",
            "commits": 1
          },
          {
            "date": "2024-02-12",
            "commits": 7
          },
          {
            "date": "2024-02-13",
            "commits": 17
          },
          {
            "date": "2024-02-14",
            "commits": 16
          },
          {
            "date": "2024-02-15",
            "commits": 17
          },
          {
            "date": "2024-02-16",
            "commits": 10
          },
          {
            "date": "2024-02-17",
            "commits": 1
          },
          {
            "date": "2024-02-18",
            "commits": 1
          },
          {
            "date": "2024-02-19",
            "commits": 11
          },
          {
            "date": "2024-02-20",
            "commits": 6
          },
          {
            "date": "2024-02-21",
            "commits": 15
          },
          {
            "date": "2024-02-22",
            "commits": 17
          },
          {
            "date": "2024-02-23",
            "commits": 5
          },
          {
            "date": "2024-02-24",
            "commits": 1
          },
          {
            "date": "2024-02-25",
            "commits": 2
          },
          {
            "date": "2024-02-26",
            "commits": 14
          },
          {
            "date": "2024-02-27",
            "commits": 12
          },
          {
            "date": "2024-02-28",
            "commits": 18
          },
          {
            "date": "2024-02-29",
            "commits": 9
          },
          {
            "date": "2024-03-01",
            "commits": 16
          },
          {
            "date": "2024-03-02",
            "commits": 1
          },
          {
            "date": "2024-03-03",
            "commits": 1
          },
          {
            "date": "2024-03-04",
            "commits": 15
          },
          {
            "date": "2024-03-05",
            "commits": 5
          },
          {
            "date": "2024-03-06",
            "commits": 9
          },
          {
            "date": "2024-03-07",
            "commits": 12
          },
          {
            "date": "2024-03-08",
            "commits": 16
          },
          {
            "date": "2024-03-09",
            "commits": 2
          },
          {
            "date": "2024-03-10",
            "commits": 1
          },
          {
            "date": "2024-03-11",
            "commits": 11
          },
          {
            "date": "2024-03-12",
            "commits": 14
          },
          {
            "date": "2024-03-13",
            "commits": 18
          },
          {
            "date": "2024-03-14",
            "commits": 14
          },
          {
            "date": "2024-03-15",
            "commits": 9
          },
          {
            "date": "2024-03-16",
            "commits": 1
          },
          {
            "date": "2024-03-17",
            "commits": 2
          },
          {
            "date": "2024-03-18",
            "commits": 9
          },
          {
            "date": "2024-03-19",
            "commits": 18
          },
          {
            "date": "2024-03-20",
            "commits": 16
          },
          {
            "date": "2024-03-21",
            "commits": 8
          },
          {
            "date": "2024-03-22",
            "commits": 18
          },
          {
            "date": "2024-03-23",
            "commits": 1
          },
          {
            "date": "2024-03-24",
            "commits": 3
          },
          {
            "date": "2024-03-25",
            "commits": 30
          },
          {
            "date": "2024-03-26",
            "commits": 19
          },
          {
            "date": "2024-03-27",
            "commits": 12
          },
          {
            "date": "2024-03-28",
            "commits": 11
          },
          {
            "date": "2024-03-29",
            "commits": 9
          },
          {
            "date": "2024-03-30",
            "commits": 1
          },
          {
            "date": "2024-03-31",
            "commits": 2
          },
          {
            "date": "2024-04-01",
            "commits": 7
          },
          {
            "date": "2024-04-02",
            "commits": 18
          },
          {
            "date": "2024-04-03",
            "commits": 19
          },
          {
            "date": "2024-04-04",
            "commits": 15
          },
          {
            "date": "2024-04-05",
            "commits": 10
          },
          {
            "date": "2024-04-06",
            "commits": 1
          },
          {
            "date": "2024-04-07",
            "commits": 1
          },
          {
            "date": "2024-04-08",
            "commits": 18
          },
          {
            "date": "2024-04-09",
            "commits": 9
          },
          {
            "date": "2024-04-10",
            "commits": 14
          },
          {
            "date": "2024-04-11",
            "commits": 16
          },
          {
            "date": "2024-04-12",
            "commits": 14
          },
          {
            "date": "2024-04-13",
            "commits": 2
          },
          {
            "date": "2024-04-14",
            "commits": 2
          },
          {
            "date": "2024-04-15",
            "commits": 15
          },
          {
            "date": "2024-04-16",
            "commits": 7
          },
          {
            "date": "2024-04-17",
            "commits": 15
          },
          {
            "date": "2024-04-18",
            "commits": 17
          },
          {
            "date": "2024-04-19",
            "commits": 8
          },
          {
            "date": "2024-04-20",
            "commits": 1
          },
          {
            "date": "2024-04-21",
            "commits": 2
          },
          {
            "date": "2024-04-22",
            "commits": 11
          },
          {
            "date": "2024-04-23",
            "commits": 19
          },
          {
            "date": "2024-04-24",
            "commits": 18
          },
          {
            "date": "2024-04-25",
            "commits": 6
          },
          {
            "date": "2024-04-26",
            "commits": 16
          },
          {
            "date": "2024-04-27",
            "commits": 1
          },
          {
            "date": "2024-04-28",
            "commits": 2
          },
          {
            "date": "2024-04-29",
            "commits": 13
          },
          {
            "date": "2024-04-30",
            "commits": 6
          },
          {
            "date": "2024-05-01",
            "commits": 16
          },
          {
            "date": "2024-05-02",
            "commits": 7
          },
          {
            "date": "2024-05-03",
            "commits": 19
          },
          {
            "date": "2024-05-04",
            "commits": 1
          },
          {
            "date": "2024-05-05",
            "commits": 1
          },
          {
            "date": "2024-05-06",
            "commits": 12
          },
          {
            "date": "2024-05-07",
            "commits": 17
          },
          {
            "date": "2024-05-08",
            "commits": 11
          },
          {
            "date": "2024-05-09",
            "commits": 10
          },
          {
            "date": "2024-05-10",
            "commits": 19
          },
          {
            "date": "2024-05-11",
            "commits": 2
          },
          {
            "date": "2024-05-12",
            "commits": 1
          },
          {
            "date": "2024-05-13",
            "commits": 7
          },
          {
            "date": "2024-05-14",
            "commits": 17
          },
          {
            "date": "2024-05-15",
            "commits": 7
          },
          {
            "date": "2024-05-16",
            "commits": 18
          },
          {
            "date": "2024-05-17",
            "commits": 10
          },
          {
            "date": "2024-05-18",
            "commits": 1
          },
          {
            "date": "2024-05-19",
            "commits": 1
          },
          {
            "date": "2024-05-20",
            "commits": 8
          },
          {
            "date": "2024-05-21",
            "commits": 18
          },
          {
            "date": "2024-05-22",
            "commits": 12
          },
          {
            "date": "2024-05-23",
            "commits": 19
          },
          {
            "date": "2024-05-24",
            "commits": 17
          },
          {
            "date": "2024-05-25",
            "commits": 2
          },
          {
            "date": "2024-05-26",
            "commits": 2
          },
          {
            "date": "2024-05-27",
            "commits": 11
          },
          {
            "date": "2024-05-28",
            "commits": 13
          },
          {
            "date": "2024-05-29",
            "commits": 14
          },
          {
            "date": "2024-05-30",
            "commits": 14
          },
          {
            "date": "2024-05-31",
            "commits": 12
          },
          {
            "date": "2024-06-01",
            "commits": 1
          },
          {
            "date": "2024-06-02",
            "commits": 1
          },
          {
            "date": "2024-06-03",
            "commits": 15
          },
          {
            "date": "2024-06-04",
            "commits": 15
          },
          {
            "date": "2024-06-05",
            "commits": 17
          },
          {
            "date": "2024-06-06",
            "commits": 8
          },
          {
            "date": "2024-06-07",
            "commits": 17
          },
          {
            "date": "2024-06-08",
            "commits": 2
          },
          {
            "date": "2024-06-09",
            "commits": 1
          },
          {
            "date": "2024-06-10",
            "commits": 9
          },
          {
            "date": "2024-06-11",
            "commits": 11
          },
          {
            "date": "2024-06-12",
            "commits": 16
          },
          {
            "date": "2024-06-13",
            "commits": 12
          },
          {
            "date": "2024-06-14",
            "commits": 11
          },
          {
            "date": "2024-06-15",
            "commits": 1
          },
          {
            "date": "2024-06-16",
            "commits": 2
          },
          {
            "date": "2024-06-17",
            "commits": 13
          },
          {
            "date": "2024-06-18",
            "commits": 16
          },
          {
            "date": "2024-06-19",
            "commits": 12
          },
          {
            "date": "2024-06-20",
            "commits": 18
          },
          {
            "date": "2024-06-21",
            "commits": 9
          },
          {
            "date": "2024-06-22",
            "commits": 1
          },
          {
            "date": "2024-06-23",
            "commits": 2
          },
          {
            "date": "2024-06-24",
            "commits": 12
          },
          {
            "date": "2024-06-25",
            "commits": 12
          },
          {
            "date": "2024-06-26",
            "commits": 14
          },
          {
            "date": "2024-06-27",
            "commits": 14
          },
          {
            "date": "2024-06-28",
            "commits": 24
          },
          {
            "date": "2024-06-29",
            "commits": 2
          },
          {
            "date": "2024-06-30",
            "commits": 2
          },
          {
            "date": "2024-07-01",
            "commits": 17
          },
          {
            "date": "2024-07-02",
            "commits": 9
          },
          {
            "date": "2024-07-03",
            "commits": 18
          },
          {
            "date": "2024-07-04",
            "commits": 16
          },
          {
            "date": "2024-07-05",
            "commits": 7
          },
          {
            "date": "2024-07-06",
            "commits": 1
          },
          {
            "date": "2024-07-07",
            "commits": 1
          },
          {
            "date": "2024-07-08",
            "commits": 21
          },
          {
            "date": "2024-07-09",
            "commits": 15
          },
          {
            "date": "2024-07-10",
            "commits": 7
          },
          {
            "date": "2024-07-11",
            "commits": 9
          },
          {
            "date": "2024-07-12",
            "commits": 21
          },
          {
            "date": "2024-07-13",
            "commits": 1
          },
          {
            "date": "2024-07-14",
            "commits": 2
          },
          {
            "date": "2024-07-15",
            "commits": 20
          },
          {
            "date": "2024-07-16",
            "commits": 20
          },
          {
            "date": "2024-07-17",
            "commits": 16
          },
          {
            "date": "2024-07-18",
            "commits": 22
          },
          {
            "date": "2024-07-19",
            "commits": 18
          },
          {
            "date": "2024-07-20",
            "commits": 2
          },
          {
            "date": "2024-07-21",
            "commits": 2
          },
          {
            "date": "2024-07-22",
            "commits": 7
          },
          {
            "date": "2024-07-23",
            "commits": 20
          },
          {
            "date": "2024-07-24",
            "commits": 22
          },
          {
            "date": "2024-07-25",
            "commits": 14
          },
          {
            "date": "2024-07-26",
            "commits": 18
          },
          {
            "date": "2024-07-27",
            "commits": 1
          },
          {
            "date": "2024-07-28",
            "commits": 2
          },
          {
            "date": "2024-07-29",
            "commits": 9
          },
          {
            "date": "2024-07-30",
            "commits": 17
          },
          {
            "date": "2024-07-31",
            "commits": 18
          },
          {
            "date": "2024-08-01",
            "commits": 17
          },
          {
            "date": "2024-08-02",
            "commits": 19
          },
          {
            "date": "2024-08-03",
            "commits": 1
          },
          {
            "date": "2024-08-04",
            "commits": 1
          },
          {
            "date": "2024-08-05",
            "commits": 2
          },
          {
            "date": "2024-08-06",
            "commits": 1
          },
          {
            "date": "2024-08-07",
            "commits": 1
          },
          {
            "date": "2024-08-08",
            "commits": 2
          },
          {
            "date": "2024-08-09",
            "commits": 2
          },
          {
            "date": "2024-08-12",
            "commits": 2
          },
          {
            "date": "2024-08-13",
            "commits": 1
          },
          {
            "date": "2024-08-14",
            "commits": 3
          },
          {
            "date": "2024-08-15",
            "commits": 2
          },
          {
            "date": "2024-08-16",
            "commits": 2
          },
          {
            "date": "2024-08-19",
            "commits": 18
          },
          {
            "date": "2024-08-20",
            "commits": 15
          },
          {
            "date": "2024-08-21",
            "commits": 19
          },
          {
            "date": "2024-08-22",
            "commits": 18
          },
          {
            "date": "2024-08-23",
            "commits": 7
          },
          {
            "date": "2024-08-24",
            "commits": 2
          },
          {
            "date": "2024-08-25",
            "commits": 1
          },
          {
            "date": "2024-08-26",
            "commits": 12
          },
          {
            "date": "2024-08-27",
            "commits": 14
          },
          {
            "date": "2024-08-28",
            "commits": 12
          },
          {
            "date": "2024-08-29",
            "commits": 11
          },
          {
            "date": "2024-08-30",
            "commits": 11
          },
          {
            "date": "2024-08-31",
            "commits": 1
          },
          {
            "date": "2024-09-01",
            "commits": 1
          },
          {
            "date": "2024-09-02",
            "commits": 20
          },
          {
            "date": "2024-09-03",
            "commits": 22
          },
          {
            "date": "2024-09-04",
            "commits": 11
          },
          {
            "date": "2024-09-05",
            "commits": 19
          },
          {
            "date": "2024-09-06",
            "commits": 15
          },
          {
            "date": "2024-09-07",
            "commits": 2
          },
          {
            "date": "2024-09-08",
            "commits": 1
          },
          {
            "date": "2024-09-09",
            "commits": 8
          },
          {
            "date": "2024-09-10",
            "commits": 22
          },
          {
            "date": "2024-09-11",
            "commits": 23
          },
          {
            "date": "2024-09-12",
            "commits": 9
          },
          {
            "date": "2024-09-13",
            "commits": 19
          },
          {
            "date": "2024-09-14",
            "commits": 1
          },
          {
            "date": "2024-09-15",
            "commits": 2
          },
          {
            "date": "2024-09-16",
            "commits": 11
          },
          {
            "date": "2024-09-17",
            "commits": 13
          },
          {
            "date": "2024-09-18",
            "commits": 11
          },
          {
            "date": "2024-09-19",
            "commits": 12
          },
          {
            "date": "2024-09-20",
            "commits": 18
          },
          {
            "date": "2024-09-21",
            "commits": 2
          },
          {
            "date": "2024-09-22",
            "commits": 1
          },
          {
            "date": "2024-09-23",
            "commits": 15
          },
          {
            "date": "2024-09-24",
            "commits": 37
          },
          {
            "date": "2024-09-25",
            "commits": 38
          },
          {
            "date": "2024-09-26",
            "commits": 30
          },
          {
            "date": "2024-09-27",
            "commits": 39
          },
          {
            "date": "2024-09-28",
            "commits": 2
          },
          {
            "date": "2024-09-29",
            "commits": 2
          },
          {
            "date": "2024-09-30",
            "commits": 30
          },
          {
            "date": "2024-10-01",
            "commits": 22
          },
          {
            "date": "2024-10-02",
            "commits": 19
          },
          {
            "date": "2024-10-03",
            "commits": 24
          },
          {
            "date": "2024-10-04",
            "commits": 19
          },
          {
            "date": "2024-10-05",
            "commits": 1
          },
          {
            "date": "2024-10-06",
            "commits": 1
          },
          {
            "date": "2024-10-07",
            "commits": 23
          },
          {
            "date": "2024-10-08",
            "commits": 9
          },
          {
            "date": "2024-10-09",
            "commits": 21
          },
          {
            "date": "2024-10-10",
            "commits": 14
          },
          {
            "date": "2024-10-11",
            "commits": 12
          },
          {
            "date": "2024-10-12",
            "commits": 1
          },
          {
            "date": "2024-10-13",
            "commits": 1
          },
          {
            "date": "2024-10-14",
            "commits": 8
          },
          {
            "date": "2024-10-15",
            "commits": 8
          },
          {
            "date": "2024-10-16",
            "commits": 21
          },
          {
            "date": "2024-10-17",
            "commits": 13
          },
          {
            "date": "2024-10-18",
            "commits": 23
          },
          {
            "date": "2024-10-19",
            "commits": 1
          },
          {
            "date": "2024-10-20",
            "commits": 1
          },
          {
            "date": "2024-10-21",
            "commits": 15
          },
          {
            "date": "2024-10-22",
            "commits": 17
          },
          {
            "date": "2024-10-23",
            "commits": 19
          },
          {
            "date": "2024-10-24",
            "commits": 24
          },
          {
            "date": "2024-10-25",
            "commits": 19
          },
          {
            "date": "2024-10-26",
            "commits": 1
          },
          {
            "date": "2024-10-27",
            "commits": 2
          },
          {
            "date": "2024-10-28",
            "commits": 16
          },
          {
            "date": "2024-10-29",
            "commits": 22
          },
          {
            "date": "2024-10-30",
            "commits": 13
          },
          {
            "date": "2024-10-31",
            "commits": 23
          },
          {
            "date": "2024-11-01",
            "commits": 16
          },
          {
            "date": "2024-11-02",
            "commits": 1
          },
          {
            "date": "2024-11-03",
            "commits": 2
          },
          {
            "date": "2024-11-04",
            "commits": 20
          },
          {
            "date": "2024-11-05",
            "commits": 17
          },
          {
            "date": "2024-11-06",
            "commits": 13
          },
          {
            "date": "2024-11-07",
            "commits": 14
          },
          {
            "date": "2024-11-08",
            "commits": 19
          },
          {
            "date": "2024-11-09",
            "commits": 1
          },
          {
            "date": "2024-11-10",
            "commits": 1
          },
          {
            "date": "2024-11-11",
            "commits": 25
          },
          {
            "date": "2024-11-12",
            "commits": 8
          },
          {
            "date": "2024-11-13",
            "commits": 24
          },
          {
            "date": "2024-11-14",
            "commits": 10
          },
          {
            "date": "2024-11-15",
            "commits": 15
          },
          {
            "date": "2024-11-16",
            "commits": 2
          },
          {
            "date": "2024-11-17",
            "commits": 2
          },
          {
            "date": "2024-11-18",
            "commits": 11
          },
          {
            "date": "2024-11-19",
            "commits": 7
          },
          {
            "date": "2024-11-20",
            "commits": 15
          },
          {
            "date": "2024-11-21",
            "commits": 22
          },
          {
            "date": "2024-11-22",
            "commits": 8
          },
          {
            "date": "2024-11-23",
            "commits": 1
          },
          {
            "date": "2024-11-24",
            "commits": 2
          },
          {
            "date": "2024-11-25",
            "commits": 19
          },
          {
            "date": "2024-11-26",
            "commits": 8
          },
          {
            "date": "2024-11-27",
            "commits": 17
          },
          {
            "date": "2024-11-28",
            "commits": 9
          },
          {
            "date": "2024-11-29",
            "commits": 25
          },
          {
            "date": "2024-11-30",
            "commits": 1
          },
          {
            "date": "2024-12-01",
            "commits": 1
          },
          {
            "date": "2024-12-02",
            "commits": 13
          },
          {
            "date": "2024-12-03",
            "commits": 12
          },
          {
            "date": "2024-12-04",
            "commits": 10
          },
          {
            "date": "2024-12-05",
            "commits": 14
          },
          {
            "date": "2024-12-06",
            "commits": 8
          },
          {
            "date": "2024-12-07",
            "commits": 1
          },
          {
            "date": "2024-12-08",
            "commits": 1
          },
          {
            "date": "2024-12-09",
            "commits": 16
          },
          {
            "date": "2024-12-10",
            "commits": 8
          },
          {
            "date": "2024-12-11",
            "commits": 19
          },
          {
            "date": "2024-12-12",
            "commits": 23
          },
          {
            "date": "2024-12-13",
            "commits": 22
          },
          {
            "date": "2024-12-14",
            "commits": 2
          },
          {
            "date": "2024-12-15",
            "commits": 2
          },
          {
            "date": "2024-12-16",
            "commits": 9
          },
          {
            "date": "2024-12-17",
            "commits": 24
          },
          {
            "date": "2024-12-18",
            "commits": 21
          },
          {
            "date": "2024-12-19",
            "commits": 18
          },
          {
            "date": "2024-12-20",
            "commits": 21
          },
          {
            "date": "2024-12-21",
            "commits": 1
          },
          {
            "date": "2024-12-22",
            "commits": 1
          },
          {
            "date": "2024-12-23",
            "commits": 24
          },
          {
            "date": "2024-12-24",
            "commits": 37
          },
          {
            "date": "2024-12-25",
            "commits": 17
          },
          {
            "date": "2024-12-26",
            "commits": 34
          },
          {
            "date": "2024-12-27",
            "commits": 24
          },
          {
            "date": "2024-12-28",
            "commits": 2
          },
          {
            "date": "2024-12-29",
            "commits": 3
          },
          {
            "date": "2024-12-30",
            "commits": 24
          },
          {
            "date": "2024-12-31",
            "commits": 33
          }
        ],
        "date": "date",
        "value": "commits",
        "title": "Commits per day to the main repository, 2024",
        "subtitle": "Number of commits each day",
        "note": "Illustrative data. Pale cells are days with no commits."
      }
    },
    {
      "name": "callout",
      "component": "Callout",
      "title": "Callout",
      "level": "atom",
      "category": "text",
      "description": "A boxed statement of fact with a short bold lead and a sentence of explanation. The tone is marked by icon and tint.",
      "whenToUse": "Beside or under a chart, to state its main fact in words, with the number. Also for a caveat the reader must not miss (`negative`).",
      "avoid": "Long narration (use `prose`), a featured number (use `big-number` or `stat-tile`), or more than one callout per chart.",
      "props": [
        {
          "name": "lead",
          "type": "string",
          "description": "The fact in a few bold words. State it with its number."
        },
        {
          "name": "text",
          "type": "string",
          "description": "One sentence of explanation or evidence."
        },
        {
          "name": "children",
          "type": "ReactNode",
          "description": "JSX alternative to `text`."
        },
        {
          "name": "tone",
          "type": "\"neutral\" | \"positive\" | \"negative\" | \"accent\"",
          "default": "\"neutral\"",
          "description": "Sets the icon shape and the tint together."
        },
        {
          "name": "label",
          "type": "string",
          "description": "Small label above the lead, e.g. \"Summary\"."
        },
        {
          "name": "variant",
          "type": "\"soft\" | \"outline\" | \"plain\"",
          "default": "\"soft\"",
          "description": "Tinted box, bordered box, or no box."
        }
      ],
      "example": {
        "type": "callout",
        "tone": "accent",
        "label": "Summary",
        "lead": "Solar supplied more than half of demand on 4 in 10 summer afternoons in 2024",
        "text": "Wholesale prices were below zero on those afternoons."
      }
    },
    {
      "level": "atom",
      "component": "ChoroplethMap",
      "name": "choropleth-map",
      "title": "Choropleth map",
      "category": "geo",
      "description": "Colours the regions of a map by a normalised value, from GeoJSON you supply.",
      "whenToUse": "A rate, share or per-capita figure that varies by place, where the geographic pattern (a north-south divide, a cluster) is what the reader needs to see. Use a diverging scale for change or for values either side of a meaningful midpoint.",
      "avoid": "Raw counts, which mostly map population (normalise first, or use bar-chart); ranking regions precisely (use bar-chart); a handful of regions with no spatial pattern (use bar-chart); regions so different in size that small ones vanish (use a tile-grid GeoJSON).",
      "props": [
        {
          "name": "geo",
          "type": "GeoJSON FeatureCollection | string",
          "required": true,
          "description": "The map: a GeoJSON object, or a URL to fetch one from. Registered with ECharts automatically."
        },
        {
          "name": "region",
          "type": "string",
          "required": true,
          "description": "Row field holding the region name that joins to the GeoJSON."
        },
        {
          "name": "value",
          "type": "string",
          "required": true,
          "description": "Row field holding the value. Use a rate or share, not a raw count."
        },
        {
          "name": "nameProperty",
          "type": "string",
          "default": "\"name\"",
          "description": "GeoJSON feature property holding each region's name."
        },
        {
          "name": "scale",
          "type": "\"sequential\" | \"diverging\"",
          "default": "\"sequential\"",
          "description": "Sequential for magnitude; diverging for values either side of a midpoint."
        },
        {
          "name": "midpoint",
          "type": "number",
          "default": "0",
          "description": "Centre of the diverging scale."
        },
        {
          "name": "domain",
          "type": "[number, number]",
          "description": "Fix the colour scale instead of using the data's range."
        },
        {
          "name": "labels",
          "type": "boolean | \"value\"",
          "default": "false",
          "description": "Print region names on the map; `\"value\"` adds the value on a second line."
        },
        {
          "name": "labelProperty",
          "type": "string",
          "description": "GeoJSON feature property to print instead of the full name, e.g. a postal or ISO code."
        },
        {
          "name": "missingLabel",
          "type": "string",
          "default": "\"No data\"",
          "description": "Legend text for regions without a value."
        },
        {
          "name": "aspectScale",
          "type": "number",
          "default": "0.75",
          "description": "Horizontal stretch for longitude; 1 for tile maps and projected coordinates."
        }
      ],
      "example": {
        "type": "choropleth-map",
        "geo": {
          "type": "FeatureCollection",
          "features": [
            {
              "type": "Feature",
              "properties": {
                "name": "Western Australia",
                "code": "WA"
              },
              "geometry": {
                "type": "Polygon",
                "coordinates": [
                  [
                    [
                      0,
                      1
                    ],
                    [
                      4,
                      1
                    ],
                    [
                      4,
                      6
                    ],
                    [
                      0,
                      6
                    ],
                    [
                      0,
                      1
                    ]
                  ]
                ]
              }
            },
            {
              "type": "Feature",
              "properties": {
                "name": "Northern Territory",
                "code": "NT"
              },
              "geometry": {
                "type": "Polygon",
                "coordinates": [
                  [
                    [
                      4,
                      3.5
                    ],
                    [
                      6.5,
                      3.5
                    ],
                    [
                      6.5,
                      6.5
                    ],
                    [
                      4,
                      6.5
                    ],
                    [
                      4,
                      3.5
                    ]
                  ]
                ]
              }
            },
            {
              "type": "Feature",
              "properties": {
                "name": "South Australia",
                "code": "SA"
              },
              "geometry": {
                "type": "Polygon",
                "coordinates": [
                  [
                    [
                      4,
                      1
                    ],
                    [
                      6.5,
                      1
                    ],
                    [
                      6.5,
                      3.5
                    ],
                    [
                      4,
                      3.5
                    ],
                    [
                      4,
                      1
                    ]
                  ]
                ]
              }
            },
            {
              "type": "Feature",
              "properties": {
                "name": "Queensland",
                "code": "Qld"
              },
              "geometry": {
                "type": "Polygon",
                "coordinates": [
                  [
                    [
                      6.5,
                      3
                    ],
                    [
                      10,
                      3
                    ],
                    [
                      10,
                      5.2
                    ],
                    [
                      8.6,
                      5.2
                    ],
                    [
                      8.6,
                      7.2
                    ],
                    [
                      7.6,
                      7.2
                    ],
                    [
                      7.6,
                      6.2
                    ],
                    [
                      6.5,
                      6.2
                    ],
                    [
                      6.5,
                      3
                    ]
                  ]
                ]
              }
            },
            {
              "type": "Feature",
              "properties": {
                "name": "New South Wales",
                "code": "NSW"
              },
              "geometry": {
                "type": "Polygon",
                "coordinates": [
                  [
                    [
                      6.5,
                      1.4
                    ],
                    [
                      10,
                      1.4
                    ],
                    [
                      10,
                      3
                    ],
                    [
                      6.5,
                      3
                    ],
                    [
                      6.5,
                      1.4
                    ]
                  ],
                  [
                    [
                      8.7,
                      1.8
                    ],
                    [
                      8.7,
                      2.4
                    ],
                    [
                      9.4,
                      2.4
                    ],
                    [
                      9.4,
                      1.8
                    ],
                    [
                      8.7,
                      1.8
                    ]
                  ]
                ]
              }
            },
            {
              "type": "Feature",
              "properties": {
                "name": "Australian Capital Territory",
                "code": "ACT"
              },
              "geometry": {
                "type": "Polygon",
                "coordinates": [
                  [
                    [
                      8.7,
                      1.8
                    ],
                    [
                      9.4,
                      1.8
                    ],
                    [
                      9.4,
                      2.4
                    ],
                    [
                      8.7,
                      2.4
                    ],
                    [
                      8.7,
                      1.8
                    ]
                  ]
                ]
              }
            },
            {
              "type": "Feature",
              "properties": {
                "name": "Victoria",
                "code": "Vic"
              },
              "geometry": {
                "type": "Polygon",
                "coordinates": [
                  [
                    [
                      6.5,
                      0.3
                    ],
                    [
                      9.2,
                      0.3
                    ],
                    [
                      9.2,
                      1.4
                    ],
                    [
                      6.5,
                      1.4
                    ],
                    [
                      6.5,
                      0.3
                    ]
                  ]
                ]
              }
            },
            {
              "type": "Feature",
              "properties": {
                "name": "Tasmania",
                "code": "Tas"
              },
              "geometry": {
                "type": "Polygon",
                "coordinates": [
                  [
                    [
                      7.6,
                      -1.1
                    ],
                    [
                      8.7,
                      -1.1
                    ],
                    [
                      8.7,
                      -0.3
                    ],
                    [
                      7.6,
                      -0.3
                    ],
                    [
                      7.6,
                      -1.1
                    ]
                  ]
                ]
              }
            }
          ]
        },
        "data": [
          {
            "state": "Queensland",
            "share": 45
          },
          {
            "state": "South Australia",
            "share": 47
          },
          {
            "state": "Western Australia",
            "share": 39
          },
          {
            "state": "New South Wales",
            "share": 31
          },
          {
            "state": "Australian Capital Territory",
            "share": 29
          },
          {
            "state": "Victoria",
            "share": 26
          },
          {
            "state": "Northern Territory",
            "share": 22
          },
          {
            "state": "Tasmania",
            "share": 19
          }
        ],
        "region": "state",
        "value": "share",
        "format": "{value}%",
        "aspectScale": 1,
        "labels": true,
        "labelProperty": "code",
        "height": 380,
        "title": "47% of homes in South Australia had rooftop solar in 2023",
        "subtitle": "Share of dwellings with a solar installation, by state and territory, 2023",
        "source": "Australian PV Institute",
        "note": "Approximate figures on a simplified block map"
      }
    },
    {
      "level": "atom",
      "component": "CirclePack",
      "name": "circle-pack",
      "title": "Circle pack",
      "category": "poster",
      "description": "Shows parts as tightly packed circles sized by value, optionally clustered into groups, with the value and name printed inside each circle.",
      "whenToUse": "A poster view of 5 to about 60 parts of very different sizes where the reader should see the largest ones and their rough proportions at once: budgets by programme, emissions by source, users by product. One level of grouping is supported.",
      "avoid": "Values that need precise comparison (use bar-chart, where bars share a baseline); parts that must visibly add up to a whole (use treemap or waffle-chart); more than about 80 parts (use treemap); negative values (use diverging-bar-chart).",
      "props": [
        {
          "name": "name",
          "type": "string",
          "required": true,
          "description": "Name field: one circle per row."
        },
        {
          "name": "value",
          "type": "string",
          "required": true,
          "description": "Value field: circle area. Non-positive values are dropped."
        },
        {
          "name": "group",
          "type": "string",
          "description": "Parent field. Circles sharing a group sit together and share a colour."
        },
        {
          "name": "ring",
          "type": "\"none\" | \"group\" | \"all\"",
          "description": "Thin dotted enclosing ring: one per group with its name and total, or one around the whole pack. Default \"group\" when `group` is set, else \"none\"."
        },
        {
          "name": "colorBy",
          "type": "\"group\" | \"value\" | \"single\"",
          "description": "Colour by group, shade by value along the sequential ramp, or use the accent for every circle. Default \"group\" when `group` is set, else \"single\"."
        },
        {
          "name": "icon",
          "type": "string | Record<string, string>",
          "description": "Icon drawn above the number in large circles: one for all, or a map from circle or group name to icon. Built-in name or `path://`."
        },
        {
          "name": "sizeLegend",
          "type": "boolean | number[]",
          "default": "false",
          "description": "Size key of nested reference circles. `true` picks round values; an array sets them."
        },
        {
          "name": "labels",
          "type": "boolean",
          "default": "true",
          "description": "Print names and values inside circles, and beside the ones too small to hold them."
        },
        {
          "name": "names",
          "type": "Record<string, string>",
          "description": "Display names for group keys."
        }
      ],
      "example": {
        "type": "circle-pack",
        "data": [
          {
            "crop": "Maize",
            "group": "Cereals",
            "mt": 1160
          },
          {
            "crop": "Wheat",
            "group": "Cereals",
            "mt": 780
          },
          {
            "crop": "Rice",
            "group": "Cereals",
            "mt": 790
          },
          {
            "crop": "Barley",
            "group": "Cereals",
            "mt": 150
          },
          {
            "crop": "Sorghum",
            "group": "Cereals",
            "mt": 60
          },
          {
            "crop": "Potatoes",
            "group": "Roots",
            "mt": 375
          },
          {
            "crop": "Cassava",
            "group": "Roots",
            "mt": 330
          },
          {
            "crop": "Sweet potatoes",
            "group": "Roots",
            "mt": 90
          },
          {
            "crop": "Yams",
            "group": "Roots",
            "mt": 85
          },
          {
            "crop": "Soybeans",
            "group": "Oil crops",
            "mt": 350
          },
          {
            "crop": "Oil palm fruit",
            "group": "Oil crops",
            "mt": 420
          },
          {
            "crop": "Rapeseed",
            "group": "Oil crops",
            "mt": 87
          },
          {
            "crop": "Sunflower seed",
            "group": "Oil crops",
            "mt": 54
          }
        ],
        "name": "crop",
        "value": "mt",
        "group": "group",
        "format": "{value} Mt",
        "title": "The world harvested 1,160 million tonnes of maize in 2022",
        "subtitle": "Harvest of 13 major crops by group, million tonnes, 2022",
        "source": "FAOSTAT",
        "note": "Rounded, approximate figures for illustration"
      }
    },
    {
      "level": "atom",
      "component": "CurveRank",
      "name": "curve-rank",
      "title": "Curve rank",
      "category": "poster",
      "description": "Places each entity on two parallel scales, one per measure, and joins its two positions with a smooth S-curve labelled with the name and both values.",
      "whenToUse": "Two different measures of the same 4 to about 15 entities, when the point is how standing on one relates to standing on the other: price against sales, staff against output, cost against usage. Each side has its own scale, stated in its heading.",
      "avoid": "The same measure at two moments (use slope-chart); ranks across many periods (use bump-chart); more than about 15 entities or a statistical relationship (use scatter-chart); exact comparison on one measure (use bar-chart).",
      "props": [
        {
          "name": "name",
          "type": "string",
          "required": true,
          "description": "Entity field: one curve per row."
        },
        {
          "name": "left",
          "type": "string",
          "required": true,
          "description": "Field for the left-hand measure."
        },
        {
          "name": "right",
          "type": "string",
          "required": true,
          "description": "Field for the right-hand measure."
        },
        {
          "name": "leftLabel",
          "type": "string",
          "description": "Heading over the left rule: the measure and its unit. Defaults to the field name."
        },
        {
          "name": "rightLabel",
          "type": "string",
          "description": "Heading over the right rule. Defaults to the field name."
        },
        {
          "name": "leftScale",
          "type": "\"linear\" | \"log\"",
          "default": "\"linear\"",
          "description": "Scale of the left measure. A log scale is stated in the heading."
        },
        {
          "name": "rightScale",
          "type": "\"linear\" | \"log\"",
          "default": "\"linear\"",
          "description": "Scale of the right measure."
        },
        {
          "name": "leftTop",
          "type": "\"high\" | \"low\"",
          "default": "\"high\"",
          "description": "Which end of the left measure sits at the top."
        },
        {
          "name": "rightTop",
          "type": "\"high\" | \"low\"",
          "default": "\"high\"",
          "description": "Which end of the right measure sits at the top."
        },
        {
          "name": "leftFormat",
          "type": "FormatSpec",
          "description": "How to print left values. Defaults to `format`."
        },
        {
          "name": "rightFormat",
          "type": "FormatSpec",
          "description": "How to print right values. Defaults to `format`."
        },
        {
          "name": "order",
          "type": "string[]",
          "description": "Entity names in the order that assigns colours. Pass the same list to glyph-table to match its row colours."
        },
        {
          "name": "colorBy",
          "type": "\"series\" | \"none\"",
          "description": "`series`: one colour per entity; past the size of the theme's palette the extra colours are derived from the theme's own (glyph-table derives the same ones). `none`: every curve in the accent. Default: series while the entities fit the palette (at most 8), none beyond."
        },
        {
          "name": "names",
          "type": "Record<string, string>",
          "description": "Display names for entity keys."
        }
      ],
      "example": {
        "type": "curve-rank",
        "data": [
          {
            "drink": "Filter coffee",
            "price": 2.4,
            "cups": 310
          },
          {
            "drink": "Espresso",
            "price": 2.6,
            "cups": 145
          },
          {
            "drink": "Flat white",
            "price": 3.6,
            "cups": 260
          },
          {
            "drink": "Iced latte",
            "price": 4.2,
            "cups": 180
          },
          {
            "drink": "Matcha latte",
            "price": 4.8,
            "cups": 75
          },
          {
            "drink": "Hot chocolate",
            "price": 3.9,
            "cups": 40
          }
        ],
        "name": "drink",
        "left": "price",
        "right": "cups",
        "leftLabel": "Price per cup",
        "rightLabel": "Cups sold a day",
        "leftFormat": {
          "style": "currency",
          "currency": "GBP"
        },
        "leftTop": "low",
        "title": "Filter coffee sold 310 cups a day at £2.40 a cup",
        "subtitle": "Price per cup and cups sold per day for six drinks at one café, cheapest at the top",
        "note": "Illustrative figures."
      }
    },
    {
      "name": "data-table",
      "component": "DataTable",
      "title": "Data table",
      "level": "atom",
      "category": "figure",
      "description": "A restrained editorial table from rows and a column config: aligned tabular numbers, an optional in-cell bar, row highlight and click-to-sort headers.",
      "whenToUse": "When readers need to look up exact values, or compare a handful of entities across several measures with different units. Sort it by the column the caption refers to and highlight the row it names.",
      "avoid": "Showing a trend or a single comparison (use `line-chart` or `bar-chart`); more than about 7 columns or 30 rows without `maxHeight`.",
      "props": [
        {
          "name": "rows",
          "type": "Row[]",
          "required": true,
          "description": "One object per row."
        },
        {
          "name": "columns",
          "type": "(string | { key, label?, format?, align?, bar?, sortable?, width? })[]",
          "description": "Columns in order. Defaults to every key of the first row. Numeric columns are right-aligned."
        },
        {
          "name": "caption",
          "type": "string",
          "description": "The table's title, as a real `<caption>`."
        },
        {
          "name": "subtitle",
          "type": "string",
          "description": "Units and period, under the caption."
        },
        {
          "name": "source",
          "type": "string",
          "description": "Shown under the table as \"Source: ...\"."
        },
        {
          "name": "highlight",
          "type": "string | number | (string | number)[]",
          "description": "Rows to emphasise, matched on the first column (or `highlightKey`)."
        },
        {
          "name": "highlightKey",
          "type": "string",
          "description": "Field `highlight` matches against."
        },
        {
          "name": "sort",
          "type": "{ key, direction?: \"asc\" | \"desc\" }",
          "description": "Initial sort."
        },
        {
          "name": "sortable",
          "type": "boolean",
          "default": "true",
          "description": "Click or press Enter on a header to sort; a third press restores the original order."
        },
        {
          "name": "maxHeight",
          "type": "number | string",
          "description": "Height before the body scrolls under the sticky header."
        },
        {
          "name": "density",
          "type": "\"comfortable\" | \"compact\"",
          "default": "\"comfortable\"",
          "description": "Row padding."
        }
      ],
      "example": {
        "type": "data-table",
        "caption": "Wind and solar supplied 67% of Denmark's electricity in 2024",
        "subtitle": "Electricity generation by country, 2024",
        "source": "Ember (illustrative figures)",
        "highlight": "Denmark",
        "sort": {
          "key": "share",
          "direction": "desc"
        },
        "columns": [
          {
            "key": "country",
            "label": "Country"
          },
          {
            "key": "share",
            "label": "Wind and solar share",
            "format": "percent",
            "bar": true,
            "width": "38%"
          },
          {
            "key": "twh",
            "label": "Generation, TWh",
            "format": "decimal:1"
          },
          {
            "key": "change",
            "label": "Change vs 2023",
            "format": {
              "style": "percent",
              "signDisplay": "exceptZero",
              "maximumFractionDigits": 1
            }
          }
        ],
        "rows": [
          {
            "country": "Germany",
            "share": 0.43,
            "twh": 209.4,
            "change": 0.031
          },
          {
            "country": "Spain",
            "share": 0.42,
            "twh": 112.8,
            "change": 0.044
          },
          {
            "country": "Denmark",
            "share": 0.67,
            "twh": 23.1,
            "change": 0.012
          },
          {
            "country": "Netherlands",
            "share": 0.41,
            "twh": 49.6,
            "change": 0.058
          },
          {
            "country": "Portugal",
            "share": 0.46,
            "twh": 22.9,
            "change": 0.027
          },
          {
            "country": "France",
            "share": 0.14,
            "twh": 73.5,
            "change": 0.009
          },
          {
            "country": "Poland",
            "share": 0.24,
            "twh": 40.2,
            "change": -0.004
          }
        ]
      }
    },
    {
      "name": "delta",
      "component": "Delta",
      "title": "Delta",
      "level": "atom",
      "category": "figure",
      "description": "An inline change indicator: direction arrow, signed value and an optional baseline label.",
      "whenToUse": "Next to a figure, in a sentence, a table cell or a tile, to say how much it changed and against what. Set `upIsGood: false` for measures where a fall is the good news (churn, emissions, wait times).",
      "avoid": "Standing alone as the main figure (use `big-number` or `stat-tile`, which include a delta). Do not omit the baseline label unless the surrounding text names it.",
      "props": [
        {
          "name": "value",
          "type": "number | string",
          "required": true,
          "description": "The change. Numbers format as signed percentages by default (0.12 is +12%)."
        },
        {
          "name": "label",
          "type": "string",
          "description": "The baseline, e.g. \"vs last quarter\"."
        },
        {
          "name": "upIsGood",
          "type": "boolean",
          "default": "true",
          "description": "Whether an increase is good news; decides the colour only."
        },
        {
          "name": "direction",
          "type": "\"up\" | \"down\" | \"flat\"",
          "description": "Override the direction inferred from the sign."
        },
        {
          "name": "format",
          "type": "FormatSpec",
          "default": "signed percent",
          "description": "Number format, e.g. `\"signed\"` for absolute changes or `\"{value} pts\"`."
        },
        {
          "name": "size",
          "type": "\"sm\" | \"md\" | \"lg\"",
          "description": "12, 14 or 16px. Inherits the surrounding size when omitted."
        },
        {
          "name": "variant",
          "type": "\"text\" | \"pill\"",
          "default": "\"text\"",
          "description": "`pill` adds a tinted capsule behind the value."
        }
      ],
      "example": {
        "type": "delta",
        "value": 0.124,
        "label": "vs last quarter",
        "size": "md"
      }
    },
    {
      "level": "atom",
      "component": "DivergingBarChart",
      "name": "diverging-bar-chart",
      "title": "Diverging bar chart",
      "category": "comparison",
      "description": "Draws bars either side of a centre line: positive against negative values, or stacked agree / disagree responses around a neutral midpoint.",
      "whenToUse": "Values that can be above or below zero (net change, balance, deviation from average); survey scales where the split between the two sides matters; two opposed groups back to back.",
      "avoid": "Values that are all positive (use bar-chart); parts that do not have a natural two-sided order (use a stacked bar-chart); change between two dates (use arrow-chart or dumbbell-chart).",
      "props": [
        {
          "name": "x",
          "type": "string",
          "required": true,
          "description": "Category field."
        },
        {
          "name": "y",
          "type": "string | string[]",
          "required": true,
          "description": "One signed value field, or several fields ordered from most negative to most positive."
        },
        {
          "name": "series",
          "type": "string",
          "description": "Field that splits rows into ordered series (long data)."
        },
        {
          "name": "neutral",
          "type": "string | false",
          "description": "Series centred on the midline. Default: the middle one of an odd number of series."
        },
        {
          "name": "percent",
          "type": "boolean",
          "default": "false",
          "description": "Normalise each row to 100% (stacked series)."
        },
        {
          "name": "colors",
          "type": "\"polarity\" | \"sentiment\"",
          "default": "\"polarity\"",
          "description": "Single series: diverging poles, or the positive / negative colours when one side is good and the other bad."
        },
        {
          "name": "sort",
          "type": "\"none\" | \"asc\" | \"desc\"",
          "description": "Row order. Default: descending by value or by the positive side; data order for dates."
        },
        {
          "name": "orientation",
          "type": "\"auto\" | \"horizontal\" | \"vertical\"",
          "default": "\"auto\"",
          "description": "Horizontal, except vertical for a single series over dates."
        },
        {
          "name": "labels",
          "type": "boolean",
          "description": "Values at bar ends (single) or inside segments (stacked). Default: on when there is room."
        },
        {
          "name": "names",
          "type": "Record<string, string>",
          "description": "Display names for series keys."
        }
      ],
      "example": {
        "type": "diverging-bar-chart",
        "data": [
          {
            "city": "Austin",
            "change": 21.3
          },
          {
            "city": "Phoenix",
            "change": 11.2
          },
          {
            "city": "Seattle",
            "change": 8.1
          },
          {
            "city": "Chicago",
            "change": -2.9
          },
          {
            "city": "San Francisco",
            "change": -7.4
          },
          {
            "city": "St. Louis",
            "change": -11.6
          }
        ],
        "x": "city",
        "y": "change",
        "format": "{value}%",
        "height": 260,
        "title": "Austin's population grew 21.3% between 2010 and 2020",
        "subtitle": "Population change, 2010 to 2020",
        "note": "Illustrative figures."
      }
    },
    {
      "level": "atom",
      "component": "DonutChart",
      "name": "donut-chart",
      "title": "Donut chart",
      "category": "part-to-whole",
      "description": "Shows how 2 to 5 parts make up one whole, with each slice labelled by name and share.",
      "whenToUse": "A simple composition with one dominant part, or one part the title is about, where the total itself is worth printing in the centre.",
      "avoid": "More than 5 categories or parts of similar size that must be ranked (use bar-chart); a single figure (use big-number); composition compared across groups or time (use bar-chart with stack \"percent\"); a compact card (use segmented-bar).",
      "props": [
        {
          "name": "name",
          "type": "string",
          "required": true,
          "description": "Category field: one slice per distinct value."
        },
        {
          "name": "value",
          "type": "string",
          "required": true,
          "description": "Value field."
        },
        {
          "name": "maxSlices",
          "type": "number",
          "default": "5",
          "description": "Most slices drawn, including \"Other\"; smaller categories are folded."
        },
        {
          "name": "centerLabel",
          "type": "string",
          "description": "Small label in the hole, e.g. \"Total\". Turns the centre text on."
        },
        {
          "name": "centerValue",
          "type": "string | number",
          "description": "Big text in the hole. Default: the formatted total when `centerLabel` is set."
        },
        {
          "name": "otherLabel",
          "type": "string",
          "default": "\"Other\"",
          "description": "Name of the folded slice."
        },
        {
          "name": "names",
          "type": "Record<string, string>",
          "description": "Display names for category keys."
        }
      ],
      "example": {
        "type": "donut-chart",
        "data": [
          {
            "tenure": "Own outright",
            "households": 8.8
          },
          {
            "tenure": "Own with a mortgage",
            "households": 7.4
          },
          {
            "tenure": "Rent privately",
            "households": 4.9
          },
          {
            "tenure": "Rent social housing",
            "households": 4.1
          }
        ],
        "name": "tenure",
        "value": "households",
        "format": "{value}m",
        "centerLabel": "households",
        "highlight": "Rent privately",
        "title": "4.9 million households in England rented privately in 2022-23",
        "subtitle": "Households by tenure, England, 2022-23",
        "source": "English Housing Survey",
        "note": "Figures are approximate, for illustration"
      }
    },
    {
      "level": "atom",
      "component": "DumbbellChart",
      "name": "dumbbell-chart",
      "title": "Dumbbell chart",
      "category": "comparison",
      "description": "Shows two values per category as dots joined by a line, so the length of the line shows the difference.",
      "whenToUse": "Before / after or group A / group B across up to about 20 categories, when the size of the difference is what the reader needs.",
      "avoid": "A single value per category (use lollipop-chart or bar-chart); when the direction of change matters more than the two levels (use arrow-chart); more than two points in time (use slope-chart or line-chart).",
      "props": [
        {
          "name": "category",
          "type": "string",
          "required": true,
          "description": "Category field: one row of the chart each."
        },
        {
          "name": "from",
          "type": "string",
          "required": true,
          "description": "Field with the first value, or (long data) the `series` value that marks it."
        },
        {
          "name": "to",
          "type": "string",
          "required": true,
          "description": "Field with the second value, or (long data) the `series` value that marks it."
        },
        {
          "name": "series",
          "type": "string",
          "description": "Long data: the field whose values are `from` and `to`."
        },
        {
          "name": "value",
          "type": "string",
          "default": "\"value\"",
          "description": "Long data: the measure field."
        },
        {
          "name": "kind",
          "type": "\"change\" | \"groups\"",
          "default": "\"change\"",
          "description": "`change` draws a tint then the accent (before / after); `groups` uses two hues (two peers)."
        },
        {
          "name": "sort",
          "type": "\"gap\" | \"from\" | \"to\" | \"none\"",
          "default": "\"gap\"",
          "description": "Row order."
        },
        {
          "name": "order",
          "type": "\"desc\" | \"asc\"",
          "default": "\"desc\"",
          "description": "Sort direction."
        },
        {
          "name": "labels",
          "type": "boolean",
          "description": "Value labels beside both dots. Default: on for up to 14 rows; otherwise a value axis is drawn."
        },
        {
          "name": "gapLabel",
          "type": "boolean | string",
          "default": "false",
          "description": "Print the gap in a right-hand column; a string sets the column header."
        },
        {
          "name": "zero",
          "type": "boolean",
          "default": "false",
          "description": "Start the value axis at zero."
        },
        {
          "name": "names",
          "type": "Record<string, string>",
          "description": "Display names for the `from` and `to` keys."
        }
      ],
      "example": {
        "type": "dumbbell-chart",
        "data": [
          {
            "country": "South Korea",
            "y2000": 76,
            "y2023": 83.6
          },
          {
            "country": "Turkey",
            "y2000": 71.1,
            "y2023": 77.3
          },
          {
            "country": "Poland",
            "y2000": 73.8,
            "y2023": 78.6
          },
          {
            "country": "Japan",
            "y2000": 81.2,
            "y2023": 84.1
          },
          {
            "country": "Germany",
            "y2000": 78.2,
            "y2023": 81.2
          },
          {
            "country": "United States",
            "y2000": 76.7,
            "y2023": 78.4
          }
        ],
        "category": "country",
        "from": "y2000",
        "to": "y2023",
        "names": {
          "y2000": "2000",
          "y2023": "2023"
        },
        "gapLabel": "Gain",
        "format": "decimal:1",
        "height": 280,
        "title": "Life expectancy in South Korea rose from 76.0 years in 2000 to 83.6 in 2023",
        "subtitle": "Life expectancy at birth, years, 2000 and 2023",
        "source": "OECD Health Statistics (rounded)",
        "note": "Figures are approximate, for illustration"
      }
    },
    {
      "level": "atom",
      "component": "FanFlow",
      "name": "fan-flow",
      "title": "Fan flow",
      "category": "poster",
      "description": "One solid block that splits into curved ribbons, each as thick as its value, ending at an aligned column of names, values and shares.",
      "whenToUse": "One total divided among 4 to about 14 destinations (or collected from that many origins, with `direction: \"in\"`) when the picture of the split is the point: where a budget goes, what a day's water is used for, which sources feed one total.",
      "avoid": "Flows with more than one stage or several sources (use sankey-chart); precise comparison of the parts (use bar-chart); 2 to 4 parts (use segmented-bar or donut-chart); values that can be negative (use bar-chart).",
      "props": [
        {
          "name": "name",
          "type": "string",
          "required": true,
          "description": "Destination field: one ribbon per distinct value."
        },
        {
          "name": "value",
          "type": "string",
          "required": true,
          "description": "Value field: ribbon thickness. Rows sharing a name are summed; non-positive values are dropped."
        },
        {
          "name": "trunk",
          "type": "string",
          "description": "Label printed in the single block, with the total."
        },
        {
          "name": "direction",
          "type": "\"out\" | \"in\"",
          "default": "\"out\"",
          "description": "`out`: one block fans out to many labels on the right. `in`: many labels on the left flow into one block."
        },
        {
          "name": "maxItems",
          "type": "number",
          "default": "12",
          "description": "Ribbons to keep; the rest are summed into the remainder ribbon."
        },
        {
          "name": "otherLabel",
          "type": "string",
          "default": "\"Other\"",
          "description": "Name of the remainder ribbon, drawn in the context colour. A row with this name is treated as the remainder."
        },
        {
          "name": "tagged",
          "type": "string[]",
          "description": "Destinations marked with a dotted outline around the name."
        },
        {
          "name": "tagLabel",
          "type": "string",
          "description": "What the dotted outline means, printed in the key. Needed for `tagged` to be drawn."
        },
        {
          "name": "share",
          "type": "boolean",
          "default": "true",
          "description": "Print each ribbon's share of the total. Dropped on narrow screens."
        },
        {
          "name": "colorBy",
          "type": "\"series\" | \"none\"",
          "description": "`series`: one colour per destination; past the size of the theme's palette the extra colours are derived from the theme's own. `none`: every ribbon in the accent. Default: series while the destinations fit the palette (at most 8), none beyond."
        },
        {
          "name": "sort",
          "type": "\"value\" | \"none\"",
          "default": "\"value\"",
          "description": "Largest first, or the data order."
        }
      ],
      "example": {
        "type": "fan-flow",
        "data": [
          {
            "use": "Shower",
            "litres": 128
          },
          {
            "use": "Toilet",
            "litres": 97
          },
          {
            "use": "Washing machine",
            "litres": 62
          },
          {
            "use": "Kitchen tap",
            "litres": 45
          },
          {
            "use": "Dishwasher",
            "litres": 24
          },
          {
            "use": "Garden",
            "litres": 21
          },
          {
            "use": "Bathroom tap",
            "litres": 19
          },
          {
            "use": "Leaks",
            "litres": 16
          }
        ],
        "name": "use",
        "value": "litres",
        "trunk": "Litres a day",
        "tagged": [
          "Shower",
          "Washing machine",
          "Dishwasher"
        ],
        "tagLabel": "Uses heated water",
        "title": "Showers used 128 of a household's 412 litres of water a day",
        "subtitle": "Daily water use of one four-person household by use, litres",
        "note": "Illustrative figures."
      }
    },
    {
      "level": "atom",
      "component": "FunnelChart",
      "name": "funnel-chart",
      "title": "Funnel chart",
      "category": "flow",
      "description": "Shows how many remain at each step of a sequence, with conversion and drop-off between steps.",
      "whenToUse": "A strict sequence where each stage is a subset of the one before: sign-up flows, hiring pipelines, sales stages, applications to admissions. 3 to 7 stages.",
      "avoid": "Stages that are not sequential subsets, or categories with no order (use bar-chart); flows that split to several destinations (use sankey-chart); conversion tracked over time (use line-chart).",
      "props": [
        {
          "name": "stage",
          "type": "string",
          "required": true,
          "description": "Stage field. Rows are read in data order; the first row is the top of the funnel."
        },
        {
          "name": "value",
          "type": "string",
          "required": true,
          "description": "Count field."
        },
        {
          "name": "dropoff",
          "type": "boolean",
          "default": "true",
          "description": "State the drop-off between consecutive stages."
        },
        {
          "name": "dropoffLabel",
          "type": "string",
          "default": "\"dropped off\"",
          "description": "Word for what is lost between stages."
        }
      ],
      "example": {
        "type": "funnel-chart",
        "data": [
          {
            "step": "Visited the pricing page",
            "users": 48200
          },
          {
            "step": "Started a free trial",
            "users": 9640
          },
          {
            "step": "Connected a data source",
            "users": 5310
          },
          {
            "step": "Invited a teammate",
            "users": 2230
          },
          {
            "step": "Became a paying customer",
            "users": 1480
          }
        ],
        "stage": "step",
        "value": "users",
        "format": "integer",
        "title": "20% of pricing-page visitors started a free trial",
        "subtitle": "Users reaching each step of the sign-up funnel, last 90 days",
        "source": "Product analytics",
        "note": "Illustrative data"
      }
    },
    {
      "name": "glyph-table",
      "component": "GlyphTable",
      "title": "Glyph table",
      "level": "atom",
      "category": "poster",
      "description": "A table with one row per entity in which every number is drawn as a small form chosen per column (counted dots, squares or icons, a ring, a sized circle, a bar) with the value printed beside it.",
      "whenToUse": "Comparing 3 to about 12 entities across 2 to 5 measures with different units, when the table should read as a graphic: profiles of products, cities, teams or species. Pair it with curve-rank by passing both the same `order`.",
      "avoid": "Looking up many exact values, sorting, or more than about 12 rows (use data-table); one measure across entities (use bar-chart or pictogram-chart); a trend over time (use line-chart or small-multiples).",
      "props": [
        {
          "name": "rows",
          "type": "Row[]",
          "required": true,
          "description": "One object per entity."
        },
        {
          "name": "name",
          "type": "string",
          "description": "Field holding the entity name. Defaults to the first key of the first row."
        },
        {
          "name": "nameLabel",
          "type": "string",
          "description": "Heading of the name column."
        },
        {
          "name": "columns",
          "type": "{ field, label?, glyph?: \"dots\" | \"blocks\" | \"ring\" | \"circle\" | \"bar\" | \"icons\" | \"text\", unit?, format?, max?, icon? }[]",
          "required": true,
          "description": "Glyph columns in order. `dots`, `blocks` and `icons` draw one mark per `unit` (default 1, raised to a round number when the largest value would not fit, and stated in the heading). `ring` fills to `max` (default 100, or 1 for fractions). `circle` is sized by area and `bar` by length against `max` (default: the column's largest value). `text` prints the value alone."
        },
        {
          "name": "order",
          "type": "string[]",
          "description": "Entity names in display order; also assigns colours. Pass the same list to curve-rank to match its curve colours."
        },
        {
          "name": "highlight",
          "type": "string | string[]",
          "description": "Entities drawn in colour; every other row turns grey."
        },
        {
          "name": "colorBy",
          "type": "\"series\" | \"none\"",
          "description": "`series`: one colour per entity; past the size of the theme's palette the extra colours are derived from the theme's own (curve-rank derives the same ones). `none`: every row in the accent. Default: series while the rows fit the palette (at most 8), none beyond."
        },
        {
          "name": "caption",
          "type": "string",
          "description": "The table's title, as a real `<caption>`."
        },
        {
          "name": "subtitle",
          "type": "string",
          "description": "Measures, units and period, under the caption."
        },
        {
          "name": "source",
          "type": "string",
          "description": "Shown under the table as \"Source: ...\"."
        },
        {
          "name": "note",
          "type": "string",
          "description": "Method notes or caveats, shown under the table."
        },
        {
          "name": "select",
          "type": "boolean",
          "description": "Let readers click a row (or press Enter on it) to select it; the other rows fade."
        },
        {
          "name": "onSelect",
          "type": "(values: string[]) => void",
          "description": "Called with the selected entity names whenever the selection changes."
        },
        {
          "name": "selected",
          "type": "string[]",
          "description": "Controlled selection: the entity names currently selected. When given, the table shows exactly this list and a click only calls `onSelect`. Omit it and the table keeps its own selection."
        }
      ],
      "example": {
        "type": "glyph-table",
        "caption": "Filter coffee sold 310 cups a day at £2.40 a cup",
        "subtitle": "Six drinks at one café: price, cups sold per day, share ordered with oat milk, seconds to prepare and caffeine",
        "note": "Illustrative figures.",
        "name": "drink",
        "nameLabel": "Drink",
        "columns": [
          {
            "field": "price",
            "label": "Price per cup",
            "glyph": "bar",
            "format": {
              "style": "currency",
              "currency": "GBP"
            },
            "max": 5
          },
          {
            "field": "cups",
            "label": "Cups sold a day",
            "glyph": "dots",
            "unit": 10
          },
          {
            "field": "oat",
            "label": "With oat milk",
            "glyph": "ring",
            "format": "{value}%"
          },
          {
            "field": "seconds",
            "label": "Seconds to prepare",
            "glyph": "blocks",
            "unit": 10
          },
          {
            "field": "caffeine",
            "label": "Caffeine, mg",
            "glyph": "circle"
          }
        ],
        "rows": [
          {
            "drink": "Filter coffee",
            "price": 2.4,
            "cups": 310,
            "oat": 8,
            "seconds": 15,
            "caffeine": 140
          },
          {
            "drink": "Espresso",
            "price": 2.6,
            "cups": 145,
            "oat": null,
            "seconds": 30,
            "caffeine": 65
          },
          {
            "drink": "Flat white",
            "price": 3.6,
            "cups": 260,
            "oat": 34,
            "seconds": 95,
            "caffeine": 130
          },
          {
            "drink": "Iced latte",
            "price": 4.2,
            "cups": 180,
            "oat": 41,
            "seconds": 70,
            "caffeine": 130
          },
          {
            "drink": "Matcha latte",
            "price": 4.8,
            "cups": 75,
            "oat": 57,
            "seconds": 110,
            "caffeine": 70
          },
          {
            "drink": "Hot chocolate",
            "price": 3.9,
            "cups": 40,
            "oat": 22,
            "seconds": 85,
            "caffeine": 10
          }
        ]
      }
    },
    {
      "name": "headline",
      "component": "Headline",
      "title": "Headline",
      "level": "atom",
      "category": "text",
      "description": "The opening block of a piece or section: kicker, title, one-sentence deck and a byline/date line.",
      "whenToUse": "At the top of a data story (`page`), at the start of each chapter (`section`), or as the heading of a tile (`card`). Write the title as one plain fact with its number. Write the deck as a second plain sentence.",
      "avoid": "Titling a single chart: charts carry their own `title` and `subtitle`. For numbered chapters use `step-marker`.",
      "props": [
        {
          "name": "title",
          "type": "string",
          "required": true,
          "description": "One fact with its number, as a plain sentence. Wraps balanced."
        },
        {
          "name": "kicker",
          "type": "string",
          "description": "Small label above the title: section, series or topic. Sentence case."
        },
        {
          "name": "deck",
          "type": "string",
          "description": "One-sentence standfirst."
        },
        {
          "name": "byline",
          "type": "string",
          "description": "Author line, shown as written, e.g. \"By Ana Ruiz\"."
        },
        {
          "name": "date",
          "type": "string",
          "description": "ISO dates are formatted for the locale and wrapped in `<time>`; other strings are shown as written."
        },
        {
          "name": "size",
          "type": "\"page\" | \"section\" | \"card\"",
          "default": "\"section\"",
          "description": "Type scale. Titles step down automatically in narrow containers."
        },
        {
          "name": "level",
          "type": "1 | 2 | 3 | 4 | 5 | 6",
          "description": "Heading level. Defaults to 1 (page), 2 (section), 3 (card)."
        },
        {
          "name": "align",
          "type": "\"left\" | \"center\"",
          "default": "\"left\"",
          "description": "Text alignment."
        },
        {
          "name": "children",
          "type": "ReactNode",
          "description": "Extra content under the byline."
        }
      ],
      "example": {
        "type": "headline",
        "size": "page",
        "kicker": "Energy transition",
        "title": "Solar generated more electricity than coal in Europe in 2024",
        "deck": "Solar was the continent's second-largest source of electricity that year.",
        "byline": "By Ana Ruiz and Tom Okafor",
        "date": "2025-03-14"
      }
    },
    {
      "level": "atom",
      "component": "Heatmap",
      "name": "heatmap",
      "title": "Heatmap",
      "category": "comparison",
      "description": "Colours a category-by-category (or time-by-category) grid by a numeric value, using a sequential or diverging scale.",
      "whenToUse": "Finding patterns across two dimensions at once: hour by weekday, cohort by month, product by region. Good when there are too many series for lines or bars and the overall pattern matters more than exact values.",
      "avoid": "Precise comparison of a few values (use bar-chart); a single series over time (use line-chart); a daily value across a year (use calendar-heatmap).",
      "props": [
        {
          "name": "x",
          "type": "string",
          "required": true,
          "description": "Column field: a category or a date."
        },
        {
          "name": "y",
          "type": "string",
          "required": true,
          "description": "Row field."
        },
        {
          "name": "value",
          "type": "string",
          "required": true,
          "description": "Numeric field that colours each cell. One data row per cell."
        },
        {
          "name": "diverging",
          "type": "boolean",
          "default": "false",
          "description": "Diverging palette centred on `midpoint`, for values with a meaningful middle."
        },
        {
          "name": "midpoint",
          "type": "number",
          "default": "0",
          "description": "Centre of the diverging scale."
        },
        {
          "name": "labels",
          "type": "boolean",
          "description": "Print the value in each cell. Default: on when cells are large enough."
        },
        {
          "name": "sort",
          "type": "\"none\" | \"asc\" | \"desc\"",
          "default": "\"none\"",
          "description": "Order rows by their total."
        },
        {
          "name": "domain",
          "type": "[number, number]",
          "description": "Fix the ends of the colour scale."
        }
      ],
      "example": {
        "type": "heatmap",
        "data": [
          {
            "day": "Mon",
            "slot": "6am",
            "orders": 12
          },
          {
            "day": "Mon",
            "slot": "9am",
            "orders": 48
          },
          {
            "day": "Mon",
            "slot": "12pm",
            "orders": 61
          },
          {
            "day": "Mon",
            "slot": "3pm",
            "orders": 55
          },
          {
            "day": "Mon",
            "slot": "6pm",
            "orders": 38
          },
          {
            "day": "Mon",
            "slot": "9pm",
            "orders": 14
          },
          {
            "day": "Tue",
            "slot": "6am",
            "orders": 11
          },
          {
            "day": "Tue",
            "slot": "9am",
            "orders": 52
          },
          {
            "day": "Tue",
            "slot": "12pm",
            "orders": 66
          },
          {
            "day": "Tue",
            "slot": "3pm",
            "orders": 58
          },
          {
            "day": "Tue",
            "slot": "6pm",
            "orders": 41
          },
          {
            "day": "Tue",
            "slot": "9pm",
            "orders": 15
          },
          {
            "day": "Wed",
            "slot": "6am",
            "orders": 13
          },
          {
            "day": "Wed",
            "slot": "9am",
            "orders": 55
          },
          {
            "day": "Wed",
            "slot": "12pm",
            "orders": 70
          },
          {
            "day": "Wed",
            "slot": "3pm",
            "orders": 63
          },
          {
            "day": "Wed",
            "slot": "6pm",
            "orders": 44
          },
          {
            "day": "Wed",
            "slot": "9pm",
            "orders": 17
          },
          {
            "day": "Thu",
            "slot": "6am",
            "orders": 12
          },
          {
            "day": "Thu",
            "slot": "9am",
            "orders": 51
          },
          {
            "day": "Thu",
            "slot": "12pm",
            "orders": 68
          },
          {
            "day": "Thu",
            "slot": "3pm",
            "orders": 60
          },
          {
            "day": "Thu",
            "slot": "6pm",
            "orders": 47
          },
          {
            "day": "Thu",
            "slot": "9pm",
            "orders": 22
          },
          {
            "day": "Fri",
            "slot": "6am",
            "orders": 10
          },
          {
            "day": "Fri",
            "slot": "9am",
            "orders": 44
          },
          {
            "day": "Fri",
            "slot": "12pm",
            "orders": 59
          },
          {
            "day": "Fri",
            "slot": "3pm",
            "orders": 49
          },
          {
            "day": "Fri",
            "slot": "6pm",
            "orders": 52
          },
          {
            "day": "Fri",
            "slot": "9pm",
            "orders": 35
          },
          {
            "day": "Sat",
            "slot": "6am",
            "orders": 6
          },
          {
            "day": "Sat",
            "slot": "9am",
            "orders": 18
          },
          {
            "day": "Sat",
            "slot": "12pm",
            "orders": 31
          },
          {
            "day": "Sat",
            "slot": "3pm",
            "orders": 36
          },
          {
            "day": "Sat",
            "slot": "6pm",
            "orders": 40
          },
          {
            "day": "Sat",
            "slot": "9pm",
            "orders": 38
          },
          {
            "day": "Sun",
            "slot": "6am",
            "orders": 5
          },
          {
            "day": "Sun",
            "slot": "9am",
            "orders": 14
          },
          {
            "day": "Sun",
            "slot": "12pm",
            "orders": 26
          },
          {
            "day": "Sun",
            "slot": "3pm",
            "orders": 30
          },
          {
            "day": "Sun",
            "slot": "6pm",
            "orders": 28
          },
          {
            "day": "Sun",
            "slot": "9pm",
            "orders": 19
          }
        ],
        "x": "slot",
        "y": "day",
        "value": "orders",
        "title": "Orders peak at 70 per hour at 12pm on Wednesdays",
        "subtitle": "Average orders per hour by weekday and time of day",
        "note": "Illustrative data"
      }
    },
    {
      "level": "atom",
      "component": "Histogram",
      "name": "histogram",
      "title": "Histogram",
      "category": "distribution",
      "description": "Bins a numeric field and shows how many rows fall in each bin, as touching bars on a continuous axis.",
      "whenToUse": "Showing the shape of a distribution for one numeric variable with roughly 50 rows or more: its centre, spread, skew, gaps and multiple peaks.",
      "avoid": "Comparing distributions across several groups (use box-plot or beeswarm-chart); under about 50 rows, where every observation can be shown (use strip-plot or beeswarm-chart); counts by category (use bar-chart).",
      "props": [
        {
          "name": "value",
          "type": "string",
          "required": true,
          "description": "Numeric field to bin."
        },
        {
          "name": "bins",
          "type": "number",
          "description": "Approximate number of bins; the width is rounded to a tidy step. Default: from the row count."
        },
        {
          "name": "step",
          "type": "number",
          "description": "Exact bin width in value units. Overrides `bins`."
        },
        {
          "name": "median",
          "type": "boolean",
          "default": "false",
          "description": "Vertical marker at the median."
        },
        {
          "name": "mean",
          "type": "boolean",
          "default": "false",
          "description": "Vertical marker at the mean."
        },
        {
          "name": "normalize",
          "type": "boolean",
          "default": "false",
          "description": "Share of rows per bin instead of a count."
        },
        {
          "name": "countLabel",
          "type": "string",
          "description": "Title for the count axis, e.g. \"Runners\"."
        }
      ],
      "example": {
        "type": "histogram",
        "data": [
          {
            "minutes": 14
          },
          {
            "minutes": 18
          },
          {
            "minutes": 18
          },
          {
            "minutes": 19
          },
          {
            "minutes": 20
          },
          {
            "minutes": 21
          },
          {
            "minutes": 21
          },
          {
            "minutes": 22
          },
          {
            "minutes": 23
          },
          {
            "minutes": 23
          },
          {
            "minutes": 23
          },
          {
            "minutes": 23
          },
          {
            "minutes": 24
          },
          {
            "minutes": 25
          },
          {
            "minutes": 25
          },
          {
            "minutes": 27
          },
          {
            "minutes": 27
          },
          {
            "minutes": 27
          },
          {
            "minutes": 27
          },
          {
            "minutes": 27
          },
          {
            "minutes": 28
          },
          {
            "minutes": 28
          },
          {
            "minutes": 28
          },
          {
            "minutes": 28
          },
          {
            "minutes": 29
          },
          {
            "minutes": 29
          },
          {
            "minutes": 29
          },
          {
            "minutes": 29
          },
          {
            "minutes": 30
          },
          {
            "minutes": 31
          },
          {
            "minutes": 31
          },
          {
            "minutes": 31
          },
          {
            "minutes": 31
          },
          {
            "minutes": 32
          },
          {
            "minutes": 32
          },
          {
            "minutes": 32
          },
          {
            "minutes": 32
          },
          {
            "minutes": 32
          },
          {
            "minutes": 33
          },
          {
            "minutes": 33
          },
          {
            "minutes": 33
          },
          {
            "minutes": 34
          },
          {
            "minutes": 34
          },
          {
            "minutes": 34
          },
          {
            "minutes": 34
          },
          {
            "minutes": 35
          },
          {
            "minutes": 35
          },
          {
            "minutes": 35
          },
          {
            "minutes": 35
          },
          {
            "minutes": 35
          },
          {
            "minutes": 35
          },
          {
            "minutes": 35
          },
          {
            "minutes": 36
          },
          {
            "minutes": 36
          },
          {
            "minutes": 36
          },
          {
            "minutes": 37
          },
          {
            "minutes": 37
          },
          {
            "minutes": 37
          },
          {
            "minutes": 38
          },
          {
            "minutes": 38
          },
          {
            "minutes": 40
          },
          {
            "minutes": 40
          },
          {
            "minutes": 40
          },
          {
            "minutes": 40
          },
          {
            "minutes": 41
          },
          {
            "minutes": 41
          },
          {
            "minutes": 42
          },
          {
            "minutes": 42
          },
          {
            "minutes": 42
          },
          {
            "minutes": 43
          },
          {
            "minutes": 45
          },
          {
            "minutes": 45
          },
          {
            "minutes": 46
          },
          {
            "minutes": 47
          },
          {
            "minutes": 47
          },
          {
            "minutes": 48
          },
          {
            "minutes": 48
          },
          {
            "minutes": 49
          },
          {
            "minutes": 49
          },
          {
            "minutes": 51
          },
          {
            "minutes": 51
          },
          {
            "minutes": 54
          },
          {
            "minutes": 54
          },
          {
            "minutes": 55
          },
          {
            "minutes": 57
          },
          {
            "minutes": 57
          },
          {
            "minutes": 59
          },
          {
            "minutes": 59
          },
          {
            "minutes": 61
          },
          {
            "minutes": 66
          }
        ],
        "value": "minutes",
        "step": 5,
        "median": true,
        "countLabel": "Commuters",
        "format": "{value} min",
        "title": "The median commute of 90 surveyed employees was 34.5 minutes",
        "subtitle": "One-way commute time, in 5-minute bins",
        "note": "Illustrative data"
      }
    },
    {
      "level": "atom",
      "component": "IconArrayGrid",
      "name": "icon-array-grid",
      "title": "Icon array grid",
      "category": "poster",
      "description": "Compares many counts as a grid of labelled tiles, each holding an array of N icons with its name and number.",
      "whenToUse": "Counts of up to about 150 in 4 to 24 categories, especially rates per 100 or per 1,000: cars per 100 households in 12 districts. Set `total` so every tile shows the same full array with the uncounted icons as ghosts.",
      "avoid": "Counts in the thousands (use unit-blocks); two to six categories where one row of icons each is enough (use pictogram-chart); values that need precise comparison or ranking (use block-bar or bar-chart); one share of one whole (use waffle-chart).",
      "props": [
        {
          "name": "name",
          "type": "string",
          "required": true,
          "description": "Category field: one tile per row."
        },
        {
          "name": "value",
          "type": "string",
          "required": true,
          "description": "Value field: the count each tile shows."
        },
        {
          "name": "unit",
          "type": "number",
          "default": "1",
          "description": "Units per icon."
        },
        {
          "name": "unitLabel",
          "type": "string",
          "description": "What one icon stands for, for the key line, e.g. \"1 car\"."
        },
        {
          "name": "total",
          "type": "number",
          "description": "Draw every tile out of this many units, with uncounted icons as ghosts: 100 for a rate per 100."
        },
        {
          "name": "icon",
          "type": "IconName | \"path://...\"",
          "default": "\"circle\"",
          "description": "Built-in icon name or a custom single SVG path."
        },
        {
          "name": "perRow",
          "type": "number",
          "default": "10",
          "description": "Icons per line inside a tile."
        },
        {
          "name": "columns",
          "type": "number",
          "description": "Most tiles per line. Default: as many as fit at a readable size. Narrow containers use fewer."
        },
        {
          "name": "sort",
          "type": "\"none\" | \"asc\" | \"desc\"",
          "default": "\"none\"",
          "description": "Order of the tiles."
        }
      ],
      "example": {
        "type": "icon-array-grid",
        "data": [
          {
            "district": "Ashby",
            "cars": 118
          },
          {
            "district": "Brook End",
            "cars": 104
          },
          {
            "district": "Castle Hill",
            "cars": 97
          },
          {
            "district": "Dockside",
            "cars": 41
          },
          {
            "district": "Eastfield",
            "cars": 88
          },
          {
            "district": "Fairmile",
            "cars": 126
          },
          {
            "district": "Greenway",
            "cars": 73
          },
          {
            "district": "Hollins",
            "cars": 110
          },
          {
            "district": "Old Town",
            "cars": 36
          },
          {
            "district": "Priory",
            "cars": 92
          },
          {
            "district": "Riverside",
            "cars": 58
          },
          {
            "district": "Station Quarter",
            "cars": 29
          }
        ],
        "name": "district",
        "value": "cars",
        "icon": "car",
        "unitLabel": "1 car per 100 households",
        "columns": 4,
        "title": "Fairmile had 126 cars per 100 households in 2025",
        "subtitle": "Cars per 100 households in 12 districts of Port Aldwin, 2025",
        "source": "Illustrative data",
        "note": "Invented figures for a fictional city"
      }
    },
    {
      "name": "icon-stat",
      "component": "IconStat",
      "title": "Icon stat",
      "level": "atom",
      "category": "figure",
      "description": "A \"1 in 4\"-style statement: a row or grid of simple glyphs with some filled, and the sentence they illustrate.",
      "whenToUse": "A single proportion that is easier to read as people or units than as a percentage: 1 in 4, 3 in 10, 37 of 100. Round to a small denominator first.",
      "avoid": "Comparing several proportions (use `bar-chart`), precise percentages (use `big-number`), or fractions that need more than 100 units.",
      "props": [
        {
          "name": "count",
          "type": "number",
          "required": true,
          "description": "How many glyphs are filled."
        },
        {
          "name": "total",
          "type": "number",
          "default": "10",
          "description": "How many glyphs are drawn. Capped at 100."
        },
        {
          "name": "text",
          "type": "string",
          "description": "The sentence completing the figure."
        },
        {
          "name": "children",
          "type": "ReactNode",
          "description": "JSX alternative to `text`."
        },
        {
          "name": "value",
          "type": "string | false",
          "description": "The figure as words. Defaults to \"{count} in {total}\"; `false` hides it."
        },
        {
          "name": "icon",
          "type": "\"circle\" | \"person\" | \"square\" | IconName",
          "default": "\"circle\"",
          "description": "Glyph per unit. Filled units are solid, the rest outlined. Any other icon name from `core/icons.ts` also works; its unfilled units are faded instead of outlined."
        },
        {
          "name": "columns",
          "type": "number",
          "description": "Glyphs per row. Defaults to `total`, at most 10."
        },
        {
          "name": "size",
          "type": "number",
          "description": "Glyph height in px; shrinks to fit narrow containers."
        },
        {
          "name": "tone",
          "type": "\"accent\" | \"positive\" | \"negative\" | \"neutral\"",
          "default": "\"accent\"",
          "description": "Fill colour by meaning."
        },
        {
          "name": "align",
          "type": "\"left\" | \"center\"",
          "default": "\"left\"",
          "description": "Alignment."
        }
      ],
      "example": {
        "type": "icon-stat",
        "icon": "person",
        "count": 1,
        "total": 4,
        "text": "households could not cover an unexpected bill of €500 without borrowing."
      }
    },
    {
      "name": "legend-key",
      "component": "LegendKey",
      "title": "Legend key",
      "level": "atom",
      "category": "figure",
      "description": "A standalone colour key: categorical swatches, or a sequential/diverging ramp with end labels.",
      "whenToUse": "When several charts (small multiples, a map and a bar chart, a dashboard row) share one colour encoding, show the key once above them and turn their own legends off.",
      "avoid": "A single chart: its built-in legend or, better, direct labels already do this. Do not list more than eight categories.",
      "props": [
        {
          "name": "scale",
          "type": "\"categorical\" | \"sequential\" | \"diverging\"",
          "description": "Defaults to `categorical` when `items` is given, otherwise `sequential`."
        },
        {
          "name": "items",
          "type": "(string | { label, color?, index?, shape?, muted? })[]",
          "description": "Categorical entries. Colours come from the theme's categorical order by position (or `index`) unless given."
        },
        {
          "name": "labels",
          "type": "string[]",
          "description": "Ramp end labels: `[low, high]` or `[low, middle, high]`."
        },
        {
          "name": "colors",
          "type": "string[]",
          "description": "Ramp colours, low to high. Defaults to the theme's sequential or diverging ramp."
        },
        {
          "name": "stepped",
          "type": "boolean",
          "default": "false",
          "description": "Discrete steps instead of a continuous gradient, for binned scales."
        },
        {
          "name": "title",
          "type": "string",
          "description": "What the key encodes."
        },
        {
          "name": "shape",
          "type": "\"square\" | \"line\" | \"dot\"",
          "default": "\"square\"",
          "description": "Default swatch shape: match the marks."
        },
        {
          "name": "orientation",
          "type": "\"horizontal\" | \"vertical\"",
          "default": "\"horizontal\"",
          "description": "Categorical layout."
        },
        {
          "name": "width",
          "type": "number",
          "default": "240",
          "description": "Ramp width in px, capped at the container."
        },
        {
          "name": "align",
          "type": "\"left\" | \"center\"",
          "default": "\"left\"",
          "description": "Alignment within the container."
        }
      ],
      "example": {
        "type": "legend-key",
        "title": "Source of electricity",
        "items": [
          "Solar",
          "Wind",
          "Hydro",
          "Gas",
          {
            "label": "Other",
            "muted": true
          }
        ]
      }
    },
    {
      "level": "atom",
      "component": "LineChart",
      "name": "line-chart",
      "title": "Line chart",
      "category": "trend",
      "description": "Shows how one or more values change over time or another ordered dimension.",
      "whenToUse": "Trends over time with roughly 5 or more points; comparing the trajectories of a few series; one highlighted series against many context series.",
      "avoid": "Unordered categories (use bar-chart); only two time points (use slope-chart); more than about 6 equally important lines (use small-multiples).",
      "props": [
        {
          "name": "x",
          "type": "string",
          "required": true,
          "description": "Time or ordered category field. ISO dates are placed on a true time axis."
        },
        {
          "name": "y",
          "type": "string | string[]",
          "required": true,
          "description": "Value field, or several (one line each)."
        },
        {
          "name": "series",
          "type": "string",
          "description": "Field that splits rows into lines (long data)."
        },
        {
          "name": "curve",
          "type": "\"linear\" | \"smooth\" | \"step\"",
          "default": "\"linear\"",
          "description": "Line shape."
        },
        {
          "name": "area",
          "type": "boolean",
          "default": "false",
          "description": "Light fill under the line; forces a zero baseline."
        },
        {
          "name": "endLabels",
          "type": "boolean",
          "description": "Name each line at its end. Default: on for up to 6 lines."
        },
        {
          "name": "points",
          "type": "boolean",
          "description": "Marker on every point. Default: on for 12 points or fewer."
        },
        {
          "name": "zero",
          "type": "boolean",
          "default": "false",
          "description": "Start the value axis at zero."
        },
        {
          "name": "log",
          "type": "boolean",
          "default": "false",
          "description": "Logarithmic value axis."
        },
        {
          "name": "names",
          "type": "Record<string, string>",
          "description": "Display names for series keys."
        },
        {
          "name": "contextLabels",
          "type": "boolean | string",
          "default": "false",
          "description": "With `highlight`: name the grey context lines. `true` adds a muted end label to each when they all fit; a string such as `\"Other cities\"` adds one muted legend entry instead. On narrow charts the legend already lists every line."
        }
      ],
      "example": {
        "type": "line-chart",
        "data": [
          {
            "year": "2019",
            "solar": 2.7,
            "wind": 5.3
          },
          {
            "year": "2020",
            "solar": 3.2,
            "wind": 5.9
          },
          {
            "year": "2021",
            "solar": 3.7,
            "wind": 6.5
          },
          {
            "year": "2022",
            "solar": 4.6,
            "wind": 7.3
          },
          {
            "year": "2023",
            "solar": 5.5,
            "wind": 7.8
          }
        ],
        "x": "year",
        "y": [
          "solar",
          "wind"
        ],
        "format": "{value}%",
        "title": "Solar's share rose from 2.7% to 5.5% between 2019 and 2023"
      }
    },
    {
      "level": "atom",
      "component": "LollipopChart",
      "name": "lollipop-chart",
      "title": "Lollipop chart",
      "category": "comparison",
      "description": "Ranks categories with a dot on a thin stem from zero, or as a stemless dot plot on a free scale.",
      "whenToUse": "Rankings of 8 to 30 categories where bars would be too heavy; dot plot mode when the values sit far from zero and the differences between them matter, or to compare a few series per row.",
      "avoid": "Fewer than about 6 categories or part-to-whole stacks (use bar-chart); exactly two values per row where the difference between them matters (use dumbbell-chart); change over time (use line-chart).",
      "props": [
        {
          "name": "x",
          "type": "string",
          "required": true,
          "description": "Category field."
        },
        {
          "name": "y",
          "type": "string | string[]",
          "required": true,
          "description": "Value field. Several fields draw several dots per row (dot plot)."
        },
        {
          "name": "series",
          "type": "string",
          "description": "Field that splits rows into series (long data). Forces the dot plot."
        },
        {
          "name": "stem",
          "type": "boolean",
          "default": "true",
          "description": "Stem from zero. `false` draws a dot plot whose scale need not start at zero."
        },
        {
          "name": "orientation",
          "type": "\"auto\" | \"horizontal\" | \"vertical\"",
          "default": "\"auto\"",
          "description": "Horizontal for rankings, vertical for dates."
        },
        {
          "name": "sort",
          "type": "\"none\" | \"asc\" | \"desc\"",
          "description": "Row order. Default: descending for rankings, data order for dates."
        },
        {
          "name": "labels",
          "type": "boolean",
          "description": "Value beside each dot. Default: on for one series of up to 16 rows."
        },
        {
          "name": "names",
          "type": "Record<string, string>",
          "description": "Display names for series keys."
        }
      ],
      "example": {
        "type": "lollipop-chart",
        "data": [
          {
            "country": "Netherlands",
            "share": 27
          },
          {
            "country": "Denmark",
            "share": 19
          },
          {
            "country": "Germany",
            "share": 10
          },
          {
            "country": "Sweden",
            "share": 9
          },
          {
            "country": "Japan",
            "share": 8
          },
          {
            "country": "France",
            "share": 4
          },
          {
            "country": "United Kingdom",
            "share": 2
          },
          {
            "country": "United States",
            "share": 1
          }
        ],
        "x": "country",
        "y": "share",
        "format": "{value}%",
        "height": 300,
        "title": "27% of trips in the Netherlands are made by bike",
        "subtitle": "Share of all trips made by bicycle",
        "source": "National travel surveys, latest available year (rounded)",
        "note": "Figures are approximate, for illustration"
      }
    },
    {
      "level": "atom",
      "component": "NestedShares",
      "name": "nested-shares",
      "title": "Nested shares",
      "category": "poster",
      "description": "Shows several shares of the same whole, such as one share at five dates, as outlines nested from one corner of a square, each enclosing an area equal to its share.",
      "whenToUse": "How much of one whole something was at 2 to 8 dates or in 2 to 8 categories, when the growth of a share is the point: bicycles made 4% of trips in 2005 and 27% in 2025.",
      "avoid": "Shares that are parts of each other and should add up (use waffle-chart or treemap); more than 8 items or precise reading of a trend (use line-chart); shares within a point or two of each other, where outlines coincide (use block-bar).",
      "props": [
        {
          "name": "name",
          "type": "string",
          "required": true,
          "description": "Field naming each share: a period (\"2025\") or a category."
        },
        {
          "name": "value",
          "type": "string",
          "required": true,
          "description": "Value field: each row's share of the whole."
        },
        {
          "name": "total",
          "type": "number",
          "description": "The whole. Default: 1 when every value is at most 1, otherwise 100."
        },
        {
          "name": "wholeLabel",
          "type": "string",
          "description": "What the outer square stands for, printed under it, e.g. \"All trips\"."
        },
        {
          "name": "corner",
          "type": "\"bottom-left\" | \"top-left\"",
          "default": "\"bottom-left\"",
          "description": "The corner every outline grows from."
        }
      ],
      "example": {
        "type": "nested-shares",
        "data": [
          {
            "year": "2005",
            "share": 4
          },
          {
            "year": "2010",
            "share": 7
          },
          {
            "year": "2015",
            "share": 12
          },
          {
            "year": "2020",
            "share": 19
          },
          {
            "year": "2025",
            "share": 27
          }
        ],
        "name": "year",
        "value": "share",
        "format": "{value}%",
        "highlight": "2025",
        "wholeLabel": "All trips in the city",
        "title": "Bicycles made 27% of trips in Port Aldwin in 2025",
        "subtitle": "Share of all trips made by bicycle, 2005 to 2025",
        "source": "Illustrative data",
        "note": "Invented figures for a fictional city"
      }
    },
    {
      "level": "atom",
      "component": "OrbitChart",
      "name": "orbit-chart",
      "title": "Orbit chart",
      "category": "poster",
      "description": "Places items as dots on concentric rings round a centre, one ring per category, level or period, with a key that explains rings, angle and dot area.",
      "whenToUse": "A designed alternative to a table for 6 to about 40 named things grouped by distance, level or era from one centre: suppliers by tier, moons by planet-distance band, releases by decade, stakeholders by closeness. One item can be highlighted.",
      "avoid": "A numeric distance that must be read exactly (use strip-plot or scatter-chart); comparison of the dot values (use bar-chart); more than about 6 rings or 40 items (use data-table); hierarchy with sizes that add up (use treemap or circle-pack).",
      "props": [
        {
          "name": "name",
          "type": "string",
          "required": true,
          "description": "Name field: one dot per row."
        },
        {
          "name": "ring",
          "type": "string",
          "required": true,
          "description": "Field naming the ring each dot sits on: a category, level or period."
        },
        {
          "name": "rings",
          "type": "string[]",
          "description": "Ring order from the centre outward. Default: first appearance in the data."
        },
        {
          "name": "value",
          "type": "string",
          "description": "Value field: dot area. Without it every dot is the same size."
        },
        {
          "name": "angle",
          "type": "string",
          "description": "Numeric field that sets the angle round the ring. Without it dots are spaced evenly."
        },
        {
          "name": "angleDomain",
          "type": "[number, number]",
          "default": "[0, 360]",
          "description": "Values of `angle` that map to a full turn, clockwise from 12 o'clock."
        },
        {
          "name": "ringLabel",
          "type": "string",
          "description": "What the rings mean, for the key. Default: the `ring` field name."
        },
        {
          "name": "angleLabel",
          "type": "string",
          "description": "What the angle means, for the key, e.g. \"Clockwise from the top: month of launch\"."
        },
        {
          "name": "sizeLabel",
          "type": "string",
          "description": "What dot area means, for the key. Default: the `value` field name."
        },
        {
          "name": "icon",
          "type": "string",
          "description": "Icon in the centre disc: a built-in name or `path://`."
        },
        {
          "name": "centerLabel",
          "type": "string",
          "description": "Short text in the centre disc, used when there is no `icon`."
        },
        {
          "name": "colorBy",
          "type": "\"ring\" | \"single\"",
          "default": "\"ring\"",
          "description": "A colour per ring, or the accent for every dot."
        },
        {
          "name": "showKey",
          "type": "boolean",
          "default": "true",
          "description": "Show the key that explains rings, angle and dot area."
        }
      ],
      "example": {
        "type": "orbit-chart",
        "data": [
          {
            "supplier": "Halden Cells",
            "tier": "Tier 1",
            "spend": 84
          },
          {
            "supplier": "Orrin Motors",
            "tier": "Tier 1",
            "spend": 61
          },
          {
            "supplier": "Vale Castings",
            "tier": "Tier 1",
            "spend": 37
          },
          {
            "supplier": "Kestrel Wiring",
            "tier": "Tier 2",
            "spend": 22
          },
          {
            "supplier": "Brask Cathodes",
            "tier": "Tier 2",
            "spend": 48
          },
          {
            "supplier": "Lumen Glass",
            "tier": "Tier 2",
            "spend": 15
          },
          {
            "supplier": "Pell Magnets",
            "tier": "Tier 2",
            "spend": 29
          },
          {
            "supplier": "Tarn Lithium",
            "tier": "Tier 3",
            "spend": 33
          },
          {
            "supplier": "Iver Copper",
            "tier": "Tier 3",
            "spend": 19
          },
          {
            "supplier": "Sorrel Graphite",
            "tier": "Tier 3",
            "spend": 12
          },
          {
            "supplier": "Moss Resins",
            "tier": "Tier 3",
            "spend": 6
          },
          {
            "supplier": "Dunmore Nickel",
            "tier": "Tier 3",
            "spend": 26
          }
        ],
        "name": "supplier",
        "ring": "tier",
        "value": "spend",
        "format": "${value}m",
        "centerLabel": "Car plant",
        "highlight": "Brask Cathodes",
        "ringLabel": "Tier 1 sells to the plant, each further ring sells to the ring inside it",
        "sizeLabel": "annual spend",
        "title": "Brask Cathodes is a tier 2 supplier with $48 million of annual spend",
        "subtitle": "12 suppliers to one car plant by supply-chain tier, annual spend in $ million",
        "note": "Illustrative figures for invented companies"
      }
    },
    {
      "level": "atom",
      "component": "PictogramChart",
      "name": "pictogram-chart",
      "title": "Pictogram chart",
      "category": "comparison",
      "description": "Compares quantities with rows of repeated icons, each icon standing for a fixed number of units.",
      "whenToUse": "A few categories (2 to 6) of countable things, people, homes, vehicles, where making the quantity concrete matters more than reading precise values.",
      "avoid": "More than about 8 categories or values that need precise comparison (use bar-chart); a share of one whole (use waffle-chart); values that would need more than about 60 icons in a row (raise `unit`, or use bar-chart).",
      "props": [
        {
          "name": "name",
          "type": "string",
          "required": true,
          "description": "Category field: one row of icons per category."
        },
        {
          "name": "value",
          "type": "string",
          "required": true,
          "description": "Value field."
        },
        {
          "name": "unit",
          "type": "number",
          "description": "Units per icon. Default: a round number giving the largest row about ten icons."
        },
        {
          "name": "unitLabel",
          "type": "string",
          "description": "What one icon stands for, for the key line, e.g. \"1 million people\"."
        },
        {
          "name": "icon",
          "type": "IconName | \"path://...\"",
          "default": "\"person\"",
          "description": "Built-in icon name from `core/icons.ts` (\"person\", \"house\", \"car\", \"tree\", \"coin\", \"drop\", \"bolt\", \"wind-turbine\", \"wheat\" and about 60 more) or a custom single SVG path."
        },
        {
          "name": "icons",
          "type": "Record<string, string>",
          "description": "Icon per category key; falls back to `icon`."
        },
        {
          "name": "perRow",
          "type": "number",
          "description": "Wrap rows after this many icons. Default: as many as fit."
        },
        {
          "name": "labels",
          "type": "boolean",
          "default": "true",
          "description": "Print the value after each row."
        },
        {
          "name": "sort",
          "type": "\"none\" | \"asc\" | \"desc\"",
          "default": "\"none\"",
          "description": "Row order."
        }
      ],
      "example": {
        "type": "pictogram-chart",
        "data": [
          {
            "city": "Tokyo",
            "people": 37.1
          },
          {
            "city": "Delhi",
            "people": 33.8
          },
          {
            "city": "Shanghai",
            "people": 29.9
          },
          {
            "city": "São Paulo",
            "people": 22.8
          },
          {
            "city": "Mexico City",
            "people": 22.5
          }
        ],
        "name": "city",
        "value": "people",
        "unit": 2,
        "unitLabel": "2 million people",
        "format": "{value}m",
        "highlight": "Delhi",
        "title": "Delhi had 33.8 million people in 2024, 3.3 million fewer than Tokyo",
        "subtitle": "Population of the five largest urban agglomerations, 2024",
        "source": "UN World Urbanization Prospects",
        "note": "Figures are approximate, for illustration"
      }
    },
    {
      "level": "atom",
      "component": "PillRank",
      "name": "pill-rank",
      "title": "Pill rank",
      "category": "poster",
      "description": "Shows a ranked list as horizontally centred rounded bars, with each name in capitals on its pill, the value at the left edge and the rank at the right.",
      "whenToUse": "A poster ranking of 4 to about 20 named things where the order and the overall silhouette matter more than fine differences: top cities, largest employers, league tables, with one row highlighted and optional reference rows set apart below.",
      "avoid": "Precise comparison of values (use bar-chart, where bars share a baseline); negative values (use diverging-bar-chart); change in rank over time (use bump-chart); more than about 25 rows (use data-table).",
      "props": [
        {
          "name": "name",
          "type": "string",
          "required": true,
          "description": "Name field: one row per data row."
        },
        {
          "name": "value",
          "type": "string",
          "required": true,
          "description": "Value field: pill width. Non-positive values are dropped."
        },
        {
          "name": "sort",
          "type": "boolean",
          "default": "true",
          "description": "Sort rows largest first. Set false to keep the data order."
        },
        {
          "name": "compare",
          "type": "string[]",
          "description": "Names of rows that are not ranked: drawn below a dotted rule, without a rank number."
        },
        {
          "name": "compareLabel",
          "type": "string",
          "description": "Short heading over the comparison rows, e.g. \"For comparison\"."
        },
        {
          "name": "max",
          "type": "number",
          "description": "Value that fills the full pill width. Default: the largest value."
        },
        {
          "name": "ranks",
          "type": "boolean",
          "default": "true",
          "description": "Show the rank number at the right edge."
        }
      ],
      "example": {
        "type": "pill-rank",
        "data": [
          {
            "river": "Nile",
            "km": 6650
          },
          {
            "river": "Amazon",
            "km": 6400
          },
          {
            "river": "Yangtze",
            "km": 6300
          },
          {
            "river": "Mississippi-Missouri",
            "km": 6275
          },
          {
            "river": "Yenisei",
            "km": 5539
          },
          {
            "river": "Yellow River",
            "km": 5464
          },
          {
            "river": "Ob-Irtysh",
            "km": 5410
          },
          {
            "river": "Congo",
            "km": 4700
          },
          {
            "river": "Danube",
            "km": 2850
          },
          {
            "river": "Rhine",
            "km": 1230
          }
        ],
        "name": "river",
        "value": "km",
        "format": "{value} km",
        "highlight": "Congo",
        "compare": [
          "Danube",
          "Rhine"
        ],
        "compareLabel": "Europe, for comparison",
        "title": "The Congo is 4,700 km long",
        "subtitle": "Length of eight major river systems and two European rivers, kilometres",
        "source": "Encyclopaedia Britannica",
        "note": "Lengths of river systems are approximate and vary by source"
      }
    },
    {
      "name": "progress-meter",
      "component": "ProgressMeter",
      "title": "Progress meter",
      "level": "atom",
      "category": "figure",
      "description": "A labelled horizontal meter of one value against a maximum or target, with the target marked and the value printed.",
      "whenToUse": "Progress toward a goal, a quota or a capacity: funds raised, budget spent, share of a target reached. Stack several with `size: \"sm\"` to compare progress across a few items.",
      "avoid": "Comparing more than a handful of items (use `bar-chart`), anything without a meaningful end point (use `big-number`), or a gauge dial.",
      "props": [
        {
          "name": "value",
          "type": "number",
          "required": true,
          "description": "The current value."
        },
        {
          "name": "label",
          "type": "string",
          "required": true,
          "description": "What is measured, sentence case."
        },
        {
          "name": "max",
          "type": "number",
          "description": "End of the track. Defaults to the target, or 1 with no target."
        },
        {
          "name": "min",
          "type": "number",
          "default": "0",
          "description": "Start of the track."
        },
        {
          "name": "target",
          "type": "number",
          "description": "A goal or threshold, drawn as a tick and labelled."
        },
        {
          "name": "targetLabel",
          "type": "string",
          "default": "\"Target\"",
          "description": "Name for the target tick."
        },
        {
          "name": "showMax",
          "type": "boolean",
          "description": "Print \"of {max}\" after the value. Default: only when `max` is given."
        },
        {
          "name": "context",
          "type": "string",
          "description": "A line under the meter that gives the number meaning."
        },
        {
          "name": "format",
          "type": "FormatSpec",
          "description": "Number format. Defaults to `\"percent\"` when the track ends at 1 or less, otherwise `\"auto\"`."
        },
        {
          "name": "tone",
          "type": "\"accent\" | \"positive\" | \"negative\" | \"neutral\"",
          "default": "\"accent\"",
          "description": "Fill colour by meaning."
        },
        {
          "name": "size",
          "type": "\"sm\" | \"md\"",
          "default": "\"md\"",
          "description": "`sm` for dense stacks."
        }
      ],
      "example": {
        "type": "progress-meter",
        "label": "Homes with a heat pump or district heating",
        "value": 0.46,
        "max": 1,
        "showMax": false,
        "target": 0.7,
        "targetLabel": "2030 target",
        "context": "Up from 31% in 2020. At the current rate the target is reached in 2033."
      }
    },
    {
      "level": "atom",
      "component": "ProportionalArea",
      "name": "proportional-area",
      "title": "Proportional area",
      "category": "poster",
      "description": "Compares two to eight values as squares or circles whose areas are proportional to the values, each printed with its name and number.",
      "whenToUse": "A few quantities that differ a lot (2 times to 100 times), where the reader should feel the difference in size: lake areas, budgets, market sizes. Nest them to show one quantity inside another.",
      "avoid": "Values within about 30% of each other, where areas look the same (use block-bar or bar-chart); more than 8 values (use treemap or bar-chart); parts of one whole (use treemap or waffle-chart); negative values.",
      "props": [
        {
          "name": "name",
          "type": "string",
          "required": true,
          "description": "Category field: one shape per row."
        },
        {
          "name": "value",
          "type": "string",
          "required": true,
          "description": "Value field. Each shape's area is proportional to it."
        },
        {
          "name": "shape",
          "type": "\"square\" | \"circle\"",
          "default": "\"square\"",
          "description": "Squares or circles."
        },
        {
          "name": "layout",
          "type": "\"row\" | \"nested\" | \"packed\"",
          "default": "\"row\"",
          "description": "`row`: side by side on a common baseline. `nested`: inside one another from a shared corner (squares) or lowest point (circles). `packed`: clustered together."
        },
        {
          "name": "ratio",
          "type": "boolean | string",
          "description": "Print how many times the largest value is the smallest. `true` for \"{largest} is {ratio} times {smallest}\", or a template with those placeholders."
        },
        {
          "name": "sort",
          "type": "\"none\" | \"asc\" | \"desc\"",
          "default": "\"none\"",
          "description": "Order of the shapes in the row layout. Nested and packed always draw largest first."
        }
      ],
      "example": {
        "type": "proportional-area",
        "data": [
          {
            "lake": "Superior",
            "area": 82100
          },
          {
            "lake": "Victoria",
            "area": 68900
          },
          {
            "lake": "Tanganyika",
            "area": 32900
          },
          {
            "lake": "Erie",
            "area": 25700
          },
          {
            "lake": "Titicaca",
            "area": 8370
          }
        ],
        "name": "lake",
        "value": "area",
        "format": "integer",
        "ratio": true,
        "title": "Lake Superior covers 82,100 km², 9.8 times Lake Titicaca",
        "subtitle": "Surface area of five lakes, square kilometres",
        "source": "Approximate published figures",
        "note": "Rounded, for illustration"
      }
    },
    {
      "name": "prose",
      "component": "Prose",
      "title": "Prose",
      "level": "atom",
      "category": "text",
      "description": "A readable block of narration at a 45-75 character measure, for the text between charts.",
      "whenToUse": "To say what a chart showed and introduce the next one. Use two or three short paragraphs at a time.",
      "avoid": "A single statement of fact (use `callout`), a quotation (use `pull-quote`), or sourcing and method notes (use `source-note`).",
      "props": [
        {
          "name": "text",
          "type": "string | string[]",
          "description": "Narration. Blank lines separate paragraphs. Supports `**bold**`, `*italic*` and `[link](url)`."
        },
        {
          "name": "children",
          "type": "ReactNode",
          "description": "JSX alternative to `text`."
        },
        {
          "name": "size",
          "type": "\"sm\" | \"md\" | \"lg\"",
          "default": "\"md\"",
          "description": "Body size: 14, 16 or 18px."
        },
        {
          "name": "measure",
          "type": "number",
          "default": "65",
          "description": "Line length in characters, clamped to 45-75."
        },
        {
          "name": "lead",
          "type": "boolean",
          "default": "false",
          "description": "Set the first paragraph a step larger."
        },
        {
          "name": "tone",
          "type": "\"default\" | \"muted\"",
          "default": "\"default\"",
          "description": "`muted` for secondary narration."
        },
        {
          "name": "align",
          "type": "\"left\" | \"center\"",
          "default": "\"left\"",
          "description": "Centres the block (text stays left-aligned)."
        }
      ],
      "example": {
        "type": "prose",
        "text": "In 2015 solar supplied **3% of Europe's electricity**. Hydro supplied more than three times as much.\n\nPanel prices fell by about 80% in the years that followed. In 2024 solar generated more electricity than coal across the EU, according to [Ember's annual review](https://ember-energy.org).\n\nSolar's share differs by country. The next chart shows it for each EU member state."
      }
    },
    {
      "name": "pull-quote",
      "component": "PullQuote",
      "title": "Pull quote",
      "level": "atom",
      "category": "text",
      "description": "A large quotation with its attribution.",
      "whenToUse": "To quote a person on a finding the charts have already shown, or to break up a long run of charts. One per section at most.",
      "avoid": "Restating a number (use `big-number`) or the main fact of a chart (use `callout`). Never quote without attribution.",
      "props": [
        {
          "name": "quote",
          "type": "string",
          "description": "The words quoted, without quotation marks."
        },
        {
          "name": "children",
          "type": "ReactNode",
          "description": "JSX alternative to `quote`."
        },
        {
          "name": "attribution",
          "type": "string",
          "description": "Who said it."
        },
        {
          "name": "role",
          "type": "string",
          "description": "Their role, or where it was said."
        },
        {
          "name": "cite",
          "type": "string",
          "description": "Source URL: set as the blockquote `cite` and linked from the attribution."
        },
        {
          "name": "size",
          "type": "\"md\" | \"lg\"",
          "default": "\"lg\"",
          "description": "`lg` anchors a section; `md` sits inside a column."
        },
        {
          "name": "align",
          "type": "\"left\" | \"center\"",
          "default": "\"left\"",
          "description": "Left-aligned quotes carry a side rule; centred ones do not."
        }
      ],
      "example": {
        "type": "pull-quote",
        "quote": "Solar set the midday wholesale price on most days in 2023. Our plans had assumed that would happen in the 2030s.",
        "attribution": "Marit de Vries",
        "role": "Head of system operations, Dutch grid operator"
      }
    },
    {
      "level": "atom",
      "component": "RadialBarChart",
      "name": "radial-bar-chart",
      "title": "Radial bar chart",
      "category": "trend",
      "description": "Draws bars around a circle for data that repeats in a cycle, such as hours of the day or months of the year.",
      "whenToUse": "Only when the dimension is cyclical and the wrap-around matters: activity by hour of day, seasonality by month, wind by compass direction. The first category is placed at 12 o'clock.",
      "avoid": "Any dimension that does not wrap around: categories, rankings, years (use bar-chart); precise comparison of values, since bars further out look bigger (use bar-chart or line-chart); more than about 3 stacked series.",
      "props": [
        {
          "name": "x",
          "type": "string",
          "required": true,
          "description": "The cyclical category field, in cycle order."
        },
        {
          "name": "y",
          "type": "string | string[]",
          "required": true,
          "description": "Value field, or several to stack."
        },
        {
          "name": "series",
          "type": "string",
          "description": "Field that splits rows into stacked series (long data)."
        },
        {
          "name": "labelEvery",
          "type": "number",
          "description": "Label every nth category around the rim. Default: chosen to avoid crowding."
        },
        {
          "name": "labels",
          "type": "boolean",
          "description": "Print the value on the tallest bar. Default: on for a single series."
        },
        {
          "name": "centerFirst",
          "type": "boolean",
          "default": "false",
          "description": "Centre the first category on 12 o'clock (compass directions) instead of starting its band there (hours)."
        },
        {
          "name": "centerLabel",
          "type": "string",
          "description": "Text in the centre of the ring, e.g. \"24 hours\"."
        },
        {
          "name": "names",
          "type": "Record<string, string>",
          "description": "Display names for series keys."
        }
      ],
      "example": {
        "type": "radial-bar-chart",
        "data": [
          {
            "hour": "00:00",
            "trips": 2.1
          },
          {
            "hour": "01:00",
            "trips": 1.2
          },
          {
            "hour": "02:00",
            "trips": 0.8
          },
          {
            "hour": "03:00",
            "trips": 0.6
          },
          {
            "hour": "04:00",
            "trips": 0.7
          },
          {
            "hour": "05:00",
            "trips": 1.4
          },
          {
            "hour": "06:00",
            "trips": 3.6
          },
          {
            "hour": "07:00",
            "trips": 6.9
          },
          {
            "hour": "08:00",
            "trips": 8.8
          },
          {
            "hour": "09:00",
            "trips": 6.4
          },
          {
            "hour": "10:00",
            "trips": 5.1
          },
          {
            "hour": "11:00",
            "trips": 5.3
          },
          {
            "hour": "12:00",
            "trips": 5.9
          },
          {
            "hour": "13:00",
            "trips": 5.6
          },
          {
            "hour": "14:00",
            "trips": 5.4
          },
          {
            "hour": "15:00",
            "trips": 6
          },
          {
            "hour": "16:00",
            "trips": 7.4
          },
          {
            "hour": "17:00",
            "trips": 9.2
          },
          {
            "hour": "18:00",
            "trips": 8.1
          },
          {
            "hour": "19:00",
            "trips": 6
          },
          {
            "hour": "20:00",
            "trips": 4.6
          },
          {
            "hour": "21:00",
            "trips": 3.9
          },
          {
            "hour": "22:00",
            "trips": 3.3
          },
          {
            "hour": "23:00",
            "trips": 2.7
          }
        ],
        "x": "hour",
        "y": "trips",
        "format": "{value}%",
        "centerLabel": "24 hours",
        "highlight": [
          "08:00",
          "17:00"
        ],
        "title": "Weekday bike-share trips peak at 9.2% in the 5pm hour and 8.8% in the 8am hour",
        "subtitle": "Share of daily bike-share trips starting in each hour, weekdays",
        "source": "Illustrative bike-share data"
      }
    },
    {
      "level": "atom",
      "component": "RadialSpokes",
      "name": "radial-spokes",
      "title": "Radial spokes",
      "category": "poster",
      "description": "Draws one spoke per row radiating from a centre disc, each as long as its value, with the name and value set on an outer ring.",
      "whenToUse": "A poster of 4 to 12 effects, causes or attributes of one subject, which sits in the centre as an icon or a short label: what a household's energy goes on, the side effects of one drug, the scores of one product.",
      "avoid": "Precise comparison or a ranking (use bar-chart or pill-rank); a dimension that is a cycle, such as hours or months (use radial-bar-chart); more than 12 rows (use bar-chart); several subjects compared on the same attributes (use small-multiples or heatmap).",
      "props": [
        {
          "name": "name",
          "type": "string",
          "required": true,
          "description": "Name field: one spoke per row, in data order, clockwise."
        },
        {
          "name": "value",
          "type": "string",
          "required": true,
          "description": "Value field: spoke length from the centre disc."
        },
        {
          "name": "shape",
          "type": "\"bar\" | \"line\"",
          "default": "\"bar\"",
          "description": "A thick tapered bar with a round head, or a thin line ending in a dot."
        },
        {
          "name": "icon",
          "type": "string",
          "description": "Icon in the centre disc: a built-in name or `path://`."
        },
        {
          "name": "centerLabel",
          "type": "string",
          "description": "Short text in the centre disc, used when there is no `icon`."
        },
        {
          "name": "ring",
          "type": "boolean",
          "default": "true",
          "description": "Dotted outer ring at the scale maximum, where the labels sit."
        },
        {
          "name": "max",
          "type": "number",
          "description": "Value that reaches the outer ring. Default: the largest value."
        },
        {
          "name": "startAngle",
          "type": "number",
          "default": "0",
          "description": "Angle of the first spoke in degrees clockwise from 12 o'clock."
        },
        {
          "name": "sweep",
          "type": "number",
          "default": "360",
          "description": "Arc the spokes are spread over, in degrees. 180 makes a fan."
        },
        {
          "name": "colorBy",
          "type": "\"single\" | \"value\"",
          "default": "\"single\"",
          "description": "The accent for every spoke, or a shade by value along the sequential ramp."
        }
      ],
      "example": {
        "type": "radial-spokes",
        "data": [
          {
            "use": "Space heating",
            "share": 42
          },
          {
            "use": "Hot water",
            "share": 17
          },
          {
            "use": "Cooking",
            "share": 6
          },
          {
            "use": "Fridge and freezer",
            "share": 8
          },
          {
            "use": "Laundry",
            "share": 7
          },
          {
            "use": "Lighting",
            "share": 5
          },
          {
            "use": "Screens and computers",
            "share": 9
          },
          {
            "use": "Everything else",
            "share": 6
          }
        ],
        "name": "use",
        "value": "share",
        "format": "{value}%",
        "icon": "house",
        "highlight": "Space heating",
        "title": "Space heating uses 42% of a home's energy",
        "subtitle": "Share of household energy by end use, a typical home in a cool climate",
        "note": "Illustrative figures"
      }
    },
    {
      "level": "atom",
      "component": "RibbonChart",
      "name": "ribbon-chart",
      "title": "Ribbon chart",
      "category": "poster",
      "description": "One filled band per entity across periods: its thickness is its value in each period and its vertical position is its rank, so bands cross as the order changes.",
      "whenToUse": "4 to 8 entities over 4 to 10 periods when both the size of each and the changing order matter, and the graphic should read as one flowing form: market shares, harvests, medal tables, budget lines.",
      "avoid": "Totals that matter more than order, or a smooth centred stream of many periods (use area-chart, which has stacked and stream modes); rank alone (use bump-chart); more than 8 entities or 10 periods (use line-chart or small-multiples); values that can be negative (use line-chart).",
      "props": [
        {
          "name": "x",
          "type": "string",
          "required": true,
          "description": "Period field: one column of the ribbon per distinct value, in data order."
        },
        {
          "name": "y",
          "type": "string | string[]",
          "required": true,
          "description": "Value field (long data), or one column per entity (wide data). Band thickness; negative values are drawn as zero."
        },
        {
          "name": "series",
          "type": "string",
          "description": "Entity field, for long data."
        },
        {
          "name": "values",
          "type": "boolean",
          "default": "true",
          "description": "Print each value inside its band at every period where it fits."
        },
        {
          "name": "labels",
          "type": "\"both\" | \"end\"",
          "description": "Entity names at both ends, or only at the end. Default: both, end only on narrow screens."
        },
        {
          "name": "names",
          "type": "Record<string, string>",
          "description": "Display names for entity keys."
        },
        {
          "name": "colorBy",
          "type": "\"auto\" | \"series\" | \"none\"",
          "default": "\"auto\"",
          "description": "`auto`: the largest entities take the theme's colours and any past the palette size (at most 8) are grey. `series`: every band gets its own colour, the extra ones derived from the theme's own. `none`: every band in the accent."
        }
      ],
      "example": {
        "type": "ribbon-chart",
        "data": [
          {
            "year": "2019",
            "Potatoes": 7.4,
            "Carrots": 5.1,
            "Onions": 4.2,
            "Beans": 2.6,
            "Squash": 1.2
          },
          {
            "year": "2020",
            "Potatoes": 6.9,
            "Carrots": 5.6,
            "Onions": 3.8,
            "Beans": 3.1,
            "Squash": 2.3
          },
          {
            "year": "2021",
            "Potatoes": 6.1,
            "Carrots": 5.9,
            "Onions": 3.1,
            "Beans": 3.4,
            "Squash": 3.9
          },
          {
            "year": "2022",
            "Potatoes": 5.2,
            "Carrots": 6.3,
            "Onions": 2.9,
            "Beans": 3.2,
            "Squash": 5
          },
          {
            "year": "2023",
            "Potatoes": 4.8,
            "Carrots": 6,
            "Onions": 2.4,
            "Beans": 3.6,
            "Squash": 6.1
          },
          {
            "year": "2024",
            "Potatoes": 4.1,
            "Carrots": 6.2,
            "Onions": 2.2,
            "Beans": 3.5,
            "Squash": 6.8
          }
        ],
        "x": "year",
        "y": [
          "Potatoes",
          "Carrots",
          "Onions",
          "Beans",
          "Squash"
        ],
        "format": "{value} t",
        "title": "The squash harvest rose from 1.2 to 6.8 tonnes between 2019 and 2024",
        "subtitle": "Harvest of five crops at one community farm, tonnes, 2019 to 2024, largest at the top each year",
        "note": "Illustrative figures."
      }
    },
    {
      "level": "atom",
      "component": "SankeyChart",
      "name": "sankey-chart",
      "title": "Sankey chart",
      "category": "flow",
      "description": "Shows how a quantity flows from sources through stages to destinations, with link width proportional to the flow.",
      "whenToUse": "Where things come from and where they go: energy from fuel to end use, budget from revenue to spending, users from channel to outcome. Works best with 2 to 4 stages and up to about 20 nodes.",
      "avoid": "A single sequence of steps with drop-off (use funnel-chart); comparing a few totals (use bar-chart); flows that loop back (cycles cannot be drawn and are dropped); more than about 40 links.",
      "props": [
        {
          "name": "from",
          "type": "string",
          "default": "\"source\"",
          "description": "Field naming where each flow starts. Not called `source` because that is the figure's source line."
        },
        {
          "name": "to",
          "type": "string",
          "default": "\"target\"",
          "description": "Field naming where each flow ends."
        },
        {
          "name": "value",
          "type": "string",
          "default": "\"value\"",
          "description": "Flow size field. Non-positive flows are dropped."
        },
        {
          "name": "orientation",
          "type": "\"auto\" | \"horizontal\" | \"vertical\"",
          "default": "\"auto\"",
          "description": "Left to right, or top to bottom for narrow columns with short names."
        },
        {
          "name": "labels",
          "type": "boolean",
          "default": "true",
          "description": "Print each node's total after its name."
        },
        {
          "name": "names",
          "type": "Record<string, string>",
          "description": "Display names for node keys."
        }
      ],
      "example": {
        "type": "sankey-chart",
        "data": [
          {
            "from": "Coal",
            "to": "Electricity",
            "twh": 290
          },
          {
            "from": "Gas",
            "to": "Electricity",
            "twh": 210
          },
          {
            "from": "Gas",
            "to": "Heat",
            "twh": 330
          },
          {
            "from": "Wind and solar",
            "to": "Electricity",
            "twh": 240
          },
          {
            "from": "Nuclear",
            "to": "Electricity",
            "twh": 120
          },
          {
            "from": "Oil",
            "to": "Transport",
            "twh": 560
          },
          {
            "from": "Oil",
            "to": "Heat",
            "twh": 90
          },
          {
            "from": "Electricity",
            "to": "Homes",
            "twh": 280
          },
          {
            "from": "Electricity",
            "to": "Industry",
            "twh": 330
          },
          {
            "from": "Electricity",
            "to": "Transport",
            "twh": 30
          },
          {
            "from": "Electricity",
            "to": "Losses",
            "twh": 220
          },
          {
            "from": "Heat",
            "to": "Homes",
            "twh": 270
          },
          {
            "from": "Heat",
            "to": "Industry",
            "twh": 150
          }
        ],
        "from": "from",
        "to": "to",
        "value": "twh",
        "format": "{value} TWh",
        "title": "220 TWh of the 860 TWh used to generate electricity was lost",
        "subtitle": "Energy flows from fuel to end use, terawatt-hours per year",
        "source": "Illustrative national energy balance"
      }
    },
    {
      "level": "atom",
      "component": "ScaleLadder",
      "name": "scale-ladder",
      "title": "Scale ladder",
      "category": "poster",
      "description": "A tall vertical band graded along one scale (linear or log, running up or down) with each item placed at its true position and labelled with an icon, its name and its value on alternating sides.",
      "whenToUse": "Answering how deep, how high, how hot, how old or how far each of 4 to about 20 things is on one shared scale, when the reader needs the positions themselves: depths, altitudes, temperatures, ages, distances. Use `zones` to name ranges along the scale.",
      "avoid": "Comparing sizes rather than positions (use bar-chart or lollipop-chart); events with start and end dates (use timeline-chart); a distribution of many unnamed values (use strip-plot or histogram).",
      "props": [
        {
          "name": "name",
          "type": "string",
          "required": true,
          "description": "Field holding each item's name."
        },
        {
          "name": "value",
          "type": "string",
          "required": true,
          "description": "Field holding each item's position on the scale."
        },
        {
          "name": "icon",
          "type": "IconInput",
          "description": "One icon for every item: a built-in name or a `path://` string. Items without an icon get a dot."
        },
        {
          "name": "iconField",
          "type": "string",
          "description": "Field holding a per-item icon name or `path://` string. Overrides `icon`."
        },
        {
          "name": "scale",
          "type": "\"linear\" | \"log\"",
          "default": "\"linear\"",
          "description": "Log spaces the scale by powers of ten; the heading over the band says so."
        },
        {
          "name": "direction",
          "type": "\"up\" | \"down\"",
          "default": "\"up\"",
          "description": "`up`: larger values higher (height, temperature). `down`: larger values lower (depth, age)."
        },
        {
          "name": "min",
          "type": "number",
          "description": "Start of the scale. Default: a round number at or below the smallest value."
        },
        {
          "name": "max",
          "type": "number",
          "description": "End of the scale. Default: a round number at or above the largest value."
        },
        {
          "name": "ticks",
          "type": "number[]",
          "description": "Values that get a boxed label on the band. Default: round steps, or powers of ten on a log scale."
        },
        {
          "name": "zones",
          "type": "{ from: number; to: number; label: string }[]",
          "description": "Named ranges along the scale, shaded across the graphic and named along its outer edge."
        },
        {
          "name": "scaleLabel",
          "type": "string",
          "description": "Heading over the band: the measure and its unit, e.g. \"Depth, metres\"."
        },
        {
          "name": "names",
          "type": "Record<string, string>",
          "description": "Display names for item keys."
        },
        {
          "name": "bandWidth",
          "type": "number",
          "description": "Width of the band in px, at most 40% of the plot width. Default 60, or 46 on narrow screens."
        }
      ],
      "example": {
        "type": "scale-ladder",
        "data": [
          {
            "thing": "Fridge",
            "c": 4
          },
          {
            "thing": "Chocolate melts",
            "c": 34
          },
          {
            "thing": "Egg white sets",
            "c": 62
          },
          {
            "thing": "Water boils",
            "c": 100,
            "icon": "drop"
          },
          {
            "thing": "Sugar caramelises",
            "c": 170
          },
          {
            "thing": "Deep-frying oil",
            "c": 180,
            "icon": "drop"
          },
          {
            "thing": "Bread bakes",
            "c": 220
          },
          {
            "thing": "Home oven at full heat",
            "c": 250
          },
          {
            "thing": "Wood-fired pizza oven",
            "c": 450
          }
        ],
        "name": "thing",
        "value": "c",
        "iconField": "icon",
        "format": "{value}°C",
        "scaleLabel": "Temperature",
        "min": 0,
        "max": 500,
        "zones": [
          {
            "from": 0,
            "to": 100,
            "label": "Below boiling"
          },
          {
            "from": 100,
            "to": 250,
            "label": "Home oven range"
          },
          {
            "from": 250,
            "to": 500,
            "label": "Wood-fired oven range"
          }
        ],
        "title": "Nine kitchen temperatures from 4°C to 450°C",
        "subtitle": "Typical temperatures in a kitchen, degrees Celsius",
        "note": "Approximate, rounded figures."
      }
    },
    {
      "level": "atom",
      "component": "ScatterChart",
      "name": "scatter-chart",
      "title": "Scatter chart",
      "category": "relationship",
      "description": "Plots two numeric measures against each other, one dot per row, with optional bubble size, group colour, selective labels and a trend line.",
      "whenToUse": "Showing whether and how two measures move together, spotting clusters and outliers, and placing named entities in a two-dimensional space (with quadrant lines via `annotations`).",
      "avoid": "A measure over time (use line-chart); one numeric measure by category (use beeswarm-chart or strip-plot); fewer than about 8 points (use bar-chart or a table); more than 4 colour groups (facet with small-multiples).",
      "props": [
        {
          "name": "x",
          "type": "string",
          "required": true,
          "description": "Numeric field on the horizontal axis."
        },
        {
          "name": "y",
          "type": "string",
          "required": true,
          "description": "Numeric field on the vertical axis."
        },
        {
          "name": "size",
          "type": "string",
          "description": "Numeric field for bubble size; area is proportional to the value."
        },
        {
          "name": "series",
          "type": "string",
          "description": "Field that colours points by group. Beyond 4 groups the smallest fold into a grey Other."
        },
        {
          "name": "label",
          "type": "string",
          "description": "Field naming each point, for tooltips, direct labels and `highlight`."
        },
        {
          "name": "labels",
          "type": "\"auto\" | \"all\" | \"none\" | string[]",
          "default": "\"auto\"",
          "description": "Which points are labelled. `auto`: highlighted points, or the outliers when nothing is highlighted. Overlapping labels are hidden."
        },
        {
          "name": "trend",
          "type": "boolean",
          "default": "false",
          "description": "Least-squares line of best fit through all points."
        },
        {
          "name": "logX",
          "type": "boolean",
          "default": "false",
          "description": "Logarithmic x axis."
        },
        {
          "name": "logY",
          "type": "boolean",
          "default": "false",
          "description": "Logarithmic y axis."
        },
        {
          "name": "xLabel",
          "type": "string",
          "description": "x axis title. Defaults to the field name."
        },
        {
          "name": "yLabel",
          "type": "string",
          "description": "y axis title. Defaults to the field name."
        },
        {
          "name": "xFormat",
          "type": "FormatSpec",
          "description": "Format for x values; `format` applies to y."
        },
        {
          "name": "opacity",
          "type": "number",
          "description": "Point opacity. Default falls as the point count rises."
        },
        {
          "name": "names",
          "type": "Record<string, string>",
          "description": "Display names for series keys."
        }
      ],
      "example": {
        "type": "scatter-chart",
        "data": [
          {
            "country": "United States",
            "spend": 12555,
            "life": 77.5
          },
          {
            "country": "Switzerland",
            "spend": 8049,
            "life": 83.9
          },
          {
            "country": "Germany",
            "spend": 8011,
            "life": 80.8
          },
          {
            "country": "Norway",
            "spend": 7771,
            "life": 83.2
          },
          {
            "country": "Netherlands",
            "spend": 6729,
            "life": 81.4
          },
          {
            "country": "France",
            "spend": 6630,
            "life": 82.4
          },
          {
            "country": "Canada",
            "spend": 6319,
            "life": 81.6
          },
          {
            "country": "Sweden",
            "spend": 6438,
            "life": 83.1
          },
          {
            "country": "Australia",
            "spend": 6372,
            "life": 83.3
          },
          {
            "country": "United Kingdom",
            "spend": 5493,
            "life": 80.7
          },
          {
            "country": "Japan",
            "spend": 5251,
            "life": 84.5
          },
          {
            "country": "Italy",
            "spend": 4291,
            "life": 82.7
          },
          {
            "country": "Spain",
            "spend": 4432,
            "life": 83.3
          },
          {
            "country": "South Korea",
            "spend": 4570,
            "life": 83.6
          },
          {
            "country": "Portugal",
            "spend": 4162,
            "life": 81.5
          },
          {
            "country": "Israel",
            "spend": 3444,
            "life": 82.6
          },
          {
            "country": "Poland",
            "spend": 2973,
            "life": 76.5
          },
          {
            "country": "Chile",
            "spend": 2699,
            "life": 81
          },
          {
            "country": "Mexico",
            "spend": 1181,
            "life": 75.4
          }
        ],
        "x": "spend",
        "y": "life",
        "label": "country",
        "highlight": "United States",
        "labels": [
          "United States",
          "Japan",
          "Mexico",
          "Switzerland",
          "Poland"
        ],
        "xFormat": "currency:USD",
        "xLabel": "Health spending per person",
        "yLabel": "Life expectancy, years",
        "title": "The United States spent $12,555 per person on health in 2022",
        "subtitle": "Health spending per person (US$, PPP) against life expectancy at birth, OECD countries, 2022. US life expectancy was 77.5 years.",
        "source": "OECD Health Statistics (rounded)",
        "note": "Figures are approximate, for illustration"
      }
    },
    {
      "level": "atom",
      "component": "SegmentedBar",
      "name": "segmented-bar",
      "title": "Segmented bar",
      "category": "part-to-whole",
      "description": "Splits one whole into proportional segments along a single horizontal bar.",
      "whenToUse": "A compact part-to-whole for cards, table cells and stat rows: budget split, device mix, vote share. The legend carries names in block order.",
      "avoid": "Comparing compositions across several groups (use bar-chart with stack \"percent\"); more than about 6 parts or many slivers (use bar-chart); when the reader should count units (use waffle-chart).",
      "props": [
        {
          "name": "name",
          "type": "string",
          "required": true,
          "description": "Category field: one segment per distinct value."
        },
        {
          "name": "value",
          "type": "string",
          "required": true,
          "description": "Value field."
        },
        {
          "name": "size",
          "type": "\"tall\" | \"thin\"",
          "default": "\"tall\"",
          "description": "28px with shares printed inside where they fit, or 8px unlabelled."
        },
        {
          "name": "sort",
          "type": "\"none\" | \"desc\"",
          "default": "\"none\"",
          "description": "Segment order; the legend follows it."
        },
        {
          "name": "names",
          "type": "Record<string, string>",
          "description": "Display names for category keys."
        }
      ],
      "example": {
        "type": "segmented-bar",
        "data": [
          {
            "party": "Labour",
            "seats": 411
          },
          {
            "party": "Conservative",
            "seats": 121
          },
          {
            "party": "Liberal Democrat",
            "seats": 72
          },
          {
            "party": "SNP",
            "seats": 9
          },
          {
            "party": "Others",
            "seats": 37
          }
        ],
        "name": "party",
        "value": "seats",
        "title": "Labour won 411 of 650 seats in the 2024 general election",
        "subtitle": "Seats in the House of Commons by party after the 2024 general election",
        "source": "House of Commons Library",
        "note": "Figures are approximate, for illustration"
      }
    },
    {
      "level": "atom",
      "component": "ShapeFill",
      "name": "shape-fill",
      "title": "Shape fill",
      "category": "poster",
      "description": "Shows one or more percentages as icon silhouettes filled from the bottom to that level, with the number printed large beside each.",
      "whenToUse": "One to six shares of a full amount where the object itself says what is measured: how full a reservoir is, how charged a fleet is, what share of homes have a heat pump.",
      "avoid": "The fill height only approximates the share, because an irregular silhouette holds less area at its narrow end, so the number must always stay printed; for values to be compared precisely use block-bar or bar-chart, for a counted share use waffle-chart, for a compact inline meter use progress-meter.",
      "props": [
        {
          "name": "name",
          "type": "string",
          "required": true,
          "description": "Label field: one silhouette per row."
        },
        {
          "name": "value",
          "type": "string",
          "required": true,
          "description": "Value field: how full each silhouette is, against `max`."
        },
        {
          "name": "max",
          "type": "number",
          "description": "The value that fills a silhouette completely. Default: 1 when every value is at most 1, otherwise 100."
        },
        {
          "name": "icon",
          "type": "IconName | \"path://...\"",
          "default": "\"drop\"",
          "description": "Built-in icon name or a custom single SVG path."
        },
        {
          "name": "icons",
          "type": "Record<string, string>",
          "description": "Icon per label; falls back to `icon`."
        },
        {
          "name": "columns",
          "type": "number",
          "description": "Silhouettes per line. Default: up to four, two on a phone."
        },
        {
          "name": "colorBy",
          "type": "\"single\" | \"item\"",
          "default": "\"single\"",
          "description": "`single`: every silhouette in the accent colour. `item`: one colour per row."
        },
        {
          "name": "iconSize",
          "type": "number",
          "description": "Silhouette height in px. Default: sized to the width, or grown to fill the box when the `height` prop or `aspect` is given, so one silhouette can be the main figure of a poster."
        }
      ],
      "example": {
        "type": "shape-fill",
        "data": [
          {
            "reservoir": "Carrow",
            "level": 63
          },
          {
            "reservoir": "Upper Fen",
            "level": 41
          },
          {
            "reservoir": "Stonebeck",
            "level": 88
          }
        ],
        "name": "reservoir",
        "value": "level",
        "icon": "drop",
        "format": "{value}%",
        "title": "Carrow Reservoir was 63% full on 1 September 2025",
        "subtitle": "Water stored as a share of capacity, three reservoirs, 1 September 2025",
        "source": "Illustrative data",
        "note": "Invented figures for fictional reservoirs"
      }
    },
    {
      "level": "atom",
      "component": "SlopeChart",
      "name": "slope-chart",
      "title": "Slope chart",
      "category": "trend",
      "description": "Connects each entity's value at two (or a few) points with a straight line, labelled with name and value at both ends.",
      "whenToUse": "Before / after for 3 to about 15 entities when the change in level and in ordering both matter; pair with `highlight` to emphasise one line.",
      "avoid": "Five or more time points (use line-chart); when only the ordering matters (use bump-chart); when only the size of each gap matters (use dumbbell-chart or arrow-chart).",
      "props": [
        {
          "name": "series",
          "type": "string",
          "required": true,
          "description": "Entity field: one line each."
        },
        {
          "name": "x",
          "type": "string",
          "description": "Period field for long data. Omit when each row is an entity with one column per period."
        },
        {
          "name": "y",
          "type": "string | string[]",
          "required": true,
          "description": "Value field (long data), or the period columns in order (one row per entity)."
        },
        {
          "name": "colorBy",
          "type": "\"none\" | \"direction\" | \"series\"",
          "default": "\"none\"",
          "description": "Accent for all lines, positive / negative by direction, or one hue per entity (up to 8)."
        },
        {
          "name": "good",
          "type": "\"up\" | \"down\"",
          "default": "\"up\"",
          "description": "Direction that takes the positive colour when colouring by direction."
        },
        {
          "name": "labels",
          "type": "\"both\" | \"end\"",
          "description": "Entity names at both ends or only the end. Default: both; end only on narrow screens."
        },
        {
          "name": "names",
          "type": "Record<string, string>",
          "description": "Display names for entity keys and period columns."
        }
      ],
      "example": {
        "type": "slope-chart",
        "data": [
          {
            "country": "United Kingdom",
            "y2012": 39,
            "y2023": 1
          },
          {
            "country": "Germany",
            "y2012": 44,
            "y2023": 26
          },
          {
            "country": "United States",
            "y2012": 37,
            "y2023": 16
          },
          {
            "country": "Poland",
            "y2012": 84,
            "y2023": 61
          },
          {
            "country": "India",
            "y2012": 71,
            "y2023": 75
          },
          {
            "country": "Japan",
            "y2012": 29,
            "y2023": 32
          }
        ],
        "series": "country",
        "y": [
          "y2012",
          "y2023"
        ],
        "names": {
          "y2012": "2012",
          "y2023": "2023"
        },
        "highlight": "United Kingdom",
        "format": "{value}%",
        "title": "The UK's coal share of electricity fell from 39% to 1% between 2012 and 2023",
        "subtitle": "Share of electricity generated from coal, 2012 and 2023",
        "source": "Ember (rounded)",
        "note": "Figures are approximate, for illustration"
      }
    },
    {
      "level": "atom",
      "component": "SmallMultiples",
      "name": "small-multiples",
      "title": "Small multiples",
      "category": "trend",
      "description": "Repeats one small line, area or bar chart per value of a facet field on a shared scale, each panel titled.",
      "whenToUse": "The same measure for 4 to about 24 entities when one chart would have too many overlapping lines; pair with `highlight` to pick out one panel, or `context` to show every other line faintly behind each.",
      "avoid": "Three or fewer series (use line-chart with direct labels); when exact comparison between two entities matters most (use line-chart with `highlight`); a single value per entity (use bar-chart).",
      "props": [
        {
          "name": "facet",
          "type": "string",
          "required": true,
          "description": "Field whose values each get a panel."
        },
        {
          "name": "x",
          "type": "string",
          "required": true,
          "description": "Time or ordered category field, shared by every panel."
        },
        {
          "name": "y",
          "type": "string | string[]",
          "required": true,
          "description": "Value field, or several (one series each in every panel)."
        },
        {
          "name": "series",
          "type": "string",
          "description": "Field that splits rows into series inside each panel (long data)."
        },
        {
          "name": "kind",
          "type": "\"line\" | \"area\" | \"bar\"",
          "default": "\"line\"",
          "description": "What each panel draws. Area and bar panels start at zero."
        },
        {
          "name": "columns",
          "type": "number",
          "description": "Preferred number of columns; reduced automatically on narrow containers."
        },
        {
          "name": "scales",
          "type": "\"shared\" | \"free\"",
          "default": "\"shared\"",
          "description": "Shared value scale across panels, or each panel on its own (say so in the subtitle)."
        },
        {
          "name": "context",
          "type": "boolean",
          "default": "false",
          "description": "Draw every other panel's line faintly behind each panel's own."
        },
        {
          "name": "latest",
          "type": "boolean",
          "description": "Print the latest value beside each panel title. Default: on for a single series."
        },
        {
          "name": "names",
          "type": "Record<string, string>",
          "description": "Display names for facet values and series keys."
        }
      ],
      "example": {
        "type": "small-multiples",
        "data": [
          {
            "country": "Germany",
            "year": "2010",
            "rate": 6.8
          },
          {
            "country": "Germany",
            "year": "2013",
            "rate": 5
          },
          {
            "country": "Germany",
            "year": "2015",
            "rate": 4.6
          },
          {
            "country": "Germany",
            "year": "2017",
            "rate": 3.8
          },
          {
            "country": "Germany",
            "year": "2019",
            "rate": 3.1
          },
          {
            "country": "Germany",
            "year": "2021",
            "rate": 3.6
          },
          {
            "country": "Germany",
            "year": "2023",
            "rate": 3
          },
          {
            "country": "Germany",
            "year": "2024",
            "rate": 3.4
          },
          {
            "country": "Spain",
            "year": "2010",
            "rate": 19.9
          },
          {
            "country": "Spain",
            "year": "2013",
            "rate": 26.1
          },
          {
            "country": "Spain",
            "year": "2015",
            "rate": 22.1
          },
          {
            "country": "Spain",
            "year": "2017",
            "rate": 17.2
          },
          {
            "country": "Spain",
            "year": "2019",
            "rate": 14.1
          },
          {
            "country": "Spain",
            "year": "2021",
            "rate": 14.8
          },
          {
            "country": "Spain",
            "year": "2023",
            "rate": 12.2
          },
          {
            "country": "Spain",
            "year": "2024",
            "rate": 11.4
          },
          {
            "country": "Italy",
            "year": "2010",
            "rate": 8.4
          },
          {
            "country": "Italy",
            "year": "2013",
            "rate": 12.1
          },
          {
            "country": "Italy",
            "year": "2015",
            "rate": 11.9
          },
          {
            "country": "Italy",
            "year": "2017",
            "rate": 11.2
          },
          {
            "country": "Italy",
            "year": "2019",
            "rate": 9.9
          },
          {
            "country": "Italy",
            "year": "2021",
            "rate": 9.5
          },
          {
            "country": "Italy",
            "year": "2023",
            "rate": 7.7
          },
          {
            "country": "Italy",
            "year": "2024",
            "rate": 6.5
          },
          {
            "country": "France",
            "year": "2010",
            "rate": 9.3
          },
          {
            "country": "France",
            "year": "2013",
            "rate": 10.3
          },
          {
            "country": "France",
            "year": "2015",
            "rate": 10.4
          },
          {
            "country": "France",
            "year": "2017",
            "rate": 9.4
          },
          {
            "country": "France",
            "year": "2019",
            "rate": 8.4
          },
          {
            "country": "France",
            "year": "2021",
            "rate": 7.9
          },
          {
            "country": "France",
            "year": "2023",
            "rate": 7.3
          },
          {
            "country": "France",
            "year": "2024",
            "rate": 7.4
          },
          {
            "country": "Greece",
            "year": "2010",
            "rate": 12.7
          },
          {
            "country": "Greece",
            "year": "2013",
            "rate": 27.5
          },
          {
            "country": "Greece",
            "year": "2015",
            "rate": 24.9
          },
          {
            "country": "Greece",
            "year": "2017",
            "rate": 21.5
          },
          {
            "country": "Greece",
            "year": "2019",
            "rate": 17.3
          },
          {
            "country": "Greece",
            "year": "2021",
            "rate": 14.7
          },
          {
            "country": "Greece",
            "year": "2023",
            "rate": 11.1
          },
          {
            "country": "Greece",
            "year": "2024",
            "rate": 10.1
          },
          {
            "country": "Poland",
            "year": "2010",
            "rate": 9.7
          },
          {
            "country": "Poland",
            "year": "2013",
            "rate": 10.3
          },
          {
            "country": "Poland",
            "year": "2015",
            "rate": 7.5
          },
          {
            "country": "Poland",
            "year": "2017",
            "rate": 4.9
          },
          {
            "country": "Poland",
            "year": "2019",
            "rate": 3.3
          },
          {
            "country": "Poland",
            "year": "2021",
            "rate": 3.4
          },
          {
            "country": "Poland",
            "year": "2023",
            "rate": 2.8
          },
          {
            "country": "Poland",
            "year": "2024",
            "rate": 2.9
          }
        ],
        "facet": "country",
        "x": "year",
        "y": "rate",
        "highlight": "Greece",
        "format": "{value}%",
        "title": "Greek unemployment fell from 27.5% in 2013 to 10.1% in 2024",
        "subtitle": "Unemployment rate, 2010 to 2024, same scale in every panel",
        "source": "Eurostat (rounded)",
        "note": "Figures are approximate, for illustration"
      }
    },
    {
      "name": "source-note",
      "component": "SourceNote",
      "title": "Source note",
      "level": "atom",
      "category": "text",
      "description": "A sourcing footer for a whole piece: sources with optional links, the period covered, method, caveats and credit.",
      "whenToUse": "Once, at the end of a story or dashboard, to say where every number came from and how it was produced.",
      "avoid": "Sourcing a single chart: use that chart's own `source` and `note` props so the citation travels with its export.",
      "props": [
        {
          "name": "sources",
          "type": "string | { name, url? } | (string | { name, url? })[]",
          "description": "Where the data comes from. Entries with a `url` are linked."
        },
        {
          "name": "period",
          "type": "string",
          "description": "The period the data covers, e.g. \"2015 to 2024\". Shown as \"Data\"."
        },
        {
          "name": "method",
          "type": "string",
          "description": "How the numbers were produced: definitions, adjustments, exclusions."
        },
        {
          "name": "note",
          "type": "string",
          "description": "Caveats the reader should know."
        },
        {
          "name": "credit",
          "type": "string",
          "description": "Who made the piece."
        },
        {
          "name": "layout",
          "type": "\"list\" | \"inline\"",
          "default": "\"list\"",
          "description": "Labelled list, or one running paragraph."
        },
        {
          "name": "rule",
          "type": "boolean",
          "default": "true",
          "description": "Hairline above the note."
        },
        {
          "name": "children",
          "type": "ReactNode",
          "description": "Extra content after the rows."
        }
      ],
      "example": {
        "type": "source-note",
        "sources": [
          {
            "name": "Ember, European Electricity Review 2025",
            "url": "https://ember-energy.org"
          },
          {
            "name": "ENTSO-E Transparency Platform",
            "url": "https://transparency.entsoe.eu"
          }
        ],
        "note": "Figures are approximate, for illustration",
        "period": "January 2015 to December 2024, monthly",
        "method": "Generation shares are net of pumped-storage consumption. Figures for 2024 are provisional and exclude Malta and Cyprus.",
        "credit": "Analysis and charts by the data desk"
      }
    },
    {
      "level": "atom",
      "component": "Sparkline",
      "name": "sparkline",
      "title": "Sparkline",
      "category": "trend",
      "description": "A word-sized line or bar series without axes, with the latest point marked.",
      "whenToUse": "Showing the recent history of a number inside a stat tile, a table cell or a sentence, where the shape of the trend matters and exact values do not.",
      "avoid": "Anything the reader must read values from or compare across series (use line-chart); fewer than about 5 points (state the change in words or use big-number with a delta).",
      "props": [
        {
          "name": "y",
          "type": "string",
          "required": true,
          "description": "Value field."
        },
        {
          "name": "x",
          "type": "string",
          "description": "Time or label field, used in the tooltip and alt text. Rows are drawn in data order."
        },
        {
          "name": "mark",
          "type": "\"line\" | \"bar\"",
          "default": "\"line\"",
          "description": "Line for a trend, bars for discrete counts or gains and losses (negative bars take the negative colour)."
        },
        {
          "name": "extremes",
          "type": "boolean",
          "default": "false",
          "description": "Mark the lowest and highest points with small dots."
        },
        {
          "name": "band",
          "type": "[number, number]",
          "description": "Shaded reference range in value units, e.g. a normal range."
        },
        {
          "name": "area",
          "type": "boolean",
          "default": "false",
          "description": "Light fill under the line; forces a zero baseline."
        },
        {
          "name": "showValue",
          "type": "boolean",
          "default": "false",
          "description": "Print the last value after the line."
        },
        {
          "name": "zero",
          "type": "boolean",
          "description": "Include zero in the scale. Default: off for lines, always on for bars."
        }
      ],
      "example": {
        "type": "sparkline",
        "data": [
          {
            "month": "2023-07",
            "rate": 3.9
          },
          {
            "month": "2023-08",
            "rate": 3.8
          },
          {
            "month": "2023-09",
            "rate": 3.8
          },
          {
            "month": "2023-10",
            "rate": 3.6
          },
          {
            "month": "2023-11",
            "rate": 3.5
          },
          {
            "month": "2023-12",
            "rate": 3.6
          },
          {
            "month": "2024-01",
            "rate": 3.4
          },
          {
            "month": "2024-02",
            "rate": 3.7
          },
          {
            "month": "2024-03",
            "rate": 3.8
          },
          {
            "month": "2024-04",
            "rate": 3.9
          },
          {
            "month": "2024-05",
            "rate": 4.1
          },
          {
            "month": "2024-06",
            "rate": 4.3
          },
          {
            "month": "2024-07",
            "rate": 4.2
          },
          {
            "month": "2024-08",
            "rate": 4.1
          },
          {
            "month": "2024-09",
            "rate": 4.2
          },
          {
            "month": "2024-10",
            "rate": 4.4
          }
        ],
        "x": "month",
        "y": "rate",
        "format": "{value}%",
        "showValue": true,
        "extremes": true,
        "style": {
          "maxWidth": 220
        },
        "alt": "Sparkline of the unemployment rate over 16 months, July 2023 to October 2024. It started at 3.9%, fell to 3.4% in January 2024 and ended at 4.4%, the highest in the period."
      }
    },
    {
      "level": "atom",
      "component": "StatCircles",
      "name": "stat-circles",
      "title": "Stat circles",
      "category": "poster",
      "description": "Shows a few related magnitudes as plain circles sized by area, each with its number set large and a short label.",
      "whenToUse": "The poster way to show two to six related magnitudes whose rough proportion is the point: kilograms per person in three countries, the same quantity in three years, a total and its parts set side by side.",
      "avoid": "More than six values or values that need precise comparison (use bar-chart); a single number (use big-number); many circles of one whole (use circle-pack); values close together, where areas look the same (use bar-chart or dumbbell-chart).",
      "props": [
        {
          "name": "name",
          "type": "string",
          "required": true,
          "description": "Label field: the short text under or beside each circle."
        },
        {
          "name": "value",
          "type": "string",
          "required": true,
          "description": "Value field: circle area, and the number printed large. Non-positive values are dropped."
        },
        {
          "name": "unit",
          "type": "string",
          "description": "Unit printed small after each number, e.g. \"kg\". Leave out when `format` already includes it."
        },
        {
          "name": "layout",
          "type": "\"row\" | \"steps\" | \"nested\"",
          "default": "\"row\"",
          "description": "Circles centred on one line, standing on a shared baseline, or drawn inside each other from a shared lowest point. Narrow containers stack a row or steps into a column."
        },
        {
          "name": "colorBy",
          "type": "\"item\" | \"value\" | \"single\"",
          "default": "\"item\"",
          "description": "A colour per circle, a shade by value along the sequential ramp, or the accent for all."
        },
        {
          "name": "icon",
          "type": "string | Record<string, string>",
          "description": "Icon above the number in large circles: one for all, or a map from label to icon. Built-in name or `path://`."
        }
      ],
      "example": {
        "type": "stat-circles",
        "data": [
          {
            "who": "per person in the United States",
            "kg": 124
          },
          {
            "who": "per person in Brazil",
            "kg": 100
          },
          {
            "who": "per person in China",
            "kg": 63
          },
          {
            "who": "per person in India",
            "kg": 6
          }
        ],
        "name": "who",
        "value": "kg",
        "unit": "kg",
        "title": "Meat supply in the United States was 124 kg per person in 2021",
        "subtitle": "Meat supply per person per year, kilograms, 2021",
        "source": "FAO food balance sheets",
        "note": "Rounded, approximate figures for illustration"
      }
    },
    {
      "name": "stat-tile",
      "component": "StatTile",
      "title": "Stat tile",
      "level": "atom",
      "category": "figure",
      "description": "A compact, bordered KPI tile: label, value, change against a baseline and an optional trend graphic.",
      "whenToUse": "Dashboard grids and KPI rows of three to six figures, each needing its own change indicator or sparkline. Give every tile a delta or a context line.",
      "avoid": "The single number a story leads with (use `big-number` with `size: \"hero\"`). Do not put a full chart with axes in the trend slot.",
      "props": [
        {
          "name": "label",
          "type": "string",
          "required": true,
          "description": "What it measures, sentence case."
        },
        {
          "name": "value",
          "type": "number | string",
          "required": true,
          "description": "The figure. Numbers are formatted with `format`."
        },
        {
          "name": "delta",
          "type": "number | string",
          "description": "Change against a baseline. Numbers render as signed percentages (0.12 is +12%)."
        },
        {
          "name": "deltaLabel",
          "type": "string",
          "description": "What the delta is against, e.g. \"vs last month\"."
        },
        {
          "name": "upIsGood",
          "type": "boolean",
          "default": "true",
          "description": "Whether an increase is an improvement. Sets the delta colour."
        },
        {
          "name": "context",
          "type": "string",
          "description": "A line that states a comparison or reference for the number."
        },
        {
          "name": "trend",
          "type": "Slot | number[]",
          "description": "A chart spec, a React element, or an array of numbers drawn as a sparkline."
        },
        {
          "name": "trendPosition",
          "type": "\"bottom\" | \"right\"",
          "default": "\"bottom\"",
          "description": "Where the trend sits; `right` falls back to `bottom` under 280px."
        },
        {
          "name": "format",
          "type": "FormatSpec",
          "default": "\"auto\"",
          "description": "Number format for `value`."
        },
        {
          "name": "children",
          "type": "ReactNode",
          "description": "Extra content at the bottom of the tile."
        }
      ],
      "example": {
        "type": "stat-tile",
        "label": "Monthly active users",
        "value": 48210,
        "format": "compact",
        "delta": 0.083,
        "deltaLabel": "vs September",
        "trend": [
          38.1,
          39.4,
          39,
          40.8,
          42.2,
          41.9,
          43.5,
          44.1,
          44.5,
          46,
          47.2,
          48.2
        ],
        "style": {
          "maxWidth": 300
        }
      }
    },
    {
      "name": "step-marker",
      "component": "StepMarker",
      "title": "Step marker",
      "level": "atom",
      "category": "text",
      "description": "A numbered chapter or step heading (\"01 — Electricity sources in 2015\") for sequencing the sections of a story.",
      "whenToUse": "When a piece has three to seven ordered chapters or steps. Open each one with a marker so the reader can see the position in the sequence.",
      "avoid": "Unordered sections (use `headline` with `size: \"section\"`), or numbering a list of items inside a paragraph.",
      "props": [
        {
          "name": "step",
          "type": "number | string",
          "required": true,
          "description": "Step number, zero-padded to two digits. Strings are shown as written."
        },
        {
          "name": "title",
          "type": "string",
          "required": true,
          "description": "The chapter or step title."
        },
        {
          "name": "total",
          "type": "number",
          "description": "Total steps; the marker then reads \"02 / 05\"."
        },
        {
          "name": "text",
          "type": "string",
          "description": "A sentence under the title."
        },
        {
          "name": "children",
          "type": "ReactNode",
          "description": "JSX alternative to `text`."
        },
        {
          "name": "level",
          "type": "1 | 2 | 3 | 4 | 5 | 6",
          "default": "2",
          "description": "Heading level."
        },
        {
          "name": "size",
          "type": "\"md\" | \"lg\"",
          "default": "\"md\"",
          "description": "`lg` for chapter openers."
        },
        {
          "name": "layout",
          "type": "\"inline\" | \"stacked\"",
          "default": "\"inline\"",
          "description": "Number beside or above the title. Narrow containers always stack."
        },
        {
          "name": "rule",
          "type": "boolean",
          "default": "false",
          "description": "Hairline above the marker."
        }
      ],
      "example": {
        "type": "step-marker",
        "step": 1,
        "title": "Electricity sources in 2015",
        "text": "Europe's electricity generation by source in 2015.",
        "size": "lg"
      }
    },
    {
      "level": "atom",
      "component": "StripPlot",
      "name": "strip-plot",
      "title": "Strip plot",
      "category": "distribution",
      "description": "Shows every observation as a tick or dot along a value axis, one row per category.",
      "whenToUse": "Comparing the spread and clustering of a numeric value across 2 to about 15 categories when there are few enough observations to show each one; many rows with few to a few hundred observations each.",
      "avoid": "A single group where the shape matters (use histogram or beeswarm-chart); when individual points must be identifiable or coloured (use beeswarm-chart); when only quartiles matter (use box-plot).",
      "props": [
        {
          "name": "value",
          "type": "string",
          "required": true,
          "description": "Numeric field positioning each mark."
        },
        {
          "name": "group",
          "type": "string",
          "required": true,
          "description": "Category field: one row per distinct value."
        },
        {
          "name": "label",
          "type": "string",
          "description": "Field naming each observation, for the tooltip."
        },
        {
          "name": "mark",
          "type": "\"tick\" | \"dot\"",
          "default": "\"tick\"",
          "description": "Thin vertical ticks (a barcode) or translucent dots."
        },
        {
          "name": "median",
          "type": "boolean",
          "default": "false",
          "description": "Heavier tick at each row's median, with its value printed."
        },
        {
          "name": "sort",
          "type": "\"none\" | \"asc\" | \"desc\"",
          "default": "\"none\"",
          "description": "Order rows by median."
        },
        {
          "name": "jitter",
          "type": "boolean",
          "default": "false",
          "description": "Deterministic vertical spread for dots, to relieve overplotting."
        }
      ],
      "example": {
        "type": "strip-plot",
        "data": [
          {
            "day": "Mon",
            "minutes": 42
          },
          {
            "day": "Mon",
            "minutes": 44
          },
          {
            "day": "Mon",
            "minutes": 45
          },
          {
            "day": "Mon",
            "minutes": 47
          },
          {
            "day": "Mon",
            "minutes": 47
          },
          {
            "day": "Mon",
            "minutes": 48
          },
          {
            "day": "Mon",
            "minutes": 49
          },
          {
            "day": "Mon",
            "minutes": 51
          },
          {
            "day": "Mon",
            "minutes": 52
          },
          {
            "day": "Mon",
            "minutes": 55
          },
          {
            "day": "Mon",
            "minutes": 58
          },
          {
            "day": "Mon",
            "minutes": 63
          },
          {
            "day": "Tue",
            "minutes": 40
          },
          {
            "day": "Tue",
            "minutes": 41
          },
          {
            "day": "Tue",
            "minutes": 43
          },
          {
            "day": "Tue",
            "minutes": 43
          },
          {
            "day": "Tue",
            "minutes": 44
          },
          {
            "day": "Tue",
            "minutes": 45
          },
          {
            "day": "Tue",
            "minutes": 46
          },
          {
            "day": "Tue",
            "minutes": 46
          },
          {
            "day": "Tue",
            "minutes": 48
          },
          {
            "day": "Tue",
            "minutes": 49
          },
          {
            "day": "Tue",
            "minutes": 53
          },
          {
            "day": "Tue",
            "minutes": 57
          },
          {
            "day": "Wed",
            "minutes": 39
          },
          {
            "day": "Wed",
            "minutes": 41
          },
          {
            "day": "Wed",
            "minutes": 42
          },
          {
            "day": "Wed",
            "minutes": 44
          },
          {
            "day": "Wed",
            "minutes": 44
          },
          {
            "day": "Wed",
            "minutes": 45
          },
          {
            "day": "Wed",
            "minutes": 45
          },
          {
            "day": "Wed",
            "minutes": 47
          },
          {
            "day": "Wed",
            "minutes": 48
          },
          {
            "day": "Wed",
            "minutes": 50
          },
          {
            "day": "Wed",
            "minutes": 52
          },
          {
            "day": "Wed",
            "minutes": 61
          },
          {
            "day": "Thu",
            "minutes": 41
          },
          {
            "day": "Thu",
            "minutes": 43
          },
          {
            "day": "Thu",
            "minutes": 44
          },
          {
            "day": "Thu",
            "minutes": 46
          },
          {
            "day": "Thu",
            "minutes": 47
          },
          {
            "day": "Thu",
            "minutes": 47
          },
          {
            "day": "Thu",
            "minutes": 49
          },
          {
            "day": "Thu",
            "minutes": 50
          },
          {
            "day": "Thu",
            "minutes": 52
          },
          {
            "day": "Thu",
            "minutes": 54
          },
          {
            "day": "Thu",
            "minutes": 59
          },
          {
            "day": "Thu",
            "minutes": 66
          },
          {
            "day": "Fri",
            "minutes": 36
          },
          {
            "day": "Fri",
            "minutes": 37
          },
          {
            "day": "Fri",
            "minutes": 38
          },
          {
            "day": "Fri",
            "minutes": 39
          },
          {
            "day": "Fri",
            "minutes": 40
          },
          {
            "day": "Fri",
            "minutes": 40
          },
          {
            "day": "Fri",
            "minutes": 41
          },
          {
            "day": "Fri",
            "minutes": 42
          },
          {
            "day": "Fri",
            "minutes": 43
          },
          {
            "day": "Fri",
            "minutes": 44
          },
          {
            "day": "Fri",
            "minutes": 46
          },
          {
            "day": "Fri",
            "minutes": 49
          }
        ],
        "value": "minutes",
        "group": "day",
        "median": true,
        "highlight": "Fri",
        "format": "{value} min",
        "title": "The median Friday commute took 40.5 minutes",
        "subtitle": "Door-to-door commute time, 12 weeks of trips by weekday. The heavy tick is the median.",
        "note": "Illustrative data"
      }
    },
    {
      "level": "atom",
      "component": "TimelineChart",
      "name": "timeline-chart",
      "title": "Timeline chart",
      "category": "trend",
      "description": "Places events and spans on a time axis in labelled lanes, with each item named directly.",
      "whenToUse": "When things happened and how long they lasted: project phases, terms of office, product launches, the sequence of events in a story. Mix spans (start and end) with point milestones (start only).",
      "avoid": "A measured value changing over time (use line-chart, with annotations for the events); more than about 40 items (filter, or split into several timelines); durations to be compared precisely with no interest in when (use bar-chart).",
      "props": [
        {
          "name": "name",
          "type": "string",
          "required": true,
          "description": "Label field: what the event or span is."
        },
        {
          "name": "start",
          "type": "string",
          "required": true,
          "description": "Start field: ISO date, year or timestamp."
        },
        {
          "name": "end",
          "type": "string",
          "description": "End field. Rows without an end are point milestones."
        },
        {
          "name": "lane",
          "type": "string",
          "description": "Field that groups rows into labelled lanes."
        },
        {
          "name": "precision",
          "type": "\"year\" | \"month\" | \"day\"",
          "description": "Date precision in tooltips. Default: chosen from the span of the data."
        }
      ],
      "example": {
        "type": "timeline-chart",
        "data": [
          {
            "mission": "Mercury",
            "programme": "Crewed programmes",
            "from": "1958-10",
            "to": "1963-05"
          },
          {
            "mission": "Gemini",
            "programme": "Crewed programmes",
            "from": "1961-12",
            "to": "1966-11"
          },
          {
            "mission": "Apollo",
            "programme": "Crewed programmes",
            "from": "1961-05",
            "to": "1972-12"
          },
          {
            "mission": "Skylab",
            "programme": "Crewed programmes",
            "from": "1973-05",
            "to": "1974-02"
          },
          {
            "mission": "Shuttle development",
            "programme": "Crewed programmes",
            "from": "1972-01",
            "to": "1981-04"
          },
          {
            "mission": "Glenn orbits Earth",
            "programme": "Milestones",
            "from": "1962-02-20"
          },
          {
            "mission": "Apollo 1 fire",
            "programme": "Milestones",
            "from": "1967-01-27"
          },
          {
            "mission": "First Moon landing",
            "programme": "Milestones",
            "from": "1969-07-20"
          },
          {
            "mission": "Apollo-Soyuz",
            "programme": "Milestones",
            "from": "1975-07-15"
          },
          {
            "mission": "First Shuttle flight",
            "programme": "Milestones",
            "from": "1981-04-12"
          }
        ],
        "name": "mission",
        "start": "from",
        "end": "to",
        "lane": "programme",
        "highlight": [
          "Apollo",
          "First Moon landing"
        ],
        "title": "The Apollo programme ran from May 1961 to December 1972",
        "subtitle": "NASA human spaceflight programmes and milestones, 1958 to 1981",
        "source": "NASA History Office",
        "note": "Figures are approximate, for illustration"
      }
    },
    {
      "level": "atom",
      "component": "Treemap",
      "name": "treemap",
      "title": "Treemap",
      "category": "part-to-whole",
      "description": "Shows parts of a whole as nested rectangles sized by value, optionally grouped into categories.",
      "whenToUse": "Many parts (10 to 100) of very different sizes, or a two-level hierarchy, where the reader needs the largest parts rather than exact comparison: budgets, market capitalisation, export baskets.",
      "avoid": "Fewer than about 6 parts or values that need precise comparison (use bar-chart); 2 to 5 parts (use donut-chart or segmented-bar); values that can be negative (use bar-chart); change over time (use line-chart).",
      "props": [
        {
          "name": "name",
          "type": "string",
          "required": true,
          "description": "Leaf name field: one tile per row."
        },
        {
          "name": "value",
          "type": "string",
          "required": true,
          "description": "Value field: tile area. Non-positive values are dropped."
        },
        {
          "name": "group",
          "type": "string",
          "description": "Parent field. Rows sharing a group are drawn together and share a colour."
        },
        {
          "name": "labels",
          "type": "boolean",
          "default": "true",
          "description": "Print the value under the name where the tile is big enough."
        },
        {
          "name": "groupLabels",
          "type": "boolean",
          "description": "Heading strip above each group. Default: on when `group` is set and not compact."
        },
        {
          "name": "names",
          "type": "Record<string, string>",
          "description": "Display names for group keys."
        }
      ],
      "example": {
        "type": "treemap",
        "data": [
          {
            "sector": "Energy",
            "source": "Electricity and heat",
            "gt": 15.8
          },
          {
            "sector": "Energy",
            "source": "Transport",
            "gt": 8.4
          },
          {
            "sector": "Energy",
            "source": "Manufacturing and construction",
            "gt": 6.3
          },
          {
            "sector": "Energy",
            "source": "Buildings",
            "gt": 3
          },
          {
            "sector": "Energy",
            "source": "Fugitive emissions",
            "gt": 3.3
          },
          {
            "sector": "Agriculture",
            "source": "Livestock and manure",
            "gt": 3.5
          },
          {
            "sector": "Agriculture",
            "source": "Soils and rice",
            "gt": 2.4
          },
          {
            "sector": "Industry",
            "source": "Cement",
            "gt": 1.6
          },
          {
            "sector": "Industry",
            "source": "Chemicals and other",
            "gt": 1.6
          },
          {
            "sector": "Waste",
            "source": "Landfill and wastewater",
            "gt": 1.7
          },
          {
            "sector": "Land use",
            "source": "Deforestation and land-use change",
            "gt": 1.4
          }
        ],
        "group": "sector",
        "name": "source",
        "value": "gt",
        "format": "{value} Gt",
        "title": "Energy produced 75% of global greenhouse gas emissions in 2021",
        "subtitle": "Global emissions by sector and source, billion tonnes of CO2 equivalent, 2021",
        "source": "Climate Watch, World Resources Institute",
        "note": "Rounded, approximate figures for illustration"
      }
    },
    {
      "level": "atom",
      "component": "UnitBlocks",
      "name": "unit-blocks",
      "title": "Unit blocks",
      "category": "poster",
      "description": "Shows the size of each group as a solid block of small icons, one icon per fixed number of units.",
      "whenToUse": "Large counts of people or things in 2 to 12 groups, where the reader should see the crowd itself: 20,350 marathon finishers by age group, one figure per 10 runners.",
      "avoid": "Precise comparison of close values (use block-bar or bar-chart); fewer than about 30 icons per group (use pictogram-chart or icon-array-grid); shares of one whole (use waffle-chart).",
      "props": [
        {
          "name": "name",
          "type": "string",
          "required": true,
          "description": "Group field: one block of icons per row of data."
        },
        {
          "name": "value",
          "type": "string",
          "required": true,
          "description": "Value field: the quantity each block counts out."
        },
        {
          "name": "unit",
          "type": "number",
          "description": "Units per icon. Default: a round number that gives about 2,000 icons in total."
        },
        {
          "name": "unitLabel",
          "type": "string",
          "description": "What one icon stands for, for the key line, e.g. \"10 finishers\"."
        },
        {
          "name": "icon",
          "type": "IconName | \"path://...\"",
          "default": "\"person\"",
          "description": "Built-in icon name or a custom single SVG path."
        },
        {
          "name": "series",
          "type": "string",
          "description": "Field that colours the blocks. Default: one colour per group, up to eight groups."
        },
        {
          "name": "layout",
          "type": "\"packed\" | \"columns\"",
          "default": "\"packed\"",
          "description": "`packed`: equal block heights, widths follow the counts. `columns`: equal widths, blocks grow upwards from a baseline."
        },
        {
          "name": "columns",
          "type": "number",
          "description": "Blocks per line in the `columns` layout. Default: all, two on a phone."
        },
        {
          "name": "iconSize",
          "type": "number",
          "description": "Icon height in px. Default: the largest that fits, chosen per chart. To put two charts on one scale, give both the same `unit` and `iconSize`: the icon size, the gaps and the worth of an icon are then identical and the chart takes the height it needs."
        },
        {
          "name": "sort",
          "type": "\"none\" | \"asc\" | \"desc\"",
          "default": "\"none\"",
          "description": "Order of the blocks."
        }
      ],
      "example": {
        "type": "unit-blocks",
        "data": [
          {
            "age": "Under 30",
            "finishers": 4180
          },
          {
            "age": "30 to 39",
            "finishers": 6920
          },
          {
            "age": "40 to 49",
            "finishers": 5340
          },
          {
            "age": "50 to 59",
            "finishers": 2760
          },
          {
            "age": "60 and over",
            "finishers": 1150
          }
        ],
        "name": "age",
        "value": "finishers",
        "unit": 10,
        "unitLabel": "10 finishers",
        "format": "integer",
        "title": "6,920 of the 20,350 finishers were aged 30 to 39",
        "subtitle": "Finishers of the Lakeshore Marathon by age group, 2025",
        "source": "Illustrative data",
        "note": "Invented figures for a fictional race"
      }
    },
    {
      "level": "atom",
      "component": "WaffleChart",
      "name": "waffle-chart",
      "title": "Waffle chart",
      "category": "part-to-whole",
      "description": "Shows shares of a whole as counted cells in a grid, such as 37 in every 100.",
      "whenToUse": "One share (\"37 in every 100\") or a handful of categories that add up to a whole, where the reader should be able to count the units.",
      "avoid": "Comparing shares across several groups (use bar-chart with stack \"percent\"); more than about 6 categories or shares under 1% (use bar-chart); a compact inline part-to-whole (use segmented-bar).",
      "props": [
        {
          "name": "value",
          "type": "string",
          "required": true,
          "description": "Value field. Each category's cells are its share of the whole."
        },
        {
          "name": "name",
          "type": "string",
          "description": "Category field. Omit for a single share."
        },
        {
          "name": "total",
          "type": "number",
          "description": "The whole. Default: the sum of the values. Set to 100 with one value to show a single share against an empty remainder."
        },
        {
          "name": "rows",
          "type": "number",
          "default": "10",
          "description": "Grid rows."
        },
        {
          "name": "columns",
          "type": "number",
          "default": "10",
          "description": "Grid columns."
        },
        {
          "name": "shape",
          "type": "\"square\" | \"circle\"",
          "default": "\"square\"",
          "description": "Cell shape."
        },
        {
          "name": "restLabel",
          "type": "string",
          "description": "Label for the unfilled remainder when `total` exceeds the sum."
        },
        {
          "name": "names",
          "type": "Record<string, string>",
          "description": "Display names for category keys."
        }
      ],
      "example": {
        "type": "waffle-chart",
        "data": [
          {
            "group": "Have never been online",
            "share": 33
          }
        ],
        "name": "group",
        "value": "share",
        "total": 100,
        "restLabel": "Use the internet",
        "title": "33% of the world's population had never used the internet in 2023",
        "subtitle": "Share of the global population, 2023. Each square is 1% of people",
        "source": "ITU Facts and Figures 2023",
        "note": "Figures are approximate, for illustration"
      }
    },
    {
      "name": "annotated-timeline",
      "component": "AnnotatedTimeline",
      "title": "Annotated timeline",
      "level": "molecule",
      "category": "story",
      "description": "A vertical sequence of dated events along a rail, each with a short explanation and, optionally, a small chart or figure.",
      "whenToUse": "Telling how something unfolded when the events matter more than a continuous measure: a policy history, an incident, a product's milestones. Four to eight events; give a figure only to the ones where a number changed.",
      "avoid": "Events that should be read against a time scale, with gaps and overlaps to scale (use `timeline-chart`); a continuous series with a few annotated moments (use `line-chart` with `annotations`); ordered steps that are not dated (use `section` with `step`).",
      "composes": [
        "step-marker",
        "prose",
        "sparkline",
        "big-number"
      ],
      "props": [
        {
          "name": "events",
          "type": "{ date, title, text?, figure?, emphasis? }[]",
          "required": true,
          "description": "The events in reading order. `date` is shown as written; `figure` is a chart spec or element; `emphasis` fills the marker to mark the most important events."
        },
        {
          "name": "figurePosition",
          "type": "\"below\" | \"side\"",
          "default": "\"below\"",
          "description": "Where an event's figure sits; `side` falls back to `below` under 640px."
        },
        {
          "name": "level",
          "type": "2 | 3 | 4 | 5 | 6",
          "default": "3",
          "description": "Heading level of the event titles."
        },
        {
          "name": "title",
          "type": "string",
          "description": "The main fact with its number, or a plain label."
        },
        {
          "name": "subtitle",
          "type": "string",
          "description": "Scope and period."
        },
        {
          "name": "source",
          "type": "string",
          "description": "Shown under the timeline as \"Source: ...\"."
        },
        {
          "name": "note",
          "type": "string",
          "description": "Method note or caveat."
        },
        {
          "name": "theme",
          "type": "ThemeInput",
          "description": "Theme override, passed on to every event."
        },
        {
          "name": "mode",
          "type": "\"auto\" | \"system\" | \"light\" | \"dark\"",
          "default": "\"auto\"",
          "description": "Colour mode."
        }
      ],
      "example": {
        "type": "annotated-timeline",
        "title": "The ECB raised its deposit rate from −0.5% to 4% between July 2022 and September 2023",
        "subtitle": "Euro area deposit facility rate and annual inflation, 2021 to 2024",
        "events": [
          {
            "date": "July 2021",
            "title": "Inflation reaches 2.2%",
            "text": "The ECB's target is 2%. The bank kept its deposit rate at −0.5%."
          },
          {
            "date": "July 2022",
            "title": "The deposit rate rises from −0.5% to 0%",
            "emphasis": true,
            "text": "This was the ECB's first rate rise in eleven years. Inflation was 8.9%.",
            "figure": {
              "type": "big-number",
              "value": "8.9%",
              "label": "Annual inflation, July 2022",
              "context": "The target is 2%"
            }
          },
          {
            "date": "October 2022",
            "title": "Inflation reaches 10.6%",
            "text": "This was the highest monthly figure of 2022. The ECB raised rates by 0.75 points at each of two consecutive meetings.",
            "figure": {
              "type": "sparkline",
              "data": [
                {
                  "month": "2022-01",
                  "rate": 5.1
                },
                {
                  "month": "2022-02",
                  "rate": 5.9
                },
                {
                  "month": "2022-03",
                  "rate": 7.4
                },
                {
                  "month": "2022-04",
                  "rate": 7.4
                },
                {
                  "month": "2022-05",
                  "rate": 8.1
                },
                {
                  "month": "2022-06",
                  "rate": 8.6
                },
                {
                  "month": "2022-07",
                  "rate": 8.9
                },
                {
                  "month": "2022-08",
                  "rate": 9.1
                },
                {
                  "month": "2022-09",
                  "rate": 9.9
                },
                {
                  "month": "2022-10",
                  "rate": 10.6
                },
                {
                  "month": "2022-11",
                  "rate": 10.1
                },
                {
                  "month": "2022-12",
                  "rate": 9.2
                }
              ],
              "x": "month",
              "y": "rate",
              "format": "{value}%",
              "extremes": true,
              "showValue": true,
              "style": {
                "maxWidth": 260
              }
            }
          },
          {
            "date": "September 2023",
            "title": "The deposit rate reaches 4%",
            "text": "This was the tenth rise in a row. Inflation was 4.3%."
          },
          {
            "date": "June 2024",
            "title": "The deposit rate is cut to 3.75%",
            "emphasis": true,
            "text": "The cut was 0.25 points. Inflation was 2.5%."
          }
        ],
        "source": "European Central Bank; Eurostat",
        "note": "Figures are approximate, for illustration"
      }
    },
    {
      "name": "before-after",
      "component": "BeforeAfter",
      "title": "Before and after",
      "level": "molecule",
      "category": "layout",
      "description": "Two states of one measure side by side, as figures or small charts, with the change between them stated in the middle.",
      "whenToUse": "A single measure at two moments or under two conditions: before and after a policy, control and treatment, last year and this. The change is computed from the two values unless you state it.",
      "avoid": "More than two states (use `line-chart` or `slope-chart`); many entities each with a before and after (use `dumbbell-chart` or `arrow-chart`); two unrelated figures (use `stat-row`).",
      "composes": [
        "big-number",
        "delta",
        "bar-chart",
        "sparkline"
      ],
      "props": [
        {
          "name": "before",
          "type": "{ heading?, value?, label?, context?, format?, chart? }",
          "required": true,
          "description": "The first state: a figure, a chart slot, or both."
        },
        {
          "name": "after",
          "type": "{ heading?, value?, label?, context?, format?, chart? }",
          "required": true,
          "description": "The second state."
        },
        {
          "name": "delta",
          "type": "number | string",
          "description": "The change stated between the two. Default: the relative change when both values are numbers."
        },
        {
          "name": "deltaLabel",
          "type": "string",
          "description": "Words under the change, e.g. \"in five years\"."
        },
        {
          "name": "deltaFormat",
          "type": "FormatSpec",
          "default": "signed percent",
          "description": "Number format for a numeric `delta`."
        },
        {
          "name": "upIsGood",
          "type": "boolean",
          "default": "true",
          "description": "Whether an increase is good news; decides the colour of the change."
        },
        {
          "name": "format",
          "type": "FormatSpec",
          "default": "\"auto\"",
          "description": "Number format shared by both values."
        },
        {
          "name": "title",
          "type": "string",
          "description": "The change stated plainly, with both values and the period."
        },
        {
          "name": "subtitle",
          "type": "string",
          "description": "Measure and population."
        },
        {
          "name": "source",
          "type": "string",
          "description": "Shown underneath as \"Source: ...\"."
        },
        {
          "name": "note",
          "type": "string",
          "description": "Method note or caveat."
        },
        {
          "name": "theme",
          "type": "ThemeInput",
          "description": "Theme override, passed on to both states."
        },
        {
          "name": "mode",
          "type": "\"auto\" | \"system\" | \"light\" | \"dark\"",
          "default": "\"auto\"",
          "description": "Colour mode."
        }
      ],
      "example": {
        "type": "before-after",
        "title": "Solar module prices fell from $0.74 to $0.15 per watt between 2013 and 2023",
        "subtitle": "Average price of crystalline silicon modules, US dollars per watt",
        "format": {
          "style": "currency",
          "currency": "USD"
        },
        "before": {
          "heading": "2013",
          "value": 0.74,
          "label": "Per watt",
          "context": "2013 average"
        },
        "after": {
          "heading": "2023",
          "value": 0.15,
          "label": "Per watt",
          "context": "2023 average"
        },
        "deltaLabel": "in ten years",
        "upIsGood": false,
        "source": "IRENA Renewable Power Generation Costs 2023",
        "note": "Rounded, approximate figures for illustration"
      }
    },
    {
      "name": "chart-callout",
      "component": "ChartCallout",
      "title": "Chart with callout",
      "level": "molecule",
      "category": "layout",
      "description": "A chart with a callout beside it that states the main fact. In narrow containers the callout goes underneath.",
      "whenToUse": "Whenever a chart needs its meaning stated in words next to it: the standard chart-plus-interpretation unit of a report. The chart's title states the main fact with its number. The callout adds a second fact, a cause or a caveat in plain words.",
      "avoid": "Repeating the chart title in the callout; long narration (put `prose` before the chart instead); a chart that is already a small tile (the callout would outweigh it).",
      "composes": [
        "callout",
        "line-chart",
        "bar-chart"
      ],
      "props": [
        {
          "name": "chart",
          "type": "Slot",
          "required": true,
          "description": "The chart: a spec or a React element."
        },
        {
          "name": "lead",
          "type": "string",
          "description": "The main fact in a few words, with its number. Shown in bold."
        },
        {
          "name": "body",
          "type": "string",
          "description": "One or two plain sentences that say what the fact means."
        },
        {
          "name": "tone",
          "type": "\"neutral\" | \"positive\" | \"negative\" | \"accent\"",
          "default": "\"accent\"",
          "description": "Sets the callout's icon and tint together."
        },
        {
          "name": "label",
          "type": "string",
          "description": "Small label above the lead, e.g. \"Takeaway\" or \"Caveat\"."
        },
        {
          "name": "side",
          "type": "\"left\" | \"right\"",
          "default": "\"right\"",
          "description": "Side of the chart the callout sits on. Under 640px it always goes underneath."
        },
        {
          "name": "variant",
          "type": "\"plain\" | \"soft\" | \"outline\"",
          "default": "\"plain\"",
          "description": "`plain` separates the callout with a hairline; the others box it."
        },
        {
          "name": "align",
          "type": "\"start\" | \"center\" | \"end\"",
          "default": "\"center\"",
          "description": "Vertical position of the callout beside the chart."
        },
        {
          "name": "children",
          "type": "ReactNode",
          "description": "JSX alternative to `chart`."
        },
        {
          "name": "theme",
          "type": "ThemeInput",
          "description": "Theme override, passed on to the chart and callout."
        },
        {
          "name": "mode",
          "type": "\"auto\" | \"system\" | \"light\" | \"dark\"",
          "default": "\"auto\"",
          "description": "Colour mode."
        }
      ],
      "example": {
        "type": "chart-callout",
        "chart": {
          "type": "line-chart",
          "data": [
            {
              "month": "2024-01-01",
              "rate": 4.2
            },
            {
              "month": "2024-02-01",
              "rate": 4.3
            },
            {
              "month": "2024-03-01",
              "rate": 4.1
            },
            {
              "month": "2024-04-01",
              "rate": 3.6
            },
            {
              "month": "2024-05-01",
              "rate": 3.1
            },
            {
              "month": "2024-06-01",
              "rate": 2.9
            },
            {
              "month": "2024-07-01",
              "rate": 2.6
            },
            {
              "month": "2024-08-01",
              "rate": 2.4
            },
            {
              "month": "2024-09-01",
              "rate": 2.3
            },
            {
              "month": "2024-10-01",
              "rate": 2.2
            },
            {
              "month": "2024-11-01",
              "rate": 2.1
            },
            {
              "month": "2024-12-01",
              "rate": 2.1
            }
          ],
          "x": "month",
          "y": "rate",
          "format": "{value}%",
          "zero": true,
          "height": 280,
          "title": "Monthly churn fell from 4.2% to 2.1% in 2024",
          "subtitle": "Monthly customer churn, 2024",
          "source": "Internal analytics",
          "note": "Illustrative data",
          "annotations": [
            {
              "type": "line",
              "axis": "x",
              "value": "2024-03-01",
              "label": "New onboarding"
            }
          ]
        },
        "label": "Takeaway",
        "lead": "Churn fell 2 points after March",
        "body": "Churn was 4.1% in March, when the new onboarding launched. It fell every month from April to November. It was 2.1% in December."
      }
    },
    {
      "name": "chart-tabs",
      "component": "ChartTabs",
      "title": "Chart tabs",
      "level": "molecule",
      "category": "layout",
      "description": "One chart frame with a segmented control that switches between several views of the same data, morphing between views that share a chart type.",
      "whenToUse": "The same entities under two to five measures or scalings where each view changes the reading: total / per capita / change, absolute / share, linear / log. Give the frame a plain label as its title. Each view's `subtitle` names its measure.",
      "avoid": "Hiding the most important view behind a tab (show it first, or show both with `small-multiple-grid`); unrelated charts (give each its own figure); more than five views.",
      "composes": [
        "bar-chart",
        "line-chart"
      ],
      "props": [
        {
          "name": "chart",
          "type": "InsightSpec",
          "description": "The base chart spec every view starts from: `{ type, data, ... }`."
        },
        {
          "name": "views",
          "type": "{ label, id?, props?, chart? }[]",
          "required": true,
          "description": "Two to five views. `props` are merged over `chart`; or give a view its own complete `chart` slot."
        },
        {
          "name": "defaultView",
          "type": "string | number",
          "description": "The view shown first: an id, a label or an index."
        },
        {
          "name": "view",
          "type": "string | number",
          "description": "Controlled active view."
        },
        {
          "name": "onViewChange",
          "type": "(id, index) => void",
          "description": "Called when the reader switches view."
        },
        {
          "name": "tabsLabel",
          "type": "string",
          "default": "\"Chart view\"",
          "description": "Accessible name of the tab list."
        },
        {
          "name": "tabsPosition",
          "type": "\"right\" | \"below\"",
          "default": "\"right\"",
          "description": "Tabs beside the title or under it. Under 560px they always go underneath."
        },
        {
          "name": "title",
          "type": "string",
          "description": "A plain label or fact that is true of every view."
        },
        {
          "name": "subtitle",
          "type": "string",
          "description": "What the views share; each view's own `subtitle` names its measure."
        },
        {
          "name": "source",
          "type": "string",
          "description": "Shown under the chart as \"Source: ...\"."
        },
        {
          "name": "note",
          "type": "string",
          "description": "Method note or caveat."
        },
        {
          "name": "children",
          "type": "ReactNode",
          "description": "Extra content under the chart."
        },
        {
          "name": "theme",
          "type": "ThemeInput",
          "description": "Theme override, passed on to the chart."
        },
        {
          "name": "mode",
          "type": "\"auto\" | \"system\" | \"light\" | \"dark\"",
          "default": "\"auto\"",
          "description": "Colour mode."
        }
      ],
      "example": {
        "type": "chart-tabs",
        "title": "CO2 emissions of six large emitters, 2023",
        "tabsPosition": "below",
        "chart": {
          "type": "bar-chart",
          "data": [
            {
              "country": "China",
              "total": 11.9,
              "perCapita": 8.4,
              "change": 0.38
            },
            {
              "country": "United States",
              "total": 4.9,
              "perCapita": 14.3,
              "change": -0.13
            },
            {
              "country": "India",
              "total": 3.1,
              "perCapita": 2.1,
              "change": 0.74
            },
            {
              "country": "Russia",
              "total": 1.8,
              "perCapita": 12.5,
              "change": 0.1
            },
            {
              "country": "Japan",
              "total": 1,
              "perCapita": 8,
              "change": -0.19
            },
            {
              "country": "Germany",
              "total": 0.6,
              "perCapita": 7.1,
              "change": -0.28
            }
          ],
          "x": "country",
          "y": "total",
          "sort": "desc",
          "orientation": "horizontal",
          "height": 280,
          "highlight": "China",
          "format": "{value} Gt",
          "subtitle": "CO2 emissions from fossil fuels, billion tonnes, 2023"
        },
        "views": [
          {
            "label": "Total",
            "id": "total"
          },
          {
            "label": "Per capita",
            "id": "per-capita",
            "props": {
              "y": "perCapita",
              "highlight": "United States",
              "format": "{value} t",
              "subtitle": "CO2 emissions per person, tonnes, 2023"
            }
          },
          {
            "label": "Change since 2010",
            "id": "change",
            "props": {
              "y": "change",
              "highlight": "India",
              "format": "percent",
              "subtitle": "Change in CO2 emissions, 2010 to 2023"
            }
          }
        ],
        "source": "Global Carbon Budget 2024",
        "note": "Rounded, approximate figures for illustration"
      }
    },
    {
      "name": "comparison-table",
      "component": "ComparisonTable",
      "title": "Comparison table",
      "level": "molecule",
      "category": "layout",
      "description": "A table whose rows carry inline sparklines, deltas and in-cell bars beside their exact values, from rows and a column config.",
      "whenToUse": "Comparing five to twenty entities on a level, a trend and a change at once: countries, products, teams. Readers look up their row. The sparkline and delta show how the value changed.",
      "avoid": "Plain lookups with no series (use `data-table`); one measure ranked (use `ranked-list` or `bar-chart`); trends the reader must compare precisely (use `small-multiples`). Each sparkline has its own scale, so it shows the direction of change and cannot be used to compare levels between rows.",
      "composes": [
        "sparkline",
        "delta"
      ],
      "props": [
        {
          "name": "data",
          "type": "DataInput",
          "description": "Wide data: one row per entity. Long data: one row per entity and period, with `groupBy`. Omit inside `<InsightData>`."
        },
        {
          "name": "transform",
          "type": "Transform | Transform[]",
          "description": "Applied before rows are grouped."
        },
        {
          "name": "columns",
          "type": "(string | { key, label?, kind?, fields?, agg?, change?, format?, upIsGood?, mark?, align?, width?, hideBelow?, sortable? })[]",
          "required": true,
          "description": "Columns in order; the first is the row header. `kind` is `text`, `number`, `sparkline`, `delta` or `bar`. For wide data give a sparkline or computed delta its series with `fields`; for long data they use the group's rows."
        },
        {
          "name": "groupBy",
          "type": "string",
          "description": "Long data: the entity field. Rows sharing a value become one table row, in data order."
        },
        {
          "name": "highlight",
          "type": "string | string[]",
          "description": "Entities to emphasise, matched on the first column."
        },
        {
          "name": "sort",
          "type": "{ key, direction?: \"asc\" | \"desc\" }",
          "description": "Order rows by a column's value. Default direction: descending. With `sortable` this is the opening order."
        },
        {
          "name": "sortable",
          "type": "boolean",
          "default": "true",
          "description": "Click or press Enter on a column header to sort by it (numbers largest first, text A to Z); a second press reverses and a third restores the original order. Sparkline columns do not sort; set `sortable: false` on a column to exclude it."
        },
        {
          "name": "limit",
          "type": "number",
          "description": "Show only the first N rows."
        },
        {
          "name": "density",
          "type": "\"comfortable\" | \"compact\"",
          "default": "\"comfortable\"",
          "description": "Row padding."
        },
        {
          "name": "title",
          "type": "string",
          "description": "The main fact with its number, or a plain label. Set as the table's `<caption>`."
        },
        {
          "name": "subtitle",
          "type": "string",
          "description": "Units and period."
        },
        {
          "name": "source",
          "type": "string",
          "description": "Shown under the table as \"Source: ...\"."
        },
        {
          "name": "note",
          "type": "string",
          "description": "Method note or caveat."
        },
        {
          "name": "theme",
          "type": "ThemeInput",
          "description": "Theme override, passed on to the sparklines and deltas."
        },
        {
          "name": "mode",
          "type": "\"auto\" | \"system\" | \"light\" | \"dark\"",
          "default": "\"auto\"",
          "description": "Colour mode."
        }
      ],
      "example": {
        "type": "comparison-table",
        "title": "The Netherlands' renewable share of electricity rose from 18.2% to 52.4% between 2019 and 2024",
        "subtitle": "Renewable share of electricity generation, selected EU countries, 2019 to 2024",
        "data": [
          {
            "2019": 40.1,
            "2020": 44.2,
            "2021": 40.9,
            "2022": 44.5,
            "2023": 52.6,
            "2024": 58.9,
            "country": "Germany",
            "share": 58.9,
            "twh": 498
          },
          {
            "2019": 37.5,
            "2020": 44.1,
            "2021": 46.7,
            "2022": 42.2,
            "2023": 50.3,
            "2024": 56.8,
            "country": "Spain",
            "share": 56.8,
            "twh": 264
          },
          {
            "2019": 18.2,
            "2020": 26.1,
            "2021": 33,
            "2022": 40.1,
            "2023": 47.7,
            "2024": 52.4,
            "country": "Netherlands",
            "share": 52.4,
            "twh": 121
          },
          {
            "2019": 39.9,
            "2020": 41.7,
            "2021": 40.3,
            "2022": 35.6,
            "2023": 43.8,
            "2024": 49,
            "country": "Italy",
            "share": 49,
            "twh": 264
          },
          {
            "2019": 20.9,
            "2020": 24.2,
            "2021": 22.5,
            "2022": 24.4,
            "2023": 27,
            "2024": 28.3,
            "country": "France",
            "share": 28.3,
            "twh": 536
          },
          {
            "2019": 14.1,
            "2020": 17.2,
            "2021": 16.9,
            "2022": 20.6,
            "2023": 26.4,
            "2024": 29.5,
            "country": "Poland",
            "share": 29.5,
            "twh": 167
          }
        ],
        "columns": [
          {
            "key": "country",
            "label": "Country"
          },
          {
            "key": "twh",
            "label": "Generation, TWh",
            "kind": "bar",
            "format": "integer",
            "hideBelow": 560
          },
          {
            "key": "share",
            "label": "Share, 2024",
            "format": "{value}%"
          },
          {
            "key": "trend",
            "label": "2019 to 2024",
            "kind": "sparkline",
            "fields": [
              "2019",
              "2020",
              "2021",
              "2022",
              "2023",
              "2024"
            ],
            "format": "{value}%"
          },
          {
            "key": "change",
            "label": "Change",
            "kind": "delta",
            "fields": [
              "2019",
              "2020",
              "2021",
              "2022",
              "2023",
              "2024"
            ],
            "change": "absolute",
            "format": "{value} pts"
          }
        ],
        "sort": {
          "key": "share"
        },
        "highlight": "Netherlands",
        "source": "Ember, European Electricity Review 2025",
        "note": "Rounded, illustrative"
      }
    },
    {
      "name": "filter-chips",
      "component": "FilterChips",
      "title": "Filter chips",
      "level": "molecule",
      "category": "layout",
      "description": "The active crossfilter selections of the surrounding `InsightData`, shown as removable chips with a reset control.",
      "whenToUse": "Inside any `<InsightData>` whose charts use `select`: put it above the charts so readers can see what is filtered, drop one value, or clear everything. `linked-charts` already includes it.",
      "avoid": "Outside `<InsightData>` it renders nothing unless you pass `selections` yourself. It is not a filter builder: selections are made by clicking marks in the charts.",
      "composes": [],
      "props": [
        {
          "name": "selections",
          "type": "Record<string, { field, values?, range? }>",
          "description": "Selections to show when there is no surrounding `<InsightData>` (or to override it), keyed by owner. Uncontrolled unless `onSelectionsChange` is given."
        },
        {
          "name": "onSelectionsChange",
          "type": "(selections) => void",
          "description": "Called when a chip is removed or the reset is pressed; makes `selections` controlled."
        },
        {
          "name": "labels",
          "type": "Record<string, string>",
          "description": "Display names for field names, e.g. `{ region: \"Region\" }`."
        },
        {
          "name": "hint",
          "type": "string",
          "description": "Shown while nothing is selected, e.g. \"Click a bar to filter\"."
        },
        {
          "name": "resetLabel",
          "type": "string",
          "default": "\"Reset\"",
          "description": "Text of the clear-all control."
        },
        {
          "name": "count",
          "type": "boolean",
          "default": "false",
          "description": "Show \"1,204 of 5,000 rows\" at the end of the line."
        },
        {
          "name": "unit",
          "type": "string",
          "default": "\"rows\"",
          "description": "Noun for the count, e.g. \"orders\"."
        },
        {
          "name": "theme",
          "type": "ThemeInput",
          "description": "Theme override."
        },
        {
          "name": "mode",
          "type": "\"auto\" | \"system\" | \"light\" | \"dark\"",
          "default": "\"auto\"",
          "description": "Colour mode."
        }
      ],
      "example": {
        "type": "filter-chips",
        "labels": {
          "region": "Region",
          "source": "Source",
          "year": "Year"
        },
        "selections": {
          "by-region": {
            "field": "region",
            "values": [
              "Asia",
              "Europe"
            ]
          },
          "by-source": {
            "field": "source",
            "values": [
              "Solar"
            ]
          },
          "by-year": {
            "field": "year",
            "range": [
              2020,
              2024
            ]
          }
        }
      }
    },
    {
      "name": "hero-figure",
      "component": "HeroFigure",
      "title": "Hero figure",
      "level": "molecule",
      "category": "story",
      "description": "The opening exhibit of a piece: kicker, headline and deck above one large chart, with a source line underneath.",
      "whenToUse": "At the very top of a data story or a one-page report. The headline states the main fact with its number. The chart shows that fact.",
      "avoid": "Giving the chart its own `title` (the headline is the title; use the chart's `subtitle` for the measure). A second hero on the same page: later sections use `section` or `chart-callout`.",
      "composes": [
        "headline",
        "source-note",
        "line-chart",
        "big-number"
      ],
      "props": [
        {
          "name": "title",
          "type": "string",
          "required": true,
          "description": "The main fact, as one plain sentence with its number."
        },
        {
          "name": "kicker",
          "type": "string",
          "description": "Small label above the title: the series or topic."
        },
        {
          "name": "deck",
          "type": "string",
          "description": "One plain sentence under the title that adds a second fact."
        },
        {
          "name": "byline",
          "type": "string",
          "description": "Author line, e.g. \"By Ana Ruiz\"."
        },
        {
          "name": "date",
          "type": "string",
          "description": "Publication date. ISO dates are formatted for the locale."
        },
        {
          "name": "chart",
          "type": "Slot",
          "required": true,
          "description": "The hero chart: a spec or a React element, without its own title."
        },
        {
          "name": "source",
          "type": "string | { name, url? } | (string | { name, url? })[]",
          "description": "Where the data comes from. Entries with a `url` are linked."
        },
        {
          "name": "period",
          "type": "string",
          "description": "The period the data covers."
        },
        {
          "name": "note",
          "type": "string",
          "description": "Caveats the reader should know."
        },
        {
          "name": "align",
          "type": "\"left\" | \"center\"",
          "default": "\"left\"",
          "description": "Headline alignment."
        },
        {
          "name": "level",
          "type": "1 | 2 | 3 | 4 | 5 | 6",
          "default": "1",
          "description": "Heading level of the title."
        },
        {
          "name": "children",
          "type": "ReactNode",
          "description": "JSX alternative to `chart`."
        },
        {
          "name": "theme",
          "type": "ThemeInput",
          "description": "Theme override, passed on to the headline, chart and source note."
        },
        {
          "name": "mode",
          "type": "\"auto\" | \"system\" | \"light\" | \"dark\"",
          "default": "\"auto\"",
          "description": "Colour mode."
        }
      ],
      "example": {
        "type": "hero-figure",
        "kicker": "Energy transition",
        "title": "Solar supplied 11.1% of EU electricity in 2024",
        "deck": "Coal's share fell from 24.6% in 2015 to 9.8% in 2024.",
        "byline": "By Ana Ruiz",
        "date": "2025-01-23",
        "chart": {
          "type": "line-chart",
          "data": [
            {
              "year": "2015",
              "Solar": 2.9,
              "Coal": 24.6
            },
            {
              "year": "2016",
              "Solar": 3,
              "Coal": 22
            },
            {
              "year": "2017",
              "Solar": 3.4,
              "Coal": 20.6
            },
            {
              "year": "2018",
              "Solar": 3.7,
              "Coal": 19.2
            },
            {
              "year": "2019",
              "Solar": 4.2,
              "Coal": 15
            },
            {
              "year": "2020",
              "Solar": 5,
              "Coal": 13
            },
            {
              "year": "2021",
              "Solar": 5.4,
              "Coal": 14.7
            },
            {
              "year": "2022",
              "Solar": 7,
              "Coal": 15.8
            },
            {
              "year": "2023",
              "Solar": 9.1,
              "Coal": 12.3
            },
            {
              "year": "2024",
              "Solar": 11.1,
              "Coal": 9.8
            }
          ],
          "x": "year",
          "y": [
            "Solar",
            "Coal"
          ],
          "format": "{value}%",
          "zero": true,
          "height": 380,
          "subtitle": "Solar and coal share of EU electricity generation, 2015 to 2024",
          "annotations": [
            {
              "type": "point",
              "x": "2024",
              "y": 11.1,
              "label": "Solar 11.1%",
              "position": "left"
            }
          ]
        },
        "source": {
          "name": "Ember, European Electricity Review 2025",
          "url": "https://ember-energy.org"
        },
        "note": "Figures are approximate, for illustration",
        "period": "2015 to 2024"
      }
    },
    {
      "name": "kpi-grid",
      "component": "KpiGrid",
      "title": "KPI grid",
      "level": "molecule",
      "category": "layout",
      "description": "A responsive grid of trend cards drawn from one dataset: each metric shows its latest value, its change and a sparkline.",
      "whenToUse": "The top of a dashboard or a monthly report: three to eight metrics tracked over the same periods. Pass one table with a column per metric.",
      "avoid": "Figures without a series behind them (use `stat-row`); comparing the metrics with each other on one scale (use `line-chart` or `small-multiples`); more than eight cards.",
      "composes": [
        "trend-card"
      ],
      "props": [
        {
          "name": "data",
          "type": "DataInput",
          "description": "One dataset in time order: a row per period, a column per metric. Omit inside `<InsightData>`."
        },
        {
          "name": "transform",
          "type": "Transform | Transform[]",
          "description": "Applied once, before the cards read the rows."
        },
        {
          "name": "x",
          "type": "string",
          "description": "Period field shared by every metric."
        },
        {
          "name": "metrics",
          "type": "{ y, label, format?, upIsGood?, compare?, change?, deltaFormat?, deltaLabel?, context?, value?, delta?, kind? }[]",
          "required": true,
          "description": "One card per metric; each takes the props of `trend-card`."
        },
        {
          "name": "compare",
          "type": "\"first\" | \"previous\" | number",
          "default": "\"first\"",
          "description": "Baseline for every card's change; a metric can override it."
        },
        {
          "name": "change",
          "type": "\"percent\" | \"absolute\"",
          "default": "\"percent\"",
          "description": "Relative change, or the difference between the two values; a metric can override it. An absolute change of a percentage metric is shown in percentage points (\"−2.1 pts\")."
        },
        {
          "name": "deltaFormat",
          "type": "FormatSpec",
          "description": "Format for absolute changes, e.g. `\"{value} pp\"` or `\"signed\"`; a metric can override it. Default: points for percentage formats, otherwise the metric's own `format`."
        },
        {
          "name": "columns",
          "type": "number",
          "description": "Preferred columns. Default: up to four, reduced to fit cards of at least 200px."
        },
        {
          "name": "title",
          "type": "string",
          "description": "The main fact with its number, or a plain label."
        },
        {
          "name": "subtitle",
          "type": "string",
          "description": "Population and period."
        },
        {
          "name": "source",
          "type": "string",
          "description": "Shown under the grid as \"Source: ...\"."
        },
        {
          "name": "note",
          "type": "string",
          "description": "Method note or caveat."
        },
        {
          "name": "theme",
          "type": "ThemeInput",
          "description": "Theme override, passed on to every card."
        },
        {
          "name": "mode",
          "type": "\"auto\" | \"system\" | \"light\" | \"dark\"",
          "default": "\"auto\"",
          "description": "Colour mode."
        }
      ],
      "example": {
        "type": "kpi-grid",
        "title": "Monthly active users rose from 38,100 to 48,200 in 2024",
        "subtitle": "Four product metrics by month, 2024. Change is measured against January.",
        "data": [
          {
            "month": "2024-01-01",
            "users": 38100,
            "mrr": 412000,
            "churn": 4.2,
            "response": 4.1
          },
          {
            "month": "2024-02-01",
            "users": 39400,
            "mrr": 428000,
            "churn": 4,
            "response": 3.9
          },
          {
            "month": "2024-03-01",
            "users": 39000,
            "mrr": 431000,
            "churn": 3.9,
            "response": 3.8
          },
          {
            "month": "2024-04-01",
            "users": 40800,
            "mrr": 455000,
            "churn": 3.6,
            "response": 3.2
          },
          {
            "month": "2024-05-01",
            "users": 42200,
            "mrr": 470000,
            "churn": 3.1,
            "response": 3.4
          },
          {
            "month": "2024-06-01",
            "users": 41900,
            "mrr": 476000,
            "churn": 2.9,
            "response": 2.9
          },
          {
            "month": "2024-07-01",
            "users": 43500,
            "mrr": 498000,
            "churn": 2.6,
            "response": 2.7
          },
          {
            "month": "2024-08-01",
            "users": 44100,
            "mrr": 511000,
            "churn": 2.4,
            "response": 2.6
          },
          {
            "month": "2024-09-01",
            "users": 44500,
            "mrr": 524000,
            "churn": 2.3,
            "response": 2.4
          },
          {
            "month": "2024-10-01",
            "users": 46000,
            "mrr": 548000,
            "churn": 2.2,
            "response": 2.3
          },
          {
            "month": "2024-11-01",
            "users": 47200,
            "mrr": 566000,
            "churn": 2.1,
            "response": 2.2
          },
          {
            "month": "2024-12-01",
            "users": 48200,
            "mrr": 589000,
            "churn": 2.1,
            "response": 2.2
          }
        ],
        "x": "month",
        "metrics": [
          {
            "y": "users",
            "label": "Monthly active users",
            "format": "compact"
          },
          {
            "y": "mrr",
            "label": "Recurring revenue",
            "format": "currency:USD"
          },
          {
            "y": "churn",
            "label": "Monthly churn",
            "format": "{value}%",
            "change": "absolute",
            "upIsGood": false
          },
          {
            "y": "response",
            "label": "Median first response",
            "format": "{value} h",
            "upIsGood": false
          }
        ],
        "source": "Internal analytics",
        "note": "Illustrative data"
      }
    },
    {
      "name": "linked-charts",
      "component": "LinkedCharts",
      "title": "Linked charts",
      "level": "molecule",
      "category": "layout",
      "description": "Two to four charts over one shared dataset in a responsive grid: clicking a mark in one filters the others, with the active filters shown as removable chips.",
      "whenToUse": "Exploration of one table along several dimensions: by region, by category and over time. Pass row-level data once; each chart aggregates it with its own `transform`, sets `select: true` and omits `data`.",
      "avoid": "A report that states one fixed fact (use `chart-callout` or `scrolly-chart`); charts over unrelated datasets (they cannot filter each other); more than four charts.",
      "composes": [
        "filter-chips",
        "bar-chart",
        "line-chart"
      ],
      "props": [
        {
          "name": "data",
          "type": "DataInput",
          "required": true,
          "description": "The shared, row-level dataset."
        },
        {
          "name": "transform",
          "type": "Transform | Transform[]",
          "description": "Applied once to the shared data."
        },
        {
          "name": "charts",
          "type": "Slot[]",
          "description": "Two to four chart specs or elements. Give each `select: true` (or a field name) and a stable `id`, and leave out `data`."
        },
        {
          "name": "columns",
          "type": "number",
          "description": "Preferred columns. Default two; fewer when charts would be narrower than `minChartWidth`."
        },
        {
          "name": "minChartWidth",
          "type": "number",
          "default": "260",
          "description": "Narrowest a chart may get before the grid drops a column."
        },
        {
          "name": "filters",
          "type": "boolean",
          "default": "true",
          "description": "Show the filter line (`filter-chips`)."
        },
        {
          "name": "hint",
          "type": "string",
          "default": "\"Click a mark to filter the other charts\"",
          "description": "Shown in the filter line while nothing is selected."
        },
        {
          "name": "labels",
          "type": "Record<string, string>",
          "description": "Display names for the fields in the chips."
        },
        {
          "name": "count",
          "type": "boolean",
          "default": "false",
          "description": "Show the filtered row count at the end of the filter line."
        },
        {
          "name": "unit",
          "type": "string",
          "default": "\"rows\"",
          "description": "Noun for the count."
        },
        {
          "name": "selections",
          "type": "Record<string, Selection>",
          "description": "Controlled selections, keyed by chart id."
        },
        {
          "name": "onSelectionsChange",
          "type": "(selections) => void",
          "description": "Called when the selections change."
        },
        {
          "name": "title",
          "type": "string",
          "description": "Heading for the group."
        },
        {
          "name": "subtitle",
          "type": "string",
          "description": "Measure, population and period shared by the charts."
        },
        {
          "name": "source",
          "type": "string",
          "description": "Shown under the grid as \"Source: ...\"."
        },
        {
          "name": "note",
          "type": "string",
          "description": "Method note or caveat."
        },
        {
          "name": "children",
          "type": "ReactNode",
          "description": "JSX alternative to `charts`."
        },
        {
          "name": "before",
          "type": "Slot | Slot[]",
          "description": "Content rendered inside the shared store above the grid: figures, text, a custom component that calls `useInsightData()`, or a spec that omits `data` (e.g. `stat-row`, `data-table`). It sees the same filtered rows as the charts."
        },
        {
          "name": "after",
          "type": "Slot | Slot[]",
          "description": "Content rendered inside the shared store below the grid, e.g. a detail table of the filtered rows."
        },
        {
          "name": "gap",
          "type": "number",
          "default": "24",
          "description": "Space in px between the `before` slot, the grid and the `after` slot."
        },
        {
          "name": "theme",
          "type": "ThemeInput",
          "description": "Theme override, passed on to every chart."
        },
        {
          "name": "mode",
          "type": "\"auto\" | \"system\" | \"light\" | \"dark\"",
          "default": "\"auto\"",
          "description": "Colour mode."
        }
      ],
      "example": {
        "type": "linked-charts",
        "title": "Asia generated 46% of the renewable electricity in these four regions from 2020 to 2024",
        "subtitle": "Electricity generation from hydro, wind and solar, TWh, four regions, 2020 to 2024",
        "data": [
          {
            "region": "Asia",
            "year": "2020",
            "source": "Hydro",
            "twh": 1810
          },
          {
            "region": "Asia",
            "year": "2020",
            "source": "Wind",
            "twh": 480
          },
          {
            "region": "Asia",
            "year": "2020",
            "source": "Solar",
            "twh": 420
          },
          {
            "region": "Asia",
            "year": "2021",
            "source": "Hydro",
            "twh": 1790
          },
          {
            "region": "Asia",
            "year": "2021",
            "source": "Wind",
            "twh": 590
          },
          {
            "region": "Asia",
            "year": "2021",
            "source": "Solar",
            "twh": 560
          },
          {
            "region": "Asia",
            "year": "2022",
            "source": "Hydro",
            "twh": 1830
          },
          {
            "region": "Asia",
            "year": "2022",
            "source": "Wind",
            "twh": 700
          },
          {
            "region": "Asia",
            "year": "2022",
            "source": "Solar",
            "twh": 730
          },
          {
            "region": "Asia",
            "year": "2023",
            "source": "Hydro",
            "twh": 1800
          },
          {
            "region": "Asia",
            "year": "2023",
            "source": "Wind",
            "twh": 880
          },
          {
            "region": "Asia",
            "year": "2023",
            "source": "Solar",
            "twh": 980
          },
          {
            "region": "Asia",
            "year": "2024",
            "source": "Hydro",
            "twh": 1860
          },
          {
            "region": "Asia",
            "year": "2024",
            "source": "Wind",
            "twh": 1010
          },
          {
            "region": "Asia",
            "year": "2024",
            "source": "Solar",
            "twh": 1290
          },
          {
            "region": "Europe",
            "year": "2020",
            "source": "Hydro",
            "twh": 640
          },
          {
            "region": "Europe",
            "year": "2020",
            "source": "Wind",
            "twh": 510
          },
          {
            "region": "Europe",
            "year": "2020",
            "source": "Solar",
            "twh": 190
          },
          {
            "region": "Europe",
            "year": "2021",
            "source": "Hydro",
            "twh": 620
          },
          {
            "region": "Europe",
            "year": "2021",
            "source": "Wind",
            "twh": 540
          },
          {
            "region": "Europe",
            "year": "2021",
            "source": "Solar",
            "twh": 220
          },
          {
            "region": "Europe",
            "year": "2022",
            "source": "Hydro",
            "twh": 560
          },
          {
            "region": "Europe",
            "year": "2022",
            "source": "Wind",
            "twh": 580
          },
          {
            "region": "Europe",
            "year": "2022",
            "source": "Solar",
            "twh": 270
          },
          {
            "region": "Europe",
            "year": "2023",
            "source": "Hydro",
            "twh": 610
          },
          {
            "region": "Europe",
            "year": "2023",
            "source": "Wind",
            "twh": 640
          },
          {
            "region": "Europe",
            "year": "2023",
            "source": "Solar",
            "twh": 330
          },
          {
            "region": "Europe",
            "year": "2024",
            "source": "Hydro",
            "twh": 650
          },
          {
            "region": "Europe",
            "year": "2024",
            "source": "Wind",
            "twh": 690
          },
          {
            "region": "Europe",
            "year": "2024",
            "source": "Solar",
            "twh": 390
          },
          {
            "region": "North America",
            "year": "2020",
            "source": "Hydro",
            "twh": 690
          },
          {
            "region": "North America",
            "year": "2020",
            "source": "Wind",
            "twh": 400
          },
          {
            "region": "North America",
            "year": "2020",
            "source": "Solar",
            "twh": 150
          },
          {
            "region": "North America",
            "year": "2021",
            "source": "Hydro",
            "twh": 680
          },
          {
            "region": "North America",
            "year": "2021",
            "source": "Wind",
            "twh": 450
          },
          {
            "region": "North America",
            "year": "2021",
            "source": "Solar",
            "twh": 190
          },
          {
            "region": "North America",
            "year": "2022",
            "source": "Hydro",
            "twh": 700
          },
          {
            "region": "North America",
            "year": "2022",
            "source": "Wind",
            "twh": 500
          },
          {
            "region": "North America",
            "year": "2022",
            "source": "Solar",
            "twh": 240
          },
          {
            "region": "North America",
            "year": "2023",
            "source": "Hydro",
            "twh": 660
          },
          {
            "region": "North America",
            "year": "2023",
            "source": "Wind",
            "twh": 520
          },
          {
            "region": "North America",
            "year": "2023",
            "source": "Solar",
            "twh": 290
          },
          {
            "region": "North America",
            "year": "2024",
            "source": "Hydro",
            "twh": 670
          },
          {
            "region": "North America",
            "year": "2024",
            "source": "Wind",
            "twh": 560
          },
          {
            "region": "North America",
            "year": "2024",
            "source": "Solar",
            "twh": 360
          },
          {
            "region": "Latin America",
            "year": "2020",
            "source": "Hydro",
            "twh": 720
          },
          {
            "region": "Latin America",
            "year": "2020",
            "source": "Wind",
            "twh": 120
          },
          {
            "region": "Latin America",
            "year": "2020",
            "source": "Solar",
            "twh": 40
          },
          {
            "region": "Latin America",
            "year": "2021",
            "source": "Hydro",
            "twh": 690
          },
          {
            "region": "Latin America",
            "year": "2021",
            "source": "Wind",
            "twh": 140
          },
          {
            "region": "Latin America",
            "year": "2021",
            "source": "Solar",
            "twh": 60
          },
          {
            "region": "Latin America",
            "year": "2022",
            "source": "Hydro",
            "twh": 740
          },
          {
            "region": "Latin America",
            "year": "2022",
            "source": "Wind",
            "twh": 170
          },
          {
            "region": "Latin America",
            "year": "2022",
            "source": "Solar",
            "twh": 90
          },
          {
            "region": "Latin America",
            "year": "2023",
            "source": "Hydro",
            "twh": 760
          },
          {
            "region": "Latin America",
            "year": "2023",
            "source": "Wind",
            "twh": 200
          },
          {
            "region": "Latin America",
            "year": "2023",
            "source": "Solar",
            "twh": 120
          },
          {
            "region": "Latin America",
            "year": "2024",
            "source": "Hydro",
            "twh": 730
          },
          {
            "region": "Latin America",
            "year": "2024",
            "source": "Wind",
            "twh": 230
          },
          {
            "region": "Latin America",
            "year": "2024",
            "source": "Solar",
            "twh": 160
          }
        ],
        "labels": {
          "region": "Region",
          "source": "Source",
          "year": "Year"
        },
        "hint": "Click a bar to filter the other charts",
        "count": true,
        "charts": [
          {
            "type": "bar-chart",
            "id": "by-region",
            "x": "region",
            "y": "twh",
            "select": true,
            "format": "compact",
            "height": 200,
            "title": "By region",
            "transform": {
              "type": "aggregate",
              "groupBy": "region",
              "measures": {
                "twh": {
                  "op": "sum",
                  "field": "twh"
                }
              }
            }
          },
          {
            "type": "bar-chart",
            "id": "by-source",
            "x": "source",
            "y": "twh",
            "select": true,
            "format": "compact",
            "height": 200,
            "title": "By source",
            "transform": {
              "type": "aggregate",
              "groupBy": "source",
              "measures": {
                "twh": {
                  "op": "sum",
                  "field": "twh"
                }
              }
            }
          },
          {
            "type": "bar-chart",
            "id": "by-year",
            "x": "year",
            "y": "twh",
            "series": "source",
            "stack": true,
            "orientation": "vertical",
            "sort": "none",
            "select": "year",
            "format": "compact",
            "height": 240,
            "title": "By year",
            "transform": {
              "type": "aggregate",
              "groupBy": [
                "year",
                "source"
              ],
              "measures": {
                "twh": {
                  "op": "sum",
                  "field": "twh"
                }
              }
            }
          }
        ],
        "source": "Ember, Global Electricity Review 2025",
        "note": "Regional figures rounded, illustrative"
      }
    },
    {
      "name": "metric-breakdown",
      "component": "MetricBreakdown",
      "title": "Metric breakdown",
      "level": "molecule",
      "category": "layout",
      "description": "A headline number with its composition underneath: one segmented bar and a key giving each part's value and share.",
      "whenToUse": "A total that the reader will immediately want split: a budget by department, revenue by product line, emissions by sector. Two to six parts that add up to the number above them.",
      "avoid": "Parts that do not sum to the headline; more than about six parts (use `bar-chart`); composition compared across groups or over time (use `bar-chart` with `stack: \"percent\"` or `area-chart`).",
      "composes": [
        "big-number",
        "segmented-bar"
      ],
      "props": [
        {
          "name": "label",
          "type": "string",
          "required": true,
          "description": "What the headline number measures."
        },
        {
          "name": "data",
          "type": "DataInput",
          "description": "One row per part. Omit inside `<InsightData>`."
        },
        {
          "name": "transform",
          "type": "Transform | Transform[]",
          "description": "Applied before the parts are summed."
        },
        {
          "name": "name",
          "type": "string",
          "required": true,
          "description": "Field naming each part."
        },
        {
          "name": "value",
          "type": "string",
          "required": true,
          "description": "Field with each part's value."
        },
        {
          "name": "total",
          "type": "number | string",
          "description": "The headline number. Default: the sum of the parts."
        },
        {
          "name": "context",
          "type": "string",
          "description": "The line under the headline number."
        },
        {
          "name": "delta",
          "type": "number | string",
          "description": "Change in the headline number. Numbers render as signed percentages."
        },
        {
          "name": "deltaLabel",
          "type": "string",
          "description": "What the delta is against."
        },
        {
          "name": "upIsGood",
          "type": "boolean",
          "default": "true",
          "description": "Whether an increase is good news."
        },
        {
          "name": "size",
          "type": "\"hero\" | \"stat\"",
          "default": "\"stat\"",
          "description": "`hero` when this opens a piece."
        },
        {
          "name": "show",
          "type": "\"both\" | \"value\" | \"share\"",
          "default": "\"both\"",
          "description": "What the key prints beside each part."
        },
        {
          "name": "sort",
          "type": "\"none\" | \"desc\"",
          "default": "\"none\"",
          "description": "Order parts by size; the bar and the key follow."
        },
        {
          "name": "names",
          "type": "Record<string, string>",
          "description": "Display names for part keys."
        },
        {
          "name": "format",
          "type": "FormatSpec",
          "default": "\"auto\"",
          "description": "Number format for the headline and the parts."
        },
        {
          "name": "source",
          "type": "string",
          "description": "Shown underneath as \"Source: ...\"."
        },
        {
          "name": "note",
          "type": "string",
          "description": "Method note or caveat."
        },
        {
          "name": "theme",
          "type": "ThemeInput",
          "description": "Theme override, passed on to the number and the bar."
        },
        {
          "name": "mode",
          "type": "\"auto\" | \"system\" | \"light\" | \"dark\"",
          "default": "\"auto\"",
          "description": "Colour mode."
        }
      ],
      "example": {
        "type": "metric-breakdown",
        "label": "Greenhouse gas emissions, EU-27, 2022",
        "context": "Million tonnes of CO2 equivalent. 31% below the 1990 level.",
        "data": [
          {
            "sector": "Energy supply",
            "mt": 862
          },
          {
            "sector": "Transport",
            "mt": 804
          },
          {
            "sector": "Industry",
            "mt": 701
          },
          {
            "sector": "Buildings",
            "mt": 437
          },
          {
            "sector": "Agriculture",
            "mt": 378
          },
          {
            "sector": "Waste",
            "mt": 109
          }
        ],
        "name": "sector",
        "value": "mt",
        "format": "integer",
        "source": "European Environment Agency",
        "note": "Rounded, approximate figures for illustration"
      }
    },
    {
      "name": "poster",
      "component": "Poster",
      "title": "Poster",
      "level": "molecule",
      "category": "poster",
      "description": "The sheet an infographic is composed on. It paints a full-bleed ground from a poster theme, forces one theme and colour mode on everything inside, and lays out a masthead, a grid of panels and a footer. It can save the whole sheet as one PNG.",
      "whenToUse": "Use it as the outer frame of any infographic: one dominant graphic with a few supporting panels, on a painted ground. Give it a poster theme (`noir`, `magenta`, `dusk`, `paper`, `ocean`, `forest`, `blueprint`, `sunrise`) and put a chart or figure spec in each panel. Put the one fact in `title` and keep panel headings short. It reflows to one column under 600px.",
      "avoid": "Long reading text or more than about six panels (use `report-page` or `visual-essay`). A single fact at a fixed social size (use `social-card`). Charts that need axes, gridlines and dense labels to be read, because a poster ground suits large flat marks with their values printed on them.",
      "composes": [
        "big-number",
        "pictogram-chart",
        "bar-chart",
        "donut-chart",
        "treemap",
        "waffle-chart",
        "segmented-bar",
        "icon-stat"
      ],
      "props": [
        {
          "name": "title",
          "type": "string",
          "description": "The poster's one fact, as a plain sentence with its number. Set large and light in the theme's display face."
        },
        {
          "name": "kicker",
          "type": "string",
          "description": "Small label above the title: the topic or series."
        },
        {
          "name": "subtitle",
          "type": "string",
          "description": "One or two lines under the title: the measure, population and period."
        },
        {
          "name": "panels",
          "type": "PosterPanel[]",
          "required": true,
          "description": "The blocks, in reading order. Each is `{ content, title?, subtitle?, span?, rowSpan?, source?, note?, align?, valign?, bleed?, id? }`. `content` is a component spec or rendered content. `span` is in units of `columns` (default: full width). `bleed` runs the content to the poster's left and right edges."
        },
        {
          "name": "columns",
          "type": "number",
          "default": "12",
          "description": "Columns of the layout grid: a 12-unit grid, or a simple count such as 2 or 3. Under 600px every panel is full width; from 600 to 760px the grid has two columns."
        },
        {
          "name": "gap",
          "type": "number",
          "description": "Space between panels in px. Default 40 (28 under 600px)."
        },
        {
          "name": "padding",
          "type": "number",
          "description": "Space between the poster's edge and its content in px. Default 56 (36 under 900px, 20 under 600px)."
        },
        {
          "name": "maxWidth",
          "type": "number | string",
          "default": "1200",
          "description": "Widest the poster is drawn. It is centred in wider containers."
        },
        {
          "name": "aspect",
          "type": "number | string",
          "description": "Fixed shape as width / height: `0.75`, `\"3:4\"`, `\"16:9\"`. The poster still grows when its content needs more height, and the ratio is dropped under 600px."
        },
        {
          "name": "theme",
          "type": "PosterThemeName | string | ThemeInput",
          "default": "\"noir\"",
          "description": "A poster theme name, any other theme name, or a theme object. It is passed on to everything inside."
        },
        {
          "name": "mode",
          "type": "\"light\" | \"dark\" | \"auto\" | \"system\"",
          "default": "\"light\"",
          "description": "Colour mode of the poster and everything in it. A poster does not follow the page unless you set `auto`. Only `paper` has a different dark variant."
        },
        {
          "name": "background",
          "type": "string",
          "description": "CSS background of the sheet, a colour or a gradient. Default: the poster theme's ground. Keep it close to the theme's `surface`, which charts use for the gaps between marks."
        },
        {
          "name": "rules",
          "type": "\"none\" | \"solid\" | \"dotted\"",
          "default": "\"dotted\"",
          "description": "Lines under the masthead, between rows of panels and above the footer."
        },
        {
          "name": "sources",
          "type": "string | string[]",
          "description": "Where the data comes from, shown in the footer."
        },
        {
          "name": "note",
          "type": "string",
          "description": "Caveat shown in the footer, e.g. \"Illustrative data\"."
        },
        {
          "name": "credit",
          "type": "string",
          "description": "Who made the poster, shown at the right of the footer."
        },
        {
          "name": "titleAlign",
          "type": "\"left\" | \"center\"",
          "default": "\"left\"",
          "description": "Alignment of the masthead."
        },
        {
          "name": "level",
          "type": "1 | 2 | 3 | 4 | 5",
          "default": "2",
          "description": "Heading level of the title. Panel headings are one level down."
        },
        {
          "name": "download",
          "type": "boolean",
          "default": "false",
          "description": "Show a \"Download PNG\" control under the poster. The image is the whole sheet, drawn from the DOM onto a canvas, without the control."
        },
        {
          "name": "exportWidth",
          "type": "number",
          "default": "2400",
          "description": "Width of the downloaded PNG in pixels."
        },
        {
          "name": "filename",
          "type": "string",
          "description": "File name without extension. Defaults to a slug of the title."
        },
        {
          "name": "downloadLabel",
          "type": "string",
          "default": "\"Download PNG\"",
          "description": "Label of the download control."
        },
        {
          "name": "onExport",
          "type": "(blob: Blob) => void",
          "description": "Called with the PNG after a download."
        }
      ],
      "example": {
        "type": "poster",
        "theme": "noir",
        "kicker": "Transport",
        "title": "Riverton's bike-share scheme carried 4.2 million trips in 2025",
        "subtitle": "Trips, bikes and reasons for riding in the city's public bike-share scheme, January to December 2025.",
        "panels": [
          {
            "span": 4,
            "valign": "center",
            "content": {
              "type": "big-number",
              "size": "hero",
              "value": "4.2 million",
              "label": "Trips in 2025",
              "context": "3.4 million in 2024"
            }
          },
          {
            "span": 8,
            "title": "Old Town had 1,150 of the scheme's 3,000 bikes",
            "content": {
              "type": "pictogram-chart",
              "data": [
                {
                  "district": "Old Town",
                  "bikes": 1150
                },
                {
                  "district": "Harbour",
                  "bikes": 820
                },
                {
                  "district": "University",
                  "bikes": 640
                },
                {
                  "district": "Northside",
                  "bikes": 390
                }
              ],
              "name": "district",
              "value": "bikes",
              "unit": 50,
              "unitLabel": "50 bikes",
              "icon": "bike",
              "highlight": "Old Town"
            },
            "source": "Riverton Transport Office"
          },
          {
            "span": 7,
            "title": "670,000 trips were made on Wednesdays in 2025",
            "subtitle": "Trips by day of the week, thousands, 2025",
            "content": {
              "type": "bar-chart",
              "data": [
                {
                  "day": "Mon",
                  "trips": 610
                },
                {
                  "day": "Tue",
                  "trips": 655
                },
                {
                  "day": "Wed",
                  "trips": 670
                },
                {
                  "day": "Thu",
                  "trips": 662
                },
                {
                  "day": "Fri",
                  "trips": 640
                },
                {
                  "day": "Sat",
                  "trips": 520
                },
                {
                  "day": "Sun",
                  "trips": 443
                }
              ],
              "x": "day",
              "y": "trips",
              "orientation": "vertical",
              "sort": "none",
              "highlight": "Wed",
              "format": "{value}k",
              "height": 260
            }
          },
          {
            "span": 5,
            "title": "46% of trips were commutes",
            "subtitle": "Share of trips by stated purpose, rider survey, 2025",
            "content": {
              "type": "donut-chart",
              "data": [
                {
                  "purpose": "Commuting",
                  "share": 46
                },
                {
                  "purpose": "Leisure",
                  "share": 27
                },
                {
                  "purpose": "Errands",
                  "share": 18
                },
                {
                  "purpose": "Other",
                  "share": 9
                }
              ],
              "name": "purpose",
              "value": "share",
              "format": "{value}%",
              "centerValue": "46%",
              "centerLabel": "Commuting",
              "height": 260
            }
          }
        ],
        "sources": [
          "Riverton Transport Office",
          "Riverton rider survey 2025"
        ],
        "note": "Riverton is an invented city. All figures are illustrative.",
        "credit": "InsightCharts"
      }
    },
    {
      "name": "proportion-statement",
      "component": "ProportionStatement",
      "title": "Proportion statement",
      "level": "molecule",
      "category": "story",
      "description": "A share stated as a countable sentence (\"37 in every 100…\"): a waffle grid or a row of icons beside the statement in large type, with the number emphasised.",
      "whenToUse": "One proportion that is easier to read as a count of people or units than as a percentage. Round to a small whole first: 1 in 4 with icons, 37 in every 100 with a waffle.",
      "avoid": "Several shares side by side (use `bar-chart`, or `waffle-chart` with categories); a precise percentage where the decimals matter (use `big-number`); totals above 100.",
      "composes": [
        "waffle-chart",
        "icon-stat"
      ],
      "props": [
        {
          "name": "value",
          "type": "number",
          "required": true,
          "description": "How many of the whole are counted."
        },
        {
          "name": "total",
          "type": "number",
          "default": "100",
          "description": "The whole."
        },
        {
          "name": "figure",
          "type": "string",
          "description": "The emphasised words. Default: \"{value} in every {total}\", or \"{value} in {total}\" up to 20."
        },
        {
          "name": "text",
          "type": "string",
          "required": true,
          "description": "The rest of the sentence, read straight after the figure."
        },
        {
          "name": "visual",
          "type": "\"waffle\" | \"icons\"",
          "description": "Grid of cells or row of glyphs. Default: icons up to a total of 20, waffle above."
        },
        {
          "name": "icon",
          "type": "\"circle\" | \"person\" | \"square\"",
          "default": "\"person\"",
          "description": "Glyph for the `icons` visual."
        },
        {
          "name": "shape",
          "type": "\"square\" | \"circle\"",
          "default": "\"square\"",
          "description": "Cell shape for the `waffle` visual."
        },
        {
          "name": "side",
          "type": "\"left\" | \"right\"",
          "default": "\"left\"",
          "description": "Side the visual sits on; under 520px it goes on top."
        },
        {
          "name": "detail",
          "type": "string",
          "description": "A second, quieter sentence under the statement."
        },
        {
          "name": "source",
          "type": "string",
          "description": "Shown under the statement as \"Source: ...\"."
        },
        {
          "name": "note",
          "type": "string",
          "description": "Method note or caveat."
        },
        {
          "name": "theme",
          "type": "ThemeInput",
          "description": "Theme override, passed on to the visual."
        },
        {
          "name": "mode",
          "type": "\"auto\" | \"system\" | \"light\" | \"dark\"",
          "default": "\"auto\"",
          "description": "Colour mode."
        }
      ],
      "example": {
        "type": "proportion-statement",
        "value": 33,
        "total": 100,
        "text": "people in the world have never used the internet.",
        "detail": "That was about 2.6 billion people in 2023.",
        "source": "ITU Facts and Figures 2023",
        "note": "Figures are approximate, for illustration"
      }
    },
    {
      "name": "quote-figure",
      "component": "QuoteFigure",
      "title": "Quote with figure",
      "level": "molecule",
      "category": "story",
      "description": "A pull-quote paired with the statistic or small chart that backs it up, side by side and stacking in narrow containers.",
      "whenToUse": "A quotation shown next to the number it refers to: an interview finding with its survey figure, an executive's statement with the metric. One per section.",
      "avoid": "A quote with no evidence (use `pull-quote`); a figure that needs axes to read (use `chart-callout` and put the quote in prose); unattributed quotations.",
      "composes": [
        "pull-quote",
        "big-number",
        "sparkline"
      ],
      "props": [
        {
          "name": "quote",
          "type": "string",
          "required": true,
          "description": "The words quoted, without quotation marks."
        },
        {
          "name": "attribution",
          "type": "string",
          "description": "Who said it."
        },
        {
          "name": "role",
          "type": "string",
          "description": "Their role, or where it was said."
        },
        {
          "name": "cite",
          "type": "string",
          "description": "Source URL, linked from the attribution."
        },
        {
          "name": "stat",
          "type": "{ value, label, context?, delta?, deltaLabel?, upIsGood?, format? }",
          "description": "The statistic that backs the quote up; takes the props of `big-number`."
        },
        {
          "name": "figure",
          "type": "Slot",
          "description": "A small chart or figure instead of, or under, the statistic: a spec or a React element."
        },
        {
          "name": "side",
          "type": "\"left\" | \"right\"",
          "default": "\"right\"",
          "description": "Side the evidence sits on; under 640px it goes underneath."
        },
        {
          "name": "source",
          "type": "string",
          "description": "Shown underneath as \"Source: ...\"."
        },
        {
          "name": "note",
          "type": "string",
          "description": "Method note or caveat."
        },
        {
          "name": "theme",
          "type": "ThemeInput",
          "description": "Theme override, passed on to the quote and the figure."
        },
        {
          "name": "mode",
          "type": "\"auto\" | \"system\" | \"light\" | \"dark\"",
          "default": "\"auto\"",
          "description": "Colour mode."
        }
      ],
      "example": {
        "type": "quote-figure",
        "quote": "Solar met more than half of demand on 41% of summer afternoons in 2024. We do not have enough storage for the midday surplus yet.",
        "attribution": "Marta Keller",
        "role": "Grid planner, quoted in the 2024 system adequacy review",
        "stat": {
          "value": 0.41,
          "format": "percent",
          "label": "Summer afternoons when solar met over half of demand",
          "context": "It was 6% of afternoons in 2019"
        },
        "figure": {
          "type": "sparkline",
          "data": [
            {
              "year": "2019",
              "share": 6
            },
            {
              "year": "2020",
              "share": 9
            },
            {
              "year": "2021",
              "share": 14
            },
            {
              "year": "2022",
              "share": 22
            },
            {
              "year": "2023",
              "share": 31
            },
            {
              "year": "2024",
              "share": 41
            }
          ],
          "x": "year",
          "y": "share",
          "format": "{value}%",
          "area": true
        },
        "source": "National grid operator, hourly generation data",
        "note": "Illustrative data"
      }
    },
    {
      "name": "ranked-list",
      "component": "RankedList",
      "title": "Ranked list",
      "level": "molecule",
      "category": "layout",
      "description": "A ranking as a list: position, name, value and a thin in-row bar, with an optional change indicator and highlighted rows.",
      "whenToUse": "A top-N list where the names matter as much as the values: league tables, best and worst performers, leaderboards with a movement column. It keeps long names readable and works in a narrow column.",
      "avoid": "Precise visual comparison of many values or more than about 15 rows (use `bar-chart`); several measures per entity (use `data-table` or `comparison-table`); rank changing over time (use `bump-chart`).",
      "composes": [
        "delta"
      ],
      "props": [
        {
          "name": "data",
          "type": "DataInput",
          "description": "One row per entity. Omit inside `<InsightData>`."
        },
        {
          "name": "transform",
          "type": "Transform | Transform[]",
          "description": "Applied before ranking."
        },
        {
          "name": "name",
          "type": "string",
          "required": true,
          "description": "Field naming each entity."
        },
        {
          "name": "value",
          "type": "string",
          "required": true,
          "description": "Field with the value that ranks them. Bars start at zero."
        },
        {
          "name": "change",
          "type": "string",
          "description": "Field with a change shown at the end of each row: a number (0.12 is +12%) or a string."
        },
        {
          "name": "changeFormat",
          "type": "FormatSpec",
          "default": "signed percent",
          "description": "Format for numeric changes, e.g. `\"signed\"` for places moved."
        },
        {
          "name": "upIsGood",
          "type": "boolean",
          "default": "true",
          "description": "Whether a positive change is good news."
        },
        {
          "name": "detail",
          "type": "string",
          "description": "Field with a short secondary line under each name."
        },
        {
          "name": "sort",
          "type": "\"desc\" | \"asc\" | \"none\"",
          "default": "\"desc\"",
          "description": "Order by value; `none` keeps the data order."
        },
        {
          "name": "limit",
          "type": "number",
          "description": "Show only the first N rows after sorting."
        },
        {
          "name": "highlight",
          "type": "string | string[]",
          "description": "Entity names to emphasise. The other bars are drawn in grey."
        },
        {
          "name": "bar",
          "type": "boolean",
          "default": "true",
          "description": "Draw the in-row bar."
        },
        {
          "name": "max",
          "type": "number",
          "description": "Value at which a bar is full. Default: the largest value shown."
        },
        {
          "name": "format",
          "type": "FormatSpec",
          "default": "\"auto\"",
          "description": "Number format for the values."
        },
        {
          "name": "title",
          "type": "string",
          "description": "The main fact with its number, or a plain label."
        },
        {
          "name": "subtitle",
          "type": "string",
          "description": "Measure, population and period."
        },
        {
          "name": "source",
          "type": "string",
          "description": "Shown under the list as \"Source: ...\"."
        },
        {
          "name": "note",
          "type": "string",
          "description": "Method note or caveat."
        },
        {
          "name": "theme",
          "type": "ThemeInput",
          "description": "Theme override."
        },
        {
          "name": "mode",
          "type": "\"auto\" | \"system\" | \"light\" | \"dark\"",
          "default": "\"auto\"",
          "description": "Colour mode."
        }
      ],
      "example": {
        "type": "ranked-list",
        "title": "11% of commuting trips in Paris are made by bicycle",
        "subtitle": "Share of commuting trips made by bicycle, selected European cities, with change since 2019",
        "data": [
          {
            "city": "Copenhagen",
            "country": "Denmark",
            "share": 49,
            "change": 0.04
          },
          {
            "city": "Amsterdam",
            "country": "Netherlands",
            "share": 35,
            "change": 0.01
          },
          {
            "city": "Utrecht",
            "country": "Netherlands",
            "share": 34,
            "change": 0.02
          },
          {
            "city": "Münster",
            "country": "Germany",
            "share": 31,
            "change": 0
          },
          {
            "city": "Antwerp",
            "country": "Belgium",
            "share": 26,
            "change": 0.05
          },
          {
            "city": "Malmö",
            "country": "Sweden",
            "share": 25,
            "change": -0.01
          },
          {
            "city": "Bordeaux",
            "country": "France",
            "share": 13,
            "change": 0.06
          },
          {
            "city": "Paris",
            "country": "France",
            "share": 11,
            "change": 0.07
          }
        ],
        "name": "city",
        "detail": "country",
        "value": "share",
        "format": "{value}%",
        "change": "change",
        "highlight": "Paris",
        "source": "City mobility surveys",
        "note": "Illustrative data"
      }
    },
    {
      "name": "scrolly-chart",
      "component": "ScrollyChart",
      "title": "Scrolly chart",
      "level": "molecule",
      "category": "story",
      "description": "A scroll-driven story from JSON: one base chart spec and a list of steps, each with narration and a patch of chart props applied while that step is active.",
      "whenToUse": "Walking a reader through one chart in stages: show everything, highlight the subject, change the measure, add the annotation. Make ONE change per step (the highlight, the data or the encoding) so the reader can see what changed.",
      "avoid": "Steps that change several things at once, or that swap to an unrelated chart (start a new `section` instead). More than about seven steps. A fact that one view can show (use `chart-callout`). In documents that will be printed or embedded in a short frame, set `scrollMode: \"steps\"`.",
      "composes": [
        "prose",
        "bar-chart"
      ],
      "props": [
        {
          "name": "chart",
          "type": "InsightSpec",
          "required": true,
          "description": "The base chart spec: `{ type, data, x, y, ... }`. Give it a `title`; steps may replace it."
        },
        {
          "name": "steps",
          "type": "{ content, id?, label?, ...chart props }[]",
          "required": true,
          "description": "The steps in order. `content` is a short paragraph that says plainly what the chart now shows (`**bold**` works). Every other key is a chart prop merged over `chart` for that step: `highlight`, `transform`, `annotations`, `x`, `y`, `title`… Use `null` to remove a base prop. Change one thing per step."
        },
        {
          "name": "cumulative",
          "type": "boolean",
          "default": "false",
          "description": "Each step's patch builds on the previous steps instead of on the base spec alone."
        },
        {
          "name": "scrollMode",
          "type": "\"auto\" | \"scroll\" | \"swipe\" | \"steps\"",
          "default": "\"auto\"",
          "description": "`scroll` pins the chart while steps scroll past; `swipe` makes steps swipeable cards; `steps` shows previous/next buttons; `auto` uses scroll, or swipe on narrow touch screens. (Named `scrollMode` because `mode` is the colour mode.)"
        },
        {
          "name": "side",
          "type": "\"left\" | \"right\"",
          "default": "\"left\"",
          "description": "Side of the text column in scroll mode. Narrow containers overlay the text on the chart."
        },
        {
          "name": "stickyTop",
          "type": "number | string",
          "default": "0",
          "description": "Offset from the top of the viewport at which the chart pins; set it to the height of a fixed header."
        },
        {
          "name": "stepHeight",
          "type": "string",
          "default": "\"80vh\"",
          "description": "Scroll distance per step, as a CSS length."
        },
        {
          "name": "onStepChange",
          "type": "(index) => void",
          "description": "Called when the active step changes."
        },
        {
          "name": "title",
          "type": "string",
          "description": "Heading above the whole story."
        },
        {
          "name": "subtitle",
          "type": "string",
          "description": "Line under the heading."
        },
        {
          "name": "source",
          "type": "string",
          "description": "Shown after the last step as \"Source: ...\". Prefer the chart's own `source`, which stays visible."
        },
        {
          "name": "note",
          "type": "string",
          "description": "Method note or caveat."
        },
        {
          "name": "theme",
          "type": "ThemeInput",
          "description": "Theme override, passed on to the chart and the step cards."
        },
        {
          "name": "mode",
          "type": "\"auto\" | \"system\" | \"light\" | \"dark\"",
          "default": "\"auto\"",
          "description": "Colour mode."
        }
      ],
      "example": {
        "type": "scrolly-chart",
        "chart": {
          "type": "bar-chart",
          "data": [
            {
              "source": "Coal",
              "y2015": 24.6,
              "y2024": 9.8
            },
            {
              "source": "Gas",
              "y2015": 15.8,
              "y2024": 15.7
            },
            {
              "source": "Nuclear",
              "y2015": 27.1,
              "y2024": 23.7
            },
            {
              "source": "Wind",
              "y2015": 9.2,
              "y2024": 17.4
            },
            {
              "source": "Hydro",
              "y2015": 12,
              "y2024": 13.2
            },
            {
              "source": "Solar",
              "y2015": 2.9,
              "y2024": 11.1
            }
          ],
          "x": "source",
          "y": "y2015",
          "sort": "none",
          "orientation": "horizontal",
          "format": "{value}%",
          "height": 300,
          "title": "EU electricity generation by source, 2015",
          "subtitle": "Share of EU electricity generation, %",
          "source": "Ember, European Electricity Review 2025",
          "note": "Rounded, approximate figures for illustration"
        },
        "steps": [
          {
            "id": "baseline",
            "label": "2015",
            "content": "In 2015 **nuclear and coal** together supplied 51.7% of the EU's electricity."
          },
          {
            "id": "coal",
            "content": "Coal supplied 24.6% in 2015. Only nuclear supplied more.",
            "highlight": "Coal"
          },
          {
            "id": "today",
            "label": "2024",
            "content": "In 2024 coal supplied 9.8%.",
            "highlight": "Coal",
            "y": "y2024",
            "title": "EU electricity generation by source, 2024"
          },
          {
            "id": "solar",
            "content": "**Solar** supplied 11.1% in 2024. It supplied 2.9% in 2015.",
            "highlight": "Solar",
            "y": "y2024",
            "title": "Solar supplied 11.1% of EU electricity in 2024"
          }
        ],
        "stepHeight": "70vh",
        "stickyTop": 16
      }
    },
    {
      "name": "section",
      "component": "Section",
      "title": "Section",
      "level": "molecule",
      "category": "story",
      "description": "A story section: a numbered step marker or a headline, introductory prose, then one or more exhibits in a responsive one- to three-column grid.",
      "whenToUse": "The building block of a longer piece. Each section states one fact with its number in the title, explains it in a paragraph and shows it in one to three exhibits. Use `step` when the sections are read in order.",
      "avoid": "The opening of a piece (use `hero-figure`); a single chart with one callout (use `chart-callout` directly); more than three exhibits in one section (split it).",
      "composes": [
        "step-marker",
        "headline",
        "prose",
        "line-chart",
        "bar-chart",
        "stat-tile"
      ],
      "props": [
        {
          "name": "title",
          "type": "string",
          "description": "The section's main fact, as one plain sentence with its number."
        },
        {
          "name": "step",
          "type": "number | string",
          "description": "Step number: turns the heading into a numbered chapter marker (\"02 — ...\")."
        },
        {
          "name": "total",
          "type": "number",
          "description": "Total steps, shown as \"02 / 05\"."
        },
        {
          "name": "kicker",
          "type": "string",
          "description": "Small label above an unnumbered heading."
        },
        {
          "name": "deck",
          "type": "string",
          "description": "One plain sentence under the heading that adds a second fact."
        },
        {
          "name": "intro",
          "type": "string | string[]",
          "description": "Introductory narration. Supports `**bold**`, `*italic*` and links."
        },
        {
          "name": "body",
          "type": "Slot | Slot[]",
          "description": "The exhibits: one slot or several, each a spec or a React element."
        },
        {
          "name": "columns",
          "type": "1 | 2 | 3",
          "default": "1",
          "description": "Columns for the body at full width; reduced under 720px and 520px."
        },
        {
          "name": "outro",
          "type": "string | string[]",
          "description": "Closing narration under the body."
        },
        {
          "name": "rule",
          "type": "boolean",
          "default": "false",
          "description": "Hairline above the section."
        },
        {
          "name": "level",
          "type": "1 | 2 | 3 | 4 | 5 | 6",
          "default": "2",
          "description": "Heading level."
        },
        {
          "name": "id",
          "type": "string",
          "description": "Anchor id for deep links."
        },
        {
          "name": "spacing",
          "type": "\"compact\" | \"comfortable\"",
          "default": "\"comfortable\"",
          "description": "Vertical rhythm between heading, intro and body."
        },
        {
          "name": "children",
          "type": "ReactNode",
          "description": "JSX alternative to `body`; each child becomes a grid item."
        },
        {
          "name": "theme",
          "type": "ThemeInput",
          "description": "Theme override, passed on to everything inside."
        },
        {
          "name": "mode",
          "type": "\"auto\" | \"system\" | \"light\" | \"dark\"",
          "default": "\"auto\"",
          "description": "Colour mode."
        }
      ],
      "example": {
        "type": "section",
        "step": 2,
        "total": 4,
        "title": "First-quarter churn was 2.0% for customers who joined after March",
        "intro": "Monthly churn fell from 4.2% in January to 2.1% in December 2024. The new onboarding launched in March. Customers who joined after March had first-quarter churn of **2.0%**. Customers who joined before March had 4.1%.",
        "columns": 2,
        "body": [
          {
            "type": "line-chart",
            "data": [
              {
                "month": "2024-01-01",
                "rate": 4.2
              },
              {
                "month": "2024-02-01",
                "rate": 4.3
              },
              {
                "month": "2024-03-01",
                "rate": 4.1
              },
              {
                "month": "2024-04-01",
                "rate": 3.6
              },
              {
                "month": "2024-05-01",
                "rate": 3.1
              },
              {
                "month": "2024-06-01",
                "rate": 2.9
              },
              {
                "month": "2024-07-01",
                "rate": 2.6
              },
              {
                "month": "2024-08-01",
                "rate": 2.4
              },
              {
                "month": "2024-09-01",
                "rate": 2.3
              },
              {
                "month": "2024-10-01",
                "rate": 2.2
              },
              {
                "month": "2024-11-01",
                "rate": 2.1
              },
              {
                "month": "2024-12-01",
                "rate": 2.1
              }
            ],
            "x": "month",
            "y": "rate",
            "format": "{value}%",
            "zero": true,
            "height": 240,
            "title": "Monthly churn fell from 4.2% to 2.1% in 2024",
            "subtitle": "Monthly customer churn, 2024",
            "annotations": [
              {
                "type": "line",
                "axis": "x",
                "value": "2024-03-01",
                "label": "New onboarding"
              }
            ]
          },
          {
            "type": "bar-chart",
            "data": [
              {
                "cohort": "Before March",
                "churn": 4.1
              },
              {
                "cohort": "After March",
                "churn": 2
              }
            ],
            "x": "cohort",
            "y": "churn",
            "format": "{value}%",
            "height": 240,
            "highlight": "After March",
            "title": "First-quarter churn was 4.1% for customers who joined before March",
            "subtitle": "First-quarter churn by sign-up cohort, 2024"
          }
        ],
        "outro": "The next section covers the first 90 days after sign-up."
      }
    },
    {
      "name": "small-multiple-grid",
      "component": "SmallMultipleGrid",
      "title": "Small multiple grid",
      "level": "molecule",
      "category": "layout",
      "description": "Repeats any chart spec once per value of a facet field, each panel with its own filtered rows and a title, in a grid that follows the container width.",
      "whenToUse": "Faceting a chart form the `small-multiples` atom does not draw: stacked bars, scatter plots, donuts, waffles, dumbbells, histograms. Also when each panel should be a full chart with its own tooltip and axis.",
      "avoid": "Line, area or bar facets that must share one value scale: use the `small-multiples` atom, which draws every panel in one chart on a common axis. Here each panel scales to its own rows unless the spec fixes the axis (for example `option: { xAxis: { max: 70 } }`), so say so in the subtitle or fix it. More than about twelve panels.",
      "composes": [
        "legend-key",
        "bar-chart",
        "waffle-chart"
      ],
      "props": [
        {
          "name": "data",
          "type": "DataInput",
          "description": "Rows for every panel together. Omit inside `<InsightData>`."
        },
        {
          "name": "transform",
          "type": "Transform | Transform[]",
          "description": "Applied before the rows are split by `facet`."
        },
        {
          "name": "facet",
          "type": "string",
          "required": true,
          "description": "Field whose values each get a panel."
        },
        {
          "name": "chart",
          "type": "InsightSpec | (facet, rows) => ReactNode",
          "required": true,
          "description": "The chart repeated in every panel: a spec without `data` and without a `title`, or a render function from JSX."
        },
        {
          "name": "columns",
          "type": "number",
          "description": "Preferred columns. Default: up to four, reduced to fit `minPanelWidth`."
        },
        {
          "name": "minPanelWidth",
          "type": "number",
          "default": "200",
          "description": "Narrowest a panel may get before the grid drops a column."
        },
        {
          "name": "order",
          "type": "\"data\" | \"asc\" | \"desc\" | string[]",
          "default": "\"data\"",
          "description": "Panel order: data order, alphabetical, or an explicit list of facet values."
        },
        {
          "name": "panelHeight",
          "type": "number",
          "default": "160",
          "description": "Plot height of each panel when the spec sets none."
        },
        {
          "name": "highlight",
          "type": "string | string[]",
          "description": "Passed to every panel as its `highlight`, so the same entity is emphasised everywhere."
        },
        {
          "name": "emphasis",
          "type": "string | string[]",
          "description": "Facet values whose panel titles are emphasised."
        },
        {
          "name": "legend",
          "type": "(string | { label, color?, index?, shape?, muted? })[]",
          "description": "One shared colour key above the panels (`legend-key` items, in series order). Set `legend: false` in the chart spec so panels do not repeat it."
        },
        {
          "name": "names",
          "type": "Record<string, string>",
          "description": "Display names for facet values."
        },
        {
          "name": "level",
          "type": "2 | 3 | 4 | 5 | 6",
          "default": "4",
          "description": "Heading level of the panel titles."
        },
        {
          "name": "title",
          "type": "string",
          "description": "The main fact with its number, or a plain label."
        },
        {
          "name": "subtitle",
          "type": "string",
          "description": "Measure, population, period, and whether the panels share a scale."
        },
        {
          "name": "source",
          "type": "string",
          "description": "Shown under the grid as \"Source: ...\"."
        },
        {
          "name": "note",
          "type": "string",
          "description": "Method note or caveat."
        },
        {
          "name": "theme",
          "type": "ThemeInput",
          "description": "Theme override, passed on to every panel."
        },
        {
          "name": "mode",
          "type": "\"auto\" | \"system\" | \"light\" | \"dark\"",
          "default": "\"auto\"",
          "description": "Colour mode."
        }
      ],
      "example": {
        "type": "small-multiple-grid",
        "title": "65% of Dutch workers aged 25 to 34 worked from home usually or sometimes in 2023",
        "subtitle": "Share of employed people working from home usually or sometimes, by age group, six EU countries, 2023. All panels run from 0 to 70%",
        "data": [
          {
            "country": "Netherlands",
            "age": "16-24",
            "Usually": 18,
            "Sometimes": 22
          },
          {
            "country": "Netherlands",
            "age": "25-34",
            "Usually": 44,
            "Sometimes": 21
          },
          {
            "country": "Netherlands",
            "age": "35-49",
            "Usually": 41,
            "Sometimes": 19
          },
          {
            "country": "Netherlands",
            "age": "50-64",
            "Usually": 33,
            "Sometimes": 17
          },
          {
            "country": "Netherlands",
            "age": "65+",
            "Usually": 14,
            "Sometimes": 9
          },
          {
            "country": "Sweden",
            "age": "16-24",
            "Usually": 14,
            "Sometimes": 20
          },
          {
            "country": "Sweden",
            "age": "25-34",
            "Usually": 39,
            "Sometimes": 22
          },
          {
            "country": "Sweden",
            "age": "35-49",
            "Usually": 37,
            "Sometimes": 20
          },
          {
            "country": "Sweden",
            "age": "50-64",
            "Usually": 30,
            "Sometimes": 16
          },
          {
            "country": "Sweden",
            "age": "65+",
            "Usually": 11,
            "Sometimes": 8
          },
          {
            "country": "Germany",
            "age": "16-24",
            "Usually": 9,
            "Sometimes": 15
          },
          {
            "country": "Germany",
            "age": "25-34",
            "Usually": 24,
            "Sometimes": 18
          },
          {
            "country": "Germany",
            "age": "35-49",
            "Usually": 23,
            "Sometimes": 16
          },
          {
            "country": "Germany",
            "age": "50-64",
            "Usually": 18,
            "Sometimes": 13
          },
          {
            "country": "Germany",
            "age": "65+",
            "Usually": 8,
            "Sometimes": 6
          },
          {
            "country": "France",
            "age": "16-24",
            "Usually": 8,
            "Sometimes": 12
          },
          {
            "country": "France",
            "age": "25-34",
            "Usually": 25,
            "Sometimes": 14
          },
          {
            "country": "France",
            "age": "35-49",
            "Usually": 24,
            "Sometimes": 13
          },
          {
            "country": "France",
            "age": "50-64",
            "Usually": 17,
            "Sometimes": 10
          },
          {
            "country": "France",
            "age": "65+",
            "Usually": 7,
            "Sometimes": 5
          },
          {
            "country": "Spain",
            "age": "16-24",
            "Usually": 5,
            "Sometimes": 9
          },
          {
            "country": "Spain",
            "age": "25-34",
            "Usually": 13,
            "Sometimes": 12
          },
          {
            "country": "Spain",
            "age": "35-49",
            "Usually": 13,
            "Sometimes": 11
          },
          {
            "country": "Spain",
            "age": "50-64",
            "Usually": 10,
            "Sometimes": 8
          },
          {
            "country": "Spain",
            "age": "65+",
            "Usually": 4,
            "Sometimes": 4
          },
          {
            "country": "Italy",
            "age": "16-24",
            "Usually": 3,
            "Sometimes": 6
          },
          {
            "country": "Italy",
            "age": "25-34",
            "Usually": 9,
            "Sometimes": 8
          },
          {
            "country": "Italy",
            "age": "35-49",
            "Usually": 9,
            "Sometimes": 8
          },
          {
            "country": "Italy",
            "age": "50-64",
            "Usually": 7,
            "Sometimes": 6
          },
          {
            "country": "Italy",
            "age": "65+",
            "Usually": 3,
            "Sometimes": 3
          }
        ],
        "facet": "country",
        "columns": 3,
        "panelHeight": 170,
        "legend": [
          "Usually",
          "Sometimes"
        ],
        "chart": {
          "type": "bar-chart",
          "x": "age",
          "y": [
            "Usually",
            "Sometimes"
          ],
          "stack": true,
          "sort": "none",
          "orientation": "horizontal",
          "format": "{value}%",
          "labels": false,
          "legend": false,
          "option": {
            "xAxis": {
              "max": 70
            }
          }
        },
        "source": "Eurostat, Labour Force Survey",
        "note": "Rounded, illustrative"
      }
    },
    {
      "name": "stat-row",
      "component": "StatRow",
      "title": "Stat row",
      "level": "molecule",
      "category": "layout",
      "description": "Two to five key figures in one row separated by hairlines, collapsing to two columns and then one as the container narrows.",
      "whenToUse": "The opening summary of a report or dashboard: the two to five most important numbers. Give every figure a delta or a context line, and order them by importance.",
      "avoid": "More than five figures (use `kpi-grid`), a single lead number (use `big-number` with `size: \"hero\"`), or figures that need their history shown (use `kpi-grid`, or `variant: \"tiles\"` with a `trend`).",
      "composes": [
        "big-number",
        "stat-tile"
      ],
      "props": [
        {
          "name": "items",
          "type": "{ value, label, context?, delta?, deltaLabel?, upIsGood?, format?, trend? }[]",
          "required": true,
          "description": "Two to five figures, most important first. Each takes the props of `big-number` (or `stat-tile` when `variant` is `tiles`)."
        },
        {
          "name": "variant",
          "type": "\"plain\" | \"tiles\"",
          "default": "\"plain\"",
          "description": "Open figures divided by hairlines, or bordered tiles that can carry a `trend`."
        },
        {
          "name": "format",
          "type": "FormatSpec",
          "default": "\"auto\"",
          "description": "Number format for every figure that does not set its own."
        },
        {
          "name": "title",
          "type": "string",
          "description": "The main fact with its number, or a plain label."
        },
        {
          "name": "subtitle",
          "type": "string",
          "description": "Population and period the figures share."
        },
        {
          "name": "source",
          "type": "string",
          "description": "Shown under the row as \"Source: ...\"."
        },
        {
          "name": "note",
          "type": "string",
          "description": "Method note or caveat."
        },
        {
          "name": "theme",
          "type": "ThemeInput",
          "description": "Theme override, passed on to every figure."
        },
        {
          "name": "mode",
          "type": "\"auto\" | \"system\" | \"light\" | \"dark\"",
          "default": "\"auto\"",
          "description": "Colour mode, passed on to every figure."
        }
      ],
      "example": {
        "type": "stat-row",
        "title": "Renewables supplied 31.7% of the world's electricity in 2024",
        "subtitle": "Global electricity generation, 2024",
        "items": [
          {
            "value": 0.317,
            "format": "percent",
            "label": "Share from renewables",
            "delta": "+1.9 pts",
            "deltaLabel": "vs 2023"
          },
          {
            "value": 2131,
            "label": "Solar generation, TWh",
            "delta": 0.29,
            "deltaLabel": "vs 2023"
          },
          {
            "value": 2494,
            "label": "Wind generation, TWh",
            "delta": 0.079,
            "deltaLabel": "vs 2023"
          },
          {
            "value": 0.409,
            "format": "percent",
            "label": "Share from all clean sources",
            "context": "Above 40% for the first time since the 1940s"
          }
        ],
        "source": "Ember, Global Electricity Review 2025",
        "note": "Figures are approximate, for illustration"
      }
    },
    {
      "name": "trend-card",
      "component": "TrendCard",
      "title": "Trend card",
      "level": "molecule",
      "category": "layout",
      "description": "A KPI card computed from a series: the latest value, its change against a baseline in the same data, and a sparkline of the series.",
      "whenToUse": "A metric whose recent history matters as much as its level: users, revenue, response time. Pass the series and field names; the figure, the change and the sparkline all come from the same rows, so they cannot disagree.",
      "avoid": "A number with no history (use `big-number` or `stat-tile`); several metrics from one dataset (use `kpi-grid`); a series the reader must read values from (use `line-chart`).",
      "composes": [
        "stat-tile",
        "sparkline"
      ],
      "props": [
        {
          "name": "label",
          "type": "string",
          "required": true,
          "description": "What the figure measures, sentence case."
        },
        {
          "name": "data",
          "type": "DataInput",
          "description": "Rows in time order, one per period. Omit inside `<InsightData>`."
        },
        {
          "name": "transform",
          "type": "Transform | Transform[]",
          "description": "Applied to the rows before anything is computed."
        },
        {
          "name": "y",
          "type": "string",
          "required": true,
          "description": "Value field: the series drawn and the source of the figure and its change."
        },
        {
          "name": "x",
          "type": "string",
          "description": "Period field, used to name the baseline and in the sparkline tooltip."
        },
        {
          "name": "value",
          "type": "number | string",
          "description": "The figure shown. Default: the last row's `y`."
        },
        {
          "name": "delta",
          "type": "number | string",
          "description": "The change shown. Default: computed from the series according to `compare`."
        },
        {
          "name": "compare",
          "type": "\"first\" | \"previous\" | number",
          "default": "\"first\"",
          "description": "Baseline for the computed change: first row, previous row, or a number of rows back (12 for year-on-year in monthly data)."
        },
        {
          "name": "change",
          "type": "\"percent\" | \"absolute\"",
          "default": "\"percent\"",
          "description": "Relative change, or the difference between the two values (use for rates and shares). An absolute change of a percentage is shown in percentage points."
        },
        {
          "name": "deltaFormat",
          "type": "FormatSpec",
          "description": "Format for a computed absolute change, e.g. `\"{value} pp\"` or `\"signed\"`. Default: points (\"+1.9 pts\") when `format` is a percentage, otherwise `format`."
        },
        {
          "name": "deltaLabel",
          "type": "string",
          "description": "What the change is against. Default: \"since {first period}\" when `x` is given."
        },
        {
          "name": "upIsGood",
          "type": "boolean",
          "default": "true",
          "description": "Whether an increase is good news; decides the delta colour."
        },
        {
          "name": "context",
          "type": "string",
          "description": "A line under the figure that gives it meaning."
        },
        {
          "name": "format",
          "type": "FormatSpec",
          "default": "\"auto\"",
          "description": "Number format for the figure and the sparkline tooltip."
        },
        {
          "name": "kind",
          "type": "\"line\" | \"bar\"",
          "default": "\"line\"",
          "description": "Sparkline form: a line for a trend, bars for discrete counts."
        },
        {
          "name": "area",
          "type": "boolean",
          "default": "false",
          "description": "Light fill under the line; the scale then includes zero."
        },
        {
          "name": "trendPosition",
          "type": "\"bottom\" | \"right\"",
          "default": "\"bottom\"",
          "description": "Where the sparkline sits; `right` falls back to `bottom` in narrow cards."
        },
        {
          "name": "theme",
          "type": "ThemeInput",
          "description": "Theme override, passed on to the tile and sparkline."
        },
        {
          "name": "mode",
          "type": "\"auto\" | \"system\" | \"light\" | \"dark\"",
          "default": "\"auto\"",
          "description": "Colour mode."
        }
      ],
      "example": {
        "type": "trend-card",
        "label": "Monthly active users",
        "data": [
          {
            "month": "2024-01-01",
            "users": 38100
          },
          {
            "month": "2024-02-01",
            "users": 39400
          },
          {
            "month": "2024-03-01",
            "users": 39000
          },
          {
            "month": "2024-04-01",
            "users": 40800
          },
          {
            "month": "2024-05-01",
            "users": 42200
          },
          {
            "month": "2024-06-01",
            "users": 41900
          },
          {
            "month": "2024-07-01",
            "users": 43500
          },
          {
            "month": "2024-08-01",
            "users": 44100
          },
          {
            "month": "2024-09-01",
            "users": 44500
          },
          {
            "month": "2024-10-01",
            "users": 46000
          },
          {
            "month": "2024-11-01",
            "users": 47200
          },
          {
            "month": "2024-12-01",
            "users": 48210
          }
        ],
        "x": "month",
        "y": "users",
        "format": "compact",
        "style": {
          "maxWidth": 300
        }
      }
    },
    {
      "name": "atlas-poster",
      "component": "AtlasPoster",
      "title": "Atlas poster",
      "level": "template",
      "category": "infographic",
      "description": "A landscape spread of up to eleven small illustrations of one subject, each a different graphic form, in three bands of unequal panels: a statement in large type, spokes round an icon and a network of circles; a smooth trend with its peak labelled, nested squares and a slope chart; a pill ranking, a treemap, a tile map and sized circles; and a closing row of icon statements.",
      "whenToUse": "Use it when one subject has many small facts of different kinds (shares, places, a trend, a change, a ranking, a map) and none of them should dominate: a crop, an industry, a service, a country's internet. Fill at least eight regions. Give every panel a short heading that states its one number. Every region is optional, and each band shares its width among the panels that are present.",
      "avoid": "One dominant fact or graphic (use `count-poster`, `flow-poster` or `ranking-poster`). The same measures across several entities (use `matrix-poster`). Fewer than six panels (use `poster` and place them yourself). A phone-first piece: the spread becomes one long column under 600px.",
      "composes": [
        "poster",
        "radial-spokes",
        "bubble-network",
        "area-chart",
        "proportional-area",
        "slope-chart",
        "pill-rank",
        "treemap",
        "choropleth-map",
        "stat-circles",
        "icon-stat"
      ],
      "props": [
        {
          "name": "statement",
          "type": "{ value, label, context?, icon? }",
          "description": "Band 1, left: the subject's main number in large light type, in the accent colour."
        },
        {
          "name": "spokes",
          "type": "{ data, name, value, icon?, centerLabel?, highlight?, title?, ...radial-spokes props } | Slot",
          "description": "Band 1, middle: 4 to 12 attributes as spokes round a centre icon (`radial-spokes`)."
        },
        {
          "name": "network",
          "type": "{ data, name, value, links, highlight?, tag?, title?, ...bubble-network props } | Slot",
          "description": "Band 1, right: named places or things as circles sized by value and joined by lines (`bubble-network`)."
        },
        {
          "name": "trend",
          "type": "{ data, x, y, peak?, format?, title?, ...area-chart props } | Slot",
          "description": "Band 2, wide: one measure over an ordered dimension as a smooth filled curve (`area-chart`). The highest point is labelled with its value and position; `peak` replaces that label, and `peak: false` removes it."
        },
        {
          "name": "area",
          "type": "{ data, name, value, ratio?, title?, ...proportional-area props } | Slot",
          "description": "Band 2: two or three quantities as squares nested from one corner, with a bracket stating the ratio (`proportional-area`, `layout: \"nested\"`)."
        },
        {
          "name": "slope",
          "type": "{ data, series, y: [from, to], names?, highlight?, title?, ...slope-chart props } | Slot",
          "description": "Band 2: each entity at two dates, joined by a line and labelled at the end (`slope-chart`)."
        },
        {
          "name": "rank",
          "type": "{ data, name, value, highlight?, title?, ...pill-rank props } | Slot",
          "description": "Band 3: a ranking of 4 to 6 rows as centred pills (`pill-rank`). Pass a `bar-chart` spec for bars instead."
        },
        {
          "name": "parts",
          "type": "{ data, name, value, title?, ...treemap props } | Slot",
          "description": "Band 3: the parts of one whole as tiles (`treemap`)."
        },
        {
          "name": "map",
          "type": "{ data, region, value, code?, geo?, title?, ...choropleth-map props } | Slot",
          "description": "Band 3: a tile map shaded by value (`choropleth-map`). Give every row `col` and `row`, the tile's place in the grid, and the template builds the squares; `code` names the field printed on each tile. Pass `geo` to use your own GeoJSON."
        },
        {
          "name": "circles",
          "type": "{ data, name, value, unit?, title?, ...stat-circles props } | Slot",
          "description": "Band 3: two to four magnitudes as sized circles (`stat-circles`)."
        },
        {
          "name": "stats",
          "type": "{ count, total?, text, icon?, value? }[]",
          "description": "Closing row: up to three \"3 in 10\" statements, each a row of icons with some filled (`icon-stat`)."
        },
        {
          "name": "title",
          "type": "string",
          "required": true,
          "description": "The poster's one fact, as a plain sentence with its number."
        },
        {
          "name": "kicker",
          "type": "string",
          "description": "Small label above the title: the topic."
        },
        {
          "name": "subtitle",
          "type": "string",
          "description": "One line under the title: the measure, population and period."
        },
        {
          "name": "theme",
          "type": "PosterThemeName | string | ThemeInput",
          "default": "\"forest\"",
          "description": "Poster theme: `noir`, `magenta`, `dusk`, `ocean`, `forest`, `blueprint`, `sunrise`, `paper`, any other theme name, or a theme object."
        },
        {
          "name": "mode",
          "type": "\"light\" | \"dark\" | \"auto\" | \"system\"",
          "default": "\"light\"",
          "description": "Colour mode of the poster and everything in it. A poster paints its own ground and does not follow the page unless you set `auto`. Only `paper` has a different dark variant."
        },
        {
          "name": "download",
          "type": "boolean",
          "default": "false",
          "description": "Show a \"Download PNG\" control under the poster that saves the whole poster as one image."
        },
        {
          "name": "maxWidth",
          "type": "number | string",
          "default": "1440",
          "description": "Widest the poster is drawn. It is centred in wider containers."
        },
        {
          "name": "linked",
          "type": "boolean",
          "default": "false",
          "description": "Make the marks clickable. Clicking a mark in one graphic highlights the same name in every other graphic that has it, and filters the graphics that read the shared `data`. Click it again, or the empty ground of that graphic, to clear."
        },
        {
          "name": "data",
          "type": "DataInput",
          "description": "Rows shared by every region whose config leaves out its own `data`. With `linked`, a click filters those regions."
        },
        {
          "name": "sources",
          "type": "string | string[]",
          "description": "Where the data comes from, shown in the footer."
        },
        {
          "name": "note",
          "type": "string",
          "description": "Caveat shown in the footer, e.g. \"Illustrative data\"."
        },
        {
          "name": "credit",
          "type": "string",
          "description": "Who made the poster, shown at the right of the footer."
        },
        {
          "name": "level",
          "type": "1 | 2 | 3 | 4 | 5",
          "default": "2",
          "description": "Heading level of the title. Panel headings are one level down."
        }
      ],
      "example": {
        "type": "atlas-poster",
        "theme": "forest",
        "kicker": "Food",
        "title": "Norland's orchards picked 2.4 million tonnes of apples in 2025",
        "subtitle": "Harvest, regions, varieties, prices and exports of the apple crop.",
        "statement": {
          "value": "2.4m",
          "label": "Tonnes of apples picked in 2025",
          "context": "2.1 million tonnes in 2024",
          "icon": "apple"
        },
        "spokes": {
          "data": [
            {
              "use": "Eaten fresh",
              "share": 38
            },
            {
              "use": "Juice",
              "share": 24
            },
            {
              "use": "Cider",
              "share": 15
            },
            {
              "use": "Sauce and purée",
              "share": 9
            },
            {
              "use": "Dried",
              "share": 5
            },
            {
              "use": "Animal feed",
              "share": 5
            },
            {
              "use": "Vinegar",
              "share": 4
            }
          ],
          "name": "use",
          "value": "share",
          "format": "{value}%",
          "icon": "apple",
          "highlight": "Eaten fresh",
          "title": "38% of the harvest was eaten fresh"
        },
        "network": {
          "data": [
            {
              "region": "Eastmark",
              "kt": 720
            },
            {
              "region": "Sundby",
              "kt": 540
            },
            {
              "region": "Lowlands",
              "kt": 460
            },
            {
              "region": "Highmoor",
              "kt": 310
            },
            {
              "region": "Fjordland",
              "kt": 220
            },
            {
              "region": "Aldern",
              "kt": 150
            }
          ],
          "name": "region",
          "value": "kt",
          "links": [
            {
              "from": "Eastmark",
              "to": "Sundby"
            },
            {
              "from": "Eastmark",
              "to": "Lowlands"
            },
            {
              "from": "Eastmark",
              "to": "Highmoor"
            },
            {
              "from": "Eastmark",
              "to": "Aldern"
            },
            {
              "from": "Sundby",
              "to": "Fjordland"
            },
            {
              "from": "Lowlands",
              "to": "Fjordland"
            }
          ],
          "highlight": "Eastmark",
          "tag": "Largest",
          "format": "{value} kt",
          "title": "Eastmark picked 720,000 tonnes",
          "subtitle": "Harvest by region, thousand tonnes. Lines are fruit-train routes."
        },
        "trend": {
          "data": [
            {
              "year": "2010",
              "mt": 1.5
            },
            {
              "year": "2011",
              "mt": 1.56
            },
            {
              "year": "2012",
              "mt": 1.48
            },
            {
              "year": "2013",
              "mt": 1.65
            },
            {
              "year": "2014",
              "mt": 1.72
            },
            {
              "year": "2015",
              "mt": 1.69
            },
            {
              "year": "2016",
              "mt": 1.81
            },
            {
              "year": "2017",
              "mt": 1.9
            },
            {
              "year": "2018",
              "mt": 2.05
            },
            {
              "year": "2019",
              "mt": 2.2
            },
            {
              "year": "2020",
              "mt": 2.52
            },
            {
              "year": "2021",
              "mt": 2.31
            },
            {
              "year": "2022",
              "mt": 1.98
            },
            {
              "year": "2023",
              "mt": 2.15
            },
            {
              "year": "2024",
              "mt": 2.1
            },
            {
              "year": "2025",
              "mt": 2.4
            }
          ],
          "x": "year",
          "y": "mt",
          "format": "{value}m t",
          "title": "The harvest was 2.52 million tonnes in 2020",
          "subtitle": "Apples picked per year, million tonnes, 2010 to 2025"
        },
        "area": {
          "data": [
            {
              "land": "All farmland",
              "ha": 2100000
            },
            {
              "land": "Orchards",
              "ha": 84000
            }
          ],
          "name": "land",
          "value": "ha",
          "format": "compact",
          "ratio": "{ratio}x the area",
          "title": "Orchards covered 84,000 hectares",
          "subtitle": "Hectares, 2025"
        },
        "slope": {
          "data": [
            {
              "variety": "Gala",
              "y2015": 22,
              "y2025": 27
            },
            {
              "variety": "Elstar",
              "y2015": 25,
              "y2025": 18
            },
            {
              "variety": "Jonagold",
              "y2015": 20,
              "y2025": 14
            },
            {
              "variety": "Braeburn",
              "y2015": 12,
              "y2025": 15
            }
          ],
          "series": "variety",
          "y": [
            "y2015",
            "y2025"
          ],
          "names": {
            "y2015": "2015",
            "y2025": "2025"
          },
          "highlight": "Gala",
          "format": "{value}%",
          "title": "Gala rose from 22% to 27% of the harvest"
        },
        "rank": {
          "data": [
            {
              "country": "Vesterhav",
              "kt": 190
            },
            {
              "country": "Kalmar",
              "kt": 140
            },
            {
              "country": "Ostland",
              "kt": 95
            },
            {
              "country": "Meridia",
              "kt": 60
            },
            {
              "country": "Tavria",
              "kt": 35
            }
          ],
          "name": "country",
          "value": "kt",
          "format": "{value} kt",
          "highlight": "Vesterhav",
          "title": "Vesterhav bought 190,000 tonnes"
        },
        "parts": {
          "data": [
            {
              "part": "Shop",
              "cents": 71
            },
            {
              "part": "Grower",
              "cents": 62
            },
            {
              "part": "Packing and storage",
              "cents": 34
            },
            {
              "part": "Transport",
              "cents": 21
            },
            {
              "part": "Sales tax",
              "cents": 17
            }
          ],
          "name": "part",
          "value": "cents",
          "format": "{value}c",
          "highlight": "Grower",
          "title": "Growers received 62 cents of a €2.05 kilo"
        },
        "map": {
          "data": [
            {
              "region": "Fjordland",
              "code": "Fj",
              "yield": 22,
              "col": 0,
              "row": 0
            },
            {
              "region": "Highmoor",
              "code": "Hi",
              "yield": 27,
              "col": 1,
              "row": 0
            },
            {
              "region": "Norvik",
              "code": "No",
              "yield": 19,
              "col": 2,
              "row": 0
            },
            {
              "region": "Aldern",
              "code": "Al",
              "yield": 24,
              "col": 0,
              "row": 1
            },
            {
              "region": "Eastmark",
              "code": "Ea",
              "yield": 41,
              "col": 1,
              "row": 1
            },
            {
              "region": "Castren",
              "code": "Ca",
              "yield": 33,
              "col": 2,
              "row": 1
            },
            {
              "region": "Lowlands",
              "code": "Lo",
              "yield": 35,
              "col": 1,
              "row": 2
            },
            {
              "region": "Sundby",
              "code": "Su",
              "yield": 38,
              "col": 2,
              "row": 2
            }
          ],
          "region": "region",
          "value": "yield",
          "code": "code",
          "title": "Eastmark picked 41 tonnes a hectare",
          "subtitle": "Yield by region, tonnes per hectare"
        },
        "circles": {
          "data": [
            {
              "who": "per person in Norland",
              "kg": 24
            },
            {
              "who": "per person in Vesterhav",
              "kg": 17
            },
            {
              "who": "per person in Kalmar",
              "kg": 9
            }
          ],
          "name": "who",
          "value": "kg",
          "unit": "kg",
          "title": "Norlanders ate 24 kg of apples each"
        },
        "stats": [
          {
            "icon": "apple",
            "count": 4,
            "total": 10,
            "text": "apples were sold loose."
          },
          {
            "icon": "book",
            "count": 2,
            "total": 10,
            "text": "pickers were students."
          },
          {
            "icon": "tree",
            "count": 3,
            "total": 10,
            "text": "orchard trees were under ten years old."
          }
        ],
        "sources": [
          "Norland Fruit Growers' Association",
          "Norland Statistics Office"
        ],
        "note": "Norland and its neighbours are invented. All figures are illustrative.",
        "credit": "InsightCharts"
      }
    },
    {
      "name": "comparison-brief",
      "component": "ComparisonBrief",
      "title": "Comparison brief",
      "level": "template",
      "category": "layout",
      "description": "A compared with B on one page. It has a headline that states the result, the headline figures mirrored either side of their labels, one hero exhibit that shows both sides, paired exhibits on a shared scale, a verdict callout and a source note.",
      "whenToUse": "Use it for two things a reader has to choose between or compare, such as two countries, two products or a before and an after. Write the result with its number in the headline. Put the four or five most important measures in `figures`. Use a two-series line, dumbbell, slope or `before-after` as the hero. In each pair, use the same chart form and the same scale for both sides. The `subject` side takes the accent colour everywhere and the other side stays grey.",
      "avoid": "Three or more things (use a `comparison-table` or `small-multiple-grid` inside a `report-page`). One thing over time with nothing to compare it with (use `timeline-story`). Paired charts on different scales, because readers compare bar lengths across the pair.",
      "composes": [
        "headline",
        "prose",
        "callout",
        "source-note",
        "line-chart",
        "bar-chart",
        "segmented-bar"
      ],
      "props": [
        {
          "name": "title",
          "type": "string",
          "required": true,
          "description": "The result of the comparison, as one plain sentence with the number in it."
        },
        {
          "name": "kicker",
          "type": "string",
          "description": "Small label above the title."
        },
        {
          "name": "deck",
          "type": "string",
          "description": "One-sentence standfirst."
        },
        {
          "name": "byline",
          "type": "string",
          "description": "Author line."
        },
        {
          "name": "date",
          "type": "string",
          "description": "Publication date."
        },
        {
          "name": "a",
          "type": "string | { name, detail? }",
          "required": true,
          "description": "Side A, shown on the left."
        },
        {
          "name": "b",
          "type": "string | { name, detail? }",
          "required": true,
          "description": "Side B, shown on the right."
        },
        {
          "name": "subject",
          "type": "\"a\" | \"b\" | \"none\"",
          "default": "\"a\"",
          "description": "The side the piece is about. It takes the accent colour; the other stays grey. Match it with `highlight` in the charts."
        },
        {
          "name": "figures",
          "type": "{ label, a, b, format?, better?: \"higher\" | \"lower\" }[]",
          "description": "Headline measures. Numeric values get mirrored bars scaled within each row; `better` sets the stronger value bolder."
        },
        {
          "name": "hero",
          "type": "Slot",
          "description": "The exhibit that shows both sides at once. Default height 360px."
        },
        {
          "name": "heroText",
          "type": "string",
          "description": "One or two sentences under the hero."
        },
        {
          "name": "pairs",
          "type": "{ title, text?, a: Slot, b: Slot }[]",
          "description": "Paired exhibits under one heading. A is on the left and B on the right; they stack in narrow containers. Use the same form and scale for both."
        },
        {
          "name": "verdict",
          "type": "{ lead?, body?, label?, tone? }",
          "description": "The closing recommendation, shown as a callout."
        },
        {
          "name": "sources",
          "type": "string | { name, url? } | (string | { name, url? })[]",
          "description": "Where the data comes from, shown in the closing source note. Entries with a `url` are linked."
        },
        {
          "name": "period",
          "type": "string",
          "description": "The period the data covers."
        },
        {
          "name": "method",
          "type": "string",
          "description": "How the numbers were produced: definitions, adjustments, exclusions."
        },
        {
          "name": "note",
          "type": "string",
          "description": "Caveats the reader should know, e.g. \"Illustrative data\"."
        },
        {
          "name": "credit",
          "type": "string",
          "description": "Who made the piece."
        },
        {
          "name": "level",
          "type": "1 | 2 | 3 | 4 | 5 | 6",
          "default": "1",
          "description": "Heading level of the title; section headings follow one level down."
        },
        {
          "name": "maxWidth",
          "type": "number | string",
          "description": "Widest the piece may get. It is centred in wider containers."
        },
        {
          "name": "theme",
          "type": "ThemeInput",
          "description": "Theme override, passed on to everything inside."
        },
        {
          "name": "mode",
          "type": "\"auto\" | \"system\" | \"light\" | \"dark\"",
          "default": "\"auto\"",
          "description": "Colour mode, passed on to everything inside."
        }
      ],
      "example": {
        "type": "comparison-brief",
        "kicker": "Home heating",
        "title": "With the £7,500 grant, a heat pump repays its extra £500 cost in 4 years",
        "deck": "After the grant a heat pump costs £3,500 to buy and fit. A gas boiler costs £3,000. The heat pump costs £855 a year to run. The boiler costs £980.",
        "a": {
          "name": "Heat pump",
          "detail": "Air source, 7 kW"
        },
        "b": {
          "name": "Gas boiler",
          "detail": "Condensing combi, 30 kW"
        },
        "figures": [
          {
            "label": "Cost to buy and fit, after grant",
            "a": 3500,
            "b": 3000,
            "format": {
              "style": "currency",
              "currency": "GBP",
              "maximumFractionDigits": 0
            },
            "better": "lower"
          },
          {
            "label": "Running cost per year",
            "a": 855,
            "b": 980,
            "format": "currency:GBP",
            "better": "lower"
          },
          {
            "label": "CO2 per year, tonnes",
            "a": 0.6,
            "b": 2.3,
            "format": "decimal:1",
            "better": "lower"
          },
          {
            "label": "Heat out per unit of energy in",
            "a": 3.2,
            "b": 0.9,
            "format": "decimal:1",
            "better": "higher"
          },
          {
            "label": "Expected life, years",
            "a": 20,
            "b": 15,
            "better": "higher"
          }
        ],
        "hero": {
          "type": "line-chart",
          "data": [
            {
              "year": "0",
              "saving": -500
            },
            {
              "year": "1",
              "saving": -375
            },
            {
              "year": "2",
              "saving": -250
            },
            {
              "year": "3",
              "saving": -125
            },
            {
              "year": "4",
              "saving": 0
            },
            {
              "year": "5",
              "saving": 125
            },
            {
              "year": "6",
              "saving": 250
            },
            {
              "year": "7",
              "saving": 375
            },
            {
              "year": "8",
              "saving": 500
            },
            {
              "year": "9",
              "saving": 625
            },
            {
              "year": "10",
              "saving": 750
            },
            {
              "year": "11",
              "saving": 875
            },
            {
              "year": "12",
              "saving": 1000
            },
            {
              "year": "13",
              "saving": 1125
            },
            {
              "year": "14",
              "saving": 1250
            },
            {
              "year": "15",
              "saving": 1375
            }
          ],
          "x": "year",
          "y": "saving",
          "format": {
            "style": "currency",
            "currency": "GBP",
            "maximumFractionDigits": 0,
            "signDisplay": "exceptZero"
          },
          "area": true,
          "title": "After 15 years the heat pump has cost £1,375 less than the boiler",
          "subtitle": "Cumulative saving of a heat pump over a gas boiler in pounds, by years since installation. Includes purchase after the £7,500 grant, energy and servicing",
          "annotations": [
            {
              "type": "line",
              "axis": "y",
              "value": 0,
              "label": "Same cost as the boiler",
              "dashed": false,
              "emphasis": true
            },
            {
              "type": "point",
              "x": "4",
              "y": 0,
              "label": "Break-even",
              "position": "top"
            }
          ]
        },
        "heroText": "Without the grant the heat pump costs £11,000 to buy and fit. That is £8,000 more than the boiler. A saving of £125 a year would take 64 years to repay it.",
        "pairs": [
          {
            "title": "The January energy bill is £142 for the heat pump and £158 for the boiler",
            "text": "Estimated monthly energy cost for heating and hot water. Both charts use the same scale. The heat pump's bill is lower in all 12 months.",
            "a": {
              "type": "bar-chart",
              "data": [
                {
                  "month": "Jan",
                  "cost": 142
                },
                {
                  "month": "Feb",
                  "cost": 128
                },
                {
                  "month": "Mar",
                  "cost": 102
                },
                {
                  "month": "Apr",
                  "cost": 68
                },
                {
                  "month": "May",
                  "cost": 40
                },
                {
                  "month": "Jun",
                  "cost": 28
                },
                {
                  "month": "Jul",
                  "cost": 25
                },
                {
                  "month": "Aug",
                  "cost": 26
                },
                {
                  "month": "Sep",
                  "cost": 38
                },
                {
                  "month": "Oct",
                  "cost": 66
                },
                {
                  "month": "Nov",
                  "cost": 98
                },
                {
                  "month": "Dec",
                  "cost": 134
                }
              ],
              "x": "month",
              "y": "cost",
              "orientation": "vertical",
              "sort": "none",
              "labels": false,
              "format": "currency:GBP",
              "height": 200,
              "option": {
                "yAxis": {
                  "max": 160
                }
              }
            },
            "b": {
              "type": "bar-chart",
              "data": [
                {
                  "month": "Jan",
                  "cost": 158
                },
                {
                  "month": "Feb",
                  "cost": 141
                },
                {
                  "month": "Mar",
                  "cost": 115
                },
                {
                  "month": "Apr",
                  "cost": 78
                },
                {
                  "month": "May",
                  "cost": 49
                },
                {
                  "month": "Jun",
                  "cost": 37
                },
                {
                  "month": "Jul",
                  "cost": 34
                },
                {
                  "month": "Aug",
                  "cost": 35
                },
                {
                  "month": "Sep",
                  "cost": 46
                },
                {
                  "month": "Oct",
                  "cost": 74
                },
                {
                  "month": "Nov",
                  "cost": 107
                },
                {
                  "month": "Dec",
                  "cost": 146
                }
              ],
              "x": "month",
              "y": "cost",
              "orientation": "vertical",
              "sort": "none",
              "labels": false,
              "format": "currency:GBP",
              "height": 200,
              "highlight": "none",
              "option": {
                "yAxis": {
                  "max": 160
                }
              }
            }
          },
          {
            "title": "Cost over 15 years by part",
            "text": "Energy is £12,075 of the heat pump's cost. It is £13,500 of the boiler's.",
            "a": {
              "type": "segmented-bar",
              "data": [
                {
                  "part": "Purchase and fitting",
                  "gbp": 3500
                },
                {
                  "part": "Energy",
                  "gbp": 12075
                },
                {
                  "part": "Servicing",
                  "gbp": 1500
                }
              ],
              "name": "part",
              "value": "gbp",
              "format": "currency:GBP"
            },
            "b": {
              "type": "segmented-bar",
              "data": [
                {
                  "part": "Purchase and fitting",
                  "gbp": 3000
                },
                {
                  "part": "Energy",
                  "gbp": 13500
                },
                {
                  "part": "Servicing",
                  "gbp": 1200
                }
              ],
              "name": "part",
              "value": "gbp",
              "format": "currency:GBP"
            }
          }
        ],
        "verdict": {
          "lead": "Choose the heat pump if you get the grant and will stay at least 5 years",
          "body": "Without the grant the boiler is cheaper. The figures assume electricity and gas prices stay at their Q4 2024 levels."
        },
        "sources": [
          "Energy regulator price cap, Q4 2024",
          "Installer quotes collected by the author"
        ],
        "method": "A house using 11,500 kWh of heat a year. Heat pump seasonal efficiency is 3.2. Boiler efficiency is 90%. Prices held flat in real terms.",
        "note": "Illustrative data"
      }
    },
    {
      "name": "count-poster",
      "component": "CountPoster",
      "title": "Count poster",
      "level": "template",
      "category": "infographic",
      "description": "A poster that counts at scale. Blocks of thousands of unit icons fill most of the sheet, a second band counts reference groups with the same icon at the same size in a muted colour, and a bottom row holds three small graphics: squares sized by area with a ratio bracket, thick bars with the numbers inside, and shares as nested outlines or filled silhouettes.",
      "whenToUse": "Use it when the subject is a large count of people or things in 2 to 6 groups and the reader should see the crowd: pupils by stage, trees planted by species, vehicles built by model. Give `hero` the groups and `reference` one to three quantities to compare them with. Both bands use the same `unit`, `icon` and icon size, so their areas compare. State the total in the title.",
      "avoid": "Counts under a few hundred (use `pictogram-chart` in a `poster`). Shares of one whole as the main point (use `waffle-chart` or `treemap`). A ranking of many named things (use `ranking-poster`). A flow from sources to destinations (use `flow-poster`).",
      "composes": [
        "poster",
        "unit-blocks",
        "proportional-area",
        "block-bar",
        "nested-shares",
        "shape-fill"
      ],
      "props": [
        {
          "name": "hero",
          "type": "{ data, name, value, title?, subtitle?, source?, ...unit-blocks props } | Slot",
          "required": true,
          "description": "The hero band: one block of unit icons per row, drawn by `unit-blocks`. A full spec or element replaces it."
        },
        {
          "name": "unit",
          "type": "number",
          "description": "Units one icon stands for, in both bands. Default: a round number that gives the hero about 2,000 icons."
        },
        {
          "name": "unitLabel",
          "type": "string",
          "description": "What one icon stands for, e.g. \"1,000 pupils\". Printed as the key of both bands."
        },
        {
          "name": "icon",
          "type": "IconName | \"path://...\"",
          "default": "\"person\"",
          "description": "Icon of both bands."
        },
        {
          "name": "format",
          "type": "FormatSpec",
          "default": "\"integer\"",
          "description": "How to print the counts."
        },
        {
          "name": "reference",
          "type": "{ data, name, value, title?, ... } | Slot",
          "description": "The second band: reference groups counted with the same icon at the same size, painted in the theme's context grey (`unit-blocks`)."
        },
        {
          "name": "area",
          "type": "{ data, name, value, ratio?, title?, ... } | Slot",
          "description": "Bottom left: two to four values as squares sized by area, with a bracket stating how many times the largest is the smallest (`proportional-area`). `ratio` defaults to `\"{ratio}x larger\"`."
        },
        {
          "name": "bars",
          "type": "{ data, name, value, title?, ... } | Slot",
          "description": "Bottom middle: 3 to 8 values as thick rounded bars with the numbers inside (`block-bar`)."
        },
        {
          "name": "shares",
          "type": "{ data, name, value, icon?, title?, ... } | Slot",
          "description": "Bottom right: shares of one whole. With `icon` each share is a silhouette filled to its level (`shape-fill`); without, the shares are outlines nested in one square (`nested-shares`)."
        },
        {
          "name": "title",
          "type": "string",
          "required": true,
          "description": "The poster's one fact, as a plain sentence with its number."
        },
        {
          "name": "kicker",
          "type": "string",
          "description": "Small label above the title: the topic."
        },
        {
          "name": "subtitle",
          "type": "string",
          "description": "One line under the title: the measure, population and period."
        },
        {
          "name": "theme",
          "type": "PosterThemeName | string | ThemeInput",
          "default": "\"noir\"",
          "description": "Poster theme: `noir`, `magenta`, `dusk`, `ocean`, `forest`, `blueprint`, `sunrise`, `paper`, any other theme name, or a theme object."
        },
        {
          "name": "mode",
          "type": "\"light\" | \"dark\" | \"auto\" | \"system\"",
          "default": "\"light\"",
          "description": "Colour mode of the poster and everything in it. A poster paints its own ground and does not follow the page unless you set `auto`. Only `paper` has a different dark variant."
        },
        {
          "name": "download",
          "type": "boolean",
          "default": "false",
          "description": "Show a \"Download PNG\" control under the poster that saves the whole poster as one image."
        },
        {
          "name": "maxWidth",
          "type": "number | string",
          "default": "1100",
          "description": "Widest the poster is drawn. It is centred in wider containers."
        },
        {
          "name": "linked",
          "type": "boolean",
          "default": "false",
          "description": "Make the marks clickable. Clicking a mark in one graphic highlights the same name in every other graphic that has it, and filters the graphics that read the shared `data`. Click it again, or the empty ground of that graphic, to clear."
        },
        {
          "name": "data",
          "type": "DataInput",
          "description": "Rows shared by every region whose config leaves out its own `data`. With `linked`, a click filters those regions."
        },
        {
          "name": "sources",
          "type": "string | string[]",
          "description": "Where the data comes from, shown in the footer."
        },
        {
          "name": "note",
          "type": "string",
          "description": "Caveat shown in the footer, e.g. \"Illustrative data\"."
        },
        {
          "name": "credit",
          "type": "string",
          "description": "Who made the poster, shown at the right of the footer."
        },
        {
          "name": "level",
          "type": "1 | 2 | 3 | 4 | 5",
          "default": "2",
          "description": "Heading level of the title. Panel headings are one level down."
        }
      ],
      "example": {
        "type": "count-poster",
        "theme": "noir",
        "kicker": "Education",
        "title": "Norland's schools taught 1.84 million pupils in 2025",
        "subtitle": "Pupils enrolled in state schools by stage, autumn term 2025.",
        "unit": 1000,
        "unitLabel": "1,000 people",
        "icon": "person",
        "hero": {
          "data": [
            {
              "stage": "Primary",
              "pupils": 820000
            },
            {
              "stage": "Lower secondary",
              "pupils": 460000
            },
            {
              "stage": "Upper secondary",
              "pupils": 390000
            },
            {
              "stage": "Vocational",
              "pupils": 170000
            }
          ],
          "name": "stage",
          "value": "pupils",
          "title": "820,000 pupils were in primary school"
        },
        "reference": {
          "data": [
            {
              "group": "Teachers",
              "people": 142000
            },
            {
              "group": "Support staff",
              "people": 61000
            },
            {
              "group": "Pupils who left without a certificate",
              "people": 38000
            }
          ],
          "name": "group",
          "value": "people",
          "title": "142,000 teachers taught them"
        },
        "area": {
          "data": [
            {
              "school": "Largest secondary school",
              "pupils": 3640
            },
            {
              "school": "Median primary school",
              "pupils": 280
            }
          ],
          "name": "school",
          "value": "pupils",
          "title": "The largest school had 3,640 pupils"
        },
        "bars": {
          "data": [
            {
              "year": "2000",
              "m": 1.42
            },
            {
              "year": "2005",
              "m": 1.51
            },
            {
              "year": "2010",
              "m": 1.6
            },
            {
              "year": "2015",
              "m": 1.69
            },
            {
              "year": "2020",
              "m": 1.77
            },
            {
              "year": "2025",
              "m": 1.84
            }
          ],
          "name": "year",
          "value": "m",
          "format": "{value}m",
          "title": "Enrolment was 1.42 million in 2000"
        },
        "shares": {
          "data": [
            {
              "year": "2013",
              "share": 6
            },
            {
              "year": "2017",
              "share": 19
            },
            {
              "year": "2021",
              "share": 44
            },
            {
              "year": "2025",
              "share": 68
            }
          ],
          "name": "year",
          "value": "share",
          "highlight": "2025",
          "wholeLabel": "All pupils",
          "title": "68% of pupils had a school laptop in 2025"
        },
        "sources": "Norland Ministry of Education, school census 2025",
        "note": "Norland is an invented country. All figures are illustrative.",
        "credit": "InsightCharts"
      }
    },
    {
      "name": "data-explorer",
      "component": "DataExplorer",
      "title": "Data explorer",
      "level": "template",
      "category": "story",
      "description": "A set of linked charts over one shared dataset that filter each other. It has a headline, a sentence of instructions with filter chips, a row of figures that recompute from the filtered rows, two to four charts and an optional detail table. It opens on selections the author chooses.",
      "whenToUse": "Use it when readers will want to filter the data themselves after seeing the main finding. State the finding with its number in the headline. Set `initialSelections` so the piece opens on the view the headline describes. Use the figures and the `finding` callout to give the numbers for that view. Keep to one dataset and two to four small, plain charts. Readers click the charts to filter, so keep them all the same size.",
      "avoid": "A fixed message that needs no exploration (use `executive-brief` or `visual-essay`). Charts over unrelated datasets, which cannot filter each other. More than four charts or five figures.",
      "composes": [
        "headline",
        "callout",
        "filter-chips",
        "stat-row",
        "linked-charts",
        "data-table",
        "source-note",
        "bar-chart"
      ],
      "props": [
        {
          "name": "title",
          "type": "string",
          "required": true,
          "description": "What the starting view shows, as one plain sentence with the number in it."
        },
        {
          "name": "kicker",
          "type": "string",
          "description": "Small label above the title."
        },
        {
          "name": "deck",
          "type": "string",
          "description": "One-sentence standfirst."
        },
        {
          "name": "byline",
          "type": "string",
          "description": "Author line."
        },
        {
          "name": "date",
          "type": "string",
          "description": "Publication date."
        },
        {
          "name": "instructions",
          "type": "string",
          "description": "One sentence saying what can be clicked and what happens. Shown in the filter line."
        },
        {
          "name": "finding",
          "type": "{ lead?, body?, label? }",
          "description": "A callout saying what to look for in the starting view."
        },
        {
          "name": "data",
          "type": "DataInput",
          "required": true,
          "description": "The shared, row-level dataset: rows, CSV text, a URL or a loader."
        },
        {
          "name": "transform",
          "type": "Transform | Transform[]",
          "description": "Applied once to the shared data."
        },
        {
          "name": "charts",
          "type": "Slot[]",
          "required": true,
          "description": "Two to four chart specs. Give each a stable `id`, `select: true` (or a field name) and its own aggregating `transform`; leave out `data`."
        },
        {
          "name": "initialSelections",
          "type": "Record<string, { field, values?, range? }>",
          "description": "Selections the piece opens with, keyed by chart `id`. A button restores them after the reader changes the filter."
        },
        {
          "name": "figures",
          "type": "{ label, field?, op?, where?: { field, value }, per?, format?, context? }[]",
          "description": "Figures recomputed from the filtered rows: `op` over `field` (default sum; count without a field), optionally restricted by `where` and divided by the sum of `per` for a rate or share. While a filter is active each shows the all-rows value as context."
        },
        {
          "name": "table",
          "type": "{ transform?, columns?, caption?, sort?, highlight?, ... }",
          "description": "A detail table of the filtered rows: the props of `data-table` without `rows`, plus a `transform` applied first."
        },
        {
          "name": "labels",
          "type": "Record<string, string>",
          "description": "Display names for fields in the filter chips."
        },
        {
          "name": "unit",
          "type": "string",
          "default": "\"rows\"",
          "description": "Noun for what the figures total, used in figure context (\"3,650 across all trips\")."
        },
        {
          "name": "count",
          "type": "boolean",
          "default": "false",
          "description": "Show the filtered row count beside the chips."
        },
        {
          "name": "rowUnit",
          "type": "string",
          "default": "\"rows\"",
          "description": "Noun for one row of `data` in that count."
        },
        {
          "name": "columns",
          "type": "number",
          "description": "Preferred chart columns."
        },
        {
          "name": "restoreLabel",
          "type": "string",
          "default": "\"Back to the starting view\"",
          "description": "Label of the button that restores `initialSelections`."
        },
        {
          "name": "sources",
          "type": "string | { name, url? } | (string | { name, url? })[]",
          "description": "Where the data comes from, shown in the closing source note. Entries with a `url` are linked."
        },
        {
          "name": "period",
          "type": "string",
          "description": "The period the data covers."
        },
        {
          "name": "method",
          "type": "string",
          "description": "How the numbers were produced: definitions, adjustments, exclusions."
        },
        {
          "name": "note",
          "type": "string",
          "description": "Caveats the reader should know, e.g. \"Illustrative data\"."
        },
        {
          "name": "credit",
          "type": "string",
          "description": "Who made the piece."
        },
        {
          "name": "level",
          "type": "1 | 2 | 3 | 4 | 5 | 6",
          "default": "1",
          "description": "Heading level of the title; section headings follow one level down."
        },
        {
          "name": "maxWidth",
          "type": "number | string",
          "description": "Widest the piece may get. It is centred in wider containers."
        },
        {
          "name": "theme",
          "type": "ThemeInput",
          "description": "Theme override, passed on to everything inside."
        },
        {
          "name": "mode",
          "type": "\"auto\" | \"system\" | \"light\" | \"dark\"",
          "default": "\"auto\"",
          "description": "Colour mode, passed on to everything inside."
        }
      ],
      "example": {
        "type": "data-explorer",
        "kicker": "City bike share · June",
        "title": "Casual riders' trips averaged 24.6 minutes in June, against 12.0 for members",
        "deck": "70% of casual trips were at weekends. 40% started in Riverside. The view below is filtered to casual riders.",
        "instructions": "Click a bar to filter the other charts and the figures. Click it again to clear.",
        "finding": {
          "lead": "Casual riders made 1.43 million of the 3.65 million trips in June",
          "body": "520,000 of them were at weekend middays. Members made 34% of their trips at weekends."
        },
        "data": [
          {
            "rider": "Member",
            "day": "Weekday",
            "daypart": "Morning",
            "district": "Centre",
            "trips": 198,
            "minutes": 2376
          },
          {
            "rider": "Member",
            "day": "Weekday",
            "daypart": "Morning",
            "district": "Riverside",
            "trips": 114,
            "minutes": 1368
          },
          {
            "rider": "Member",
            "day": "Weekday",
            "daypart": "Morning",
            "district": "University",
            "trips": 125,
            "minutes": 1500
          },
          {
            "rider": "Member",
            "day": "Weekday",
            "daypart": "Morning",
            "district": "Docklands",
            "trips": 83,
            "minutes": 996
          },
          {
            "rider": "Member",
            "day": "Weekday",
            "daypart": "Midday",
            "district": "Centre",
            "trips": 99,
            "minutes": 1188
          },
          {
            "rider": "Member",
            "day": "Weekday",
            "daypart": "Midday",
            "district": "Riverside",
            "trips": 57,
            "minutes": 684
          },
          {
            "rider": "Member",
            "day": "Weekday",
            "daypart": "Midday",
            "district": "University",
            "trips": 62,
            "minutes": 744
          },
          {
            "rider": "Member",
            "day": "Weekday",
            "daypart": "Midday",
            "district": "Docklands",
            "trips": 42,
            "minutes": 504
          },
          {
            "rider": "Member",
            "day": "Weekday",
            "daypart": "Evening",
            "district": "Centre",
            "trips": 213,
            "minutes": 2556
          },
          {
            "rider": "Member",
            "day": "Weekday",
            "daypart": "Evening",
            "district": "Riverside",
            "trips": 123,
            "minutes": 1476
          },
          {
            "rider": "Member",
            "day": "Weekday",
            "daypart": "Evening",
            "district": "University",
            "trips": 134,
            "minutes": 1608
          },
          {
            "rider": "Member",
            "day": "Weekday",
            "daypart": "Evening",
            "district": "Docklands",
            "trips": 90,
            "minutes": 1080
          },
          {
            "rider": "Member",
            "day": "Weekday",
            "daypart": "Night",
            "district": "Centre",
            "trips": 46,
            "minutes": 552
          },
          {
            "rider": "Member",
            "day": "Weekday",
            "daypart": "Night",
            "district": "Riverside",
            "trips": 26,
            "minutes": 312
          },
          {
            "rider": "Member",
            "day": "Weekday",
            "daypart": "Night",
            "district": "University",
            "trips": 29,
            "minutes": 348
          },
          {
            "rider": "Member",
            "day": "Weekday",
            "daypart": "Night",
            "district": "Docklands",
            "trips": 19,
            "minutes": 228
          },
          {
            "rider": "Member",
            "day": "Weekend",
            "daypart": "Morning",
            "district": "Centre",
            "trips": 57,
            "minutes": 684
          },
          {
            "rider": "Member",
            "day": "Weekend",
            "daypart": "Morning",
            "district": "Riverside",
            "trips": 33,
            "minutes": 396
          },
          {
            "rider": "Member",
            "day": "Weekend",
            "daypart": "Morning",
            "district": "University",
            "trips": 36,
            "minutes": 432
          },
          {
            "rider": "Member",
            "day": "Weekend",
            "daypart": "Morning",
            "district": "Docklands",
            "trips": 24,
            "minutes": 288
          },
          {
            "rider": "Member",
            "day": "Weekend",
            "daypart": "Midday",
            "district": "Centre",
            "trips": 114,
            "minutes": 1368
          },
          {
            "rider": "Member",
            "day": "Weekend",
            "daypart": "Midday",
            "district": "Riverside",
            "trips": 66,
            "minutes": 792
          },
          {
            "rider": "Member",
            "day": "Weekend",
            "daypart": "Midday",
            "district": "University",
            "trips": 72,
            "minutes": 864
          },
          {
            "rider": "Member",
            "day": "Weekend",
            "daypart": "Midday",
            "district": "Docklands",
            "trips": 48,
            "minutes": 576
          },
          {
            "rider": "Member",
            "day": "Weekend",
            "daypart": "Evening",
            "district": "Centre",
            "trips": 84,
            "minutes": 1008
          },
          {
            "rider": "Member",
            "day": "Weekend",
            "daypart": "Evening",
            "district": "Riverside",
            "trips": 48,
            "minutes": 576
          },
          {
            "rider": "Member",
            "day": "Weekend",
            "daypart": "Evening",
            "district": "University",
            "trips": 53,
            "minutes": 636
          },
          {
            "rider": "Member",
            "day": "Weekend",
            "daypart": "Evening",
            "district": "Docklands",
            "trips": 35,
            "minutes": 420
          },
          {
            "rider": "Member",
            "day": "Weekend",
            "daypart": "Night",
            "district": "Centre",
            "trips": 34,
            "minutes": 408
          },
          {
            "rider": "Member",
            "day": "Weekend",
            "daypart": "Night",
            "district": "Riverside",
            "trips": 20,
            "minutes": 240
          },
          {
            "rider": "Member",
            "day": "Weekend",
            "daypart": "Night",
            "district": "University",
            "trips": 22,
            "minutes": 264
          },
          {
            "rider": "Member",
            "day": "Weekend",
            "daypart": "Night",
            "district": "Docklands",
            "trips": 14,
            "minutes": 168
          },
          {
            "rider": "Casual",
            "day": "Weekday",
            "daypart": "Morning",
            "district": "Centre",
            "trips": 18,
            "minutes": 342
          },
          {
            "rider": "Casual",
            "day": "Weekday",
            "daypart": "Morning",
            "district": "Riverside",
            "trips": 24,
            "minutes": 456
          },
          {
            "rider": "Casual",
            "day": "Weekday",
            "daypart": "Morning",
            "district": "University",
            "trips": 10,
            "minutes": 190
          },
          {
            "rider": "Casual",
            "day": "Weekday",
            "daypart": "Morning",
            "district": "Docklands",
            "trips": 8,
            "minutes": 152
          },
          {
            "rider": "Casual",
            "day": "Weekday",
            "daypart": "Midday",
            "district": "Centre",
            "trips": 54,
            "minutes": 1026
          },
          {
            "rider": "Casual",
            "day": "Weekday",
            "daypart": "Midday",
            "district": "Riverside",
            "trips": 72,
            "minutes": 1368
          },
          {
            "rider": "Casual",
            "day": "Weekday",
            "daypart": "Midday",
            "district": "University",
            "trips": 29,
            "minutes": 551
          },
          {
            "rider": "Casual",
            "day": "Weekday",
            "daypart": "Midday",
            "district": "Docklands",
            "trips": 25,
            "minutes": 475
          },
          {
            "rider": "Casual",
            "day": "Weekday",
            "daypart": "Evening",
            "district": "Centre",
            "trips": 42,
            "minutes": 798
          },
          {
            "rider": "Casual",
            "day": "Weekday",
            "daypart": "Evening",
            "district": "Riverside",
            "trips": 56,
            "minutes": 1064
          },
          {
            "rider": "Casual",
            "day": "Weekday",
            "daypart": "Evening",
            "district": "University",
            "trips": 22,
            "minutes": 418
          },
          {
            "rider": "Casual",
            "day": "Weekday",
            "daypart": "Evening",
            "district": "Docklands",
            "trips": 20,
            "minutes": 380
          },
          {
            "rider": "Casual",
            "day": "Weekday",
            "daypart": "Night",
            "district": "Centre",
            "trips": 15,
            "minutes": 285
          },
          {
            "rider": "Casual",
            "day": "Weekday",
            "daypart": "Night",
            "district": "Riverside",
            "trips": 20,
            "minutes": 380
          },
          {
            "rider": "Casual",
            "day": "Weekday",
            "daypart": "Night",
            "district": "University",
            "trips": 8,
            "minutes": 152
          },
          {
            "rider": "Casual",
            "day": "Weekday",
            "daypart": "Night",
            "district": "Docklands",
            "trips": 7,
            "minutes": 133
          },
          {
            "rider": "Casual",
            "day": "Weekend",
            "daypart": "Morning",
            "district": "Centre",
            "trips": 27,
            "minutes": 729
          },
          {
            "rider": "Casual",
            "day": "Weekend",
            "daypart": "Morning",
            "district": "Riverside",
            "trips": 36,
            "minutes": 972
          },
          {
            "rider": "Casual",
            "day": "Weekend",
            "daypart": "Morning",
            "district": "University",
            "trips": 14,
            "minutes": 378
          },
          {
            "rider": "Casual",
            "day": "Weekend",
            "daypart": "Morning",
            "district": "Docklands",
            "trips": 13,
            "minutes": 351
          },
          {
            "rider": "Casual",
            "day": "Weekend",
            "daypart": "Midday",
            "district": "Centre",
            "trips": 156,
            "minutes": 4212
          },
          {
            "rider": "Casual",
            "day": "Weekend",
            "daypart": "Midday",
            "district": "Riverside",
            "trips": 208,
            "minutes": 5616
          },
          {
            "rider": "Casual",
            "day": "Weekend",
            "daypart": "Midday",
            "district": "University",
            "trips": 83,
            "minutes": 2241
          },
          {
            "rider": "Casual",
            "day": "Weekend",
            "daypart": "Midday",
            "district": "Docklands",
            "trips": 73,
            "minutes": 1971
          },
          {
            "rider": "Casual",
            "day": "Weekend",
            "daypart": "Evening",
            "district": "Centre",
            "trips": 93,
            "minutes": 2511
          },
          {
            "rider": "Casual",
            "day": "Weekend",
            "daypart": "Evening",
            "district": "Riverside",
            "trips": 124,
            "minutes": 3348
          },
          {
            "rider": "Casual",
            "day": "Weekend",
            "daypart": "Evening",
            "district": "University",
            "trips": 50,
            "minutes": 1350
          },
          {
            "rider": "Casual",
            "day": "Weekend",
            "daypart": "Evening",
            "district": "Docklands",
            "trips": 43,
            "minutes": 1161
          },
          {
            "rider": "Casual",
            "day": "Weekend",
            "daypart": "Night",
            "district": "Centre",
            "trips": 24,
            "minutes": 648
          },
          {
            "rider": "Casual",
            "day": "Weekend",
            "daypart": "Night",
            "district": "Riverside",
            "trips": 32,
            "minutes": 864
          },
          {
            "rider": "Casual",
            "day": "Weekend",
            "daypart": "Night",
            "district": "University",
            "trips": 13,
            "minutes": 351
          },
          {
            "rider": "Casual",
            "day": "Weekend",
            "daypart": "Night",
            "district": "Docklands",
            "trips": 11,
            "minutes": 297
          }
        ],
        "unit": "trips",
        "labels": {
          "rider": "Rider",
          "daypart": "Time of day",
          "district": "District",
          "day": "Day"
        },
        "initialSelections": {
          "by-rider": {
            "field": "rider",
            "values": [
              "Casual"
            ]
          }
        },
        "figures": [
          {
            "label": "Trips, thousands",
            "field": "trips",
            "format": "integer"
          },
          {
            "label": "Average trip length, minutes",
            "field": "minutes",
            "per": "trips",
            "format": "decimal:1"
          },
          {
            "label": "Share taken at weekends",
            "field": "trips",
            "where": {
              "field": "day",
              "value": "Weekend"
            },
            "per": "trips",
            "format": "percent"
          },
          {
            "label": "Share starting in Riverside",
            "field": "trips",
            "where": {
              "field": "district",
              "value": "Riverside"
            },
            "per": "trips",
            "format": "percent"
          }
        ],
        "charts": [
          {
            "type": "bar-chart",
            "id": "by-rider",
            "x": "rider",
            "y": "trips",
            "select": true,
            "sort": "none",
            "orientation": "horizontal",
            "format": "integer",
            "title": "Rider",
            "subtitle": "Trips, thousands",
            "transform": {
              "type": "aggregate",
              "groupBy": "rider",
              "measures": {
                "trips": {
                  "op": "sum",
                  "field": "trips"
                }
              }
            }
          },
          {
            "type": "bar-chart",
            "id": "by-day",
            "x": "day",
            "y": "trips",
            "select": true,
            "sort": "none",
            "orientation": "horizontal",
            "format": "integer",
            "title": "Day",
            "subtitle": "Trips, thousands",
            "transform": {
              "type": "aggregate",
              "groupBy": "day",
              "measures": {
                "trips": {
                  "op": "sum",
                  "field": "trips"
                }
              }
            }
          },
          {
            "type": "bar-chart",
            "id": "by-daypart",
            "x": "daypart",
            "y": "trips",
            "select": true,
            "sort": "none",
            "orientation": "vertical",
            "format": "integer",
            "height": 220,
            "title": "Time of day",
            "subtitle": "Trips, thousands",
            "transform": {
              "type": "aggregate",
              "groupBy": "daypart",
              "measures": {
                "trips": {
                  "op": "sum",
                  "field": "trips"
                }
              }
            }
          },
          {
            "type": "bar-chart",
            "id": "by-district",
            "x": "district",
            "y": "trips",
            "select": true,
            "sort": "desc",
            "orientation": "horizontal",
            "format": "integer",
            "title": "Start district",
            "subtitle": "Trips, thousands",
            "transform": {
              "type": "aggregate",
              "groupBy": "district",
              "measures": {
                "trips": {
                  "op": "sum",
                  "field": "trips"
                }
              }
            }
          }
        ],
        "table": {
          "caption": "Selected trips by start district and time of day",
          "transform": [
            {
              "type": "aggregate",
              "groupBy": [
                "district",
                "daypart"
              ],
              "measures": {
                "trips": {
                  "op": "sum",
                  "field": "trips"
                },
                "minutes": {
                  "op": "sum",
                  "field": "minutes"
                }
              }
            },
            {
              "type": "derive",
              "as": "length",
              "op": "divide",
              "fields": [
                "minutes",
                "trips"
              ]
            },
            {
              "type": "sort",
              "by": "trips",
              "order": "desc"
            },
            {
              "type": "limit",
              "n": 6
            }
          ],
          "columns": [
            {
              "key": "district",
              "label": "District"
            },
            {
              "key": "daypart",
              "label": "Time of day"
            },
            {
              "key": "trips",
              "label": "Trips, thousands",
              "format": "integer",
              "bar": true,
              "width": "34%"
            },
            {
              "key": "length",
              "label": "Average minutes",
              "format": "decimal:1"
            }
          ]
        },
        "sources": "City bike-share operator, trip records",
        "period": "June 2024",
        "method": "A trip is a hire of more than one minute. Morning is 6 to 10am, evening 4 to 8pm.",
        "note": "Illustrative data"
      }
    },
    {
      "name": "executive-brief",
      "component": "ExecutiveBrief",
      "title": "Executive brief",
      "level": "template",
      "category": "layout",
      "description": "A one-page summary. It has a headline that states the main finding with its number, a deck, a row of three or four key figures, one hero chart with a takeaway callout, two smaller supporting exhibits, a short list of actions and a source note.",
      "whenToUse": "Use it when a decision-maker will spend about a minute on the page. State the main finding with its number and period in the headline. The headline and the hero chart should be enough for a reader who stops there. The figures, supporting exhibits and actions add detail. Use one accent colour for the subject (`highlight`) and leave everything else grey. Mark the cause on the hero as an annotation. The proportions suit a printed page.",
      "avoid": "More than one hero or more than three supporting exhibits (use `report-page`). A recurring set of numbers with no single finding (use `scorecard`). A story told in several steps (use `visual-essay`).",
      "composes": [
        "headline",
        "stat-row",
        "chart-callout",
        "prose",
        "source-note",
        "line-chart",
        "dumbbell-chart",
        "bar-chart"
      ],
      "props": [
        {
          "name": "title",
          "type": "string",
          "required": true,
          "description": "The main finding, as one plain sentence with the number and the period in it."
        },
        {
          "name": "kicker",
          "type": "string",
          "description": "Small label above the title: the report series or team."
        },
        {
          "name": "deck",
          "type": "string",
          "description": "One to three short sentences. Each states one more fact."
        },
        {
          "name": "byline",
          "type": "string",
          "description": "Author line, shown as written."
        },
        {
          "name": "date",
          "type": "string",
          "description": "Publication date. ISO dates are formatted for the locale."
        },
        {
          "name": "figures",
          "type": "{ value, label, context?, delta?, deltaLabel?, upIsGood?, format? }[]",
          "description": "Three or four key figures, most important first. Rendered with `stat-row`."
        },
        {
          "name": "hero",
          "type": "Slot",
          "required": true,
          "description": "The hero exhibit: a chart spec or element whose `title` states one fact with its number. Gets a height of 340px (260px on phones) unless it sets one."
        },
        {
          "name": "takeaway",
          "type": "{ lead?, body?, label?, tone? }",
          "description": "The callout beside the hero. `lead` is one short sentence stating the main fact. `body` gives the supporting numbers."
        },
        {
          "name": "supporting",
          "type": "Slot[]",
          "description": "Two (at most three) smaller exhibits shown side by side; they stack in narrow containers. Default height 220px."
        },
        {
          "name": "actions",
          "type": "(string | { title, text?, owner? })[]",
          "description": "Recommended actions, numbered. Two to four."
        },
        {
          "name": "actionsTitle",
          "type": "string",
          "default": "\"What to do\"",
          "description": "Heading of the actions list."
        },
        {
          "name": "sources",
          "type": "string | { name, url? } | (string | { name, url? })[]",
          "description": "Where the data comes from, shown in the closing source note. Entries with a `url` are linked."
        },
        {
          "name": "period",
          "type": "string",
          "description": "The period the data covers."
        },
        {
          "name": "method",
          "type": "string",
          "description": "How the numbers were produced: definitions, adjustments, exclusions."
        },
        {
          "name": "note",
          "type": "string",
          "description": "Caveats the reader should know, e.g. \"Illustrative data\"."
        },
        {
          "name": "credit",
          "type": "string",
          "description": "Who made the piece."
        },
        {
          "name": "level",
          "type": "1 | 2 | 3 | 4 | 5 | 6",
          "default": "1",
          "description": "Heading level of the title; section headings follow one level down."
        },
        {
          "name": "maxWidth",
          "type": "number | string",
          "description": "Widest the piece may get. It is centred in wider containers."
        },
        {
          "name": "theme",
          "type": "ThemeInput",
          "description": "Theme override, passed on to everything inside."
        },
        {
          "name": "mode",
          "type": "\"auto\" | \"system\" | \"light\" | \"dark\"",
          "default": "\"auto\"",
          "description": "Colour mode, passed on to everything inside."
        }
      ],
      "example": {
        "type": "executive-brief",
        "kicker": "Retention review · Q4 2024",
        "title": "Monthly churn fell from 4.2% to 2.1% in 2024",
        "deck": "The new onboarding shipped in March. Accounts that joined before March still churn at 3.8% a month.",
        "byline": "Growth analytics",
        "date": "2025-01-14",
        "figures": [
          {
            "value": "2.1%",
            "label": "Monthly churn, December",
            "delta": "−2.1 pts",
            "deltaLabel": "vs January",
            "upIsGood": false
          },
          {
            "value": 0.61,
            "format": "percent",
            "label": "New accounts finishing setup in week one",
            "delta": "+19 pts",
            "deltaLabel": "vs January"
          },
          {
            "value": 1.12,
            "format": "percent",
            "label": "Net revenue retention",
            "context": "Top quartile for the sector is 110%"
          },
          {
            "value": "$1.9m",
            "label": "Annual revenue retained",
            "context": "Compared with churn staying at the January rate of 4.2%"
          }
        ],
        "hero": {
          "type": "line-chart",
          "data": [
            {
              "month": "2024-01-01",
              "churn": 4.2
            },
            {
              "month": "2024-02-01",
              "churn": 4.3
            },
            {
              "month": "2024-03-01",
              "churn": 4.1
            },
            {
              "month": "2024-04-01",
              "churn": 3.6
            },
            {
              "month": "2024-05-01",
              "churn": 3.1
            },
            {
              "month": "2024-06-01",
              "churn": 2.9
            },
            {
              "month": "2024-07-01",
              "churn": 2.6
            },
            {
              "month": "2024-08-01",
              "churn": 2.4
            },
            {
              "month": "2024-09-01",
              "churn": 2.3
            },
            {
              "month": "2024-10-01",
              "churn": 2.2
            },
            {
              "month": "2024-11-01",
              "churn": 2.1
            },
            {
              "month": "2024-12-01",
              "churn": 2.1
            }
          ],
          "x": "month",
          "y": "churn",
          "format": "{value}%",
          "zero": true,
          "title": "Monthly churn fell from 4.1% in March to 2.1% in December",
          "subtitle": "Share of paying accounts cancelling each month, 2024",
          "annotations": [
            {
              "type": "line",
              "axis": "x",
              "value": "2024-03-01",
              "label": "New onboarding ships"
            },
            {
              "type": "band",
              "axis": "y",
              "from": 2,
              "to": 2.5,
              "label": "Target range"
            }
          ],
          "source": "Billing system"
        },
        "takeaway": {
          "lead": "Churn was between 2.1% and 2.4% from August to December",
          "body": "The new onboarding shipped in March. Churn did not fall in the same months of 2023."
        },
        "supporting": [
          {
            "type": "dumbbell-chart",
            "data": [
              {
                "plan": "Starter",
                "jan": 6.1,
                "dec": 2.9
              },
              {
                "plan": "Team",
                "jan": 4,
                "dec": 2
              },
              {
                "plan": "Business",
                "jan": 2.4,
                "dec": 1.6
              },
              {
                "plan": "Enterprise",
                "jan": 0.9,
                "dec": 0.8
              }
            ],
            "category": "plan",
            "from": "jan",
            "to": "dec",
            "names": {
              "jan": "January",
              "dec": "December"
            },
            "format": "{value}%",
            "sort": "none",
            "highlight": "Starter",
            "title": "Starter plan churn fell from 6.1% to 2.9%",
            "subtitle": "Monthly churn by plan, January and December 2024"
          },
          {
            "type": "bar-chart",
            "data": [
              {
                "reason": "Setup not finished",
                "before": 38,
                "after": 14
              },
              {
                "reason": "Too expensive",
                "before": 22,
                "after": 27
              },
              {
                "reason": "Missing a feature",
                "before": 17,
                "after": 24
              },
              {
                "reason": "Went to a rival",
                "before": 13,
                "after": 19
              },
              {
                "reason": "Company closed",
                "before": 10,
                "after": 16
              }
            ],
            "x": "reason",
            "y": "after",
            "sort": "none",
            "orientation": "horizontal",
            "format": "{value}%",
            "highlight": "Setup not finished",
            "title": "“Setup not finished” was the reason for 14% of cancellations in Q4",
            "subtitle": "Share of cancellations by stated reason, Q4 2024. “Setup not finished” was 38% in Q1"
          }
        ],
        "actions": [
          {
            "title": "Offer the guided setup to the 2,400 accounts that joined before March",
            "text": "These accounts churn at 3.8% a month.",
            "owner": "Lifecycle · by end of February"
          },
          {
            "title": "Make the setup checklist the default on the Team plan",
            "text": "The checklist is opt-in on Team today. Team churn was 2.0% in December.",
            "owner": "Product · Q1"
          },
          {
            "title": "Measure churn again in March 2025",
            "text": "That will give 12 months of data since the new onboarding shipped.",
            "owner": "Growth analytics"
          }
        ],
        "sources": [
          "Billing system",
          "Product analytics warehouse"
        ],
        "period": "January to December 2024, monthly",
        "method": "Churn is the share of accounts paying at the start of a month that cancel during it. Downgrades are not counted.",
        "note": "Illustrative data"
      }
    },
    {
      "name": "explainer",
      "component": "Explainer",
      "title": "Explainer",
      "level": "template",
      "category": "story",
      "description": "A vertical infographic in one narrow column. It opens with a big number or a proportion statement (`hook`), then the hero exhibit, then numbered sections with one exhibit each, a conclusion with an optional link, and sources.",
      "whenToUse": "Use it to explain one subject to a general audience in a single narrow column. The column also works as a tall image or on a phone screen. State the main finding with its number in the headline and repeat the main number in `hook`. Make the hero exhibit the largest thing on the page. Give each numbered section one heading, one exhibit and one sentence with its main number. Write each section so it can be read without the others. Keep one accent colour on the subject (`highlight`) throughout.",
      "avoid": "Findings that need caveats and separate interpretation (use `report-page`). A chart that has to change step by step (use `visual-essay` with a scrolly block). More than five sections, because readers stop before the end.",
      "composes": [
        "headline",
        "big-number",
        "proportion-statement",
        "step-marker",
        "prose",
        "callout",
        "source-note",
        "bar-chart",
        "icon-stat",
        "line-chart",
        "segmented-bar"
      ],
      "props": [
        {
          "name": "title",
          "type": "string",
          "required": true,
          "description": "The main finding, as one plain sentence with the number and the period in it."
        },
        {
          "name": "kicker",
          "type": "string",
          "description": "Small label above the title."
        },
        {
          "name": "deck",
          "type": "string",
          "description": "One-sentence standfirst."
        },
        {
          "name": "byline",
          "type": "string",
          "description": "Author line."
        },
        {
          "name": "date",
          "type": "string",
          "description": "Publication date."
        },
        {
          "name": "hook",
          "type": "{ value, total?, text?, figure?, visual?, icon?, detail?, label?, context?, format? } | Slot",
          "description": "The opening number. With `text` it renders a `proportion-statement` (\"19 in every 100 ...\"); with `label` a hero `big-number`. A spec or element is rendered as given."
        },
        {
          "name": "hero",
          "type": "Slot",
          "description": "The main exhibit. Its `title` states one fact with its number. Default height 360px."
        },
        {
          "name": "heroText",
          "type": "string",
          "description": "One or two sentences under the hero. Each states one more fact."
        },
        {
          "name": "sections",
          "type": "{ title, text?, exhibit?, takeaway?, id? }[]",
          "description": "Numbered sections. Each has a heading, one exhibit and a `takeaway` sentence with its main number."
        },
        {
          "name": "screenful",
          "type": "boolean",
          "default": "false",
          "description": "Give every section at least the height of the viewport and centre its content, so each fills a screen."
        },
        {
          "name": "conclusion",
          "type": "{ title, text?, points?, action?: { label, href } }",
          "description": "The closing block: a heading, text, a list of actions and an optional link."
        },
        {
          "name": "sources",
          "type": "string | { name, url? } | (string | { name, url? })[]",
          "description": "Where the data comes from, shown in the closing source note. Entries with a `url` are linked."
        },
        {
          "name": "period",
          "type": "string",
          "description": "The period the data covers."
        },
        {
          "name": "method",
          "type": "string",
          "description": "How the numbers were produced: definitions, adjustments, exclusions."
        },
        {
          "name": "note",
          "type": "string",
          "description": "Caveats the reader should know, e.g. \"Illustrative data\"."
        },
        {
          "name": "credit",
          "type": "string",
          "description": "Who made the piece."
        },
        {
          "name": "level",
          "type": "1 | 2 | 3 | 4 | 5 | 6",
          "default": "1",
          "description": "Heading level of the title; section headings follow one level down."
        },
        {
          "name": "maxWidth",
          "type": "number | string",
          "description": "Widest the piece may get. It is centred in wider containers."
        },
        {
          "name": "theme",
          "type": "ThemeInput",
          "description": "Theme override, passed on to everything inside."
        },
        {
          "name": "mode",
          "type": "\"auto\" | \"system\" | \"light\" | \"dark\"",
          "default": "\"auto\"",
          "description": "Colour mode, passed on to everything inside."
        }
      ],
      "example": {
        "type": "explainer",
        "kicker": "Food and climate",
        "title": "Households threw away 631 million tonnes of food in 2022",
        "deck": "That is 60% of the 1.05 billion tonnes of food wasted worldwide. Food service wasted 290 million tonnes and retail 131 million.",
        "hook": {
          "value": 19,
          "total": 100,
          "text": "tonnes of food that reach shops, restaurants and homes are thrown away.",
          "detail": "That was 1.05 billion tonnes in 2022."
        },
        "hero": {
          "type": "bar-chart",
          "data": [
            {
              "sector": "Households",
              "mt": 631
            },
            {
              "sector": "Food service",
              "mt": 290
            },
            {
              "sector": "Retail",
              "mt": 131
            }
          ],
          "x": "sector",
          "y": "mt",
          "sort": "none",
          "orientation": "horizontal",
          "highlight": "Households",
          "format": "{value} Mt",
          "title": "Households produced 60% of the world's food waste in 2022",
          "subtitle": "Food waste by sector, million tonnes (Mt), 2022",
          "source": "UNEP Food Waste Index Report 2024",
          "note": "Figures are approximate, for illustration"
        },
        "heroText": "Food service produced 290 million tonnes, or 28%. Retail produced 131 million tonnes, or 12%.",
        "sections": [
          {
            "title": "Household food waste by income group",
            "text": "Households in high-income countries waste 81 kg of food per person a year. In upper-middle-income countries the figure is 88 kg. In lower-middle-income countries it is 86 kg.",
            "exhibit": {
              "type": "bar-chart",
              "data": [
                {
                  "group": "High income",
                  "kg": 81
                },
                {
                  "group": "Upper-middle",
                  "kg": 88
                },
                {
                  "group": "Lower-middle",
                  "kg": 86
                }
              ],
              "x": "group",
              "y": "kg",
              "sort": "none",
              "orientation": "horizontal",
              "format": "{value} kg",
              "subtitle": "Household food waste per person per year, by country income group"
            },
            "takeaway": "The three income groups are within 7 kg per person of each other."
          },
          {
            "title": "Lost and wasted food causes 4.4 billion tonnes of emissions a year",
            "text": "Growing, transporting and chilling food uses land, fertiliser and fuel. Food releases methane when it rots. Lost and wasted food causes 8 to 10% of global greenhouse gas emissions.",
            "exhibit": {
              "type": "bar-chart",
              "data": [
                {
                  "emitter": "China",
                  "gt": 13
                },
                {
                  "emitter": "United States",
                  "gt": 6
                },
                {
                  "emitter": "Wasted food",
                  "gt": 4.4
                },
                {
                  "emitter": "India",
                  "gt": 3.9
                },
                {
                  "emitter": "European Union",
                  "gt": 3.4
                }
              ],
              "x": "emitter",
              "y": "gt",
              "sort": "desc",
              "orientation": "horizontal",
              "highlight": "Wasted food",
              "format": "{value} Gt",
              "subtitle": "Annual greenhouse gas emissions, billion tonnes of CO2 equivalent. Wasted food includes losses before retail"
            },
            "takeaway": "Only China and the United States emit more than lost and wasted food."
          },
          {
            "title": "733 million people faced hunger in 2023",
            "exhibit": {
              "type": "icon-stat",
              "icon": "person",
              "count": 1,
              "total": 11,
              "columns": 11,
              "text": "people in the world faced hunger in 2023."
            }
          }
        ],
        "conclusion": {
          "title": "The UN target is to halve food waste by 2030",
          "text": "Britain and Japan publish household food waste figures. Britain has cut household waste by 18% and Japan by 31%.",
          "points": [
            "Measure household food waste and publish the figure every year.",
            "State on packaging that “best before” refers to quality and “use by” refers to safety.",
            "Plan meals before shopping and freeze food that will not be eaten in time."
          ],
          "action": {
            "label": "Read the UNEP report",
            "href": "https://www.unep.org/resources/publication/food-waste-index-report-2024"
          }
        },
        "sources": [
          {
            "name": "UNEP Food Waste Index Report 2024",
            "url": "https://www.unep.org/resources/publication/food-waste-index-report-2024"
          },
          "FAO, State of Food Security and Nutrition in the World 2024",
          "Emissions figures rounded from national inventories"
        ],
        "note": "Figures are approximate, for illustration",
        "period": "2022 (waste), 2023 (hunger and emissions)",
        "method": "Food waste is measured at retail, food service and household level and includes inedible parts. Losses on farms and in transport are counted separately."
      }
    },
    {
      "name": "flow-poster",
      "component": "FlowPoster",
      "title": "Flow poster",
      "level": "template",
      "category": "infographic",
      "description": "A portrait poster of where something comes from and where it goes, in three stacked bands painted in one colour: the producers as large labelled tiles, the leading producer's output fanning out as ribbons to an aligned column of destinations, and the destinations as bubbles on a scale of how much each depends on that producer.",
      "whenToUse": "Use it when one producer leads and the reader should follow its output: ports and the buyers of one port's catch, regions and the users of one region's electricity, terminals and the routes out of the largest. Give `producers` 4 to 10 rows, `flow` 4 to 12 destinations of the leading producer, and `scale` the same destinations with a share (position) and a size. Keep the destination names identical in `flow` and `scale` so `linked` can join them.",
      "avoid": "Flows through more than one stage or between many sources and many destinations (use `sankey-chart` in a `poster`). A count of people or things (use `count-poster`). Many measures of the same entities (use `matrix-poster`).",
      "composes": [
        "poster",
        "treemap",
        "fan-flow",
        "bubble-scale"
      ],
      "props": [
        {
          "name": "producers",
          "type": "{ data, name, value, title?, subtitle?, source?, ...treemap props } | Slot",
          "required": true,
          "description": "Top band: the producers as tiles sized by output (`treemap`). The leading one is painted in the accent."
        },
        {
          "name": "lead",
          "type": "string",
          "description": "The producer the flow starts from. Default: the largest row of `producers`. It names the block the ribbons leave."
        },
        {
          "name": "flow",
          "type": "{ data, name, value, trunk?, tagged?, tagLabel?, title?, ...fan-flow props } | Slot",
          "required": true,
          "description": "Middle band: one ribbon per destination, as thick as its value, ending at a column of names, values and shares (`fan-flow`)."
        },
        {
          "name": "scale",
          "type": "{ data, name, value, size, scaleLabel?, sizeFormat?, title?, ...bubble-scale props } | Slot",
          "description": "Bottom band: one bubble per destination, placed by `value` on a 0 to 100 scale and sized by `size` (`bubble-scale`). Set `domain` and `ticks` for a scale that is not a percentage."
        },
        {
          "name": "format",
          "type": "FormatSpec",
          "description": "How to print the quantities of the top and middle bands, e.g. `\"{value} kt\"`."
        },
        {
          "name": "title",
          "type": "string",
          "required": true,
          "description": "The poster's one fact, as a plain sentence with its number."
        },
        {
          "name": "kicker",
          "type": "string",
          "description": "Small label above the title: the topic."
        },
        {
          "name": "subtitle",
          "type": "string",
          "description": "One line under the title: the measure, population and period."
        },
        {
          "name": "theme",
          "type": "PosterThemeName | string | ThemeInput",
          "default": "\"magenta\"",
          "description": "Poster theme: `noir`, `magenta`, `dusk`, `ocean`, `forest`, `blueprint`, `sunrise`, `paper`, any other theme name, or a theme object."
        },
        {
          "name": "mode",
          "type": "\"light\" | \"dark\" | \"auto\" | \"system\"",
          "default": "\"light\"",
          "description": "Colour mode of the poster and everything in it. A poster paints its own ground and does not follow the page unless you set `auto`. Only `paper` has a different dark variant."
        },
        {
          "name": "download",
          "type": "boolean",
          "default": "false",
          "description": "Show a \"Download PNG\" control under the poster that saves the whole poster as one image."
        },
        {
          "name": "maxWidth",
          "type": "number | string",
          "default": "920",
          "description": "Widest the poster is drawn. It is centred in wider containers."
        },
        {
          "name": "linked",
          "type": "boolean",
          "default": "false",
          "description": "Make the marks clickable. Clicking a mark in one graphic highlights the same name in every other graphic that has it, and filters the graphics that read the shared `data`. Click it again, or the empty ground of that graphic, to clear."
        },
        {
          "name": "data",
          "type": "DataInput",
          "description": "Rows shared by every region whose config leaves out its own `data`. With `linked`, a click filters those regions."
        },
        {
          "name": "sources",
          "type": "string | string[]",
          "description": "Where the data comes from, shown in the footer."
        },
        {
          "name": "note",
          "type": "string",
          "description": "Caveat shown in the footer, e.g. \"Illustrative data\"."
        },
        {
          "name": "credit",
          "type": "string",
          "description": "Who made the poster, shown at the right of the footer."
        },
        {
          "name": "level",
          "type": "1 | 2 | 3 | 4 | 5",
          "default": "2",
          "description": "Heading level of the title. Panel headings are one level down."
        }
      ],
      "example": {
        "type": "flow-poster",
        "theme": "magenta",
        "kicker": "Fishing",
        "title": "Six ports on the Verran Coast landed 412,000 tonnes of fish in 2025",
        "subtitle": "Landings by port, the buyers of Port Alda's catch, and how much each buyer relies on Port Alda. Thousand tonnes (kt).",
        "format": "{value} kt",
        "producers": {
          "data": [
            {
              "port": "Port Alda",
              "kt": 148
            },
            {
              "port": "Skerry",
              "kt": 96
            },
            {
              "port": "Marrow Bay",
              "kt": 71
            },
            {
              "port": "Tolven",
              "kt": 52
            },
            {
              "port": "Hask",
              "kt": 28
            },
            {
              "port": "Lund",
              "kt": 17
            }
          ],
          "name": "port",
          "value": "kt",
          "title": "Port Alda landed 148,000 of the 412,000 tonnes"
        },
        "flow": {
          "data": [
            {
              "buyer": "Canneries",
              "kt": 46
            },
            {
              "buyer": "Fresh markets",
              "kt": 34
            },
            {
              "buyer": "Frozen export",
              "kt": 29
            },
            {
              "buyer": "Fishmeal plants",
              "kt": 21
            },
            {
              "buyer": "Restaurants",
              "kt": 11
            },
            {
              "buyer": "Smokehouses",
              "kt": 7
            }
          ],
          "name": "buyer",
          "value": "kt",
          "trunk": "Port Alda",
          "title": "Canneries bought 46,000 tonnes of Port Alda's catch"
        },
        "scale": {
          "data": [
            {
              "buyer": "Canneries",
              "fromAlda": 56,
              "kt": 82
            },
            {
              "buyer": "Fresh markets",
              "fromAlda": 35,
              "kt": 96
            },
            {
              "buyer": "Frozen export",
              "fromAlda": 23,
              "kt": 124
            },
            {
              "buyer": "Fishmeal plants",
              "fromAlda": 34,
              "kt": 61
            },
            {
              "buyer": "Restaurants",
              "fromAlda": 29,
              "kt": 38
            },
            {
              "buyer": "Smokehouses",
              "fromAlda": 64,
              "kt": 11
            }
          ],
          "name": "buyer",
          "value": "fromAlda",
          "size": "kt",
          "sizeFormat": "{value} kt",
          "scaleLabel": "From Port Alda",
          "title": "Smokehouses bought 64% of their fish from Port Alda",
          "subtitle": "Share of each buyer's fish landed at Port Alda (position) and all the fish it bought (area)"
        },
        "sources": "Verran Coast Fisheries Board landing returns",
        "note": "The Verran Coast is an invented place. All figures are illustrative.",
        "credit": "InsightCharts"
      }
    },
    {
      "name": "matrix-poster",
      "component": "MatrixPoster",
      "title": "Matrix poster",
      "level": "template",
      "category": "infographic",
      "description": "One wide landscape graphic that compares 4 to 12 entities on five to seven measures: two measures as S-curves between parallel scales on the left, joined to a table on the right in which every other number is drawn as dots, squares, icons, a ring, a circle or a bar. Both halves share one row order and one colour per entity.",
      "whenToUse": "Use it for profiles of a handful of comparable things with measures in different units: cities, species, products, stations, teams. Pass one row per entity with every measure in it. Put the two measures whose relationship matters in `curve` and the rest in `columns`, choosing a counted glyph (`dots`, `blocks`, `icons`) for small whole numbers, `ring` for shares, `bar` or `circle` for magnitudes. Leave `highlight` out to give every entity its own colour, derived from the theme when its palette is smaller than the number of entities; set it to draw one or two entities in colour and the rest in grey.",
      "avoid": "One measure across entities (use `pill-rank` or `bar-chart`). More than 12 entities (use `data-table` or `heatmap`). A subject with a total and its parts (use `ranking-poster` or `flow-poster`). A phone-first piece: the table stacks into cards under 600px and the poster becomes long.",
      "composes": [
        "poster",
        "curve-rank",
        "glyph-table"
      ],
      "props": [
        {
          "name": "rows",
          "type": "Row[]",
          "required": true,
          "description": "One object per entity, holding the name and every measure."
        },
        {
          "name": "name",
          "type": "string",
          "description": "Field holding the entity name. Default: the first key of the first row."
        },
        {
          "name": "nameLabel",
          "type": "string",
          "description": "Heading of the table's name column."
        },
        {
          "name": "curve",
          "type": "{ left, right, leftLabel?, rightLabel?, leftFormat?, rightFormat?, leftTop?, rightTop?, title?, subtitle?, source?, ...curve-rank props }",
          "required": true,
          "description": "Left graphic: the two measures joined by one S-curve per entity (`curve-rank`). The poster's rows are ordered by `left`, largest first."
        },
        {
          "name": "columns",
          "type": "{ field, label?, glyph?, unit?, format?, max?, icon? }[]",
          "required": true,
          "description": "Right graphic: the glyph columns of the table, in order (`glyph-table`). Three to five read well beside the curves."
        },
        {
          "name": "table",
          "type": "{ title?, subtitle?, source?, note? }",
          "description": "Short heading and source line over and under the table."
        },
        {
          "name": "order",
          "type": "string[]",
          "description": "Entity names in display order, for both graphics. Default: largest first on the curve's left measure."
        },
        {
          "name": "highlight",
          "type": "string | string[]",
          "description": "Entities drawn in colour while every other one turns grey. Without it each entity has its own colour in both graphics."
        },
        {
          "name": "title",
          "type": "string",
          "required": true,
          "description": "The poster's one fact, as a plain sentence with its number."
        },
        {
          "name": "kicker",
          "type": "string",
          "description": "Small label above the title: the topic."
        },
        {
          "name": "subtitle",
          "type": "string",
          "description": "One line under the title: the measure, population and period."
        },
        {
          "name": "theme",
          "type": "PosterThemeName | string | ThemeInput",
          "default": "\"paper\"",
          "description": "Poster theme: `noir`, `magenta`, `dusk`, `ocean`, `forest`, `blueprint`, `sunrise`, `paper`, any other theme name, or a theme object."
        },
        {
          "name": "mode",
          "type": "\"light\" | \"dark\" | \"auto\" | \"system\"",
          "default": "\"light\"",
          "description": "Colour mode of the poster and everything in it. A poster paints its own ground and does not follow the page unless you set `auto`. Only `paper` has a different dark variant."
        },
        {
          "name": "download",
          "type": "boolean",
          "default": "false",
          "description": "Show a \"Download PNG\" control under the poster that saves the whole poster as one image."
        },
        {
          "name": "maxWidth",
          "type": "number | string",
          "default": "1400",
          "description": "Widest the poster is drawn. It is centred in wider containers."
        },
        {
          "name": "linked",
          "type": "boolean",
          "default": "false",
          "description": "Make the marks clickable. Clicking a mark in one graphic highlights the same name in every other graphic that has it, and filters the graphics that read the shared `data`. Click it again, or the empty ground of that graphic, to clear."
        },
        {
          "name": "sources",
          "type": "string | string[]",
          "description": "Where the data comes from, shown in the footer."
        },
        {
          "name": "note",
          "type": "string",
          "description": "Caveat shown in the footer, e.g. \"Illustrative data\"."
        },
        {
          "name": "credit",
          "type": "string",
          "description": "Who made the poster, shown at the right of the footer."
        },
        {
          "name": "level",
          "type": "1 | 2 | 3 | 4 | 5",
          "default": "2",
          "description": "Heading level of the title. Panel headings are one level down."
        }
      ],
      "example": {
        "type": "matrix-poster",
        "theme": "paper",
        "kicker": "Cities",
        "title": "A one-bedroom flat in Avenmouth rented for €1,640 a month in 2025",
        "subtitle": "Eight cities of Norland compared on rent, pay and five measures of daily life, 2025.",
        "rows": [
          {
            "city": "Avenmouth",
            "rent": 1640,
            "pay": 3900,
            "transit": 38,
            "parks": 14,
            "lanes": 212,
            "cafes": 23,
            "sun": 148
          },
          {
            "city": "Brackley Bay",
            "rent": 1480,
            "pay": 3300,
            "transit": 22,
            "parks": 9,
            "lanes": 96,
            "cafes": 17,
            "sun": 201
          },
          {
            "city": "Castren",
            "rent": 1310,
            "pay": 3600,
            "transit": 41,
            "parks": 17,
            "lanes": 184,
            "cafes": 19,
            "sun": 132
          },
          {
            "city": "Dunmore",
            "rent": 1150,
            "pay": 2900,
            "transit": 27,
            "parks": 11,
            "lanes": 120,
            "cafes": 12,
            "sun": 165
          },
          {
            "city": "Eastwick",
            "rent": 980,
            "pay": 3100,
            "transit": 33,
            "parks": 21,
            "lanes": 240,
            "cafes": 15,
            "sun": 121
          },
          {
            "city": "Fenhallow",
            "rent": 860,
            "pay": 2500,
            "transit": 18,
            "parks": 8,
            "lanes": 54,
            "cafes": 9,
            "sun": 176
          },
          {
            "city": "Greyport",
            "rent": 740,
            "pay": 2700,
            "transit": 24,
            "parks": 12,
            "lanes": 88,
            "cafes": 11,
            "sun": 190
          },
          {
            "city": "Hallin",
            "rent": 610,
            "pay": 2200,
            "transit": 12,
            "parks": 6,
            "lanes": 31,
            "cafes": 7,
            "sun": 210
          }
        ],
        "name": "city",
        "nameLabel": "City",
        "curve": {
          "left": "rent",
          "right": "pay",
          "leftLabel": "Rent, € a month",
          "rightLabel": "Median pay, € a month",
          "format": "integer",
          "title": "Median pay in Avenmouth was €3,900 a month"
        },
        "columns": [
          {
            "field": "transit",
            "label": "Trips by public transport",
            "glyph": "ring",
            "format": "{value}%"
          },
          {
            "field": "parks",
            "label": "Parks per 100,000 people",
            "glyph": "dots"
          },
          {
            "field": "lanes",
            "label": "Cycle lanes, km",
            "glyph": "bar"
          },
          {
            "field": "cafes",
            "label": "Cafés per 10,000 people",
            "glyph": "blocks"
          },
          {
            "field": "sun",
            "label": "Sunny days a year",
            "glyph": "circle"
          }
        ],
        "table": {
          "title": "Eastwick had 240 km of cycle lanes"
        },
        "sources": "Norland Statistics Office, city indicators 2025",
        "note": "Norland and its cities are invented. All figures are illustrative.",
        "credit": "InsightCharts"
      }
    },
    {
      "name": "network-poster",
      "component": "NetworkPoster",
      "title": "Network poster",
      "level": "template",
      "category": "infographic",
      "description": "A poster of who is connected to whom: a full-width arc diagram with one arc per connection, as thick as its value (`arc-diagram`), a strip of two to four large figures, and a bottom row where the same entities return as circles joined by lines (`bubble-network`) and as a ranking of centred pills (`pill-rank`).",
      "whenToUse": "Use it when the connections between 5 to about 20 named things are the subject: ferry crossings between harbours, joint papers between institutes, freight between rail yards. Give `hero` one row per connection and the `order` the entities sit in, `map` the entities with a size and optional hand-placed `x` and `y`, and `rank` one measure of each entity. With `linked`, a click on an entity in any of the three highlights it in the other two.",
      "avoid": "A flow from sources to destinations in one direction (use `flow-poster` or `sankey-chart`). More than about 20 entities or 40 connections, because the arcs cover each other. Entities with no meaningful order and few links (use `bubble-network` in a `poster`). Parts of one subject (use `radial-poster`).",
      "composes": [
        "poster",
        "arc-diagram",
        "bubble-network",
        "pill-rank"
      ],
      "props": [
        {
          "name": "hero",
          "type": "{ data, from, to, value?, order?, colorBy?, arcLabels?, orientation?, format?, highlight?, title?, ... } | Slot",
          "required": true,
          "description": "The hero: one arc per pair of connected entities, drawn by `arc-diagram`. Rows joining the same pair are summed. A full spec or element replaces it."
        },
        {
          "name": "figures",
          "type": "{ value: string; label: string; context?: string; icon?: string; tag?: string }[]",
          "description": "Two to four large figures in a strip under the hero."
        },
        {
          "name": "map",
          "type": "{ data, name, value, links?, x?, y?, layout?, tag?, highlight?, format?, title?, ... } | Slot",
          "description": "Bottom left: the entities as circles sized by `value` and joined by `links: { from, to, value? }[]` (`bubble-network`). `x` and `y` (0 to 100) place the circles by hand."
        },
        {
          "name": "rank",
          "type": "{ data, name, value, highlight?, compare?, format?, title?, ... } | Slot",
          "description": "Bottom right: the entities ranked by one measure as centred pills (`pill-rank`). With only one of `map` and `rank`, that one takes the full width."
        },
        {
          "name": "title",
          "type": "string",
          "required": true,
          "description": "The poster's one fact, as a plain sentence with its number."
        },
        {
          "name": "kicker",
          "type": "string",
          "description": "Small label above the title: the topic."
        },
        {
          "name": "subtitle",
          "type": "string",
          "description": "One line under the title: the measure, population and period."
        },
        {
          "name": "theme",
          "type": "PosterThemeName | string | ThemeInput",
          "default": "\"dusk\"",
          "description": "Poster theme: `noir`, `magenta`, `dusk`, `ocean`, `forest`, `blueprint`, `sunrise`, `paper`, any other theme name, or a theme object."
        },
        {
          "name": "mode",
          "type": "\"light\" | \"dark\" | \"auto\" | \"system\"",
          "default": "\"light\"",
          "description": "Colour mode of the poster and everything in it. A poster paints its own ground and does not follow the page unless you set `auto`. Only `paper` has a different dark variant."
        },
        {
          "name": "download",
          "type": "boolean",
          "default": "false",
          "description": "Show a \"Download PNG\" control under the poster that saves the whole poster as one image."
        },
        {
          "name": "maxWidth",
          "type": "number | string",
          "default": "1100",
          "description": "Widest the poster is drawn. It is centred in wider containers."
        },
        {
          "name": "linked",
          "type": "boolean",
          "default": "false",
          "description": "Make the marks clickable. Clicking a mark in one graphic highlights the same name in every other graphic that has it, and filters the graphics that read the shared `data`. Click it again, or the empty ground of that graphic, to clear."
        },
        {
          "name": "data",
          "type": "DataInput",
          "description": "Rows shared by every region whose config leaves out its own `data`. With `linked`, a click filters those regions."
        },
        {
          "name": "sources",
          "type": "string | string[]",
          "description": "Where the data comes from, shown in the footer."
        },
        {
          "name": "note",
          "type": "string",
          "description": "Caveat shown in the footer, e.g. \"Illustrative data\"."
        },
        {
          "name": "credit",
          "type": "string",
          "description": "Who made the poster, shown at the right of the footer."
        },
        {
          "name": "level",
          "type": "1 | 2 | 3 | 4 | 5",
          "default": "2",
          "description": "Heading level of the title. Panel headings are one level down."
        }
      ],
      "example": {
        "type": "network-poster",
        "theme": "dusk",
        "linked": true,
        "kicker": "Ferries",
        "title": "Skerry Isles ferries carried 1.66 million passengers on 12 routes in 2025",
        "subtitle": "Passengers carried between pairs of harbours, thousands, both directions, 2025.",
        "hero": {
          "data": [
            {
              "a": "Port Aven",
              "b": "Brae",
              "k": 412
            },
            {
              "a": "Port Aven",
              "b": "Storholm",
              "k": 268
            },
            {
              "a": "Port Aven",
              "b": "Little Skerry",
              "k": 96
            },
            {
              "a": "Brae",
              "b": "Storholm",
              "k": 187
            },
            {
              "a": "Brae",
              "b": "Tarn Head",
              "k": 74
            },
            {
              "a": "Storholm",
              "b": "Tarn Head",
              "k": 153
            },
            {
              "a": "Storholm",
              "b": "Ness",
              "k": 121
            },
            {
              "a": "Tarn Head",
              "b": "Ness",
              "k": 88
            },
            {
              "a": "Ness",
              "b": "Outer Holm",
              "k": 43
            },
            {
              "a": "Little Skerry",
              "b": "Brae",
              "k": 52
            },
            {
              "a": "Port Aven",
              "b": "Ness",
              "k": 139
            },
            {
              "a": "Storholm",
              "b": "Outer Holm",
              "k": 31
            }
          ],
          "from": "a",
          "to": "b",
          "value": "k",
          "order": [
            "Port Aven",
            "Little Skerry",
            "Brae",
            "Storholm",
            "Tarn Head",
            "Ness",
            "Outer Holm"
          ],
          "colorBy": "series",
          "format": "{value}k",
          "title": "412,000 passengers crossed between Port Aven and Brae"
        },
        "figures": [
          {
            "value": "412k",
            "label": "passengers on the busiest route",
            "context": "Port Aven to Brae"
          },
          {
            "value": "31k",
            "label": "passengers on the quietest route",
            "context": "Storholm to Outer Holm"
          },
          {
            "value": "4",
            "label": "of the 12 routes call at Port Aven"
          }
        ],
        "map": {
          "data": [
            {
              "harbour": "Port Aven",
              "k": 915,
              "x": 12,
              "y": 62
            },
            {
              "harbour": "Little Skerry",
              "k": 148,
              "x": 22,
              "y": 18
            },
            {
              "harbour": "Brae",
              "k": 725,
              "x": 38,
              "y": 40
            },
            {
              "harbour": "Storholm",
              "k": 760,
              "x": 56,
              "y": 80
            },
            {
              "harbour": "Tarn Head",
              "k": 315,
              "x": 66,
              "y": 22
            },
            {
              "harbour": "Ness",
              "k": 391,
              "x": 84,
              "y": 46
            },
            {
              "harbour": "Outer Holm",
              "k": 74,
              "x": 93,
              "y": 82
            }
          ],
          "name": "harbour",
          "value": "k",
          "x": "x",
          "y": "y",
          "links": [
            {
              "from": "Port Aven",
              "to": "Brae"
            },
            {
              "from": "Port Aven",
              "to": "Storholm"
            },
            {
              "from": "Port Aven",
              "to": "Little Skerry"
            },
            {
              "from": "Brae",
              "to": "Storholm"
            },
            {
              "from": "Brae",
              "to": "Tarn Head"
            },
            {
              "from": "Storholm",
              "to": "Tarn Head"
            },
            {
              "from": "Storholm",
              "to": "Ness"
            },
            {
              "from": "Tarn Head",
              "to": "Ness"
            },
            {
              "from": "Ness",
              "to": "Outer Holm"
            },
            {
              "from": "Little Skerry",
              "to": "Brae"
            },
            {
              "from": "Port Aven",
              "to": "Ness"
            },
            {
              "from": "Storholm",
              "to": "Outer Holm"
            }
          ],
          "format": "{value}k",
          "highlight": "Port Aven",
          "tag": "Busiest harbour",
          "title": "915,000 passengers used Port Aven",
          "subtitle": "Passengers through each harbour, thousands. Positions are approximate."
        },
        "rank": {
          "data": [
            {
              "harbour": "Port Aven",
              "sailings": 46
            },
            {
              "harbour": "Storholm",
              "sailings": 38
            },
            {
              "harbour": "Brae",
              "sailings": 33
            },
            {
              "harbour": "Ness",
              "sailings": 21
            },
            {
              "harbour": "Tarn Head",
              "sailings": 16
            },
            {
              "harbour": "Little Skerry",
              "sailings": 8
            },
            {
              "harbour": "Outer Holm",
              "sailings": 4
            }
          ],
          "name": "harbour",
          "value": "sailings",
          "title": "Port Aven had 46 sailings a day",
          "subtitle": "Scheduled departures per day, summer 2025."
        },
        "sources": "Skerry Isles Ferries, annual traffic return 2025",
        "note": "The Skerry Isles are invented. All figures are illustrative.",
        "credit": "InsightCharts"
      }
    },
    {
      "name": "profile-poster",
      "component": "ProfilePoster",
      "title": "Profile poster",
      "level": "template",
      "category": "infographic",
      "description": "A poster that describes one subject from four sides: the subject as one large silhouette filled to a share (`shape-fill`), two to four large figures, its place among other things on one scale (`scale-ladder`), and a table that sets it beside its peers with every number drawn as a small form (`glyph-table`).",
      "whenToUse": "Use it for a fact sheet about one named thing: a vehicle, a reservoir, a building, a machine, a species. Give `portrait` one row with the share that defines the subject and the `icon` that stands for it, `figures` the numbers a reader should take away, `scale` the subject among 4 to 10 reference points, and `table` the subject beside 2 to 6 peers. Highlight the subject in `scale` and `table`. On a sheet at least 960px wide the silhouette, the figures and the scale stand in three columns.",
      "avoid": "Several subjects of equal weight (use `glyph-table` in a `poster`, or `ranking-poster`). A subject whose parts are the point (use `radial-poster`). A subject with no defining share: leave `portrait` to a full spec such as `stat-circles`, or use `scale-poster` when the scale is the whole subject. Change over time (use `timeline-poster`).",
      "composes": [
        "poster",
        "shape-fill",
        "scale-ladder",
        "glyph-table"
      ],
      "props": [
        {
          "name": "portrait",
          "type": "{ data, name, value, icon, max?, format?, title?, ... } | Slot",
          "required": true,
          "description": "The portrait: one silhouette filled from the bottom to the share in its row, with the number beside it, drawn by `shape-fill`. `format` defaults to `\"{value}%\"`. A full spec or element replaces it."
        },
        {
          "name": "figures",
          "type": "{ value: string; label: string; context?: string; icon?: string; tag?: string }[]",
          "description": "Two to four large figures about the subject. In the three-column layout they stand between the silhouette and the scale; otherwise they run in a strip."
        },
        {
          "name": "scale",
          "type": "{ data, name, value, iconField?, direction?, scale?, min?, max?, zones?, scaleLabel?, highlight?, format?, title?, ... } | Slot",
          "description": "The subject among 4 to 10 other things on one scale (`scale-ladder`). It is drawn as tall as the silhouette."
        },
        {
          "name": "table",
          "type": "{ rows, name, columns, nameLabel?, highlight?, order?, title?, ... } | Slot",
          "description": "The subject beside its peers, one row each, with a glyph per measure (`glyph-table`). `columns` is `{ field, label?, glyph?, unit?, format?, max?, icon? }[]`."
        },
        {
          "name": "title",
          "type": "string",
          "required": true,
          "description": "The poster's one fact, as a plain sentence with its number."
        },
        {
          "name": "kicker",
          "type": "string",
          "description": "Small label above the title: the topic."
        },
        {
          "name": "subtitle",
          "type": "string",
          "description": "One line under the title: the measure, population and period."
        },
        {
          "name": "theme",
          "type": "PosterThemeName | string | ThemeInput",
          "default": "\"blueprint\"",
          "description": "Poster theme: `noir`, `magenta`, `dusk`, `ocean`, `forest`, `blueprint`, `sunrise`, `paper`, any other theme name, or a theme object."
        },
        {
          "name": "mode",
          "type": "\"light\" | \"dark\" | \"auto\" | \"system\"",
          "default": "\"light\"",
          "description": "Colour mode of the poster and everything in it. A poster paints its own ground and does not follow the page unless you set `auto`. Only `paper` has a different dark variant."
        },
        {
          "name": "download",
          "type": "boolean",
          "default": "false",
          "description": "Show a \"Download PNG\" control under the poster that saves the whole poster as one image."
        },
        {
          "name": "maxWidth",
          "type": "number | string",
          "default": "1100",
          "description": "Widest the poster is drawn. It is centred in wider containers."
        },
        {
          "name": "linked",
          "type": "boolean",
          "default": "false",
          "description": "Make the marks clickable. Clicking a mark in one graphic highlights the same name in every other graphic that has it, and filters the graphics that read the shared `data`. Click it again, or the empty ground of that graphic, to clear."
        },
        {
          "name": "data",
          "type": "DataInput",
          "description": "Rows shared by every region whose config leaves out its own `data`. With `linked`, a click filters those regions."
        },
        {
          "name": "sources",
          "type": "string | string[]",
          "description": "Where the data comes from, shown in the footer."
        },
        {
          "name": "note",
          "type": "string",
          "description": "Caveat shown in the footer, e.g. \"Illustrative data\"."
        },
        {
          "name": "credit",
          "type": "string",
          "description": "Who made the poster, shown at the right of the footer."
        },
        {
          "name": "level",
          "type": "1 | 2 | 3 | 4 | 5",
          "default": "2",
          "description": "Heading level of the title. Panel headings are one level down."
        }
      ],
      "example": {
        "type": "profile-poster",
        "theme": "blueprint",
        "kicker": "Public transport",
        "title": "The Line 9 electric bus ended a working day with 34% of its charge",
        "subtitle": "One 12-metre battery bus on Marlow's Line 9, average of 60 working days in 2025.",
        "portrait": {
          "data": [
            {
              "state": "Charge left at 23:00",
              "charge": 34
            }
          ],
          "name": "state",
          "value": "charge",
          "icon": "bus",
          "title": "Charge left at the end of the day"
        },
        "figures": [
          {
            "value": "204 km",
            "label": "driven on a working day"
          },
          {
            "value": "310 km",
            "label": "of range on a full charge"
          },
          {
            "value": "4 h",
            "label": "to recharge overnight at the depot"
          }
        ],
        "scale": {
          "data": [
            {
              "vehicle": "Electric scooter",
              "km": 35,
              "icon": "bolt"
            },
            {
              "vehicle": "Electric bike",
              "km": 70,
              "icon": "bike"
            },
            {
              "vehicle": "Delivery van",
              "km": 210,
              "icon": "truck"
            },
            {
              "vehicle": "Line 9 bus",
              "km": 310,
              "icon": "bus"
            },
            {
              "vehicle": "Family car",
              "km": 420,
              "icon": "car"
            },
            {
              "vehicle": "Long-haul truck",
              "km": 520,
              "icon": "truck"
            }
          ],
          "name": "vehicle",
          "value": "km",
          "iconField": "icon",
          "min": 0,
          "max": 600,
          "format": "{value} km",
          "scaleLabel": "Range, km",
          "highlight": "Line 9 bus",
          "title": "Range of six electric vehicles"
        },
        "table": {
          "rows": [
            {
              "vehicle": "Line 9 bus",
              "seats": 41,
              "cost": 0.21,
              "co2": 14,
              "noise": 62
            },
            {
              "vehicle": "Diesel bus",
              "seats": 43,
              "cost": 0.58,
              "co2": 71,
              "noise": 78
            },
            {
              "vehicle": "Tram",
              "seats": 64,
              "cost": 0.17,
              "co2": 9,
              "noise": 70
            }
          ],
          "name": "vehicle",
          "nameLabel": "Vehicle",
          "highlight": "Line 9 bus",
          "title": "The Line 9 bus emitted 14 g of CO2 per passenger-kilometre",
          "columns": [
            {
              "field": "seats",
              "label": "Seats",
              "glyph": "icons",
              "icon": "person",
              "unit": 5
            },
            {
              "field": "cost",
              "label": "Energy cost per km",
              "glyph": "bar",
              "format": "€{value}",
              "max": 0.6
            },
            {
              "field": "co2",
              "label": "CO2 per passenger-km",
              "glyph": "circle",
              "format": "{value} g"
            },
            {
              "field": "noise",
              "label": "Noise at 10 m",
              "glyph": "bar",
              "format": "{value} dB",
              "max": 80
            }
          ]
        },
        "sources": "Marlow Transit depot telemetry, 2025",
        "note": "Marlow Transit and the Line 9 bus are invented. All figures are illustrative.",
        "credit": "InsightCharts"
      }
    },
    {
      "name": "radial-poster",
      "component": "RadialPoster",
      "title": "Radial poster",
      "level": "template",
      "category": "infographic",
      "description": "A poster with one subject in the middle of the sheet: a wheel of spokes round an icon (`radial-spokes`), two to four large figures standing either side of it, and a bottom row with the same subject's surroundings on concentric rings (`orbit-chart`) and a ranking as centred pills (`pill-rank`).",
      "whenToUse": "Use it when the poster is about the 4 to 12 parts, causes, effects or scores of one thing: what a household's water goes on, the species one tree supports, where one home's energy goes. Give `hero` the spokes and an `icon` for the centre, state the total in the title, and use `figures` for the two to four numbers a reader should take away.",
      "avoid": "Parts of a whole that must be compared precisely (use `bar-chart` in a `poster`). Positions on one scale (use `scale-poster`). Change over time (use `timeline-poster`). Connections between many things (use `network-poster`). Large counts shown as crowds (use `count-poster`).",
      "composes": [
        "poster",
        "radial-spokes",
        "orbit-chart",
        "pill-rank"
      ],
      "props": [
        {
          "name": "hero",
          "type": "{ data, name, value, icon?, centerLabel?, shape?, sweep?, max?, highlight?, format?, title?, ... } | Slot",
          "required": true,
          "description": "The hero: one spoke per row round a centre disc, drawn by `radial-spokes`. A full spec or element replaces it."
        },
        {
          "name": "figures",
          "type": "{ value: string; label: string; context?: string; icon?: string; tag?: string }[]",
          "description": "Two to four large figures. On a sheet at least 960px wide they stand in two columns either side of the hero; on a narrower one they run in a row under it."
        },
        {
          "name": "rings",
          "type": "{ data, name, ring, value?, rings?, ringLabel?, sizeLabel?, icon?, centerLabel?, title?, ... } | Slot",
          "description": "Bottom left: named things on concentric rings, one ring per category, level or period (`orbit-chart`)."
        },
        {
          "name": "rank",
          "type": "{ data, name, value, highlight?, compare?, format?, title?, ... } | Slot",
          "description": "Bottom right: a ranking as centred pills (`pill-rank`). With only one of `rings` and `rank`, that one takes the full width."
        },
        {
          "name": "title",
          "type": "string",
          "required": true,
          "description": "The poster's one fact, as a plain sentence with its number."
        },
        {
          "name": "kicker",
          "type": "string",
          "description": "Small label above the title: the topic."
        },
        {
          "name": "subtitle",
          "type": "string",
          "description": "One line under the title: the measure, population and period."
        },
        {
          "name": "theme",
          "type": "PosterThemeName | string | ThemeInput",
          "default": "\"magenta\"",
          "description": "Poster theme: `noir`, `magenta`, `dusk`, `ocean`, `forest`, `blueprint`, `sunrise`, `paper`, any other theme name, or a theme object."
        },
        {
          "name": "mode",
          "type": "\"light\" | \"dark\" | \"auto\" | \"system\"",
          "default": "\"light\"",
          "description": "Colour mode of the poster and everything in it. A poster paints its own ground and does not follow the page unless you set `auto`. Only `paper` has a different dark variant."
        },
        {
          "name": "download",
          "type": "boolean",
          "default": "false",
          "description": "Show a \"Download PNG\" control under the poster that saves the whole poster as one image."
        },
        {
          "name": "maxWidth",
          "type": "number | string",
          "default": "1100",
          "description": "Widest the poster is drawn. It is centred in wider containers."
        },
        {
          "name": "linked",
          "type": "boolean",
          "default": "false",
          "description": "Make the marks clickable. Clicking a mark in one graphic highlights the same name in every other graphic that has it, and filters the graphics that read the shared `data`. Click it again, or the empty ground of that graphic, to clear."
        },
        {
          "name": "data",
          "type": "DataInput",
          "description": "Rows shared by every region whose config leaves out its own `data`. With `linked`, a click filters those regions."
        },
        {
          "name": "sources",
          "type": "string | string[]",
          "description": "Where the data comes from, shown in the footer."
        },
        {
          "name": "note",
          "type": "string",
          "description": "Caveat shown in the footer, e.g. \"Illustrative data\"."
        },
        {
          "name": "credit",
          "type": "string",
          "description": "Who made the poster, shown at the right of the footer."
        },
        {
          "name": "level",
          "type": "1 | 2 | 3 | 4 | 5",
          "default": "2",
          "description": "Heading level of the title. Panel headings are one level down."
        }
      ],
      "example": {
        "type": "radial-poster",
        "theme": "magenta",
        "kicker": "Water",
        "title": "A person on Verne Street used 142 litres of water a day in 2025",
        "subtitle": "Litres of mains water per person per day by use, 212 metered homes, 2025.",
        "hero": {
          "data": [
            {
              "use": "Shower",
              "litres": 44
            },
            {
              "use": "Toilet",
              "litres": 31
            },
            {
              "use": "Washing machine",
              "litres": 19
            },
            {
              "use": "Kitchen tap",
              "litres": 17
            },
            {
              "use": "Garden",
              "litres": 12
            },
            {
              "use": "Dishwasher",
              "litres": 9
            },
            {
              "use": "Bath",
              "litres": 6
            },
            {
              "use": "Other",
              "litres": 4
            }
          ],
          "name": "use",
          "value": "litres",
          "icon": "drop",
          "format": "{value} L",
          "highlight": "Shower"
        },
        "figures": [
          {
            "value": "44 L",
            "label": "went on showers",
            "context": "31% of the daily total"
          },
          {
            "value": "9 min",
            "label": "was the average length of a shower"
          },
          {
            "value": "6 L",
            "label": "went on baths"
          },
          {
            "value": "€1.12",
            "label": "was the cost of one person's water for a day"
          }
        ],
        "rings": {
          "data": [
            {
              "fixture": "Bath",
              "room": "Bathroom",
              "litres": 80
            },
            {
              "fixture": "Shower, 8 min",
              "room": "Bathroom",
              "litres": 64
            },
            {
              "fixture": "Toilet flush",
              "room": "Bathroom",
              "litres": 6
            },
            {
              "fixture": "Washing machine",
              "room": "Kitchen",
              "litres": 50
            },
            {
              "fixture": "Dishwasher",
              "room": "Kitchen",
              "litres": 12
            },
            {
              "fixture": "Washing-up bowl",
              "room": "Kitchen",
              "litres": 8
            },
            {
              "fixture": "Hose, 10 min",
              "room": "Garden",
              "litres": 150
            },
            {
              "fixture": "Watering can",
              "room": "Garden",
              "litres": 10
            }
          ],
          "name": "fixture",
          "ring": "room",
          "value": "litres",
          "format": "{value} L",
          "icon": "house",
          "ringLabel": "One ring per room",
          "sizeLabel": "litres per use",
          "highlight": "Hose, 10 min",
          "title": "Ten minutes with a hose used 150 litres",
          "height": 460
        },
        "rank": {
          "data": [
            {
              "district": "Harbour",
              "litres": 168
            },
            {
              "district": "Old Town",
              "litres": 155
            },
            {
              "district": "Verne Street",
              "litres": 142
            },
            {
              "district": "Northgate",
              "litres": 136
            },
            {
              "district": "Riverside",
              "litres": 129
            },
            {
              "district": "Mill Lane",
              "litres": 117
            }
          ],
          "name": "district",
          "value": "litres",
          "format": "{value} L",
          "highlight": "Verne Street",
          "title": "Verne Street ranked third of six districts"
        },
        "sources": "Verne Street metering study 2025",
        "note": "Verne Street and its figures are invented. All figures are illustrative.",
        "credit": "InsightCharts"
      }
    },
    {
      "name": "ranking-poster",
      "component": "RankingPoster",
      "title": "Ranking poster",
      "level": "template",
      "category": "infographic",
      "description": "A poster of one subject by the numbers, in four rows: two figures in large type or as filled silhouettes, two treemaps side by side, a full-width ranking of centred pills with one row highlighted, and packed circles with icons beside a column of sized circles and a closing figure.",
      "whenToUse": "Use it for a profile of one event, place or organisation that has a headline total, two ways to split it, a ranking of 6 to 12 named things and a few supporting magnitudes: a festival, a race, a nature reserve, a library service. Put the total in the title and repeat it as the first figure. Highlight the ranking row the heading is about.",
      "avoid": "A subject whose point is a count of people or things (use `count-poster`). Several entities compared on the same measures (use `matrix-poster`). Eight or more small graphics on one subject (use `atlas-poster`). A ranking alone (use `pill-rank` in a `poster`).",
      "composes": [
        "poster",
        "shape-fill",
        "treemap",
        "pill-rank",
        "circle-pack",
        "stat-circles"
      ],
      "props": [
        {
          "name": "figures",
          "type": "({ value, label, context?, icon? } | { data, name, value, icon, title?, ...shape-fill props } | Slot)[]",
          "description": "Top row, up to two. A statement `{ value, label }` is set in large light type (the first in the accent colour); a config with `data` draws silhouettes filled to their level (`shape-fill`)."
        },
        {
          "name": "breakdowns",
          "type": "({ data, name, value, title?, ...treemap props } | Slot)[]",
          "description": "Second row, up to two: the subject's total split two ways, as treemaps side by side."
        },
        {
          "name": "ranking",
          "type": "{ data, name, value, highlight?, compare?, compareLabel?, title?, ...pill-rank props } | Slot",
          "required": true,
          "description": "Third row, full width: a ranking as centred pills with the value at the left and the rank at the right (`pill-rank`). `highlight` names the row drawn in the accent."
        },
        {
          "name": "pack",
          "type": "{ data, name, value, icon?, group?, title?, ...circle-pack props } | Slot",
          "description": "Bottom left: parts as packed circles (`circle-pack`). `icon` is one icon name or a map from circle name to icon."
        },
        {
          "name": "circles",
          "type": "{ data, name, value, unit?, title?, ...stat-circles props } | Slot",
          "description": "Bottom right: two to four related magnitudes as circles standing on one baseline (`stat-circles`)."
        },
        {
          "name": "figure",
          "type": "{ value, label, context?, icon? }",
          "description": "Bottom right, under the circles: one closing figure in large type."
        },
        {
          "name": "format",
          "type": "FormatSpec",
          "description": "How to print values in the regions that set no `format` of their own."
        },
        {
          "name": "title",
          "type": "string",
          "required": true,
          "description": "The poster's one fact, as a plain sentence with its number."
        },
        {
          "name": "kicker",
          "type": "string",
          "description": "Small label above the title: the topic."
        },
        {
          "name": "subtitle",
          "type": "string",
          "description": "One line under the title: the measure, population and period."
        },
        {
          "name": "theme",
          "type": "PosterThemeName | string | ThemeInput",
          "default": "\"dusk\"",
          "description": "Poster theme: `noir`, `magenta`, `dusk`, `ocean`, `forest`, `blueprint`, `sunrise`, `paper`, any other theme name, or a theme object."
        },
        {
          "name": "mode",
          "type": "\"light\" | \"dark\" | \"auto\" | \"system\"",
          "default": "\"light\"",
          "description": "Colour mode of the poster and everything in it. A poster paints its own ground and does not follow the page unless you set `auto`. Only `paper` has a different dark variant."
        },
        {
          "name": "download",
          "type": "boolean",
          "default": "false",
          "description": "Show a \"Download PNG\" control under the poster that saves the whole poster as one image."
        },
        {
          "name": "maxWidth",
          "type": "number | string",
          "default": "1100",
          "description": "Widest the poster is drawn. It is centred in wider containers."
        },
        {
          "name": "linked",
          "type": "boolean",
          "default": "false",
          "description": "Make the marks clickable. Clicking a mark in one graphic highlights the same name in every other graphic that has it, and filters the graphics that read the shared `data`. Click it again, or the empty ground of that graphic, to clear."
        },
        {
          "name": "data",
          "type": "DataInput",
          "description": "Rows shared by every region whose config leaves out its own `data`. With `linked`, a click filters those regions."
        },
        {
          "name": "sources",
          "type": "string | string[]",
          "description": "Where the data comes from, shown in the footer."
        },
        {
          "name": "note",
          "type": "string",
          "description": "Caveat shown in the footer, e.g. \"Illustrative data\"."
        },
        {
          "name": "credit",
          "type": "string",
          "description": "Who made the poster, shown at the right of the footer."
        },
        {
          "name": "level",
          "type": "1 | 2 | 3 | 4 | 5",
          "default": "2",
          "description": "Heading level of the title. Panel headings are one level down."
        }
      ],
      "example": {
        "type": "ranking-poster",
        "theme": "dusk",
        "kicker": "Music",
        "title": "The Solvik Festival sold 184,000 tickets in 2025",
        "subtitle": "Tickets, crowds and spending at a three-day music festival, 11 to 13 July 2025.",
        "figures": [
          {
            "value": "184,000",
            "label": "Tickets sold in 2025",
            "context": "171,000 in 2024"
          },
          {
            "data": [
              {
                "day": "Friday",
                "share": 84
              },
              {
                "day": "Saturday",
                "share": 100
              },
              {
                "day": "Sunday",
                "share": 91
              }
            ],
            "name": "day",
            "value": "share",
            "icon": "group",
            "title": "Saturday sold 100% of its 64,000 places"
          }
        ],
        "breakdowns": [
          {
            "data": [
              {
                "ticket": "Weekend pass",
                "k": 92
              },
              {
                "ticket": "Day ticket",
                "k": 61
              },
              {
                "ticket": "Camping pass",
                "k": 23
              },
              {
                "ticket": "VIP pass",
                "k": 8
              }
            ],
            "name": "ticket",
            "value": "k",
            "format": "{value}k",
            "title": "92,000 tickets were weekend passes"
          },
          {
            "data": [
              {
                "home": "Solvik region",
                "k": 71
              },
              {
                "home": "Rest of the country",
                "k": 64
              },
              {
                "home": "Neighbouring countries",
                "k": 37
              },
              {
                "home": "Further away",
                "k": 12
              }
            ],
            "name": "home",
            "value": "k",
            "format": "{value}k",
            "title": "71,000 ticket holders lived in the Solvik region"
          }
        ],
        "ranking": {
          "data": [
            {
              "act": "Halden Lights",
              "crowd": 61000
            },
            {
              "act": "Mirra Sol",
              "crowd": 58500
            },
            {
              "act": "The Quiet Engines",
              "crowd": 52000
            },
            {
              "act": "Ada Vance",
              "crowd": 47500
            },
            {
              "act": "Northfold",
              "crowd": 41000
            },
            {
              "act": "Juno Marsh",
              "crowd": 36500
            },
            {
              "act": "Saltwater Choir",
              "crowd": 31000
            },
            {
              "act": "Okapi Club",
              "crowd": 26500
            },
            {
              "act": "Tess Arden",
              "crowd": 22000
            },
            {
              "act": "Low Tide Radio",
              "crowd": 17500
            }
          ],
          "name": "act",
          "value": "crowd",
          "format": "integer",
          "highlight": "Halden Lights",
          "title": "Halden Lights played to 61,000 people on Saturday",
          "subtitle": "Largest crowd at each of the ten most-attended acts"
        },
        "pack": {
          "data": [
            {
              "item": "Food stalls",
              "k": 3400
            },
            {
              "item": "Beer",
              "k": 2900
            },
            {
              "item": "Merchandise",
              "k": 2300
            },
            {
              "item": "Wine and cocktails",
              "k": 1600
            },
            {
              "item": "Soft drinks",
              "k": 1100
            },
            {
              "item": "Camping shop",
              "k": 900
            },
            {
              "item": "Coffee",
              "k": 700
            },
            {
              "item": "Lockers and charging",
              "k": 500
            },
            {
              "item": "Water",
              "k": 400
            },
            {
              "item": "Ice cream",
              "k": 400
            }
          ],
          "name": "item",
          "value": "k",
          "format": "€{value}k",
          "icon": {
            "Food stalls": "fork-knife",
            "Beer": "cup",
            "Merchandise": "bag",
            "Wine and cocktails": "bottle",
            "Soft drinks": "bottle",
            "Camping shop": "cart",
            "Coffee": "cup"
          },
          "title": "Visitors spent €3.4 million at food stalls",
          "subtitle": "Spending on site by category, thousand euros"
        },
        "circles": {
          "data": [
            {
              "year": "per visitor in 2019",
              "kg": 2.6
            },
            {
              "year": "per visitor in 2022",
              "kg": 1.9
            },
            {
              "year": "per visitor in 2025",
              "kg": 1.2
            }
          ],
          "name": "year",
          "value": "kg",
          "unit": "kg",
          "title": "Each visitor left 1.2 kg of waste in 2025"
        },
        "figure": {
          "value": "€14.2m",
          "label": "Spent on site by visitors in 2025",
          "context": "€77 for each ticket sold"
        },
        "sources": "Solvik Festival annual report 2025",
        "note": "The Solvik Festival and its acts are invented. All figures are illustrative.",
        "credit": "InsightCharts"
      }
    },
    {
      "name": "report-page",
      "component": "ReportPage",
      "title": "Report page",
      "level": "template",
      "category": "layout",
      "description": "A section of an analytical report laid out as a document. It has a title block, a summary, numbered findings, then method, limitations and sources. Each finding has one exhibit, an observation and a separately labelled interpretation with a confidence rating and a caveat.",
      "whenToUse": "Use it for readers who will check the reasoning, as in evaluations, research notes, audits and experiment write-ups. State the main finding with its number in the title. Put all the main findings and the recommendation in the summary. Write each finding title as one fact with its number, and give it an exhibit that shows that fact. Put what the data shows in `observation` and what you infer from it in `interpretation`. End with method, limitations and sources.",
      "avoid": "A decision-maker who wants only the result (use `executive-brief`). A general audience (use `explainer` or `visual-essay`). Findings without an exhibit. Put those in the summary.",
      "composes": [
        "headline",
        "prose",
        "source-note",
        "bar-chart",
        "progress-meter",
        "line-chart"
      ],
      "props": [
        {
          "name": "title",
          "type": "string",
          "required": true,
          "description": "The report's main finding, as one plain sentence with the number in it."
        },
        {
          "name": "kicker",
          "type": "string",
          "description": "The report series or section, e.g. \"Evaluation report · Section 3\"."
        },
        {
          "name": "deck",
          "type": "string",
          "description": "One-sentence standfirst."
        },
        {
          "name": "byline",
          "type": "string",
          "description": "Author line."
        },
        {
          "name": "date",
          "type": "string",
          "description": "Publication date."
        },
        {
          "name": "summary",
          "type": "string | string[]",
          "description": "One paragraph with the main findings, their numbers and the recommendation. Supports `**bold**`."
        },
        {
          "name": "summaryTitle",
          "type": "string",
          "default": "\"Summary\"",
          "description": "Heading of the summary."
        },
        {
          "name": "findings",
          "type": "{ title, exhibit?, observation?, interpretation?, confidence?: \"high\" | \"medium\" | \"low\", caveat?, id? }[]",
          "description": "Numbered findings. `observation` is what the data shows; `interpretation` is what the authors infer, with an optional confidence rating and caveat."
        },
        {
          "name": "observationLabel",
          "type": "string",
          "default": "\"What the data shows\"",
          "description": "Label over each observation."
        },
        {
          "name": "interpretationLabel",
          "type": "string",
          "default": "\"What we think it means\"",
          "description": "Label over each interpretation."
        },
        {
          "name": "method",
          "type": "string | string[]",
          "description": "How the analysis was done, as its own section."
        },
        {
          "name": "limitations",
          "type": "string[]",
          "description": "What the analysis cannot show."
        },
        {
          "name": "sources",
          "type": "string | { name, url? } | (string | { name, url? })[]",
          "description": "Where the data comes from, shown in the closing source note. Entries with a `url` are linked."
        },
        {
          "name": "period",
          "type": "string",
          "description": "The period the data covers."
        },
        {
          "name": "note",
          "type": "string",
          "description": "Caveats the reader should know, e.g. \"Illustrative data\"."
        },
        {
          "name": "credit",
          "type": "string",
          "description": "Who made the piece."
        },
        {
          "name": "level",
          "type": "1 | 2 | 3 | 4 | 5 | 6",
          "default": "1",
          "description": "Heading level of the title; section headings follow one level down."
        },
        {
          "name": "maxWidth",
          "type": "number | string",
          "description": "Widest the piece may get. It is centred in wider containers."
        },
        {
          "name": "theme",
          "type": "ThemeInput",
          "description": "Theme override, passed on to everything inside."
        },
        {
          "name": "mode",
          "type": "\"auto\" | \"system\" | \"light\" | \"dark\"",
          "default": "\"auto\"",
          "description": "Colour mode, passed on to everything inside."
        }
      ],
      "example": {
        "type": "report-page",
        "kicker": "Small-group tutoring programme · Year 1 evaluation",
        "title": "Tutored pupils gained 2.9 more scaled-score points in maths than matched pupils",
        "deck": "The difference in reading was 0.5 points. 31% of tutored pupils were eligible for free school meals against a funding target of 50%.",
        "byline": "Evaluation team",
        "date": "2025-02-20",
        "summary": "Pupils offered tutoring gained **2.9 more scaled-score points in maths** over the year than matched pupils who were not. That is about three months of additional learning. The difference in reading was 0.5 points and could be chance. Pupils who attended fewer than 12 sessions gained less than 1 point over their matches. 58% of pupils attended 12 or more. We recommend continuing the maths strand. Attendance should be the main delivery target. The way pupils are selected should change.",
        "findings": [
          {
            "title": "Tutored pupils gained 8.1 points in maths against 5.2 for comparison pupils",
            "exhibit": {
              "type": "bar-chart",
              "data": [
                {
                  "subject": "Maths",
                  "Tutored": 8.1,
                  "Comparison": 5.2
                },
                {
                  "subject": "Reading",
                  "Tutored": 5.9,
                  "Comparison": 5.4
                }
              ],
              "x": "subject",
              "y": [
                "Tutored",
                "Comparison"
              ],
              "orientation": "horizontal",
              "sort": "none",
              "highlight": "Tutored",
              "format": "decimal:1",
              "subtitle": "Mean gain in scaled score, autumn 2023 to summer 2024",
              "source": "Standardised assessments, 42 schools"
            },
            "observation": "The difference in maths was 2.9 points (95% interval 1.6 to 4.2). In reading tutored pupils gained 5.9 points and comparison pupils 5.4. That difference was 0.5 points (−0.7 to 1.7).",
            "interpretation": "The maths difference is unlikely to be chance. Trials of similar programmes found effects of about the same size. We do not conclude that reading tutoring failed, because half the reading tutors were recruited after January.",
            "confidence": "medium",
            "caveat": "Pupils were matched on prior attainment and background. They were not randomised. Unmeasured differences, such as parental engagement, could account for part of the difference."
          },
          {
            "title": "Pupils who attended 18 or more sessions gained 4.4 points over their matches",
            "exhibit": {
              "type": "bar-chart",
              "data": [
                {
                  "sessions": "Under 6",
                  "effect": 0.2
                },
                {
                  "sessions": "6–11",
                  "effect": 0.9
                },
                {
                  "sessions": "12–17",
                  "effect": 3.1
                },
                {
                  "sessions": "18+",
                  "effect": 4.4
                }
              ],
              "x": "sessions",
              "y": "effect",
              "orientation": "vertical",
              "sort": "none",
              "format": {
                "signDisplay": "exceptZero",
                "maximumFractionDigits": 1
              },
              "height": 260,
              "subtitle": "Maths gain over comparison pupils, scaled-score points, by sessions attended (of 20 offered)"
            },
            "observation": "Pupils who attended fewer than 6 sessions gained 0.2 points more than their matches. Those who attended 6 to 11 gained 0.9 points more. 42% of tutored pupils attended fewer than 12 sessions.",
            "interpretation": "A gain that rises with attendance is consistent with a real effect. Pupils who attend more may also differ in ways that raise attainment. The effect of extra sessions is therefore probably smaller than the bars show.",
            "confidence": "low"
          },
          {
            "title": "31% of tutored pupils were eligible for free school meals against a target of 50%",
            "exhibit": {
              "type": "progress-meter",
              "label": "Tutored pupils eligible for free school meals",
              "value": 0.31,
              "max": 1,
              "showMax": false,
              "target": 0.5,
              "targetLabel": "Funding target",
              "format": "percent",
              "context": "984 of 3,180 pupils. In the same schools, 38% of all pupils are eligible."
            },
            "observation": "984 of the 3,180 tutored pupils were eligible for free school meals. The funding agreement specifies 50%. 38% of all pupils in the participating schools are eligible.",
            "interpretation": "Schools selected pupils by teacher nomination. Nomination appears to favour pupils just below the expected standard over the most disadvantaged. The schools have enough eligible pupils to meet the target, so we think the selection rules cause the shortfall.",
            "confidence": "high"
          }
        ],
        "method": "We compared 3,180 tutored pupils in 42 schools with 3,180 pupils in the same schools and year groups, matched one to one on prior attainment, free-school-meal eligibility, special educational needs and attendance in the previous year. Outcomes are scaled scores on the standardised assessments schools already use. Intervals are 95% and account for clustering by school.",
        "limitations": [
          "Matching cannot rule out differences we did not measure. A randomised design is planned for year two.",
          "Attendance was recorded by tutors and may be overstated.",
          "One year of outcomes does not show whether the gain lasts."
        ],
        "sources": [
          "School assessment returns",
          "Tutor session logs",
          "National pupil database extract"
        ],
        "period": "September 2023 to July 2024",
        "note": "Illustrative data"
      }
    },
    {
      "name": "scale-poster",
      "component": "ScalePoster",
      "title": "Scale poster",
      "level": "template",
      "category": "infographic",
      "description": "A tall poster that is one illustrated scale: a full-height `scale-ladder` with icons, leader-labelled items and named zones, and a narrow side column of large figures and one small graphic, each set level with the part of the scale it describes.",
      "whenToUse": "Use it when the positions of 6 to about 16 named things on one scale are the whole subject: depths, altitudes, temperatures, distances, ages. Give the scale's rows and zones in `scale`, up to five `figures` with the scale position each belongs to, and optionally a `side` graphic of two to four magnitudes. Beside the scale the figures line up with their positions; under 760px they move below it.",
      "avoid": "Comparing sizes rather than positions (use `ranking-poster` or `count-poster`); change over time (use `timeline-poster`); more than about 16 items, because the labels run together; several unrelated graphics (use `poster` and lay the panels out yourself).",
      "composes": [
        "poster",
        "scale-ladder",
        "stat-circles"
      ],
      "props": [
        {
          "name": "scale",
          "type": "ScaleLadderConfig | Slot",
          "required": true,
          "description": "The scale. A compact `scale-ladder` config: `{ data, name, value, iconField?, icon?, direction?, scale?, min?, max?, ticks?, zones?, scaleLabel?, format?, highlight? }`. Or a full spec, which replaces the ladder."
        },
        {
          "name": "figures",
          "type": "{ at: number; value: string; label: string; context?: string; icon?: string }[]",
          "description": "Large figures for the side column. `at` is the position on the scale the figure belongs to; beside the scale the figure is set level with it under a dotted rule."
        },
        {
          "name": "side",
          "type": "StatCirclesConfig | Slot",
          "description": "One small graphic in the side column. A compact `stat-circles` config: `{ data, name, value, unit?, format?, title?, subtitle? }`. Or a full spec."
        },
        {
          "name": "sideAt",
          "type": "number",
          "description": "Position on the scale where the `side` graphic starts. Default: under the last figure."
        },
        {
          "name": "height",
          "type": "number",
          "description": "Height of the scale in px. Default: 68px per item, at least 920 (760 when stacked)."
        },
        {
          "name": "title",
          "type": "string",
          "required": true,
          "description": "The poster's one fact, as a plain sentence with its number."
        },
        {
          "name": "kicker",
          "type": "string",
          "description": "Small label above the title: the topic."
        },
        {
          "name": "subtitle",
          "type": "string",
          "description": "One line under the title: the measure, population and period."
        },
        {
          "name": "theme",
          "type": "PosterThemeName | string | ThemeInput",
          "default": "\"ocean\"",
          "description": "Poster theme: `noir`, `magenta`, `dusk`, `ocean`, `forest`, `blueprint`, `sunrise`, `paper`, any other theme name, or a theme object."
        },
        {
          "name": "mode",
          "type": "\"light\" | \"dark\" | \"auto\" | \"system\"",
          "default": "\"light\"",
          "description": "Colour mode of the poster and everything in it. A poster paints its own ground and does not follow the page unless you set `auto`. Only `paper` has a different dark variant."
        },
        {
          "name": "download",
          "type": "boolean",
          "default": "false",
          "description": "Show a \"Download PNG\" control under the poster that saves the whole poster as one image."
        },
        {
          "name": "maxWidth",
          "type": "number | string",
          "default": "980",
          "description": "Widest the poster is drawn. It is centred in wider containers."
        },
        {
          "name": "linked",
          "type": "boolean",
          "default": "false",
          "description": "Make the marks clickable. Clicking a mark in one graphic highlights the same name in every other graphic that has it, and filters the graphics that read the shared `data`. Click it again, or the empty ground of that graphic, to clear."
        },
        {
          "name": "data",
          "type": "DataInput",
          "description": "Rows shared by every region whose config leaves out its own `data`. With `linked`, a click filters those regions."
        },
        {
          "name": "sources",
          "type": "string | string[]",
          "description": "Where the data comes from, shown in the footer."
        },
        {
          "name": "note",
          "type": "string",
          "description": "Caveat shown in the footer, e.g. \"Illustrative data\"."
        },
        {
          "name": "credit",
          "type": "string",
          "description": "Who made the poster, shown at the right of the footer."
        },
        {
          "name": "level",
          "type": "1 | 2 | 3 | 4 | 5",
          "default": "2",
          "description": "Heading level of the title. Panel headings are one level down."
        }
      ],
      "example": {
        "type": "scale-poster",
        "theme": "ocean",
        "kicker": "Oceans",
        "title": "The Meridian Deep survey logged 12 sightings between 8 m and 3,750 m",
        "subtitle": "Depth of each sighting on one research dive, metres below the surface.",
        "scale": {
          "data": [
            {
              "sighting": "Survey ship hull",
              "m": 8,
              "icon": "ship"
            },
            {
              "sighting": "Kelp canopy",
              "m": 120,
              "icon": "leaf"
            },
            {
              "sighting": "Sardine shoal",
              "m": 310,
              "icon": "fish"
            },
            {
              "sighting": "Lanternfish layer",
              "m": 560,
              "icon": "fish"
            },
            {
              "sighting": "Swordfish",
              "m": 820,
              "icon": "fish"
            },
            {
              "sighting": "Coral mound",
              "m": 1150,
              "icon": "tree"
            },
            {
              "sighting": "Sperm whale",
              "m": 1480,
              "icon": "wave"
            },
            {
              "sighting": "Anglerfish",
              "m": 1900,
              "icon": "fish"
            },
            {
              "sighting": "Sensor array",
              "m": 2300,
              "icon": "satellite"
            },
            {
              "sighting": "Dumbo octopus",
              "m": 2750,
              "icon": "star"
            },
            {
              "sighting": "Sea cucumbers",
              "m": 3200,
              "icon": "circle"
            },
            {
              "sighting": "Seabed lander",
              "m": 3750,
              "icon": "pin"
            }
          ],
          "name": "sighting",
          "value": "m",
          "iconField": "icon",
          "direction": "down",
          "min": 0,
          "max": 4000,
          "format": "{value} m",
          "scaleLabel": "Depth, metres",
          "zones": [
            {
              "from": 0,
              "to": 200,
              "label": "Sunlit"
            },
            {
              "from": 200,
              "to": 1000,
              "label": "Twilight zone"
            },
            {
              "from": 1000,
              "to": 4000,
              "label": "Dark zone"
            }
          ]
        },
        "figures": [
          {
            "at": 200,
            "value": "1%",
            "label": "of surface light is left"
          },
          {
            "at": 1000,
            "value": "4°C",
            "label": "water temperature"
          },
          {
            "at": 3000,
            "value": "376 atm",
            "label": "pressure at the lander, 3,750 m down"
          }
        ],
        "side": {
          "data": [
            {
              "zone": "species in the sunlit zone",
              "species": 148
            },
            {
              "zone": "species in the twilight zone",
              "species": 61
            },
            {
              "zone": "species in the dark zone",
              "species": 23
            }
          ],
          "name": "zone",
          "value": "species",
          "title": "Species logged",
          "height": 250
        },
        "sideAt": 1900,
        "sources": "Meridian Deep survey log",
        "note": "The Meridian Deep survey is invented. All figures are illustrative.",
        "credit": "InsightCharts"
      }
    },
    {
      "name": "scorecard",
      "component": "Scorecard",
      "title": "Scorecard",
      "level": "template",
      "category": "layout",
      "description": "A recurring report. It has a masthead with the report name and period, a headline that states the main change, a KPI grid with changes and sparklines, a ranked list beside one or two trend charts, an optional comparison table, short notes and a source note.",
      "whenToUse": "Use it for weekly or monthly numbers. Keep the layout the same in every edition so readers know where each number is. Change the headline each edition to state the main change with its number. The headline and the KPI row should be enough for a reader who stops there. The ranking, trends and notes add detail. Highlight the one row or series the headline is about and leave the rest grey.",
      "avoid": "A one-off finding (use `executive-brief`). Readers who need to filter the data themselves (use `data-explorer`). More than six KPIs.",
      "composes": [
        "headline",
        "kpi-grid",
        "ranked-list",
        "comparison-table",
        "prose",
        "source-note",
        "line-chart",
        "bar-chart"
      ],
      "props": [
        {
          "name": "title",
          "type": "string",
          "required": true,
          "description": "The main change this period, as one plain sentence with the number in it."
        },
        {
          "name": "kicker",
          "type": "string",
          "description": "The name of the report, e.g. \"Store scorecard\". Shown in the masthead."
        },
        {
          "name": "reportPeriod",
          "type": "string",
          "description": "The period reported, e.g. \"October 2024\". Shown opposite the report name."
        },
        {
          "name": "deck",
          "type": "string",
          "description": "One-sentence standfirst."
        },
        {
          "name": "byline",
          "type": "string",
          "description": "Author line."
        },
        {
          "name": "date",
          "type": "string",
          "description": "Publication date."
        },
        {
          "name": "kpis",
          "type": "{ data, x, metrics, compare?, change?, columns? }",
          "description": "The headline metrics: the props of `kpi-grid`. One time-ordered dataset, one card per metric."
        },
        {
          "name": "ranking",
          "type": "{ data, name, value, change?, highlight?, title?, ... } | Slot",
          "description": "A ranking: the props of `ranked-list`, or any spec or element."
        },
        {
          "name": "trends",
          "type": "Slot[]",
          "description": "One or two trend exhibits, shown beside the ranking in wide containers."
        },
        {
          "name": "table",
          "type": "{ data, columns, highlight?, sort?, title?, ... } | Slot",
          "description": "A comparison across entities: the props of `comparison-table`, or any spec or element."
        },
        {
          "name": "notes",
          "type": "(string | { title, text? })[]",
          "description": "Short notes on what changed in the period. Each states one fact with its number."
        },
        {
          "name": "notesTitle",
          "type": "string",
          "default": "\"Notes\"",
          "description": "Heading of the notes."
        },
        {
          "name": "sources",
          "type": "string | { name, url? } | (string | { name, url? })[]",
          "description": "Where the data comes from, shown in the closing source note. Entries with a `url` are linked."
        },
        {
          "name": "period",
          "type": "string",
          "description": "The period the data covers."
        },
        {
          "name": "method",
          "type": "string",
          "description": "How the numbers were produced: definitions, adjustments, exclusions."
        },
        {
          "name": "note",
          "type": "string",
          "description": "Caveats the reader should know, e.g. \"Illustrative data\"."
        },
        {
          "name": "credit",
          "type": "string",
          "description": "Who made the piece."
        },
        {
          "name": "level",
          "type": "1 | 2 | 3 | 4 | 5 | 6",
          "default": "1",
          "description": "Heading level of the title; section headings follow one level down."
        },
        {
          "name": "maxWidth",
          "type": "number | string",
          "description": "Widest the piece may get. It is centred in wider containers."
        },
        {
          "name": "theme",
          "type": "ThemeInput",
          "description": "Theme override, passed on to everything inside."
        },
        {
          "name": "mode",
          "type": "\"auto\" | \"system\" | \"light\" | \"dark\"",
          "default": "\"auto\"",
          "description": "Colour mode, passed on to everything inside."
        }
      ],
      "example": {
        "type": "scorecard",
        "kicker": "Online store scorecard",
        "reportPeriod": "October 2024",
        "title": "Orders rose 12% in October to 24,900",
        "deck": "The return rate was 11.4%. It was 8.4% in July, the month before free returns launched. Outerwear had the highest return rate at 19.2%.",
        "kpis": {
          "data": [
            {
              "month": "2023-11-01",
              "orders": 18200,
              "conversion": 2.61,
              "aov": 64.1,
              "returns": 8.1
            },
            {
              "month": "2023-12-01",
              "orders": 26900,
              "conversion": 3.02,
              "aov": 66.8,
              "returns": 8.4
            },
            {
              "month": "2024-01-01",
              "orders": 17100,
              "conversion": 2.55,
              "aov": 63,
              "returns": 9
            },
            {
              "month": "2024-02-01",
              "orders": 16400,
              "conversion": 2.58,
              "aov": 62.4,
              "returns": 8.2
            },
            {
              "month": "2024-03-01",
              "orders": 17900,
              "conversion": 2.66,
              "aov": 62.9,
              "returns": 8
            },
            {
              "month": "2024-04-01",
              "orders": 18800,
              "conversion": 2.71,
              "aov": 62.2,
              "returns": 7.9
            },
            {
              "month": "2024-05-01",
              "orders": 19600,
              "conversion": 2.74,
              "aov": 61.8,
              "returns": 8.3
            },
            {
              "month": "2024-06-01",
              "orders": 19900,
              "conversion": 2.78,
              "aov": 61.5,
              "returns": 8.1
            },
            {
              "month": "2024-07-01",
              "orders": 20400,
              "conversion": 2.83,
              "aov": 61.9,
              "returns": 8.4
            },
            {
              "month": "2024-08-01",
              "orders": 21300,
              "conversion": 2.9,
              "aov": 61.2,
              "returns": 9.6
            },
            {
              "month": "2024-09-01",
              "orders": 22200,
              "conversion": 2.97,
              "aov": 60.8,
              "returns": 10.5
            },
            {
              "month": "2024-10-01",
              "orders": 24900,
              "conversion": 3.1,
              "aov": 61,
              "returns": 11.4
            }
          ],
          "x": "month",
          "compare": "previous",
          "metrics": [
            {
              "y": "orders",
              "label": "Orders",
              "format": "compact",
              "deltaLabel": "vs September"
            },
            {
              "y": "conversion",
              "label": "Conversion rate",
              "format": "{value}%",
              "delta": "+0.13 pts",
              "deltaLabel": "vs September"
            },
            {
              "y": "aov",
              "label": "Average order value",
              "format": "currency:EUR",
              "deltaLabel": "vs September"
            },
            {
              "y": "returns",
              "label": "Return rate",
              "format": "{value}%",
              "delta": "+0.9 pts",
              "upIsGood": false,
              "deltaLabel": "vs September"
            }
          ]
        },
        "ranking": {
          "data": [
            {
              "category": "Outerwear",
              "revenue": 412,
              "change": 0.31,
              "returns": 19.2
            },
            {
              "category": "Footwear",
              "revenue": 371,
              "change": 0.08,
              "returns": 14.1
            },
            {
              "category": "Knitwear",
              "revenue": 298,
              "change": 0.22,
              "returns": 9.8
            },
            {
              "category": "Accessories",
              "revenue": 164,
              "change": -0.04,
              "returns": 4.2
            },
            {
              "category": "Denim",
              "revenue": 152,
              "change": -0.11,
              "returns": 12.6
            },
            {
              "category": "Home",
              "revenue": 88,
              "change": 0.02,
              "returns": 3.1
            }
          ],
          "name": "category",
          "value": "revenue",
          "change": "change",
          "format": "€{value}k",
          "highlight": "Outerwear",
          "title": "Outerwear revenue rose 31% to €412k in October",
          "subtitle": "Revenue by category, October, with change on September"
        },
        "trends": [
          {
            "type": "line-chart",
            "data": [
              {
                "month": "2023-11-01",
                "orders": 18200,
                "conversion": 2.61,
                "aov": 64.1,
                "returns": 8.1
              },
              {
                "month": "2023-12-01",
                "orders": 26900,
                "conversion": 3.02,
                "aov": 66.8,
                "returns": 8.4
              },
              {
                "month": "2024-01-01",
                "orders": 17100,
                "conversion": 2.55,
                "aov": 63,
                "returns": 9
              },
              {
                "month": "2024-02-01",
                "orders": 16400,
                "conversion": 2.58,
                "aov": 62.4,
                "returns": 8.2
              },
              {
                "month": "2024-03-01",
                "orders": 17900,
                "conversion": 2.66,
                "aov": 62.9,
                "returns": 8
              },
              {
                "month": "2024-04-01",
                "orders": 18800,
                "conversion": 2.71,
                "aov": 62.2,
                "returns": 7.9
              },
              {
                "month": "2024-05-01",
                "orders": 19600,
                "conversion": 2.74,
                "aov": 61.8,
                "returns": 8.3
              },
              {
                "month": "2024-06-01",
                "orders": 19900,
                "conversion": 2.78,
                "aov": 61.5,
                "returns": 8.1
              },
              {
                "month": "2024-07-01",
                "orders": 20400,
                "conversion": 2.83,
                "aov": 61.9,
                "returns": 8.4
              },
              {
                "month": "2024-08-01",
                "orders": 21300,
                "conversion": 2.9,
                "aov": 61.2,
                "returns": 9.6
              },
              {
                "month": "2024-09-01",
                "orders": 22200,
                "conversion": 2.97,
                "aov": 60.8,
                "returns": 10.5
              },
              {
                "month": "2024-10-01",
                "orders": 24900,
                "conversion": 3.1,
                "aov": 61,
                "returns": 11.4
              }
            ],
            "x": "month",
            "y": "returns",
            "format": "{value}%",
            "zero": true,
            "title": "The return rate rose from 8.4% in July to 11.4% in October",
            "subtitle": "Share of orders returned, by month of order",
            "annotations": [
              {
                "type": "line",
                "axis": "x",
                "value": "2024-08-01",
                "label": "Free returns"
              }
            ]
          },
          {
            "type": "bar-chart",
            "data": [
              {
                "category": "Outerwear",
                "revenue": 412,
                "change": 0.31,
                "returns": 19.2
              },
              {
                "category": "Footwear",
                "revenue": 371,
                "change": 0.08,
                "returns": 14.1
              },
              {
                "category": "Knitwear",
                "revenue": 298,
                "change": 0.22,
                "returns": 9.8
              },
              {
                "category": "Accessories",
                "revenue": 164,
                "change": -0.04,
                "returns": 4.2
              },
              {
                "category": "Denim",
                "revenue": 152,
                "change": -0.11,
                "returns": 12.6
              },
              {
                "category": "Home",
                "revenue": 88,
                "change": 0.02,
                "returns": 3.1
              }
            ],
            "x": "category",
            "y": "returns",
            "sort": "desc",
            "orientation": "horizontal",
            "format": "{value}%",
            "highlight": "Outerwear",
            "title": "19.2% of outerwear orders were returned in October",
            "subtitle": "Return rate by category, October"
          }
        ],
        "notes": [
          {
            "title": "Returns cost about €62k in October",
            "text": "About 2,840 parcels came back. Carriage and handling cost €22 a parcel. At September's return rate of 10.5% the cost would have been about €58k."
          },
          {
            "title": "62% of outerwear returns give “too small” as the reason",
            "text": "A size guide goes into test on 4 November."
          },
          {
            "title": "Orders were highest in December 2023 at 26,900",
            "text": "October 2024 was the second-highest month of the 12."
          }
        ],
        "sources": [
          "Store order database",
          "Returns portal"
        ],
        "period": "November 2023 to October 2024",
        "method": "Return rate is by month of order and is revised for 30 days as parcels arrive.",
        "note": "Illustrative data"
      }
    },
    {
      "name": "social-card",
      "component": "SocialCard",
      "title": "Social card",
      "level": "template",
      "category": "layout",
      "description": "A graphic that states one fact, at a fixed aspect ratio (1:1, 4:5, 16:9 or 9:16). It has a kicker, a headline, one exhibit, a source and a credit, painted on the theme surface. A button downloads the whole card as a PNG.",
      "whenToUse": "Use it for one finding that will be shared in a feed, a slide or a chat thread with no text around it. State the fact with its number in the headline. Use one simple exhibit, such as one highlighted bar, one line or one number. Put the source and your name on the card, because it will be shared without the page. Use 1:1 or 4:5 for feeds, 16:9 for slides and link previews, 9:16 for stories.",
      "avoid": "More than one fact or more than one exhibit (use `story-deck` for a sequence, `executive-brief` for a page). Charts that need a legend, small labels or hovering to read, because a shared image is not interactive. An exhibit with its own `title`, because the card's headline is the title.",
      "composes": [
        "bar-chart",
        "big-number",
        "waffle-chart",
        "source-note"
      ],
      "props": [
        {
          "name": "title",
          "type": "string",
          "required": true,
          "description": "The card's one fact, as a plain sentence with the number in it."
        },
        {
          "name": "kicker",
          "type": "string",
          "description": "Small label above the title."
        },
        {
          "name": "deck",
          "type": "string",
          "description": "One short line under the title: the measure, or the number in words."
        },
        {
          "name": "exhibit",
          "type": "Slot",
          "description": "The one exhibit. A chart spec is given the height left in the card (any `height` you set wins); leave out its `title`."
        },
        {
          "name": "source",
          "type": "string",
          "description": "Where the data comes from, shown as \"Source: ...\"."
        },
        {
          "name": "note",
          "type": "string",
          "description": "Caveat shown after the source, e.g. \"Illustrative data\"."
        },
        {
          "name": "credit",
          "type": "string",
          "description": "Who made it: a name, handle or publication."
        },
        {
          "name": "aspect",
          "type": "\"1:1\" | \"4:5\" | \"16:9\" | \"9:16\"",
          "default": "\"1:1\"",
          "description": "Shape of the card."
        },
        {
          "name": "tone",
          "type": "\"surface\" | \"inverse\"",
          "default": "\"surface\"",
          "description": "`inverse` paints the card in the opposite colour mode."
        },
        {
          "name": "downloadable",
          "type": "boolean",
          "default": "true",
          "description": "Show the download button under the card. The image is the whole card, drawn from the DOM onto a canvas."
        },
        {
          "name": "exportWidth",
          "type": "number",
          "default": "1080",
          "description": "Width of the downloaded PNG in pixels (1920 for 16:9)."
        },
        {
          "name": "filename",
          "type": "string",
          "description": "File name without extension. Defaults to a slug of the title."
        },
        {
          "name": "downloadLabel",
          "type": "string",
          "default": "\"Download PNG\"",
          "description": "Label of the download button."
        },
        {
          "name": "maxWidth",
          "type": "number",
          "description": "Widest the card is drawn on screen, in px. Default 540 (520 for 4:5, 720 for 16:9, 400 for 9:16). In narrower containers the whole card is scaled down, like an image, so its composition never changes."
        },
        {
          "name": "onExport",
          "type": "(blob: Blob) => void",
          "description": "Called with the PNG after a download."
        },
        {
          "name": "level",
          "type": "1 | 2 | 3 | 4 | 5 | 6",
          "default": "2",
          "description": "Heading level of the title."
        },
        {
          "name": "theme",
          "type": "ThemeInput",
          "description": "Theme override, passed on to the exhibit."
        },
        {
          "name": "mode",
          "type": "\"auto\" | \"system\" | \"light\" | \"dark\"",
          "default": "\"auto\"",
          "description": "Colour mode."
        }
      ],
      "example": {
        "type": "social-card",
        "aspect": "1:1",
        "kicker": "Climate",
        "title": "The world was 1.6°C warmer in 2024 than in 1850 to 1900",
        "exhibit": {
          "type": "bar-chart",
          "data": [
            {
              "year": "2015",
              "c": 1.17
            },
            {
              "year": "2016",
              "c": 1.31
            },
            {
              "year": "2017",
              "c": 1.22
            },
            {
              "year": "2018",
              "c": 1.14
            },
            {
              "year": "2019",
              "c": 1.26
            },
            {
              "year": "2020",
              "c": 1.31
            },
            {
              "year": "2021",
              "c": 1.14
            },
            {
              "year": "2022",
              "c": 1.18
            },
            {
              "year": "2023",
              "c": 1.48
            },
            {
              "year": "2024",
              "c": 1.6
            }
          ],
          "x": "year",
          "y": "c",
          "orientation": "vertical",
          "sort": "none",
          "highlight": "2024",
          "format": "{value}°C",
          "labels": false,
          "subtitle": "Global average temperature above the 1850 to 1900 level",
          "annotations": [
            {
              "type": "line",
              "axis": "y",
              "value": 1.5,
              "label": "Paris Agreement limit"
            }
          ]
        },
        "source": "Copernicus Climate Change Service, ERA5 (rounded)",
        "note": "Figures are approximate, for illustration",
        "credit": "InsightCharts"
      }
    },
    {
      "name": "story-deck",
      "component": "StoryDeck",
      "title": "Story deck",
      "level": "template",
      "category": "story",
      "description": "A sequence of full-frame cards (title, stat, chart, quote, closing) that the reader swipes or steps through. Each card states one fact. It has an optional fixed aspect ratio, a keyboard-accessible pager and a stacked layout for print.",
      "whenToUse": "Use it for a short sequence read on a phone, such as a “year in review”. Each card should be readable in a couple of seconds. State the main fact with its number on the title card. Put one fact on each later card, as a number or as one chart with one highlighted mark under a headline that states the fact. Use five to ten cards. End with a closing card and the sources. Charts in cards should have few data points and direct labels, with no legend.",
      "avoid": "Findings that need two exhibits in view at once (use `executive-brief`). A single shareable image (use `social-card`). Dense charts or long paragraphs, because a card has little room. One chart that changes step by step (use `scrolly-chart` in `steps` mode).",
      "composes": [
        "big-number",
        "prose",
        "pull-quote",
        "source-note",
        "bar-chart",
        "line-chart",
        "radial-bar-chart"
      ],
      "props": [
        {
          "name": "cards",
          "type": "Card[]",
          "required": true,
          "description": "The cards in order. Each has a `kind`: `title` `{ title, deck?, byline? }`; `stat` `{ value, label, context?, delta?, deltaLabel?, upIsGood?, format?, text? }`; `chart` `{ title, chart, text? }`; `quote` `{ quote, attribution?, role? }`; `closing` `{ title, text?, points?, action?: { label, href } }`. Any card takes `kicker`, `tone: \"surface\" | \"inverse\" | \"accent\"` and `id`."
        },
        {
          "name": "label",
          "type": "string",
          "description": "Accessible name of the deck. Defaults to the first card's title."
        },
        {
          "name": "aspect",
          "type": "\"auto\" | \"9:16\" | \"4:5\" | \"1:1\" | \"16:9\"",
          "default": "\"4:5\"",
          "description": "Shape of every card. `auto` lets cards take the height their content needs. `16:9` falls back to `4:5` in containers under 520px."
        },
        {
          "name": "layout",
          "type": "\"pager\" | \"stack\"",
          "default": "\"pager\"",
          "description": "`pager` shows one card at a time (swipe, buttons, dots, arrow keys, Home/End). `stack` lists every card in a column."
        },
        {
          "name": "startAt",
          "type": "number",
          "default": "0",
          "description": "Card shown first (zero-based)."
        },
        {
          "name": "onCardChange",
          "type": "(index: number) => void",
          "description": "Called when the visible card changes."
        },
        {
          "name": "maxWidth",
          "type": "number",
          "description": "Widest a card is drawn, in px. Default 440 (400 for 9:16, 520 for 1:1, 840 for 16:9). Type and spacing scale with the card."
        },
        {
          "name": "previousLabel",
          "type": "string",
          "default": "\"Previous\"",
          "description": "Label of the previous button."
        },
        {
          "name": "nextLabel",
          "type": "string",
          "default": "\"Next\"",
          "description": "Label of the next button."
        },
        {
          "name": "sources",
          "type": "string | { name, url? } | (string | { name, url? })[]",
          "description": "Where the data comes from, shown in the closing source note. Entries with a `url` are linked."
        },
        {
          "name": "period",
          "type": "string",
          "description": "The period the data covers."
        },
        {
          "name": "method",
          "type": "string",
          "description": "How the numbers were produced: definitions, adjustments, exclusions."
        },
        {
          "name": "note",
          "type": "string",
          "description": "Caveats the reader should know, e.g. \"Illustrative data\"."
        },
        {
          "name": "credit",
          "type": "string",
          "description": "Who made the piece."
        },
        {
          "name": "level",
          "type": "1 | 2 | 3 | 4 | 5 | 6",
          "default": "1",
          "description": "Heading level of the title; section headings follow one level down."
        },
        {
          "name": "theme",
          "type": "ThemeInput",
          "description": "Theme override, passed on to everything inside."
        },
        {
          "name": "mode",
          "type": "\"auto\" | \"system\" | \"light\" | \"dark\"",
          "default": "\"auto\"",
          "description": "Colour mode, passed on to everything inside."
        }
      ],
      "example": {
        "type": "story-deck",
        "aspect": "4:5",
        "cards": [
          {
            "kind": "title",
            "tone": "inverse",
            "kicker": "Your 2024 on the road",
            "title": "You ran 1,842 km in 2024",
            "deck": "That was 23% more than in 2023. You ran 187 times.",
            "byline": "Riverside Runners · member report"
          },
          {
            "kind": "stat",
            "kicker": "Distance",
            "value": 1842,
            "format": "integer",
            "label": "Kilometres run in 2024",
            "delta": 0.23,
            "deltaLabel": "vs 2023",
            "context": "An average of 154 km a month",
            "text": "You ran in 46 of the year's 52 weeks."
          },
          {
            "kind": "chart",
            "kicker": "Month by month",
            "title": "October was your highest month at 224 km",
            "chart": {
              "type": "bar-chart",
              "data": [
                {
                  "month": "Jan",
                  "km": 96
                },
                {
                  "month": "Feb",
                  "km": 104
                },
                {
                  "month": "Mar",
                  "km": 131
                },
                {
                  "month": "Apr",
                  "km": 148
                },
                {
                  "month": "May",
                  "km": 162
                },
                {
                  "month": "Jun",
                  "km": 171
                },
                {
                  "month": "Jul",
                  "km": 120
                },
                {
                  "month": "Aug",
                  "km": 158
                },
                {
                  "month": "Sep",
                  "km": 196
                },
                {
                  "month": "Oct",
                  "km": 224
                },
                {
                  "month": "Nov",
                  "km": 181
                },
                {
                  "month": "Dec",
                  "km": 151
                }
              ],
              "x": "month",
              "y": "km",
              "orientation": "vertical",
              "sort": "none",
              "highlight": "Oct",
              "labels": false,
              "format": "{value} km",
              "height": 240
            },
            "text": "You ran 120 km in July while you were injured. You ran 158 km in August."
          },
          {
            "kind": "chart",
            "kicker": "Pace",
            "title": "Your easy pace fell from 5.52 to 5.04 minutes per kilometre",
            "chart": {
              "type": "line-chart",
              "data": [
                {
                  "month": "Jan",
                  "pace": 5.52
                },
                {
                  "month": "Feb",
                  "pace": 5.47
                },
                {
                  "month": "Mar",
                  "pace": 5.41
                },
                {
                  "month": "Apr",
                  "pace": 5.36
                },
                {
                  "month": "May",
                  "pace": 5.33
                },
                {
                  "month": "Jun",
                  "pace": 5.28
                },
                {
                  "month": "Jul",
                  "pace": 5.31
                },
                {
                  "month": "Aug",
                  "pace": 5.24
                },
                {
                  "month": "Sep",
                  "pace": 5.16
                },
                {
                  "month": "Oct",
                  "pace": 5.09
                },
                {
                  "month": "Nov",
                  "pace": 5.07
                },
                {
                  "month": "Dec",
                  "pace": 5.04
                }
              ],
              "x": "month",
              "y": "pace",
              "format": "{value} min/km",
              "height": 240
            },
            "text": "A lower number is a faster pace. Your pace in August was 5.24."
          },
          {
            "kind": "chart",
            "kicker": "Habits",
            "tone": "accent",
            "title": "41 of your 187 runs started between 6am and 7am",
            "chart": {
              "type": "radial-bar-chart",
              "data": [
                {
                  "hour": "00",
                  "runs": 0
                },
                {
                  "hour": "01",
                  "runs": 0
                },
                {
                  "hour": "02",
                  "runs": 0
                },
                {
                  "hour": "03",
                  "runs": 0
                },
                {
                  "hour": "04",
                  "runs": 0
                },
                {
                  "hour": "05",
                  "runs": 9
                },
                {
                  "hour": "06",
                  "runs": 41
                },
                {
                  "hour": "07",
                  "runs": 38
                },
                {
                  "hour": "08",
                  "runs": 12
                },
                {
                  "hour": "09",
                  "runs": 4
                },
                {
                  "hour": "10",
                  "runs": 2
                },
                {
                  "hour": "11",
                  "runs": 3
                },
                {
                  "hour": "12",
                  "runs": 8
                },
                {
                  "hour": "13",
                  "runs": 6
                },
                {
                  "hour": "14",
                  "runs": 2
                },
                {
                  "hour": "15",
                  "runs": 1
                },
                {
                  "hour": "16",
                  "runs": 3
                },
                {
                  "hour": "17",
                  "runs": 11
                },
                {
                  "hour": "18",
                  "runs": 22
                },
                {
                  "hour": "19",
                  "runs": 17
                },
                {
                  "hour": "20",
                  "runs": 6
                },
                {
                  "hour": "21",
                  "runs": 2
                },
                {
                  "hour": "22",
                  "runs": 0
                },
                {
                  "hour": "23",
                  "runs": 0
                }
              ],
              "x": "hour",
              "y": "runs",
              "labelEvery": 3,
              "labels": false,
              "height": 260
            },
            "text": "Another 38 started between 7am and 8am. 22 started between 6pm and 7pm."
          },
          {
            "kind": "quote",
            "quote": "I started running before work in the spring. I have kept to it since.",
            "attribution": "Club member",
            "role": "Club newsletter, December 2024"
          },
          {
            "kind": "closing",
            "kicker": "Next year",
            "title": "2,000 km in 2025 needs 167 km a month",
            "text": "You averaged 154 km a month in 2024.",
            "points": [
              "You ran 167 km or more in 4 months of 2024.",
              "July was 51 km below June."
            ],
            "action": {
              "label": "Set a 2025 goal",
              "href": "#goal"
            }
          }
        ],
        "sources": "Club activity tracker",
        "period": "1 January to 31 December 2024",
        "note": "Illustrative data"
      }
    },
    {
      "name": "timeline-poster",
      "component": "TimelinePoster",
      "title": "Timeline poster",
      "level": "template",
      "category": "infographic",
      "description": "A poster of change over a run of periods: a full-width ribbon of bands whose thickness is each entity's value and whose order is its rank (`ribbon-chart`), a strip of two to four dated figures under it, and a bottom row with one measure per period as thick bars (`block-bar`) and one share at several dates as nested outlines (`nested-shares`).",
      "whenToUse": "Use it when the subject is how 4 to 8 named things grew, shrank and changed places over 4 to 10 periods: electricity by source, library loans by format, commuters by mode. Give `hero` the periods and entities, `milestones` the dated numbers a reader should take away, `bars` one measure of the whole at each period and `shares` one share at a few dates. State the latest value in the title.",
      "avoid": "A single series over time (use `line-chart` or `area-chart` in a `poster`). Events with start and end dates (use `timeline-chart` or the `timeline-story` template). More than 8 entities or 10 periods, because the bands get too thin to label. Positions on one scale (use `scale-poster`). Parts of one subject at one date (use `radial-poster`).",
      "composes": [
        "poster",
        "ribbon-chart",
        "block-bar",
        "nested-shares"
      ],
      "props": [
        {
          "name": "hero",
          "type": "{ data, x, y, series?, values?, labels?, colorBy?, format?, highlight?, title?, ... } | Slot",
          "required": true,
          "description": "The hero: one band per entity across the periods, drawn by `ribbon-chart`. Wide rows take `y` as a list of columns; long rows take `y` and `series`. A full spec or element replaces it."
        },
        {
          "name": "milestones",
          "type": "{ at: string; value: string; label: string; context?: string; icon?: string }[]",
          "description": "Two to four dated figures in a strip under the hero, in time order. `at` is the period, printed in small capitals over the figure."
        },
        {
          "name": "bars",
          "type": "{ data, name, value, marker?, detail?, format?, title?, ... } | Slot",
          "description": "Bottom left: one measure at each period as thick bars with the numbers inside (`block-bar`). `marker: { after, label }` draws a dotted line after one period to mark an event."
        },
        {
          "name": "shares",
          "type": "{ data, name, value, wholeLabel?, highlight?, format?, title?, ... } | Slot",
          "description": "Bottom right: one share at 2 to 8 dates as outlines nested in one square (`nested-shares`). `format` defaults to `\"{value}%\"`. With only one of `bars` and `shares`, that one takes the full width."
        },
        {
          "name": "title",
          "type": "string",
          "required": true,
          "description": "The poster's one fact, as a plain sentence with its number."
        },
        {
          "name": "kicker",
          "type": "string",
          "description": "Small label above the title: the topic."
        },
        {
          "name": "subtitle",
          "type": "string",
          "description": "One line under the title: the measure, population and period."
        },
        {
          "name": "theme",
          "type": "PosterThemeName | string | ThemeInput",
          "default": "\"sunrise\"",
          "description": "Poster theme: `noir`, `magenta`, `dusk`, `ocean`, `forest`, `blueprint`, `sunrise`, `paper`, any other theme name, or a theme object."
        },
        {
          "name": "mode",
          "type": "\"light\" | \"dark\" | \"auto\" | \"system\"",
          "default": "\"light\"",
          "description": "Colour mode of the poster and everything in it. A poster paints its own ground and does not follow the page unless you set `auto`. Only `paper` has a different dark variant."
        },
        {
          "name": "download",
          "type": "boolean",
          "default": "false",
          "description": "Show a \"Download PNG\" control under the poster that saves the whole poster as one image."
        },
        {
          "name": "maxWidth",
          "type": "number | string",
          "default": "1100",
          "description": "Widest the poster is drawn. It is centred in wider containers."
        },
        {
          "name": "linked",
          "type": "boolean",
          "default": "false",
          "description": "Make the marks clickable. Clicking a mark in one graphic highlights the same name in every other graphic that has it, and filters the graphics that read the shared `data`. Click it again, or the empty ground of that graphic, to clear."
        },
        {
          "name": "data",
          "type": "DataInput",
          "description": "Rows shared by every region whose config leaves out its own `data`. With `linked`, a click filters those regions."
        },
        {
          "name": "sources",
          "type": "string | string[]",
          "description": "Where the data comes from, shown in the footer."
        },
        {
          "name": "note",
          "type": "string",
          "description": "Caveat shown in the footer, e.g. \"Illustrative data\"."
        },
        {
          "name": "credit",
          "type": "string",
          "description": "Who made the poster, shown at the right of the footer."
        },
        {
          "name": "level",
          "type": "1 | 2 | 3 | 4 | 5",
          "default": "2",
          "description": "Heading level of the title. Panel headings are one level down."
        }
      ],
      "example": {
        "type": "timeline-poster",
        "theme": "sunrise",
        "kicker": "Electricity",
        "title": "Wind supplied 8.6 TWh of Orrin Island's electricity in 2025",
        "subtitle": "Electricity generated on Orrin Island by source, terawatt-hours, every second year from 2015 to 2025.",
        "hero": {
          "data": [
            {
              "year": "2015",
              "Coal": 9.2,
              "Gas": 6.1,
              "Hydro": 2.2,
              "Wind": 1.4,
              "Solar": 0.3
            },
            {
              "year": "2017",
              "Coal": 8,
              "Gas": 6.4,
              "Hydro": 2.3,
              "Wind": 2.6,
              "Solar": 0.6
            },
            {
              "year": "2019",
              "Coal": 6.1,
              "Gas": 6,
              "Hydro": 2.1,
              "Wind": 4.3,
              "Solar": 1.3
            },
            {
              "year": "2021",
              "Coal": 4,
              "Gas": 5.4,
              "Hydro": 2.4,
              "Wind": 5.9,
              "Solar": 2.4
            },
            {
              "year": "2023",
              "Coal": 2.3,
              "Gas": 4.7,
              "Hydro": 2.2,
              "Wind": 7.4,
              "Solar": 3.7
            },
            {
              "year": "2025",
              "Coal": 1.1,
              "Gas": 4,
              "Hydro": 2.3,
              "Wind": 8.6,
              "Solar": 4.9
            }
          ],
          "x": "year",
          "y": [
            "Coal",
            "Gas",
            "Hydro",
            "Wind",
            "Solar"
          ],
          "colorBy": "series",
          "title": "Coal supplied 9.2 TWh in 2015"
        },
        "milestones": [
          {
            "at": "2017",
            "value": "62",
            "label": "turbines started at the Skerry wind farm"
          },
          {
            "at": "2021",
            "value": "5.9 TWh",
            "label": "came from wind",
            "context": "Gas supplied 5.4 TWh"
          },
          {
            "at": "2025",
            "value": "1.1 TWh",
            "label": "came from coal",
            "context": "9.2 TWh in 2015"
          }
        ],
        "bars": {
          "data": [
            {
              "year": "2015",
              "g": 520
            },
            {
              "year": "2017",
              "g": 455
            },
            {
              "year": "2019",
              "g": 370
            },
            {
              "year": "2021",
              "g": 290
            },
            {
              "year": "2023",
              "g": 215
            },
            {
              "year": "2025",
              "g": 160
            }
          ],
          "name": "year",
          "value": "g",
          "format": "{value} g",
          "marker": {
            "after": "2017",
            "label": "Skerry wind farm opened"
          },
          "title": "A kilowatt-hour emitted 160 g of CO2 in 2025",
          "subtitle": "Grams of CO2 per kilowatt-hour generated."
        },
        "shares": {
          "data": [
            {
              "year": "2015",
              "share": 9
            },
            {
              "year": "2019",
              "share": 28
            },
            {
              "year": "2021",
              "share": 41
            },
            {
              "year": "2025",
              "share": 65
            }
          ],
          "name": "year",
          "value": "share",
          "highlight": "2025",
          "wholeLabel": "All electricity generated",
          "title": "Wind and solar supplied 65% of electricity in 2025"
        },
        "sources": "Orrin Island Grid Office, annual generation statistics",
        "note": "Orrin Island is an invented place. All figures are illustrative.",
        "credit": "InsightCharts"
      }
    },
    {
      "name": "timeline-story",
      "component": "TimelineStory",
      "title": "Timeline story",
      "level": "template",
      "category": "story",
      "description": "A story told in date order. It has a headline, one overview exhibit of the whole period (a timeline chart or an annotated line), dated chapters along a rail, a closing paragraph and a source note. Each chapter has a short text and an optional small exhibit.",
      "whenToUse": "Use it when the reader needs the events in date order to understand a change. State the change over the whole period in the headline, with the numbers and dates. Mark the main events on the overview exhibit as annotations. Give each chapter one event. Add a small figure to a chapter only when it has a number to show. Mark the two or three most important events with `emphasis`.",
      "avoid": "Events with no measure that runs through the period (use a plain `annotated-timeline`). A comparison of two states with nothing in between (use `comparison-brief`). More than eight chapters. Merge minor events into the text of the chapter before or after.",
      "composes": [
        "headline",
        "prose",
        "annotated-timeline",
        "source-note",
        "line-chart",
        "timeline-chart",
        "bar-chart",
        "big-number"
      ],
      "props": [
        {
          "name": "title",
          "type": "string",
          "required": true,
          "description": "The change over the whole period, as one plain sentence with the numbers and dates in it."
        },
        {
          "name": "kicker",
          "type": "string",
          "description": "Small label above the title."
        },
        {
          "name": "deck",
          "type": "string",
          "description": "One-sentence standfirst."
        },
        {
          "name": "byline",
          "type": "string",
          "description": "Author line."
        },
        {
          "name": "date",
          "type": "string",
          "description": "Publication date."
        },
        {
          "name": "timeline",
          "type": "Slot",
          "description": "The overview exhibit: a `timeline-chart` spec, or a `line-chart` with the events as `annotations`. Default height 360px for line charts."
        },
        {
          "name": "intro",
          "type": "string | string[]",
          "description": "A paragraph between the overview and the chapters."
        },
        {
          "name": "chaptersTitle",
          "type": "string",
          "description": "Heading over the chapters."
        },
        {
          "name": "chapters",
          "type": "{ date, title, text?, exhibit?, emphasis? }[]",
          "required": true,
          "description": "The dated chapters, oldest first. `date` is shown as written; `exhibit` is a small chart or figure; `emphasis` fills the marker for a major event."
        },
        {
          "name": "exhibitPosition",
          "type": "\"below\" | \"side\"",
          "default": "\"side\"",
          "description": "Where a chapter's exhibit sits; `side` falls back to `below` under 640px."
        },
        {
          "name": "closing",
          "type": "string | string[]",
          "description": "A closing paragraph that states the latest figures."
        },
        {
          "name": "sources",
          "type": "string | { name, url? } | (string | { name, url? })[]",
          "description": "Where the data comes from, shown in the closing source note. Entries with a `url` are linked."
        },
        {
          "name": "period",
          "type": "string",
          "description": "The period the data covers."
        },
        {
          "name": "method",
          "type": "string",
          "description": "How the numbers were produced: definitions, adjustments, exclusions."
        },
        {
          "name": "note",
          "type": "string",
          "description": "Caveats the reader should know, e.g. \"Illustrative data\"."
        },
        {
          "name": "credit",
          "type": "string",
          "description": "Who made the piece."
        },
        {
          "name": "level",
          "type": "1 | 2 | 3 | 4 | 5 | 6",
          "default": "1",
          "description": "Heading level of the title; section headings follow one level down."
        },
        {
          "name": "maxWidth",
          "type": "number | string",
          "description": "Widest the piece may get. It is centred in wider containers."
        },
        {
          "name": "theme",
          "type": "ThemeInput",
          "description": "Theme override, passed on to everything inside."
        },
        {
          "name": "mode",
          "type": "\"auto\" | \"system\" | \"light\" | \"dark\"",
          "default": "\"auto\"",
          "description": "Colour mode, passed on to everything inside."
        }
      ],
      "example": {
        "type": "timeline-story",
        "kicker": "Energy",
        "title": "Coal generated 39% of Britain's electricity in 2012 and 0.6% in 2024",
        "deck": "The first public coal power station opened in 1882. The last coal plant closed on 30 September 2024.",
        "byline": "By the data desk",
        "date": "2024-10-01",
        "timeline": {
          "type": "line-chart",
          "data": [
            {
              "year": "1990-01-01",
              "share": 65
            },
            {
              "year": "1994-01-01",
              "share": 50
            },
            {
              "year": "1998-01-01",
              "share": 35
            },
            {
              "year": "2002-01-01",
              "share": 32
            },
            {
              "year": "2006-01-01",
              "share": 38
            },
            {
              "year": "2010-01-01",
              "share": 28
            },
            {
              "year": "2012-01-01",
              "share": 39
            },
            {
              "year": "2013-01-01",
              "share": 36
            },
            {
              "year": "2014-01-01",
              "share": 30
            },
            {
              "year": "2015-01-01",
              "share": 22
            },
            {
              "year": "2016-01-01",
              "share": 9
            },
            {
              "year": "2017-01-01",
              "share": 7
            },
            {
              "year": "2018-01-01",
              "share": 5
            },
            {
              "year": "2019-01-01",
              "share": 2
            },
            {
              "year": "2020-01-01",
              "share": 2
            },
            {
              "year": "2021-01-01",
              "share": 2
            },
            {
              "year": "2022-01-01",
              "share": 1.5
            },
            {
              "year": "2023-01-01",
              "share": 1
            },
            {
              "year": "2024-01-01",
              "share": 0.6
            }
          ],
          "x": "year",
          "y": "share",
          "format": "{value}%",
          "zero": true,
          "area": true,
          "title": "Coal's share of generation fell from 65% in 1990 to 0.6% in 2024",
          "subtitle": "Share of Great Britain's electricity generated from coal, 1990 to 2024",
          "annotations": [
            {
              "type": "band",
              "axis": "x",
              "from": "1990-01-01",
              "to": "1998-01-01",
              "label": "Gas plants built"
            },
            {
              "type": "line",
              "axis": "x",
              "value": "2013-01-01",
              "label": "Carbon price floor"
            },
            {
              "type": "point",
              "x": "2012-01-01",
              "y": 39,
              "label": "39% in 2012",
              "position": "top"
            }
          ],
          "source": "Department for Energy Security and Net Zero and National Grid ESO (rounded)",
          "note": "Figures are approximate, for illustration"
        },
        "intro": "Coal's share fell in two periods. It fell from 65% to 35% between 1990 and 1998, when generators built gas plants. It fell from 39% to 9% between 2012 and 2016, after the carbon price floor was introduced.",
        "chapters": [
          {
            "date": "1882",
            "title": "The first public coal power station opens in London",
            "text": "Thomas Edison's company built the plant at Holborn Viaduct. It supplied street lamps and nearby buildings."
          },
          {
            "date": "1990s",
            "title": "Coal's share falls from 65% to 35% between 1990 and 1998",
            "text": "Privatised generators built gas plants, which were cheaper and quicker to build than coal plants. Coal's share was between 28% and 39% from 1998 to 2013."
          },
          {
            "date": "April 2013",
            "title": "The carbon price floor takes effect",
            "emphasis": true,
            "text": "The floor set a minimum price on power-station emissions. The rate doubled in 2015. Coal's share fell from 22% in 2015 to 9% in 2016.",
            "exhibit": {
              "type": "bar-chart",
              "data": [
                {
                  "year": "2012",
                  "share": 39
                },
                {
                  "year": "2013",
                  "share": 36
                },
                {
                  "year": "2014",
                  "share": 30
                },
                {
                  "year": "2015",
                  "share": 22
                },
                {
                  "year": "2016",
                  "share": 9
                }
              ],
              "x": "year",
              "y": "share",
              "orientation": "vertical",
              "sort": "none",
              "highlight": "2016",
              "format": "{value}%",
              "height": 170,
              "subtitle": "Coal share of generation, 2012 to 2016"
            }
          },
          {
            "date": "21 April 2017",
            "title": "The first full day without coal since 1882",
            "text": "Britain had 1 coal-free day in 2017. It had 180 in 2020.",
            "exhibit": {
              "type": "bar-chart",
              "data": [
                {
                  "year": "2017",
                  "days": 1
                },
                {
                  "year": "2018",
                  "days": 21
                },
                {
                  "year": "2019",
                  "days": 83
                },
                {
                  "year": "2020",
                  "days": 180
                }
              ],
              "x": "year",
              "y": "days",
              "orientation": "vertical",
              "sort": "none",
              "highlight": "2020",
              "height": 170,
              "subtitle": "Coal-free days per year"
            }
          },
          {
            "date": "30 September 2024",
            "title": "Ratcliffe-on-Soar, the last coal plant, shuts down",
            "emphasis": true,
            "text": "The plant had run since 1968. In 2024 wind generated 30% of the country's electricity and gas generated 26%.",
            "exhibit": {
              "type": "big-number",
              "value": "0%",
              "label": "Coal share of generation since October 2024",
              "context": "Britain was the first G7 country to end coal power"
            }
          }
        ],
        "closing": "Coal generated 0.6% of Britain's electricity in 2024, all of it before 30 September. Gas generated 26%.",
        "sources": [
          "Digest of UK Energy Statistics",
          "National Grid ESO"
        ],
        "period": "1990 to 2024",
        "note": "Annual shares are rounded. Intermediate years before 2012 are interpolated for the chart. Figures are approximate, for illustration."
      }
    },
    {
      "name": "visual-essay",
      "component": "VisualEssay",
      "title": "Visual essay",
      "level": "template",
      "category": "story",
      "description": "A long article read by scrolling. It has a headline, then an ordered list of blocks in a reading column, then a source note. The blocks are prose, figures, charts with a callout, scrolly sequences, pull quotes, stat rows, small multiples and chapter breaks. Exhibits can be wider than the column.",
      "whenToUse": "Use it for a finding that takes several charts and some text to explain. State the main finding with its number in the headline. Put the main figure within the first screen. Alternate text and charts, with two or three short paragraphs before each exhibit. In a scrolly block change one thing per step, such as the highlight, the year or an annotation. Use one accent colour for the subject in every chart.",
      "avoid": "A summary for someone who will not scroll (use `executive-brief`). Reference material read out of order (use `report-page`). More than one or two scrolly blocks. Use one only where the chart changes between steps.",
      "composes": [
        "headline",
        "prose",
        "pull-quote",
        "step-marker",
        "small-multiples",
        "chart-callout",
        "scrolly-chart",
        "small-multiple-grid",
        "stat-row",
        "source-note",
        "line-chart",
        "bar-chart"
      ],
      "props": [
        {
          "name": "title",
          "type": "string",
          "required": true,
          "description": "The essay's main finding, as one plain sentence with the number and the period in it."
        },
        {
          "name": "kicker",
          "type": "string",
          "description": "Small label above the title."
        },
        {
          "name": "deck",
          "type": "string",
          "description": "One-sentence standfirst."
        },
        {
          "name": "byline",
          "type": "string",
          "description": "Author line."
        },
        {
          "name": "date",
          "type": "string",
          "description": "Publication date. ISO dates are formatted for the locale."
        },
        {
          "name": "blocks",
          "type": "(Block | Slot)[]",
          "required": true,
          "description": "The story in reading order. Each block has a `kind`: `prose` `{ text, lead? }`; `figure` `{ chart, caption? }`; `callout` `{ chart, lead, body, label?, tone?, side? }`; `scrolly` `{ chart, steps, cumulative?, scrollMode?, side?, stepHeight? }` (the props of `scrolly-chart`); `quote` `{ quote, attribution?, role? }`; `stats` `{ items, title? }`; `multiples` `{ facet, ... }` (props of `small-multiples`, or of `small-multiple-grid` when it has a `chart`); `break` `{ title, step?, total?, text? }`. Any block takes `width: \"text\" | \"wide\" | \"full\"` and an `id`. A bare chart spec is treated as a wide figure."
        },
        {
          "name": "measure",
          "type": "number",
          "default": "680",
          "description": "Width of the reading column in px."
        },
        {
          "name": "textSize",
          "type": "\"md\" | \"lg\"",
          "description": "Body text size. Default 18px, 16px in narrow containers."
        },
        {
          "name": "sources",
          "type": "string | { name, url? } | (string | { name, url? })[]",
          "description": "Where the data comes from, shown in the closing source note. Entries with a `url` are linked."
        },
        {
          "name": "period",
          "type": "string",
          "description": "The period the data covers."
        },
        {
          "name": "method",
          "type": "string",
          "description": "How the numbers were produced: definitions, adjustments, exclusions."
        },
        {
          "name": "note",
          "type": "string",
          "description": "Caveats the reader should know, e.g. \"Illustrative data\"."
        },
        {
          "name": "credit",
          "type": "string",
          "description": "Who made the piece."
        },
        {
          "name": "level",
          "type": "1 | 2 | 3 | 4 | 5 | 6",
          "default": "1",
          "description": "Heading level of the title; section headings follow one level down."
        },
        {
          "name": "maxWidth",
          "type": "number | string",
          "description": "Widest the piece may get. It is centred in wider containers."
        },
        {
          "name": "theme",
          "type": "ThemeInput",
          "description": "Theme override, passed on to everything inside."
        },
        {
          "name": "mode",
          "type": "\"auto\" | \"system\" | \"light\" | \"dark\"",
          "default": "\"auto\"",
          "description": "Colour mode, passed on to everything inside."
        }
      ],
      "example": {
        "type": "visual-essay",
        "kicker": "Energy transition",
        "title": "Solar supplied 11.1% of EU electricity in 2024, more than coal's 9.8%",
        "deck": "Solar's share was 2.9% in 2015. Coal's share was 24.6% in 2015.",
        "byline": "By the data desk",
        "date": "2025-03-14",
        "blocks": [
          {
            "kind": "prose",
            "lead": true,
            "text": "Solar supplied **2.9% of the EU's electricity** in 2015. Hydro supplied 11.5%.\n\nIn 2024 solar supplied 11.1%. It was the first full year in which solar generated more electricity than coal."
          },
          {
            "kind": "figure",
            "chart": {
              "type": "line-chart",
              "data": [
                {
                  "year": "2015",
                  "Solar": 2.9,
                  "Coal": 24.6
                },
                {
                  "year": "2016",
                  "Solar": 3,
                  "Coal": 22
                },
                {
                  "year": "2017",
                  "Solar": 3.4,
                  "Coal": 20.6
                },
                {
                  "year": "2018",
                  "Solar": 3.7,
                  "Coal": 19.2
                },
                {
                  "year": "2019",
                  "Solar": 4.2,
                  "Coal": 15
                },
                {
                  "year": "2020",
                  "Solar": 5,
                  "Coal": 13
                },
                {
                  "year": "2021",
                  "Solar": 5.4,
                  "Coal": 14.7
                },
                {
                  "year": "2022",
                  "Solar": 7,
                  "Coal": 15.8
                },
                {
                  "year": "2023",
                  "Solar": 9.1,
                  "Coal": 12.3
                },
                {
                  "year": "2024",
                  "Solar": 11.1,
                  "Coal": 9.8
                }
              ],
              "x": "year",
              "y": [
                "Solar",
                "Coal"
              ],
              "highlight": "Solar",
              "format": "{value}%",
              "zero": true,
              "title": "Solar's share rose from 2.9% to 11.1% between 2015 and 2024",
              "subtitle": "Share of EU electricity generation, 2015 to 2024",
              "annotations": [
                {
                  "type": "band",
                  "axis": "x",
                  "from": "2021",
                  "to": "2022",
                  "label": "Gas price crisis"
                },
                {
                  "type": "point",
                  "x": "2016",
                  "y": 22,
                  "label": "Coal",
                  "position": "top",
                  "dot": false
                }
              ],
              "source": "Ember, European Electricity Review 2025"
            }
          },
          {
            "kind": "prose",
            "text": "Coal's share was 15.8% in 2022. It fell to 12.3% in 2023 and 9.8% in 2024. Solar's share rose from 7.0% to 11.1% in the same two years."
          },
          {
            "kind": "break",
            "step": 1,
            "total": 3,
            "title": "Solar module prices, 2010 to 2024",
            "text": "A module cost $2.00 a watt in 2010. It cost $0.11 a watt in 2024."
          },
          {
            "kind": "callout",
            "chart": {
              "type": "line-chart",
              "data": [
                {
                  "year": "2010",
                  "usd": 2
                },
                {
                  "year": "2012",
                  "usd": 0.9
                },
                {
                  "year": "2014",
                  "usd": 0.65
                },
                {
                  "year": "2016",
                  "usd": 0.5
                },
                {
                  "year": "2018",
                  "usd": 0.3
                },
                {
                  "year": "2020",
                  "usd": 0.22
                },
                {
                  "year": "2022",
                  "usd": 0.26
                },
                {
                  "year": "2024",
                  "usd": 0.11
                }
              ],
              "x": "year",
              "y": "usd",
              "format": {
                "style": "currency",
                "currency": "USD"
              },
              "zero": true,
              "area": true,
              "title": "Solar module prices fell from $2.00 to $0.11 a watt between 2010 and 2024",
              "subtitle": "Average price of crystalline silicon solar modules, US dollars per watt",
              "source": "IRENA and BloombergNEF (rounded)"
            },
            "label": "Rooftop system cost",
            "lead": "Modules are now the smaller part of the cost of a rooftop system",
            "body": "Mounting, wiring, labour and the grid connection make up most of the cost."
          },
          {
            "kind": "stats",
            "items": [
              {
                "value": "338 GW",
                "label": "Solar capacity installed in the EU, end of 2024",
                "delta": "+66 GW",
                "deltaLabel": "in 2024"
              },
              {
                "value": "304 TWh",
                "label": "Solar generation, 2024",
                "delta": 0.22,
                "deltaLabel": "vs 2023"
              },
              {
                "value": "269 TWh",
                "label": "Coal generation, 2024",
                "delta": -0.16,
                "deltaLabel": "vs 2023",
                "upIsGood": false
              }
            ]
          },
          {
            "kind": "break",
            "step": 2,
            "total": 3,
            "title": "EU electricity generation by source, 2015 and 2024",
            "text": "Scroll to see the shares in 2015 and then in 2024."
          },
          {
            "kind": "scrolly",
            "chart": {
              "type": "bar-chart",
              "data": [
                {
                  "source": "Nuclear",
                  "y2015": 26.5,
                  "y2024": 23.7
                },
                {
                  "source": "Coal",
                  "y2015": 24.6,
                  "y2024": 9.8
                },
                {
                  "source": "Gas",
                  "y2015": 15.8,
                  "y2024": 15.7
                },
                {
                  "source": "Hydro",
                  "y2015": 11.5,
                  "y2024": 12.5
                },
                {
                  "source": "Wind",
                  "y2015": 9.3,
                  "y2024": 17.4
                },
                {
                  "source": "Solar",
                  "y2015": 2.9,
                  "y2024": 11.1
                }
              ],
              "x": "source",
              "y": "y2015",
              "sort": "none",
              "orientation": "horizontal",
              "format": "{value}%",
              "height": 320,
              "title": "EU electricity generation by source, 2015",
              "subtitle": "Share of electricity generation",
              "source": "Ember, European Electricity Review 2025",
              "option": {
                "xAxis": {
                  "max": 30
                }
              }
            },
            "stepHeight": "70vh",
            "stickyTop": 16,
            "steps": [
              {
                "id": "baseline",
                "label": "2015",
                "content": "In 2015 **nuclear and coal** together supplied 51% of the EU's electricity."
              },
              {
                "id": "coal",
                "content": "Coal supplied 24.6% in 2015. It was the second-largest source after nuclear.",
                "highlight": "Coal"
              },
              {
                "id": "coal-now",
                "label": "2024",
                "content": "Coal supplied 9.8% in 2024. Plants closed in Germany, Spain and Portugal.",
                "highlight": "Coal",
                "y": "y2024",
                "title": "EU electricity generation by source, 2024"
              },
              {
                "id": "solar",
                "content": "**Solar** supplied 11.1% in 2024. It supplied 2.9% in 2015. Wind supplied 17.4% in 2024 and gas 15.7%.",
                "highlight": "Solar",
                "y": "y2024",
                "title": "Solar supplied 11.1% of EU electricity in 2024"
              }
            ]
          },
          {
            "kind": "quote",
            "quote": "Solar was 1% of Dutch generation in 2015. It was 18% in 2024. We have had to change how we run the grid at midday.",
            "attribution": "Marit de Vries",
            "role": "Head of system operations at a Dutch grid operator"
          },
          {
            "kind": "break",
            "step": 3,
            "total": 3,
            "title": "Solar share by country",
            "text": "Greece had the highest share in 2024 at 22.1%. Spain had 21.0%. France had 4.6%."
          },
          {
            "kind": "multiples",
            "data": [
              {
                "country": "Greece",
                "year": "2015",
                "share": 7.8
              },
              {
                "country": "Greece",
                "year": "2018",
                "share": 8.1
              },
              {
                "country": "Greece",
                "year": "2021",
                "share": 10.9
              },
              {
                "country": "Greece",
                "year": "2024",
                "share": 22.1
              },
              {
                "country": "Spain",
                "year": "2015",
                "share": 4.9
              },
              {
                "country": "Spain",
                "year": "2018",
                "share": 4.7
              },
              {
                "country": "Spain",
                "year": "2021",
                "share": 9.6
              },
              {
                "country": "Spain",
                "year": "2024",
                "share": 21
              },
              {
                "country": "Netherlands",
                "year": "2015",
                "share": 1
              },
              {
                "country": "Netherlands",
                "year": "2018",
                "share": 3.2
              },
              {
                "country": "Netherlands",
                "year": "2021",
                "share": 9.3
              },
              {
                "country": "Netherlands",
                "year": "2024",
                "share": 17.9
              },
              {
                "country": "Germany",
                "year": "2015",
                "share": 6
              },
              {
                "country": "Germany",
                "year": "2018",
                "share": 7.1
              },
              {
                "country": "Germany",
                "year": "2021",
                "share": 8.4
              },
              {
                "country": "Germany",
                "year": "2024",
                "share": 14.7
              },
              {
                "country": "Poland",
                "year": "2015",
                "share": 0
              },
              {
                "country": "Poland",
                "year": "2018",
                "share": 0.2
              },
              {
                "country": "Poland",
                "year": "2021",
                "share": 2.2
              },
              {
                "country": "Poland",
                "year": "2024",
                "share": 10.8
              },
              {
                "country": "France",
                "year": "2015",
                "share": 1.3
              },
              {
                "country": "France",
                "year": "2018",
                "share": 1.9
              },
              {
                "country": "France",
                "year": "2021",
                "share": 2.8
              },
              {
                "country": "France",
                "year": "2024",
                "share": 4.6
              }
            ],
            "facet": "country",
            "x": "year",
            "y": "share",
            "highlight": [
              "Netherlands",
              "Poland"
            ],
            "format": "{value}%",
            "columns": 3,
            "title": "Solar's share in the Netherlands rose from 1.0% to 17.9% between 2015 and 2024",
            "subtitle": "Solar share of electricity generation, 2015 to 2024, same scale in every panel",
            "source": "Ember (country series rounded, illustrative)",
            "width": "wide"
          },
          {
            "kind": "prose",
            "text": "Poland's solar share rose from 0.0% in 2015 to 10.8% in 2024. Retail electricity prices in the Netherlands and Poland rose after 2022. At those prices a rooftop system paid for itself in under seven years without subsidy.\n\nFrance has cheap nuclear power and slow permitting. Its solar share was 4.6% in 2024. Germany's was 14.7%."
          }
        ],
        "sources": [
          {
            "name": "Ember, European Electricity Review 2025",
            "url": "https://ember-energy.org"
          },
          "IRENA Renewable Power Generation Costs",
          "BloombergNEF"
        ],
        "period": "2010 to 2024, annual",
        "method": "Generation shares are of total net generation in the EU-27. Module prices are annual averages in nominal dollars.",
        "note": "EU totals are approximate, rounded for illustration. Country series and the quotation are illustrative.",
        "credit": "Analysis and charts by the data desk"
      }
    }
  ]
}
