{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "orbit",
  "type": "registry:component",
  "title": "Orbit",
  "description": "Tiers around a centre, with how active each is. Sample data: active wallets in four tiers, whales 42 (92% active), small 10.2K (64% active).",
  "registryDependencies": [
    "@chartory/core",
    "@chartory/plot-orbits2"
  ],
  "files": [
    {
      "path": "registry/chartory/components/orbit.tsx",
      "type": "registry:component",
      "target": "components/chartory/orbit.tsx",
      "content": "// SPDX-License-Identifier: MIT · Copyright (c) 2026 BEY AGENCY LTD · Chartory, usechartory.com\n// Orbit (Chartory chart 041): 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/orbit\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/041-orbit/data\"\nimport { def } from \"@/lib/chartory/charts/types/041-orbit/layout\"\n\nregisterChart(def, sample)\n\nexport type OrbitData = typeof sample\nexport type OrbitProps = Omit<ChartViewProps, \"def\" | \"data\" | \"states\"> & {\n  /** the data to draw (default: the sample) */\n  data?: OrbitData\n  /** further data states: the chart morphs to them (update) or ticks through them (live, with interval) */\n  states?: OrbitData[]\n}\n\nexport function Orbit(props: OrbitProps) {\n  return <ChartView def={def} {...props} />\n}\n"
    },
    {
      "path": "registry/chartory/charts/types/041-orbit/data.ts",
      "type": "registry:lib",
      "target": "lib/chartory/charts/types/041-orbit/data.ts",
      "content": "// SPDX-License-Identifier: MIT · Copyright (c) 2026 BEY AGENCY LTD · Chartory, usechartory.com\n// 041 Orbit · sample data (Figma chart/041, crypto: active wallets this week by holder tier).\nimport type { Orbits2Data } from \"../../plots/orbits2\";\n\nexport const data: Orbits2Data = {\n  active: 87,\n  rings: [\n    { label: \"Whales\", n: 12, size: 10, active: 92, tok: \"data/primary\", text: \"42\" },\n    { label: \"Large\", n: 20, size: 8, active: 88, tok: \"data/cat-4\", text: \"1.2K\" },\n    { label: \"Mid\", n: 30, size: 6, active: 81, tok: \"data/cat-2\", text: \"6.8K\" },\n    { label: \"Small\", n: 44, size: 4, active: 64, tok: \"data/cat-8\", text: \"10.2K\" },\n  ],\n};\n"
    },
    {
      "path": "registry/chartory/charts/types/041-orbit/layout.ts",
      "type": "registry:lib",
      "target": "lib/chartory/charts/types/041-orbit/layout.ts",
      "content": "// SPDX-License-Identifier: MIT · Copyright (c) 2026 BEY AGENCY LTD · Chartory, usechartory.com\n// 041 Orbit (Figma chart/041): C.orbits2; no legend (the side list names every tier). Radius-bound: XS legend=off\n// keeps the legend-on plot height (flatXs; the Figma XS plot is 150 high, centred in 212).\nimport { buildVariant, type ChartDef, type Size, type TooltipSpec } from \"../../core/shell\";\nimport { orbits2 } from \"../../plots/orbits2\";\nimport { data } from \"./data\";\n\nconst num = (t: string) => parseFloat(t) * (t.endsWith(\"K\") ? 1000 : 1);\nconst fmt = (v: number) => (v >= 1000 ? (v / 1000).toFixed(1) + \"K\" : String(Math.round(v)));\n\n/** Next week, moved by `a` (0–1) with the phase `ph`: wallet counts ±10 % per tier, each tier's active share ±3\n *  points inside the band that keeps its count of active dots; the overall share moves by the tiers' weighted change. */\nfunction later(o: typeof data, step: number, a: number, ph: number): typeof data {\n  const rings = o.rings.map((r, i) => {\n    const act = Math.round((r.n * r.active) / 100);\n    const lo = Math.ceil(((act - 0.5) * 100) / r.n), hi = Math.floor(((act + 0.5) * 100) / r.n - 1e-9);\n    const active = Math.max(lo, Math.min(hi, Math.round(r.active + a * 3 * Math.sin(step * 1.9 + i * 1.4 + ph))));\n    return { ...r, active, text: fmt(num(r.text) * (1 + a * 0.1 * Math.sin(step * 1.5 + i * 2.2 + ph))) };\n  });\n  const w = rings.map((r) => num(r.text)), tw = w.reduce((s, v) => s + v, 0);\n  const shift = rings.reduce((s, r, i) => s + ((r.active - o.rings[i].active) * w[i]) / tw, 0);\n  return { ...o, active: Math.round(o.active + shift), rings };\n}\n\n// The side list drops its percentages, stacks or truncates when a row gets too wide for its size; the update takes the\n// first move (full first, then other phases, then smaller) that keeps every variant's marks.\nconst shape = (d: unknown) => ([\"xs\", \"s\", \"m\", \"l\"] as Size[]).flatMap((s) => buildVariant(def, s, false, d).marks.map((m) => m.kind + \"|\" + (m.kind === \"text\" ? m.style : m.name)).join(\"\\n\")).join(\"\\n\\n\");\nlet base: string | undefined;\nfunction next(o: typeof data, step: number): typeof data {\n  base ??= shape(data);\n  for (const a of [1, 0.6]) for (const ph of [0, 0.8, 1.6, 2.4, 3.2]) { const c = later(o, step, a, ph); if (shape(c) === base) return c; }\n  return later(o, step, 0, 0);\n}\n\n/** Tooltip for one tier (group = the tier's orbit of dots): its active share against all wallets. */\nfunction tipOf(o: typeof data, g: number): TooltipSpec | null {\n  const r = o.rings[g];\n  if (!r) return null;\n  const d = r.active - o.active;\n  return {\n    kind: \"compare\", caps: `${r.label} · ${r.text} WALLETS`.toUpperCase(), value: r.active + \"% active\",\n    delta: { text: (d > 0 ? \"+\" : d < 0 ? \"−\" : \"±\") + Math.abs(d) + \" pts\", tone: d > 0 ? \"success\" : d < 0 ? \"danger\" : \"neutral\" }, comparison: `VS ALL TIERS ${o.active}%`,\n  };\n}\n\nexport const def: ChartDef = {\n  no: 41,\n  slug: \"orbit\",\n  legend: null,\n  flatXs: true,\n  // update: next week (see later), with the same marks at every size (see next)\n  update: (d, step) => next(d as typeof data, step),\n  plot: (K, p, w, d) => orbits2(K, p, w, (d as typeof data | undefined) ?? data),\n  tip: (d, g) => tipOf(d as typeof data, g),\n  describe: {\n    title: \"Active wallets by tier\",\n    summary: `${data.active}% of wallets were active this week: ${data.rings.map((r) => `${r.label} ${r.text} (${r.active}% active)`).join(\", \")}.`,\n    table: { head: [\"Tier\", \"Wallets\", \"Active\"], rows: data.rings.map((r) => [r.label, r.text, r.active + \"%\"]) },\n  },\n};\n"
    }
  ],
  "categories": [
    "charts",
    "relationship"
  ],
  "meta": {
    "access": "free",
    "no": 41,
    "family": "Relationship"
  }
}
