{"spec_id":"scatter-categorical","library":"highcharts","language":"javascript","code":"// anyplot.ai\n// scatter-categorical: Categorical Scatter Plot\n// Library: highcharts 12.6.0 | JavaScript 22.23.2\n// Quality: 92/100 | Created: 2026-09-05\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic LCG) -----------------------------------\nfunction makeLcg(seed) {\n  let state = seed;\n  return function lcg() {\n    state = (state * 1664525 + 1013904223) % 4294967296;\n    return state / 4294967296;\n  };\n}\n\nfunction gaussian(rng) {\n  const u1 = Math.max(rng(), 1e-9);\n  const u2 = rng();\n  return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);\n}\n\n// Ordinary least-squares fit, used to draw a per-tier trend overlay.\nfunction linearFit(points) {\n  const n = points.length;\n  const sumX = points.reduce((acc, [x]) => acc + x, 0);\n  const sumY = points.reduce((acc, [, y]) => acc + y, 0);\n  const sumXY = points.reduce((acc, [x, y]) => acc + x * y, 0);\n  const sumXX = points.reduce((acc, [x]) => acc + x * x, 0);\n  const slope = (n * sumXY - sumX * sumY) / (n * sumXX - sumX * sumX);\n  const intercept = (sumY - slope * sumX) / n;\n  return { slope, intercept };\n}\n\nconst rng = makeLcg(42);\n\nconst tiers = [\n  { name: \"Basic\", engagement: 30, spend: 25, spread: 8, symbol: \"circle\" },\n  { name: \"Pro\", engagement: 55, spend: 65, spread: 9, symbol: \"diamond\" },\n  { name: \"Enterprise\", engagement: 78, spend: 140, spread: 10, symbol: \"triangle\" },\n];\n\nconst scatterSeries = tiers.map((tier, i) => {\n  const data = [];\n  for (let j = 0; j < 60; j += 1) {\n    const engagement = tier.engagement + gaussian(rng) * 9;\n    const spend = tier.spend + engagement * 0.6 + gaussian(rng) * tier.spread;\n    data.push([Math.round(engagement * 10) / 10, Math.round(spend * 10) / 10]);\n  }\n  return {\n    type: \"scatter\",\n    name: tier.name,\n    data,\n    color: t.palette[i],\n    marker: { symbol: tier.symbol, radius: 5, lineWidth: 1, lineColor: t.pageBg },\n  };\n});\n\n// Trend overlay: one thin dashed regression line per tier, sharing its\n// series color — a genuine per-group fit, not a decorative flourish.\nconst trendSeries = scatterSeries.map((s) => {\n  const xs = s.data.map((p) => p[0]);\n  const { slope, intercept } = linearFit(s.data);\n  const xMin = Math.min(...xs);\n  const xMax = Math.max(...xs);\n  return {\n    type: \"line\",\n    name: `${s.name} trend`,\n    data: [\n      [xMin, slope * xMin + intercept],\n      [xMax, slope * xMax + intercept],\n    ],\n    color: s.color,\n    lineWidth: 2,\n    dashStyle: \"ShortDash\",\n    marker: { enabled: false },\n    enableMouseTracking: false,\n    showInLegend: false,\n  };\n});\n\nconst series = [...scatterSeries, ...trendSeries];\n\n// --- Chart -----------------------------------------------------------------\nHighcharts.chart(\"container\", {\n  chart: {\n    type: \"scatter\",\n    backgroundColor: \"transparent\",\n    animation: false,\n    style: { fontFamily: \"inherit\" },\n  },\n  credits: { enabled: false },\n  colors: t.palette,\n  title: {\n    text: \"scatter-categorical · javascript · highcharts · anyplot.ai\",\n    style: { color: t.ink, fontSize: \"22px\", fontWeight: \"700\" },\n  },\n  xAxis: {\n    title: { text: \"Engagement Score\", style: { color: t.inkSoft, fontSize: \"16px\" } },\n    lineColor: t.inkSoft,\n    tickLength: 0,\n    gridLineWidth: 0,\n    labels: { style: { color: t.inkSoft, fontSize: \"14px\" } },\n  },\n  yAxis: {\n    title: { text: \"Monthly Spend ($)\", style: { color: t.inkSoft, fontSize: \"16px\" } },\n    lineColor: t.inkSoft,\n    tickLength: 0,\n    gridLineWidth: 1,\n    gridLineColor: t.grid,\n    labels: { style: { color: t.inkSoft, fontSize: \"14px\" } },\n  },\n  legend: {\n    title: {\n      text: \"Subscription Tier\",\n      style: { color: t.ink, fontSize: \"14px\", fontWeight: \"400\" },\n    },\n    itemStyle: { color: t.inkSoft, fontSize: \"14px\" },\n    itemHoverStyle: { color: t.ink },\n  },\n  plotOptions: {\n    series: { animation: false },\n    scatter: { opacity: 0.65, states: { hover: { enabled: true } } },\n  },\n  series,\n});\n"}