{"spec_id":"wordcloud-basic","library":"highcharts","language":"javascript","code":"// anyplot.ai\n// wordcloud-basic: Basic Word Cloud\n// Library: highcharts 12.6.0 | JavaScript 22.23.1\n// Quality: 88/100 | Created: 2026-08-04\n\n// Only the core `highcharts` bundle is loaded — the `wordcloud` series type\n// lives in modules/wordcloud.js, which is not vendored (see prompts/library/\n// highcharts.md \"No add-on modules\"). Instead of NOT_FEASIBLE, this snippet\n// builds the layout itself: an Archimedean spiral packer measures each word\n// with canvas `measureText`, places it at the tightest non-overlapping spot,\n// and renders the words as a plain scatter series (invisible markers, sized\n// dataLabels) — a genuine word cloud, not a simulation of one.\n\nconst t = window.ANYPLOT_TOKENS;\nconst size = window.ANYPLOT_SIZE;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Term frequencies mined from customer-support tickets for a cloud analytics\n// platform — a common word-cloud use case (spec: \"survey responses ...\n// feedback patterns\").\nconst terms = [\n  [\"performance\", 187],\n  [\"reliability\", 156],\n  [\"dashboard\", 142],\n  [\"latency\", 128],\n  [\"integration\", 119],\n  [\"security\", 108],\n  [\"api\", 97],\n  [\"documentation\", 89],\n  [\"pricing\", 82],\n  [\"support\", 76],\n  [\"scalability\", 71],\n  [\"usability\", 66],\n  [\"onboarding\", 61],\n  [\"automation\", 57],\n  [\"monitoring\", 53],\n  [\"alerts\", 49],\n  [\"backup\", 46],\n  [\"compliance\", 43],\n  [\"migration\", 40],\n  [\"uptime\", 37],\n  [\"analytics\", 35],\n  [\"reporting\", 33],\n  [\"customization\", 31],\n  [\"mobile\", 29],\n  [\"collaboration\", 27],\n  [\"workflow\", 25],\n  [\"notifications\", 23],\n  [\"authentication\", 21],\n  [\"deployment\", 19],\n  [\"feedback\", 17],\n];\n\n// --- Layout: Archimedean spiral packer --------------------------------------\nconst MIN_FONT = 22;\nconst MAX_FONT = 88;\nconst FONT_STACK =\n  '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif';\n\nconst marginTop = 76;\nconst marginBottom = 44;\nconst marginSide = 30;\nconst plotWidth = size.width - marginSide * 2;\nconst plotHeight = size.height - marginTop - marginBottom;\n\nconst freqs = terms.map((d) => d[1]);\nconst freqMin = Math.min(...freqs);\nconst freqMax = Math.max(...freqs);\nconst freqTotal = freqs.reduce((sum, f) => sum + f, 0);\n\nconst measureCtx = document.createElement(\"canvas\").getContext(\"2d\");\n\nfunction fontSizeFor(freq) {\n  const ratio = Math.sqrt((freq - freqMin) / (freqMax - freqMin));\n  return Math.round(MIN_FONT + (MAX_FONT - MIN_FONT) * ratio);\n}\n\nfunction fontWeightFor(fontSize) {\n  if (fontSize >= 65) return \"700\";\n  if (fontSize >= 42) return \"600\";\n  return \"500\";\n}\n\n// A handful of words tilt off-horizontal (a common word-cloud convention) so\n// the cloud reads as organically packed rather than size-sorted; most stay\n// horizontal for legibility.\nfunction rotationFor(i) {\n  if (i % 4 === 3) return 90;\n  if (i % 6 === 1) return -30;\n  return 0;\n}\n\nfunction rotatedBounds(w, h, rotationDeg) {\n  const rad = (rotationDeg * Math.PI) / 180;\n  return {\n    w: Math.abs(w * Math.cos(rad)) + Math.abs(h * Math.sin(rad)),\n    h: Math.abs(w * Math.sin(rad)) + Math.abs(h * Math.cos(rad)),\n  };\n}\n\nfunction rectsOverlap(a, b, pad) {\n  return (\n    a.x - pad < b.x + b.w &&\n    a.x + a.w + pad > b.x &&\n    a.y - pad < b.y + b.h &&\n    a.y + a.h + pad > b.y\n  );\n}\n\nconst placedBoxes = [];\nconst points = [];\n// Spiral center nudged slightly down-right of plot-area center so the packer\n// fills the lower-right quadrant as evenly as the rest of the canvas.\nconst cx = plotWidth * 0.52;\nconst cy = plotHeight * 0.55;\n\nterms.forEach(([word, freq], i) => {\n  const fontSize = fontSizeFor(freq);\n  const fontWeight = fontWeightFor(fontSize);\n  const rotation = rotationFor(i);\n  measureCtx.font = `${fontWeight} ${fontSize}px ${FONT_STACK}`;\n  const rawW = measureCtx.measureText(word).width * 1.06; // small safety margin\n  const rawH = fontSize * 1.25;\n  const { w, h } = rotatedBounds(rawW, rawH, rotation);\n\n  let angle = 0;\n  let radius = 0;\n  let x = cx;\n  let y = cy;\n  let ok = false;\n\n  for (let attempt = 0; attempt < 4000; attempt++) {\n    const box = { x: x - w / 2, y: y - h / 2, w, h };\n    const inBounds =\n      box.x >= 2 && box.y >= 2 && box.x + w <= plotWidth - 2 && box.y + h <= plotHeight - 2;\n    if (inBounds && !placedBoxes.some((b) => rectsOverlap(box, b, 3))) {\n      placedBoxes.push(box);\n      ok = true;\n      break;\n    }\n    angle += 0.32;\n    radius += 1.8;\n    x = cx + radius * Math.cos(angle);\n    y = cy + radius * Math.sin(angle) * (plotHeight / plotWidth);\n  }\n\n  if (ok) {\n    points.push({\n      x,\n      y,\n      name: word,\n      freq,\n      share: freq / freqTotal,\n      color: t.palette[i % t.palette.length],\n      dataLabels: {\n        rotation,\n        style: {\n          fontSize: `${fontSize}px`,\n          fontWeight,\n          color: t.palette[i % t.palette.length],\n        },\n      },\n    });\n  }\n});\n\n// --- Chart -------------------------------------------------------------------\nHighcharts.chart(\"container\", {\n  chart: {\n    type: \"scatter\",\n    backgroundColor: \"transparent\",\n    animation: false,\n    margin: [marginTop, marginSide, marginBottom, marginSide],\n    style: { fontFamily: \"inherit\" },\n  },\n  credits: { enabled: false },\n  colors: t.palette,\n  title: {\n    text: \"wordcloud-basic · javascript · highcharts · anyplot.ai\",\n    style: { color: t.ink, fontSize: \"22px\", fontWeight: \"600\" },\n  },\n  xAxis: { min: 0, max: plotWidth, visible: false },\n  yAxis: { min: 0, max: plotHeight, reversed: true, visible: false, title: { text: null } },\n  legend: { enabled: false },\n  tooltip: {\n    backgroundColor: t.elevatedBg,\n    style: { color: t.ink },\n    formatter() {\n      const pct = (this.point.share * 100).toFixed(1);\n      return `<b>${this.point.name}</b>: ${this.point.freq} mentions (${pct}% of corpus)`;\n    },\n  },\n  plotOptions: {\n    series: { animation: false },\n    scatter: {\n      marker: { enabled: false, states: { hover: { enabled: false } } },\n      dataLabels: {\n        enabled: true,\n        format: \"{point.name}\",\n        align: \"center\",\n        verticalAlign: \"middle\",\n        allowOverlap: true,\n        crop: false,\n        overflow: \"allow\",\n        style: { fontFamily: \"inherit\", textOutline: \"none\" },\n      },\n      states: { inactive: { opacity: 1 } },\n      point: {\n        events: {\n          // Bring the hovered word's label above its spiral-packed neighbors\n          // — an idiomatic use of Highcharts' SVGElement.toFront().\n          mouseOver() {\n            if (this.dataLabel) this.dataLabel.toFront();\n          },\n        },\n      },\n    },\n  },\n  series: [{ name: \"Support ticket terms\", data: points }],\n});\n"}