{"spec_id":"dot-matrix-proportional","library":"echarts","language":"javascript","code":"// anyplot.ai\n// dot-matrix-proportional: Dot Matrix Chart for Proportional Counts\n// Library: echarts 6.1.0 | JavaScript 22.23.2\n// Quality: 89/100 | Created: 2026-09-05\n\n//# anyplot-orientation: landscape\nconst t = window.ANYPLOT_TOKENS;\n// \"muted\" semantic anchor (other/rest/neutral) isn't in ANYPLOT_TOKENS — derive it\n// from the theme-adaptive hexes in default-style-guide.md \"Semantic anchors\".\nconst muted = t.theme === \"dark\" ? \"#A8A79F\" : \"#6B6A63\";\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Customer satisfaction survey: 200 respondents, filled left-to-right, top-to-bottom.\nconst total = 200;\nconst cols = 20;\nconst rows = 10;\nconst categories = [\n  { name: \"Satisfied\", count: 118, color: t.palette[0] }, // positive sentiment -> Imprint brand green\n  { name: \"Neutral\", count: 52, color: muted }, // neutral sentiment -> Imprint neutral anchor\n  // negative sentiment -> Imprint semantic red; dashed ink border is a non-color\n  // secondary cue so the two sentiment extremes stay distinguishable under\n  // red-green color-vision deficiency, not just by hue.\n  { name: \"Dissatisfied\", count: 30, color: t.palette[4], accent: true },\n];\n\nlet cursor = 0;\nconst series = categories.map((cat) => {\n  const points = [];\n  for (let i = 0; i < cat.count; i += 1) {\n    const idx = cursor + i;\n    points.push([idx % cols, Math.floor(idx / cols)]);\n  }\n  cursor += cat.count;\n  const pct = Math.round((cat.count / total) * 100);\n  return {\n    name: `${cat.name} — ${cat.count} (${pct}%)`,\n    type: \"scatter\",\n    symbol: \"circle\",\n    symbolSize: 44,\n    data: points,\n    itemStyle: cat.accent\n      ? { color: cat.color, borderColor: t.ink, borderWidth: 2, borderType: \"dashed\" }\n      : { color: cat.color, borderColor: t.pageBg, borderWidth: 1.5 },\n  };\n});\n\n// Emphasize the majority category (ECharts markArea) to lift plain grid-of-\n// circles into a small \"at a glance\" callout, and to showcase a native\n// ECharts feature beyond a generic scatter port.\nconst majority = categories.reduce((best, cat) => (cat.count > best.count ? cat : best));\nconst majorityFullRows = majority === categories[0] ? Math.floor(majority.count / cols) : 0;\nif (majorityFullRows > 0) {\n  series[0].markArea = {\n    silent: true,\n    itemStyle: { color: majority.color, opacity: 0.08 },\n    label: {\n      show: true,\n      position: \"insideTopLeft\",\n      color: t.inkSoft,\n      fontSize: 13,\n      fontWeight: 500,\n      formatter: `Majority — ${Math.round((majority.count / total) * 100)}%`,\n    },\n    data: [\n      [\n        { xAxis: -0.6, yAxis: -0.6 },\n        { xAxis: cols - 0.4, yAxis: majorityFullRows - 0.4 },\n      ],\n    ],\n  };\n}\n\n// --- Init --------------------------------------------------------------------\nconst chart = echarts.init(document.getElementById(\"container\"));\n\n// --- Option --------------------------------------------------------------------\nchart.setOption({\n  animation: false,\n  color: t.palette,\n  backgroundColor: \"transparent\",\n  title: {\n    text: \"dot-matrix-proportional · javascript · echarts · anyplot.ai\",\n    left: \"center\",\n    top: 24,\n    textStyle: { color: t.ink, fontSize: 22, fontWeight: 500 },\n  },\n  legend: {\n    bottom: 20,\n    left: \"center\",\n    itemGap: 32,\n    itemWidth: 16,\n    itemHeight: 16,\n    icon: \"circle\",\n    textStyle: { color: t.inkSoft, fontSize: 16 },\n  },\n  grid: { left: 90, right: 90, top: 140, bottom: 110 },\n  xAxis: {\n    type: \"value\",\n    min: -0.6,\n    max: cols - 0.4,\n    show: false,\n    splitLine: { show: false },\n  },\n  yAxis: {\n    type: \"value\",\n    min: -0.6,\n    max: rows - 0.4,\n    inverse: true,\n    show: false,\n    splitLine: { show: false },\n  },\n  tooltip: {\n    trigger: \"item\",\n    formatter: (p) => p.seriesName,\n  },\n  series,\n});\n"}