{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "cohort",
  "type": "registry:component",
  "title": "Cohort",
  "description": "How each group holds on over time. Sample data: monthly cohorts April to September, revenue retention, 86% to 91% at month 2.",
  "registryDependencies": [
    "@chartory/core",
    "@chartory/plot-cohort"
  ],
  "files": [
    {
      "path": "registry/chartory/components/cohort.tsx",
      "type": "registry:component",
      "target": "components/chartory/cohort.tsx",
      "content": "// SPDX-License-Identifier: MIT · Copyright (c) 2026 BEY AGENCY LTD · Chartory, usechartory.com\n// Cohort (Chartory chart 026): sizes xs | s | m | l, laid out again from the data at each\n// size; collection \"hearth\" (default) or \"signal\" (with @chartory/signal, Code and Complete). Data in the type's shape (default: the sample). Docs:\n// https://usechartory.com/docs/charts/cohort\nimport { ChartView, type ChartViewProps } from \"@/lib/chartory/charts/chart-view\"\nimport { registerChart } from \"@/lib/chartory/charts/defs\"\nimport { data as sample } from \"@/lib/chartory/charts/types/026-cohort/data\"\nimport { def } from \"@/lib/chartory/charts/types/026-cohort/layout\"\n\nregisterChart(def, sample)\n\nexport type CohortData = typeof sample\nexport type CohortProps = Omit<ChartViewProps, \"def\" | \"data\" | \"states\"> & {\n  /** the data to draw (default: the sample) */\n  data?: CohortData\n  /** further data states: the chart morphs to them (update) or ticks through them (live, with interval) */\n  states?: CohortData[]\n}\n\nexport function Cohort(props: CohortProps) {\n  return <ChartView def={def} {...props} />\n}\n"
    },
    {
      "path": "registry/chartory/charts/types/026-cohort/data.ts",
      "type": "registry:lib",
      "target": "lib/chartory/charts/types/026-cohort/data.ts",
      "content": "// SPDX-License-Identifier: MIT · Copyright (c) 2026 BEY AGENCY LTD · Chartory, usechartory.com\n// 026 Cohort · sample data (Figma chart/026, SaaS: net revenue retention by monthly cohort, M0–M5; label = month ·\n// accounts). The name column is 72 px on plots wider than 480, else 64.\nimport type { CohortData } from \"../../plots/cohort\";\n\nexport const data: Omit<CohortData, \"yW\"> = {\n  cols: [\"M0\", \"M1\", \"M2\", \"M3\", \"M4\", \"M5\"],\n  lo: 78,\n  hi: 100,\n  fmt: (v) => v + \"%\",\n  cohorts: [\n    { label: \"APR · 412\", vals: [100, 91, 86, 83, 81, 80] },\n    { label: \"MAY · 388\", vals: [100, 93, 88, 85, 84] },\n    { label: \"JUN · 455\", vals: [100, 92, 87, 86] },\n    { label: \"JUL · 471\", vals: [100, 95, 91] },\n    { label: \"AUG · 502\", vals: [100, 94] },\n    { label: \"SEP · 536\", vals: [100] },\n  ],\n};\n"
    },
    {
      "path": "registry/chartory/charts/types/026-cohort/layout.ts",
      "type": "registry:lib",
      "target": "lib/chartory/charts/types/026-cohort/layout.ts",
      "content": "// SPDX-License-Identifier: MIT · Copyright (c) 2026 BEY AGENCY LTD · Chartory, usechartory.com\n// 026 Cohort (Figma chart/026, \"Revenue retention by cohort\"): C.cohort, six monthly cohorts over six months.\nimport type { ChartDef, TooltipSpec } from \"../../core/shell\";\nimport { cohort } from \"../../plots/cohort\";\nimport { data } from \"./data\";\n\n/** Tooltip for one cell (group = the cell's index in reading order): cohort and month, the value on its ramp step and\n *  the change from the month before. */\nfunction tipOf(o: typeof data, g: number): TooltipSpec | null {\n  let i = 0;\n  for (const co of o.cohorts)\n    for (let c = 0; c < co.vals.length; c++, i++) {\n      if (i !== g) continue;\n      const v = co.vals[c], k = Math.max(1, Math.min(6, Math.ceil(((v - o.lo) / (o.hi - o.lo)) * 6))), dv = c ? v - co.vals[c - 1] : 0;\n      const share = c ? (dv > 0 ? \"+\" : dv < 0 ? \"−\" : \"±\") + Math.abs(dv) + \" PTS VS \" + o.cols[c - 1] : \"COHORT START\";\n      return { kind: \"cell\", caps: (co.label + \" · \" + o.cols[c]).toUpperCase(), value: o.fmt(v), unit: \"retained\", swatch: \"data/seq-\" + k, share };\n    }\n  return null;\n}\n\nexport const def: ChartDef = {\n  no: 26,\n  slug: \"cohort\",\n  legend: [[\"data/seq-1\", \"Lower\"], [\"data/seq-6\", \"Higher\"]],\n  // update: the next month's revision of the same cohorts (each value moves 1–4 points, M0 stays 100, retention\n  // never rises along a cohort, values stay inside the ramp's lo..hi)\n  update: (d, step) => {\n    const o = d as typeof data;\n    return {\n      ...o,\n      cohorts: o.cohorts.map((c, r) => {\n        const vals: number[] = [];\n        c.vals.forEach((v, i) => {\n          const nv = i === 0 ? v : Math.round(v + 3 * Math.sin(step * 1.9 + r * 1.3 + i * 0.7));\n          vals.push(Math.max(o.lo + 1, Math.min(i === 0 ? o.hi : vals[i - 1], nv)));\n        });\n        return { ...c, vals };\n      }),\n    };\n  },\n  plot: (K, p, w, d) => cohort(K, p, w, { ...((d as typeof data | undefined) ?? data), yW: w > 480 ? 72 : 64 }),\n  tip: (d, g) => tipOf(d as typeof data, g),\n  describe: {\n    title: \"Revenue retention by cohort\",\n    summary: \"Net revenue retention by monthly cohort, April to September: month 2 retention runs from 86% (April) to 91% (July); the April cohort keeps 80% at month 5.\",\n    table: {\n      head: [\"Cohort\", \"Accounts\", ...data.cols],\n      rows: data.cohorts.map((c) => {\n        const [m, n] = c.label.split(\" · \");\n        return [m[0] + m.slice(1).toLowerCase(), +n, ...data.cols.map((_, i) => (i < c.vals.length ? data.fmt(c.vals[i]) : \"\"))];\n      }),\n    },\n  },\n};\n"
    }
  ],
  "categories": [
    "charts",
    "change-over-time"
  ],
  "meta": {
    "access": "free",
    "no": 26,
    "family": "Change over time"
  }
}
