{"spec_id":"scatter-constellation-diagram","library":"matplotlib","language":"python","code":"\"\"\" anyplot.ai\nscatter-constellation-diagram: Digital Modulation Constellation Diagram\nLibrary: matplotlib 3.11.0 | Python 3.13.13\nQuality: 92/100 | Updated: 2026-06-18\n\"\"\"\n\nimport os\n\nimport matplotlib.colors as mcolors\nimport matplotlib.patheffects as pe\nimport matplotlib.pyplot as plt\nimport numpy as np\nfrom matplotlib.colors import LinearSegmentedColormap\n\n\n# Theme tokens — Imprint palette, see prompts/default-style-guide.md\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\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Imprint sequential colormap for error magnitude (single-polarity continuous data)\nimprint_seq = LinearSegmentedColormap.from_list(\"imprint_seq\", [\"#009E73\", \"#4467A3\"])\n\n# Data — 16-QAM constellation\nnp.random.seed(42)\n\nideal_levels = np.array([-3, -1, 1, 3])\nideal_i, ideal_q = np.meshgrid(ideal_levels, ideal_levels)\nideal_i = ideal_i.ravel()\nideal_q = ideal_q.ravel()\n\nn_symbols = 1200\nsymbol_indices = np.random.randint(0, 16, n_symbols)\n\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)\n\nreceived_i = ideal_i[symbol_indices] + np.random.normal(0, noise_std, n_symbols)\nreceived_q = ideal_q[symbol_indices] + np.random.normal(0, noise_std, n_symbols)\n\nerror_vectors = np.sqrt((received_i - ideal_i[symbol_indices]) ** 2 + (received_q - ideal_q[symbol_indices]) ** 2)\nrms_evm = np.sqrt(np.mean(error_vectors**2)) / np.sqrt(signal_power) * 100\n\n# Plot — square canvas for symmetric I/Q geometry (2400×2400 px)\nfig, ax = plt.subplots(figsize=(6, 6), dpi=400, facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\n# Decision boundary regions — subtle alternating shading, theme-adaptive\nfor row in range(4):\n    for col in range(4):\n        x0 = [-5, -2, 0, 2][col]\n        x1 = [-2, 0, 2, 5][col]\n        y0 = [-5, -2, 0, 2][row]\n        y1 = [-2, 0, 2, 5][row]\n        if (row + col) % 2 == 0:\n            ax.fill_between([x0, x1], y0, y1, color=INK, alpha=0.05, zorder=0)\n\n# Decision boundaries — theme-adaptive dashed lines\nfor boundary in [-2, 0, 2]:\n    ax.axhline(boundary, color=INK_SOFT, linestyle=\"--\", linewidth=0.9, alpha=0.4, zorder=1)\n    ax.axvline(boundary, color=INK_SOFT, linestyle=\"--\", linewidth=0.9, alpha=0.4, zorder=1)\n\n# Received symbols — Imprint sequential colormap for error magnitude\nnorm = mcolors.PowerNorm(gamma=0.7, vmin=error_vectors.min(), vmax=error_vectors.max())\nscatter = ax.scatter(\n    received_i, received_q, c=error_vectors, cmap=imprint_seq, norm=norm, s=32, alpha=0.45, edgecolors=\"none\", zorder=2\n)\n\n# Colorbar — theme-adaptive labels and frame\ncbar = fig.colorbar(scatter, ax=ax, shrink=0.68, pad=0.02, aspect=28)\ncbar.set_label(\"Error Magnitude\", fontsize=8, labelpad=8, color=INK_SOFT)\ncbar.ax.tick_params(labelsize=8, colors=INK_SOFT, labelcolor=INK_SOFT)\ncbar.outline.set_edgecolor(INK_SOFT)\ncbar.outline.set_linewidth(0.8)\n\n# Ideal constellation points — Imprint matte red (#AE3030, semantic anchor for reference/target)\nax.scatter(\n    ideal_i,\n    ideal_q,\n    s=350,\n    marker=\"X\",\n    color=\"#AE3030\",\n    edgecolors=PAGE_BG,\n    linewidth=1.5,\n    zorder=4,\n    label=\"Ideal symbols\",\n    path_effects=[pe.withStroke(linewidth=3, foreground=PAGE_BG)],\n)\n\n# Concentric rings around ideal points to delineate decision regions\nfor ii, iq in zip(ideal_i, ideal_q, strict=True):\n    circle = plt.Circle((ii, iq), 0.5, fill=False, color=\"#AE3030\", linewidth=0.4, alpha=0.25, zorder=1)\n    ax.add_patch(circle)\n\n# Style\ntitle = \"scatter-constellation-diagram · python · matplotlib · anyplot.ai\"\ntitle_fontsize = max(8, round(12 * 67 / len(title))) if len(title) > 67 else 12\n\nax.set_xlabel(\"In-Phase (I)\", fontsize=10, color=INK, labelpad=8)\nax.set_ylabel(\"Quadrature (Q)\", fontsize=10, color=INK, labelpad=8)\nax.set_title(title, fontsize=title_fontsize, fontweight=\"medium\", color=INK, pad=12)\nax.tick_params(axis=\"both\", labelsize=8, colors=INK_SOFT, labelcolor=INK_SOFT)\n\nax.set_xlim(-5, 5)\nax.set_ylim(-5, 5)\nax.set_aspect(\"equal\")\n\nfor spine in ax.spines.values():\n    spine.set_edgecolor(INK_SOFT)\n    spine.set_linewidth(0.6)\n\nleg = ax.legend(fontsize=8, loc=\"upper left\", framealpha=0.9)\nif leg:\n    leg.get_frame().set_facecolor(ELEVATED_BG)\n    leg.get_frame().set_edgecolor(INK_SOFT)\n    plt.setp(leg.get_texts(), color=INK_SOFT)\n\n# EVM annotation — theme-adaptive\nax.text(\n    0.97,\n    0.03,\n    f\"EVM = {rms_evm:.1f}%\",\n    transform=ax.transAxes,\n    fontsize=10,\n    fontweight=\"bold\",\n    ha=\"right\",\n    va=\"bottom\",\n    color=INK,\n    bbox={\n        \"boxstyle\": \"round,pad=0.4\",\n        \"facecolor\": ELEVATED_BG,\n        \"edgecolor\": INK_SOFT,\n        \"linewidth\": 1.2,\n        \"alpha\": 0.95,\n    },\n)\n\n# SNR / symbols info — theme-adaptive secondary text\nax.text(\n    0.97,\n    0.10,\n    f\"SNR = {snr_db} dB  |  {n_symbols} symbols\",\n    transform=ax.transAxes,\n    fontsize=8,\n    ha=\"right\",\n    va=\"bottom\",\n    color=INK_MUTED,\n    fontstyle=\"italic\",\n)\n\nplt.tight_layout()\n\n# Save — do NOT add bbox_inches='tight' (would trim canvas from 2400×2400 target)\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}