{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "term-cloud",
  "type": "registry:component",
  "title": "Term cloud",
  "description": "The words people use, sized by weight. Sample data: support conversations, 20 terms.",
  "registryDependencies": [
    "@chartory/core",
    "@chartory/plot-cloud"
  ],
  "files": [
    {
      "path": "registry/chartory/components/term-cloud.tsx",
      "type": "registry:component",
      "target": "components/chartory/term-cloud.tsx",
      "content": "// SPDX-License-Identifier: MIT · Copyright (c) 2026 BEY AGENCY LTD · Chartory, usechartory.com\n// Term cloud (Chartory chart 073): 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/term-cloud\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/073-term-cloud/data\"\nimport { def } from \"@/lib/chartory/charts/types/073-term-cloud/layout\"\n\nregisterChart(def, sample)\n\nexport type TermCloudData = typeof sample\nexport type TermCloudProps = Omit<ChartViewProps, \"def\" | \"data\" | \"states\"> & {\n  /** the data to draw (default: the sample) */\n  data?: TermCloudData\n  /** further data states: the chart morphs to them (update) or ticks through them (live, with interval) */\n  states?: TermCloudData[]\n}\n\nexport function TermCloud(props: TermCloudProps) {\n  return <ChartView def={def} {...props} />\n}\n"
    },
    {
      "path": "registry/chartory/charts/types/073-term-cloud/data.ts",
      "type": "registry:lib",
      "target": "lib/chartory/charts/types/073-term-cloud/data.ts",
      "content": "// SPDX-License-Identifier: MIT · Copyright (c) 2026 BEY AGENCY LTD · Chartory, usechartory.com\n// 073 Term cloud · sample data (Figma chart/073-term-cloud-support, SaaS: support ticket topics by weight and tone).\nimport type { CloudData } from \"../../plots/cloud\";\n\nexport const data: CloudData = {\n  words: [\n    [\"refund\", 0.62, \"neg\"], [\"invoice\", 0.4], [\"login\", 0.9], [\"SSO\", 0.5], [\"export\", 0.3], [\"API key\", 0.7],\n    [\"slow sync\", 0.55, \"neg\"], [\"pricing\", 0.28], [\"webhooks\", 0.35], [\"thanks\", 0.45, \"pos\"], [\"2FA\", 0.2], [\"seats\", 0.33],\n    [\"dark mode\", 0.22, \"pos\"], [\"billing\", 0.66], [\"timeout\", 0.42, \"neg\"], [\"onboarding\", 0.26, \"pos\"], [\"Slack\", 0.3], [\"CSV\", 0.18],\n    [\"rate limit\", 0.38, \"neg\"], [\"great\", 0.24, \"pos\"],\n  ],\n};\n"
    },
    {
      "path": "registry/chartory/charts/types/073-term-cloud/layout.ts",
      "type": "registry:lib",
      "target": "lib/chartory/charts/types/073-term-cloud/layout.ts",
      "content": "// SPDX-License-Identifier: MIT · Copyright (c) 2026 BEY AGENCY LTD · Chartory, usechartory.com\n// 073 Term cloud (Figma chart/073-term-cloud-support): C.cloud; the legend names the three tones.\nimport type { ChartDef, TooltipSpec } from \"../../core/shell\";\nimport { cloud } from \"../../plots/cloud\";\nimport { data } from \"./data\";\n\n// weight tiers of C.cloud (Body/S … Display/M); the neutral colour also switches at 0.45, a tier edge\nconst TIERS = [0, 0.25, 0.45, 0.65, 0.85, 1];\n\n// tooltip: one term (group = term index): weight and tone\nconst tip = (o: typeof data, g: number): TooltipSpec | null => {\n  const w = o.words[g];\n  if (!w) return null;\n  const [t, wt, tone] = w;\n  return { kind: \"value\", text: t + \" · \" + Math.round(wt * 100) + \"% · \" + (tone === \"neg\" ? \"Negative\" : tone === \"pos\" ? \"Positive\" : \"Neutral\") };\n};\n\nexport const def: ChartDef = {\n  no: 73,\n  slug: \"term-cloud\",\n  legend: [[\"status/danger\", \"Negative\"], [\"text/action\", \"Neutral\"], [\"status/success\", \"Positive\"]],\n  plot: (K, p, w, d) => cloud(K, p, w, (d as typeof data | undefined) ?? data),\n  tip: (d, g) => tip(d as typeof data, g),\n  // the next week: every term's weight moves up to ±0.04 inside its own tier, so every term keeps its style; the\n  // cloud then re-deals the terms of each tier over that tier's places (rotated by the step), so the terms trade\n  // places and the wrapped rows re-flow while every place keeps its style (same marks, same sequence of styles)\n  update: (d, step) => {\n    const o = d as typeof data;\n    const tierOf = (wt: number) => TIERS.findIndex((e, j) => wt >= e && wt < (TIERS[j + 1] ?? 2));\n    const words = o.words.map(([t, wt, tone], i) => {\n      const k = tierOf(wt);\n      const v = Math.min(TIERS[k + 1] - 0.01, Math.max(TIERS[k] + 0.01, Math.round((wt + 0.04 * Math.sin(i * 1.9 + step * 2.3)) * 100) / 100));\n      return (tone ? [t, v, tone] : [t, v]) as (typeof o.words)[number];\n    });\n    const out = [...words];\n    for (let k = 0; k < TIERS.length - 1; k++) {\n      const slots = words.map((w, i) => (tierOf(w[1]) === k ? i : -1)).filter((i) => i >= 0);\n      slots.forEach((s, j) => { out[s] = words[slots[(j + step) % slots.length]]; });\n    }\n    return { ...o, words: out };\n  },\n  describe: {\n    title: \"Support topics\",\n    summary: `Support ticket topics sized by weight: \"${[...data.words].sort((a, b) => b[1] - a[1])[0][0]}\" is the largest; ${data.words.filter((x) => x[2] === \"neg\").length} topics read negative, ${data.words.filter((x) => x[2] === \"pos\").length} positive.`,\n    table: {\n      head: [\"Topic\", \"Weight\", \"Tone\"],\n      rows: [...data.words].sort((a, b) => b[1] - a[1]).map(([t, wt, tone]) => [t, Math.round(wt * 100) + \"%\", tone === \"neg\" ? \"Negative\" : tone === \"pos\" ? \"Positive\" : \"Neutral\"]),\n    },\n  },\n};\n"
    }
  ],
  "categories": [
    "charts",
    "compare-items"
  ],
  "meta": {
    "access": "free",
    "no": 73,
    "family": "Compare items"
  }
}
