{"spec_id":"scatter-constellation-diagram","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\nscatter-constellation-diagram: Digital Modulation Constellation Diagram\nLibrary: seaborn 0.13.2 | Python 3.13.14\nQuality: 89/100 | Updated: 2026-06-18\n\"\"\"\n\nimport os\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\n\n\n# ── Theme ──────────────────────────────────────────────────────────────────\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 palette — canonical order, first series always #009E73\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"]\n\n# ── Data ───────────────────────────────────────────────────────────────────\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, size=n_symbols)\n\nsnr_db = 20\nnoise_std = np.sqrt(5 / (10 ** (snr_db / 10)))\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_signal = np.sqrt(np.mean(ideal_i**2 + ideal_q**2))\nevm_pct = np.sqrt(np.mean(error_vectors**2)) / rms_signal * 100\n\n# Vectorised quadrant labelling\nqi_vals = ideal_i[symbol_indices]\nqq_vals = ideal_q[symbol_indices]\nquad_labels = np.where(\n    (qi_vals > 0) & (qq_vals > 0),\n    \"Q1 (+I, +Q)\",\n    np.where(\n        (qi_vals < 0) & (qq_vals > 0),\n        \"Q2 (−I, +Q)\",\n        np.where((qi_vals < 0) & (qq_vals < 0), \"Q3 (−I, −Q)\", \"Q4 (+I, −Q)\"),\n    ),\n)\n\ndf_received = pd.DataFrame({\"In-Phase (I)\": received_i, \"Quadrature (Q)\": received_q, \"Quadrant\": quad_labels})\ndf_ideal = pd.DataFrame({\"In-Phase (I)\": ideal_i, \"Quadrature (Q)\": ideal_q})\n\n# ── Style ──────────────────────────────────────────────────────────────────\nsns.set_theme(\n    style=\"ticks\",\n    rc={\n        \"figure.facecolor\": PAGE_BG,\n        \"axes.facecolor\": PAGE_BG,\n        \"axes.edgecolor\": INK_SOFT,\n        \"axes.labelcolor\": INK,\n        \"text.color\": INK,\n        \"xtick.color\": INK_SOFT,\n        \"ytick.color\": INK_SOFT,\n        \"grid.color\": INK,\n        \"grid.alpha\": 0.15,\n        \"legend.facecolor\": ELEVATED_BG,\n        \"legend.edgecolor\": INK_SOFT,\n    },\n)\n\n# ── Canvas ─────────────────────────────────────────────────────────────────\nfig, ax = plt.subplots(figsize=(6, 6), dpi=400)  # → 2400 × 2400 px (square)\n\n# ── Decision boundaries ────────────────────────────────────────────────────\nfor b in [-2, 0, 2]:\n    ax.axhline(y=b, color=INK_MUTED, linestyle=\"--\", linewidth=0.8, alpha=0.45)\n    ax.axvline(x=b, color=INK_MUTED, linestyle=\"--\", linewidth=0.8, alpha=0.45)\n\n# ── Quadrant ordering and palette ─────────────────────────────────────────\nQUAD_ORDER = [\"Q1 (+I, +Q)\", \"Q2 (−I, +Q)\", \"Q3 (−I, −Q)\", \"Q4 (+I, −Q)\"]\nQUAD_PALETTE = IMPRINT[:4]\n\n# Clip regions per quadrant to prevent KDE bleed across decision boundaries\nQUAD_CLIPS = [\n    ((0, 4.5), (0, 4.5)),  # Q1: +I, +Q\n    ((-4.5, 0), (0, 4.5)),  # Q2: −I, +Q\n    ((-4.5, 0), (-4.5, 0)),  # Q3: −I, −Q\n    ((0, 4.5), (-4.5, 0)),  # Q4: +I, −Q\n]\n\n# ── KDE density contours per quadrant ──────────────────────────────────────\nfor i, (quad, clip) in enumerate(zip(QUAD_ORDER, QUAD_CLIPS, strict=True)):\n    subset = df_received[df_received[\"Quadrant\"] == quad]\n    sns.kdeplot(\n        data=subset,\n        x=\"In-Phase (I)\",\n        y=\"Quadrature (Q)\",\n        levels=3,\n        color=QUAD_PALETTE[i],\n        alpha=0.4,\n        linewidths=1.0,\n        clip=clip,\n        ax=ax,\n    )\n\n# ── Received symbols ───────────────────────────────────────────────────────\nsns.scatterplot(\n    data=df_received,\n    x=\"In-Phase (I)\",\n    y=\"Quadrature (Q)\",\n    hue=\"Quadrant\",\n    hue_order=QUAD_ORDER,\n    palette=QUAD_PALETTE,\n    alpha=0.45,\n    s=20,\n    edgecolor=\"none\",\n    ax=ax,\n    legend=True,\n)\n\n# ── Ideal constellation markers ────────────────────────────────────────────\nIDEAL_EDGE = \"#FFFFFF\" if THEME == \"light\" else \"#000000\"\nsns.scatterplot(\n    data=df_ideal,\n    x=\"In-Phase (I)\",\n    y=\"Quadrature (Q)\",\n    color=INK,\n    s=200,\n    marker=\"X\",\n    edgecolor=IDEAL_EDGE,\n    linewidth=1.2,\n    ax=ax,\n    legend=False,\n    zorder=5,\n)\n\n# ── Chrome ─────────────────────────────────────────────────────────────────\nax.set_title(\n    \"scatter-constellation-diagram · python · seaborn · anyplot.ai\", fontsize=12, fontweight=\"medium\", color=INK, pad=10\n)\nax.set_xlabel(\"In-Phase (I)\", fontsize=10, color=INK)\nax.set_ylabel(\"Quadrature (Q)\", fontsize=10, color=INK)\nax.tick_params(axis=\"both\", labelsize=8, colors=INK_SOFT)\n\nax.set_xlim(-4.5, 4.5)\nax.set_ylim(-4.5, 4.5)\nax.set_aspect(\"equal\")\n\nax.spines[\"top\"].set_visible(False)\nax.spines[\"right\"].set_visible(False)\n\n# ── Legend ─────────────────────────────────────────────────────────────────\nlegend = ax.legend(\n    title=\"Quadrant\",\n    title_fontsize=8,\n    fontsize=7,\n    loc=\"lower right\",\n    framealpha=0.9,\n    edgecolor=INK_SOFT,\n    markerscale=1.5,\n)\nlegend.get_title().set_color(INK)\nfor text in legend.get_texts():\n    text.set_color(INK_SOFT)\n\n# ── EVM annotation ─────────────────────────────────────────────────────────\nax.text(\n    0.97,\n    0.97,\n    f\"EVM = {evm_pct:.1f}%\",\n    transform=ax.transAxes,\n    fontsize=9,\n    fontweight=\"medium\",\n    ha=\"right\",\n    va=\"top\",\n    color=INK,\n    bbox={\"boxstyle\": \"round,pad=0.4\", \"facecolor\": ELEVATED_BG, \"edgecolor\": INK_SOFT, \"alpha\": 0.9},\n)\n\n# ── Save ───────────────────────────────────────────────────────────────────\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\nplt.close()\n"}