{"spec_id":"scatter-constellation-diagram","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\nscatter-constellation-diagram: Digital Modulation Constellation Diagram\nLibrary: plotnine 0.15.7 | Python 3.13.13\nQuality: 88/100 | Updated: 2026-06-18\n\"\"\"\n\nimport os\nimport sys\n\n\n# Remove this script's own directory from sys.path to prevent it from\n# shadowing the installed plotnine library when run as `python plotnine.py`.\nsys.path = [p for p in sys.path if os.path.abspath(p or \".\") != os.path.dirname(os.path.abspath(__file__))]\n\nimport numpy as np\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    annotate,\n    coord_fixed,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_hline,\n    geom_point,\n    geom_rect,\n    geom_segment,\n    geom_vline,\n    ggplot,\n    guide_legend,\n    guides,\n    labs,\n    scale_color_manual,\n    scale_x_continuous,\n    scale_y_continuous,\n    theme,\n    theme_minimal,\n)\n\n\n# Theme tokens — Imprint palette\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 — received symbols use position 1 (brand green); ideal points use matte red (semantic: target reference)\nRECEIVED_COLOR = \"#009E73\"  # Imprint position 1 — always first series\nIDEAL_COLOR = \"#AE3030\"  # Imprint matte red — ideal reference markers\n\n# Data\nnp.random.seed(42)\n\nideal_coords = [-3, -1, 1, 3]\nideal_i = np.array([i for i in ideal_coords for _ in ideal_coords])\nideal_q = np.array([q for _ in ideal_coords for q in ideal_coords])\n\nn_symbols = 1000\nsnr_db = 20\nsnr_linear = 10 ** (snr_db / 10)\navg_power = np.mean(ideal_i**2 + ideal_q**2)\nnoise_std = np.sqrt(avg_power / (2 * snr_linear))\n\nsymbol_indices = np.random.randint(0, 16, size=n_symbols)\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_i = received_i - ideal_i[symbol_indices]\nerror_q = received_q - ideal_q[symbol_indices]\nevm = np.sqrt(np.mean(error_i**2 + error_q**2)) / np.sqrt(avg_power) * 100\n\ndf_received = pd.DataFrame({\"i\": received_i, \"q\": received_q, \"series\": \"Received Symbols\"})\ndf_ideal = pd.DataFrame({\"i\": ideal_i, \"q\": ideal_q, \"series\": \"Ideal Points\"})\n\n# Decision region shading — CHECKER_ALT provides more visible contrast than ELEVATED_BG in light mode\nCHECKER_ALT = \"#EDEEE7\" if THEME == \"light\" else ELEVATED_BG\nregion_edges = [-4.5, -2, 0, 2, 4.5]\nrects = []\nfor ri, xmin in enumerate(region_edges[:-1]):\n    for ci, ymin in enumerate(region_edges[:-1]):\n        rects.append(\n            {\n                \"xmin\": xmin,\n                \"xmax\": region_edges[ri + 1],\n                \"ymin\": ymin,\n                \"ymax\": region_edges[ci + 1],\n                \"shade\": PAGE_BG if (ri + ci) % 2 == 0 else CHECKER_ALT,\n            }\n        )\ndf_rects = pd.DataFrame(rects)\n\n# Decision boundaries at ±2 and 0\nboundary_vals = [-2, 0, 2]\n\n# Error vector samples — connect ideal to received for visual storytelling\nrng = np.random.default_rng(42)\nev_idx = rng.choice(n_symbols, size=12, replace=False)\ndf_ev = pd.DataFrame(\n    {\n        \"i_start\": ideal_i[symbol_indices[ev_idx]],\n        \"q_start\": ideal_q[symbol_indices[ev_idx]],\n        \"i_end\": received_i[ev_idx],\n        \"q_end\": received_q[ev_idx],\n    }\n)\n\n# Title — 62 chars; reduce to 9pt to prevent overflow with right legend narrowing the panel\ntitle = \"scatter-constellation-diagram · python · plotnine · anyplot.ai\"\ntitle_size = 9\n\nCOLOR_MAP = {\"Received Symbols\": RECEIVED_COLOR, \"Ideal Points\": IDEAL_COLOR}\n\n# Plot\nplot = (\n    ggplot(df_received, aes(x=\"i\", y=\"q\"))\n    # Decision region shading\n    + geom_rect(\n        data=df_rects,\n        mapping=aes(xmin=\"xmin\", xmax=\"xmax\", ymin=\"ymin\", ymax=\"ymax\"),\n        fill=df_rects[\"shade\"].tolist(),\n        alpha=0.8,\n        inherit_aes=False,\n        show_legend=False,\n    )\n    # Decision boundary lines\n    + geom_vline(xintercept=boundary_vals, linetype=\"dashed\", color=INK_SOFT, size=0.5, show_legend=False)\n    + geom_hline(yintercept=boundary_vals, linetype=\"dashed\", color=INK_SOFT, size=0.5, show_legend=False)\n    # Received symbols\n    + geom_point(data=df_received, mapping=aes(x=\"i\", y=\"q\", color=\"series\"), alpha=0.4, size=3.5)\n    # Error vectors — made more prominent to highlight signal impairment\n    + geom_segment(\n        data=df_ev,\n        mapping=aes(x=\"i_start\", y=\"q_start\", xend=\"i_end\", yend=\"q_end\"),\n        color=IDEAL_COLOR,\n        alpha=0.75,\n        size=0.9,\n        inherit_aes=False,\n        show_legend=False,\n    )\n    # Ideal constellation points (X markers)\n    + geom_point(data=df_ideal, mapping=aes(x=\"i\", y=\"q\", color=\"series\"), shape=\"X\", stroke=1.5, alpha=1.0, size=6.0)\n    + scale_color_manual(values=COLOR_MAP)\n    + guides(color=guide_legend(override_aes={\"shape\": [\"o\", \"X\"], \"size\": [3.5, 6.0], \"alpha\": [0.7, 1.0]}))\n    # Tick positions at constellation coordinate values\n    + scale_x_continuous(breaks=[-3, -1, 0, 1, 3], minor_breaks=[])\n    + scale_y_continuous(breaks=[-3, -1, 0, 1, 3], minor_breaks=[])\n    # EVM and SNR annotations\n    + annotate(\"text\", x=4.2, y=-3.7, label=f\"EVM = {evm:.1f}%\", size=4.5, ha=\"right\", color=INK, fontweight=\"bold\")\n    + annotate(\n        \"text\", x=4.2, y=-4.15, label=f\"SNR = {snr_db} dB  ·  {n_symbols} symbols\", size=3.8, ha=\"right\", color=INK_SOFT\n    )\n    + coord_fixed(ratio=1, xlim=(-4.5, 4.5), ylim=(-4.5, 4.5))\n    + labs(x=\"In-Phase (I)\", y=\"Quadrature (Q)\", title=title)\n    + theme_minimal()\n    + theme(\n        figure_size=(6, 6),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG),\n        plot_title=element_text(size=title_size, weight=\"bold\", ha=\"center\", color=INK),\n        axis_title=element_text(size=10, color=INK),\n        axis_text=element_text(size=8, color=INK_SOFT),\n        panel_grid_major=element_blank(),\n        panel_grid_minor=element_blank(),\n        axis_line=element_line(color=INK_SOFT, size=0.6),\n        legend_position=\"right\",\n        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n        legend_text=element_text(size=8, color=INK_SOFT),\n        legend_title=element_blank(),\n        legend_key=element_rect(fill=PAGE_BG),\n    )\n)\n\n# Save\nplot.save(f\"plot-{THEME}.png\", dpi=400, width=6, height=6, units=\"in\", verbose=False)\n"}