{"spec_id":"dot-matrix-proportional","library":"highcharts","language":"javascript","code":"// anyplot.ai\n// dot-matrix-proportional: Dot Matrix Chart for Proportional Counts\n// Library: highcharts 12.6.0 | JavaScript 22.23.2\n// Quality: 93/100 | Created: 2026-09-05\n//# anyplot-orientation: square\n\nconst t = window.ANYPLOT_TOKENS;\n// ANYPLOT_TOKENS has no \"muted\" field — derive the theme-adaptive muted-ink\n// anchor locally (see prompts/default-style-guide.md \"Theme-adaptive Chrome\").\nconst inkMuted = t.theme === \"dark\" ? \"#A8A79F\" : \"#6B6A63\";\n\n// --- Data (in-memory, deterministic) ---------------------------------------\nconst total = 100;\nconst gridCols = 10;\nconst gridRows = 10;\n\n// Sentiment/polarity semantic exception: satisfied -> green, dissatisfied ->\n// red, neutral -> the theme-adaptive muted anchor (default-style-guide.md\n// \"Color Philosophy\" -> \"Semantic exception\").\nconst categories = [\n  { name: \"Satisfied\", count: 47, color: t.palette[0] },\n  { name: \"Neutral\", count: 31, color: inkMuted },\n  { name: \"Dissatisfied\", count: 22, color: t.palette[4] },\n];\n\nconst majority = categories.reduce((a, b) => (b.count > a.count ? b : a));\nconst majorityPct = Math.round((majority.count / total) * 100);\n\n// Extra breathing room between dots (uniform size is a spec requirement, so\n// the gutter comes from spacing the grid coordinates, not shrinking dots).\nconst gutter = 1.12;\n\n// Fill dots left-to-right, top-to-bottom, sequentially across categories.\nlet cursor = 0;\nconst series = categories.map((cat) => {\n  const data = [];\n  for (let i = 0; i < cat.count; i += 1, cursor += 1) {\n    data.push([\n      (cursor % gridCols) * gutter,\n      Math.floor(cursor / gridCols) * gutter,\n    ]);\n  }\n  const pct = Math.round((cat.count / total) * 100);\n  return {\n    name: cat.name,\n    color: cat.color,\n    // Arbitrary user data slot (Highcharts \"custom\" option) so the legend\n    // formatter and tooltip below can surface count/pct without re-parsing\n    // the series name.\n    custom: { count: cat.count, pct },\n    // Subtle radial gradient (via Highcharts' own Color.brighten helper)\n    // gives the flat circles a touch of depth while keeping uniform size.\n    marker: {\n      fillColor: {\n        radialGradient: { cx: 0.35, cy: 0.35, r: 0.75 },\n        stops: [\n          [0, Highcharts.color(cat.color).brighten(0.3).get()],\n          [1, cat.color],\n        ],\n      },\n    },\n    data,\n  };\n});\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  title: {\n    text: \"dot-matrix-proportional · javascript · highcharts · anyplot.ai\",\n    style: { color: t.ink, fontSize: \"22px\", fontWeight: \"600\" },\n  },\n  subtitle: {\n    text: `Customer satisfaction survey · 100 respondents, one dot each — ${majority.name} leads at ${majorityPct}%`,\n    style: { color: t.inkSoft, fontSize: \"14px\" },\n  },\n  xAxis: {\n    min: -0.65,\n    max: (gridCols - 1) * gutter + 0.65,\n    visible: false,\n  },\n  yAxis: {\n    min: -0.65,\n    max: (gridRows - 1) * gutter + 0.65,\n    reversed: true,\n    title: { text: null },\n    visible: false,\n  },\n  legend: {\n    align: \"center\",\n    verticalAlign: \"bottom\",\n    symbolRadius: 6,\n    useHTML: true,\n    itemStyle: { color: t.inkSoft, fontSize: \"14px\" },\n    itemHoverStyle: { color: t.ink },\n    // Highcharts-distinctive touch: useHTML legend items with an inline\n    // mini count-bar next to each label, instead of plain text.\n    labelFormatter: function () {\n      const { count, pct } = this.userOptions.custom;\n      const barWidth = Math.max(4, Math.round((pct / 100) * 60));\n      return (\n        `<span style=\"display:inline-flex;align-items:center;gap:8px;color:${t.inkSoft};font-size:14px;\">` +\n        `<span>${this.name}</span>` +\n        `<span style=\"display:inline-block;width:60px;height:6px;border-radius:3px;background:${t.grid};overflow:hidden;\">` +\n        `<span style=\"display:block;width:${barWidth}px;height:100%;background:${this.color};\"></span>` +\n        `</span>` +\n        `<span style=\"font-variant-numeric:tabular-nums;\">${count} (${pct}%)</span>` +\n        `</span>`\n      );\n    },\n  },\n  tooltip: {\n    backgroundColor: t.elevatedBg,\n    borderColor: t.grid,\n    style: { color: t.ink, fontSize: \"13px\" },\n    headerFormat: \"\",\n    pointFormatter: function () {\n      const { count, pct } = this.series.userOptions.custom;\n      return `${this.series.name}: ${count} (${pct}%)`;\n    },\n  },\n  plotOptions: {\n    series: {\n      animation: false,\n      marker: {\n        radius: 36,\n        symbol: \"circle\",\n        lineWidth: 2,\n        lineColor: t.pageBg,\n      },\n      shadow: { color: \"rgba(0,0,0,0.18)\", offsetX: 0, offsetY: 2, width: 4 },\n      states: { hover: { halo: { size: 0 } } },\n    },\n  },\n  series,\n});\n"}