{"spec_id":"scatter-constellation-diagram","library":"muix","language":"javascript","code":"// anyplot.ai\n// scatter-constellation-diagram: Digital Modulation Constellation Diagram\n// Library: muix 7.29.1 | JavaScript 22.22.3\n// Quality: 86/100 | Created: 2026-06-18\n//# anyplot-orientation: square\n// anyplot.ai\n// scatter-constellation-diagram: Digital Modulation Constellation Diagram\n// Library: MUI X Charts | React | Node 22\n// License: @mui/x-charts — MIT (community). Pro/Premium are out of scope.\n// Quality: pending | Created: 2026-06-18\n\nimport { ScatterChart } from \"@mui/x-charts/ScatterChart\";\nimport { ChartsReferenceLine } from \"@mui/x-charts/ChartsReferenceLine\";\nimport { useDrawingArea } from \"@mui/x-charts/hooks\";\n\nconst t = window.ANYPLOT_TOKENS;\nconst THEME = window.ANYPLOT_THEME;\n\n// Theme-adaptive chrome tokens\nconst ink = THEME === \"light\" ? \"#1A1A17\" : \"#F0EFE8\";\nconst inkSoft = THEME === \"light\" ? \"#4A4A44\" : \"#B8B7B0\";\nconst boundaryStroke =\n  THEME === \"light\" ? \"rgba(26,26,23,0.35)\" : \"rgba(240,239,232,0.35)\";\n\n// --- Seeded LCG RNG for deterministic data ---\nlet _seed = 42;\nfunction lcg() {\n  _seed = (Math.imul(1664525, _seed) + 1013904223) >>> 0;\n  return _seed / 0x100000000;\n}\n\nfunction randNorm(mu, sigma) {\n  const u1 = Math.max(1e-10, lcg());\n  const u2 = lcg();\n  return mu + sigma * Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);\n}\n\n// --- 16-QAM ideal constellation points (4×4 grid at ±1, ±3) ---\nconst idealPoints = [];\nfor (let q = -3; q <= 3; q += 2) {\n  for (let i = -3; i <= 3; i += 2) {\n    idealPoints.push({ x: i, y: q });\n  }\n}\n\n// Received symbols with AWGN (σ=0.15 per axis → EVM ≈ 6.7%)\nconst SIGMA = 0.15;\nconst N_PER_POINT = 62; // 16 × 62 = 992 received symbols total\n\nconst receivedData = idealPoints.flatMap((pt, idx) =>\n  Array.from({ length: N_PER_POINT }, (_, n) => ({\n    id: `rx-${idx}-${n}`,\n    x: randNorm(pt.x, SIGMA),\n    y: randNorm(pt.y, SIGMA),\n  }))\n);\n\nconst idealData = idealPoints.map((pt, i) => ({\n  id: `id-${i}`,\n  x: pt.x,\n  y: pt.y,\n}));\n\n// EVM = σ·√2 / √E_s  where E_s(16-QAM) = 10\nconst evmPct = ((SIGMA * Math.SQRT2) / Math.sqrt(10) * 100).toFixed(1);\n\n// Decision boundaries for 16-QAM: midpoints between symbol positions ±1, ±3\nconst BOUNDARIES = [-2, 0, 2];\n\nconst TITLE = \"scatter-constellation-diagram · javascript · muix · anyplot.ai\";\n\n// --- Custom SVG overlays (rendered inside ChartContainer's SVG context) ---\nfunction PlotTitle() {\n  const { left, top, width } = useDrawingArea();\n  return (\n    <text\n      x={left + width / 2}\n      y={Math.round(top * 0.52)}\n      textAnchor=\"middle\"\n      dominantBaseline=\"middle\"\n      fill={ink}\n      fontSize={22}\n      fontWeight={500}\n      fontFamily=\"sans-serif\"\n    >\n      {TITLE}\n    </text>\n  );\n}\n\nfunction EvmAnnotation() {\n  const { left, top, width } = useDrawingArea();\n  return (\n    <g>\n      <text\n        x={left + 18}\n        y={top + 30}\n        fill={ink}\n        fontSize={17}\n        fontWeight={600}\n        fontFamily=\"monospace, sans-serif\"\n      >\n        {`EVM = ${evmPct}%`}\n      </text>\n      {/* Series key: received symbols */}\n      <circle cx={left + 18} cy={top + 62} r={6} fill=\"rgba(0,158,115,0.5)\" />\n      <text x={left + 34} y={top + 67} fill={inkSoft} fontSize={14} fontFamily=\"sans-serif\">\n        Received symbols\n      </text>\n      {/* Series key: ideal points */}\n      <circle cx={left + 18} cy={top + 88} r={10} fill={t.palette[4]} />\n      <text x={left + 34} y={top + 93} fill={inkSoft} fontSize={14} fontFamily=\"sans-serif\">\n        Ideal points\n      </text>\n    </g>\n  );\n}\n\n// --- Chart ---\nexport default function Chart() {\n  return (\n    <ScatterChart\n      width={window.ANYPLOT_SIZE.width}\n      height={window.ANYPLOT_SIZE.height}\n      skipAnimation\n      colors={[\"rgba(0,158,115,0.5)\", t.palette[4]]}\n      margin={{ top: 72, bottom: 72, left: 72, right: 72 }}\n      series={[\n        {\n          id: \"received\",\n          label: \"Received symbols\",\n          data: receivedData,\n          markerSize: 5,\n          valueFormatter: (v) => `I=${v.x.toFixed(2)}, Q=${v.y.toFixed(2)}`,\n        },\n        {\n          id: \"ideal\",\n          label: \"Ideal points\",\n          data: idealData,\n          markerSize: 12,\n          valueFormatter: (v) => `I=${v.x}, Q=${v.y}`,\n        },\n      ]}\n      xAxis={[{\n        min: -4.5,\n        max: 4.5,\n        label: \"In-Phase (I)\",\n        tickNumber: 5,\n        tickLabelStyle: { fontSize: 14, fill: inkSoft },\n        labelStyle: { fontSize: 16, fill: ink },\n      }]}\n      yAxis={[{\n        min: -4.5,\n        max: 4.5,\n        label: \"Quadrature (Q)\",\n        tickNumber: 5,\n        tickLabelStyle: { fontSize: 14, fill: inkSoft },\n        labelStyle: { fontSize: 16, fill: ink },\n      }]}\n      slotProps={{\n        legend: { hidden: true },\n      }}\n      sx={{\n        \"& .MuiChartsAxis-line\": { stroke: inkSoft },\n        \"& .MuiChartsAxis-tick\": { stroke: inkSoft },\n        \"& .MuiChartsAxis-top .MuiChartsAxis-line\": { display: \"none\" },\n        \"& .MuiChartsAxis-right .MuiChartsAxis-line\": { display: \"none\" },\n      }}\n    >\n      {BOUNDARIES.map((val) => (\n        <ChartsReferenceLine\n          key={`xb${val}`}\n          x={val}\n          lineStyle={{\n            stroke: boundaryStroke,\n            strokeDasharray: \"9 5\",\n            strokeWidth: 1.5,\n          }}\n        />\n      ))}\n      {BOUNDARIES.map((val) => (\n        <ChartsReferenceLine\n          key={`yb${val}`}\n          y={val}\n          lineStyle={{\n            stroke: boundaryStroke,\n            strokeDasharray: \"9 5\",\n            strokeWidth: 1.5,\n          }}\n        />\n      ))}\n      <PlotTitle />\n      <EvmAnnotation />\n    </ScatterChart>\n  );\n}\n"}