{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "sankey",
  "type": "registry:component",
  "title": "Sankey",
  "description": "Where things come from and where they go. Sample data: sessions from four sources to four outcomes, search 4,200, purchased 2,100.",
  "registryDependencies": [
    "@chartory/core",
    "@chartory/plot-sankey"
  ],
  "files": [
    {
      "path": "registry/chartory/components/sankey.tsx",
      "type": "registry:component",
      "target": "components/chartory/sankey.tsx",
      "content": "// SPDX-License-Identifier: MIT · Copyright (c) 2026 BEY AGENCY LTD · Chartory, usechartory.com\n// Sankey (Chartory chart 031): 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/sankey\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/031-sankey/data\"\nimport { def } from \"@/lib/chartory/charts/types/031-sankey/layout\"\n\nregisterChart(def, sample)\n\nexport type SankeyData = typeof sample\nexport type SankeyProps = Omit<ChartViewProps, \"def\" | \"data\" | \"states\"> & {\n  /** the data to draw (default: the sample) */\n  data?: SankeyData\n  /** further data states: the chart morphs to them (update) or ticks through them (live, with interval) */\n  states?: SankeyData[]\n}\n\nexport function Sankey(props: SankeyProps) {\n  return <ChartView def={def} {...props} />\n}\n"
    },
    {
      "path": "registry/chartory/charts/types/031-sankey/data.ts",
      "type": "registry:lib",
      "target": "lib/chartory/charts/types/031-sankey/data.ts",
      "content": "// SPDX-License-Identifier: MIT · Copyright (c) 2026 BEY AGENCY LTD · Chartory, usechartory.com\n// 031 Sankey · sample data (Figma chart/031, e-commerce: this week's sessions from source to outcome).\nimport type { SankeyData } from \"../../plots/sankey\";\n\nexport const data: SankeyData = {\n  left: [\n    {  label: \"Search\", tok: \"data/cat-1\", v: 4200  },\n    {  label: \"Social\", tok: \"data/cat-2\", v: 2600  },\n    {  label: \"Email\", tok: \"data/cat-3\", v: 1900  },\n    {  label: \"Direct\", tok: \"data/cat-5\", v: 1300  },\n  ],\n  right: [\n    {  label: \"Purchased\", v: 2100  },\n    {  label: \"Abandoned cart\", v: 2600  },\n    {  label: \"Browsed\", v: 3100  },\n    {  label: \"Bounced\", v: 2200  },\n  ],\n  flows: [\n    [0, 0, 1100], [0, 1, 1200], [0, 2, 1200], [0, 3, 700],\n    [1, 0, 300], [1, 1, 600], [1, 2, 900], [1, 3, 800],\n    [2, 0, 500], [2, 1, 500], [2, 2, 600], [2, 3, 300],\n    [3, 0, 200], [3, 1, 300], [3, 2, 400], [3, 3, 400],\n  ],\n};\n"
    },
    {
      "path": "registry/chartory/charts/types/031-sankey/layout.ts",
      "type": "registry:lib",
      "target": "lib/chartory/charts/types/031-sankey/layout.ts",
      "content": "// SPDX-License-Identifier: MIT · Copyright (c) 2026 BEY AGENCY LTD · Chartory, usechartory.com\n// 031 Sankey (Figma chart/031): C.sankey; no legend (sources and outcomes are labelled).\nimport type { ChartDef, TooltipSpec } from \"../../core/shell\";\nimport { sankey } from \"../../plots/sankey\";\nimport { data } from \"./data\";\n\nconst tot = data.left.reduce((s, n) => s + n.v, 0);\nconst fmt = (v: number) => v.toLocaleString(\"en-US\");\n\n/** Tooltip for one source (group = the source node and its flows): its sessions by outcome and their total. */\nfunction tipOf(o: typeof data, g: number): TooltipSpec | null {\n  const src = o.left[g];\n  if (!src) return null;\n  const rows = o.right.map((r, ri) => ({ label: r.label, value: fmt(o.flows.find((f) => f[0] === g && f[1] === ri)?.[2] ?? 0), swatch: src.tok }));\n  return { kind: \"series\", caps: `${src.label.toUpperCase()} · SESSIONS BY OUTCOME`, rows, total: { label: \"Total\", value: fmt(o.flows.reduce((s, f) => s + (f[0] === g ? f[2] : 0), 0)) } };\n}\n\nexport const def: ChartDef = {\n  no: 31,\n  slug: \"sankey\",\n  legend: null,\n  // update: next week; every flow moves up to ±12 % (whole tens of sessions) and both node columns are the sums of\n  // their flows again, so the ribbons still fill the nodes and the labels match them\n  update: (d, step) => {\n    const o = d as typeof data;\n    const flows = o.flows.map(([l, r, v], i) => [l, r, Math.round((v * (1 + 0.12 * Math.sin(step * 1.7 + i * 0.9))) / 10) * 10] as typeof o.flows[number]);\n    const sum = (side: 0 | 1, i: number) => flows.reduce((s, f) => s + (f[side] === i ? f[2] : 0), 0);\n    return { ...o, flows, left: o.left.map((n, i) => ({ ...n, v: sum(0, i) })), right: o.right.map((n, i) => ({ ...n, v: sum(1, i) })) };\n  },\n  plot: (K, p, w, d) => sankey(K, p, w, (d as typeof data | undefined) ?? data),\n  tip: (d, g) => tipOf(d as typeof data, g),\n  describe: {\n    title: \"Sessions to outcome\",\n    summary: `${fmt(tot)} sessions this week from ${data.left.length} sources; ${fmt(data.right[0].v)} ended in a purchase (${Math.round((data.right[0].v / tot) * 100)}%), ${data.left[0].label} brought the most at ${fmt(data.left[0].v)}.`,\n    table: { head: [\"Source\", ...data.right.map((r) => r.label)], rows: data.left.map((l, li) => [l.label, ...data.right.map((_, ri) => fmt(data.flows.find((f) => f[0] === li && f[1] === ri)?.[2] ?? 0))]) },\n  },\n};\n"
    }
  ],
  "categories": [
    "charts",
    "flow-between-steps"
  ],
  "meta": {
    "access": "free",
    "no": 31,
    "family": "Flow between steps"
  }
}
