{"spec_id":"scatter-constellation-diagram","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\nscatter-constellation-diagram: Digital Modulation Constellation Diagram\nLibrary: plotly 6.8.0 | Python 3.13.14\nQuality: 93/100 | Updated: 2026-06-18\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport plotly.graph_objects as go\n\n\n# Theme tokens — Imprint palette, theme-adaptive chrome\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nGRID = \"rgba(26,26,23,0.15)\" if THEME == \"light\" else \"rgba(240,239,232,0.15)\"\n\n# Imprint sequential colormap for continuous error magnitude (single-polarity)\nimprint_seq = [[0.0, \"#009E73\"], [1.0, \"#4467A3\"]]\n\n# Data\nnp.random.seed(42)\n\nideal_values = np.array([-3, -1, 1, 3])\nideal_i, ideal_q = np.meshgrid(ideal_values, ideal_values)\nideal_i = ideal_i.flatten()\nideal_q = ideal_q.flatten()\n\nn_symbols = 1200\nsnr_db = 20\nsnr_linear = 10 ** (snr_db / 10)\nsignal_power = np.mean(ideal_i**2 + ideal_q**2)\nnoise_std = np.sqrt(signal_power / snr_linear / 2)\n\nsymbol_indices = np.random.randint(0, 16, n_symbols)\n\nnoise_i = np.random.normal(0, noise_std, n_symbols)\nnoise_q = np.random.normal(0, noise_std, n_symbols)\n\n# Phase offset impairment (~5 degrees carrier phase error)\nphase_offset = np.deg2rad(5)\nref_i = ideal_i[symbol_indices]\nref_q = ideal_q[symbol_indices]\nrotated_i = ref_i * np.cos(phase_offset) - ref_q * np.sin(phase_offset)\nrotated_q = ref_i * np.sin(phase_offset) + ref_q * np.cos(phase_offset)\n\nreceived_i = rotated_i + noise_i\nreceived_q = rotated_q + noise_q\n\n# Error vector magnitude (relative to ideal, not rotated)\nerror_vectors = np.sqrt((received_i - ref_i) ** 2 + (received_q - ref_q) ** 2)\nevm_rms = np.sqrt(np.mean(error_vectors**2)) / np.sqrt(signal_power) * 100\n\n# Plot\nfig = go.Figure()\n\n# Received symbols colored by error magnitude — Imprint sequential colormap\nfig.add_trace(\n    go.Scattergl(\n        x=received_i,\n        y=received_q,\n        mode=\"markers\",\n        marker={\n            \"size\": 9,\n            \"color\": error_vectors,\n            \"colorscale\": imprint_seq,\n            \"opacity\": 0.6,\n            \"colorbar\": {\n                \"title\": {\"text\": \"Error<br>Magnitude\", \"font\": {\"size\": 12, \"color\": INK_SOFT}},\n                \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n                \"thickness\": 16,\n                \"len\": 0.45,\n                \"y\": 0.25,\n                \"outlinewidth\": 0,\n                \"bgcolor\": ELEVATED_BG,\n                \"bordercolor\": INK_SOFT,\n                \"borderwidth\": 1,\n            },\n            \"cmin\": 0,\n            \"cmax\": np.percentile(error_vectors, 97),\n        },\n        name=\"Received symbols\",\n        hovertemplate=\"I: %{x:.3f}<br>Q: %{y:.3f}<br>Error: %{marker.color:.3f}<extra></extra>\",\n        showlegend=True,\n    )\n)\n\n# Ideal constellation points — matte red X markers (semantic anchor for reference targets)\nfig.add_trace(\n    go.Scatter(\n        x=ideal_i,\n        y=ideal_q,\n        mode=\"markers\",\n        marker={\"size\": 20, \"color\": \"rgba(0,0,0,0)\", \"symbol\": \"x\", \"line\": {\"width\": 3, \"color\": \"#AE3030\"}},\n        name=\"Ideal points\",\n        hovertemplate=\"I: %{x}<br>Q: %{y}<extra></extra>\",\n    )\n)\n\n# Decision boundaries — theme-adaptive grid color\nfor boundary in [-2, 0, 2]:\n    fig.add_shape(\n        type=\"line\", x0=boundary, y0=-4.8, x1=boundary, y1=4.8, line={\"color\": GRID, \"width\": 1, \"dash\": \"dash\"}\n    )\n    fig.add_shape(\n        type=\"line\", x0=-4.8, y0=boundary, x1=4.8, y1=boundary, line={\"color\": GRID, \"width\": 1, \"dash\": \"dash\"}\n    )\n\n# EVM annotation — theme-adaptive colors\nfig.add_annotation(\n    x=0.98,\n    y=0.98,\n    xref=\"paper\",\n    yref=\"paper\",\n    text=f\"<b>EVM = {evm_rms:.1f}%</b><br><span style='font-size:10px'>SNR = {snr_db} dB · Phase offset = 5°</span>\",\n    showarrow=False,\n    font={\"size\": 12, \"color\": INK, \"family\": \"Arial\"},\n    bgcolor=ELEVATED_BG,\n    bordercolor=INK_SOFT,\n    borderwidth=1,\n    borderpad=10,\n    xanchor=\"right\",\n    yanchor=\"top\",\n    align=\"right\",\n)\n\nTITLE = \"scatter-constellation-diagram · python · plotly · anyplot.ai\"\n\nfig.update_layout(\n    autosize=False,\n    title={\"text\": TITLE, \"font\": {\"size\": 16, \"color\": INK, \"family\": \"Arial\"}, \"x\": 0.5, \"xanchor\": \"center\"},\n    xaxis={\n        \"title\": {\"text\": \"In-Phase (I)\", \"font\": {\"size\": 12, \"color\": INK, \"family\": \"Arial\"}},\n        \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n        \"range\": [-4.8, 4.8],\n        \"zeroline\": True,\n        \"zerolinewidth\": 1.5,\n        \"zerolinecolor\": INK_SOFT,\n        \"dtick\": 1,\n        \"showgrid\": False,\n        \"constrain\": \"domain\",\n        \"linecolor\": INK_SOFT,\n    },\n    yaxis={\n        \"title\": {\"text\": \"Quadrature (Q)\", \"font\": {\"size\": 12, \"color\": INK, \"family\": \"Arial\"}},\n        \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n        \"range\": [-4.8, 4.8],\n        \"scaleanchor\": \"x\",\n        \"scaleratio\": 1,\n        \"constrain\": \"domain\",\n        \"zeroline\": True,\n        \"zerolinewidth\": 1.5,\n        \"zerolinecolor\": INK_SOFT,\n        \"dtick\": 1,\n        \"showgrid\": False,\n        \"linecolor\": INK_SOFT,\n    },\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    font={\"color\": INK},\n    legend={\n        \"font\": {\"size\": 10, \"color\": INK_SOFT, \"family\": \"Arial\"},\n        \"x\": 0.02,\n        \"y\": 0.98,\n        \"bgcolor\": ELEVATED_BG,\n        \"bordercolor\": INK_SOFT,\n        \"borderwidth\": 1,\n    },\n    margin={\"l\": 80, \"r\": 100, \"t\": 80, \"b\": 60},\n)\n\n# Save — square 2400×2400 (equal aspect required for constellation geometry)\nfig.write_image(f\"plot-{THEME}.png\", width=600, height=600, scale=4)\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}