{"spec_id":"scatter-constellation-diagram","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nscatter-constellation-diagram: Digital Modulation Constellation Diagram\nLibrary: bokeh 3.9.1 | Python 3.13.13\nQuality: 88/100 | Updated: 2026-06-18\n\"\"\"\n\nimport os\nimport time\nfrom pathlib import Path\n\nimport numpy as np\nfrom bokeh.io import output_file, save\nfrom bokeh.models import ColumnDataSource, HoverTool, Label, Legend, LegendItem, LinearColorMapper, Span\nfrom bokeh.plotting import figure\nfrom bokeh.transform import transform\nfrom selenium import webdriver\nfrom selenium.webdriver.chrome.options import Options\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\"\n\n# Imprint sequential colormap (green→blue) for single-polarity continuous data\n_c0 = np.array([0x00, 0x9E, 0x73])  # #009E73\n_c1 = np.array([0x44, 0x67, 0xA3])  # #4467A3\nIMPRINT_SEQ256 = [\n    \"#{:02X}{:02X}{:02X}\".format(*np.round(_c0 + (_c1 - _c0) * t / 255.0).astype(int)) for t in range(256)\n]\n\n# Data — 16-QAM constellation with SNR-based noise model\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.flatten()\nideal_q = ideal_q.flatten()\n\nn_symbols = 1000\nsnr_db = 20\nsnr_linear = 10 ** (snr_db / 10)\navg_power = np.mean(ideal_levels**2)\nnoise_std = np.sqrt(avg_power / snr_linear)\n\nsymbol_indices = np.random.randint(0, 16, 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_vectors = np.sqrt((received_i - ideal_i[symbol_indices]) ** 2 + (received_q - ideal_q[symbol_indices]) ** 2)\nrms_error = np.sqrt(np.mean(error_vectors**2))\nmax_amplitude = np.sqrt(3**2 + 3**2)\nevm_percent = (rms_error / max_amplitude) * 100\n\ncolor_mapper = LinearColorMapper(palette=IMPRINT_SEQ256, low=0, high=float(np.percentile(error_vectors, 95)))\n\n# Plot — square canvas (2400×2400) with equal aspect ratio for constellation geometry\ntitle = \"scatter-constellation-diagram · python · bokeh · anyplot.ai\"\np = figure(\n    width=2400,\n    height=2400,\n    title=title,\n    x_axis_label=\"In-Phase (I)\",\n    y_axis_label=\"Quadrature (Q)\",\n    x_range=(-4.5, 4.5),\n    y_range=(-4.5, 4.5),\n    match_aspect=True,\n    toolbar_location=None,\n    min_border_bottom=160,\n    min_border_left=180,\n    min_border_top=110,\n    min_border_right=50,\n)\n\n# Font sizes — canonical bokeh values for 2400×2400 canvas\np.title.text_font_size = \"50pt\"\np.title.text_color = INK\np.xaxis.axis_label_text_font_size = \"42pt\"\np.yaxis.axis_label_text_font_size = \"42pt\"\np.xaxis.axis_label_text_color = INK\np.yaxis.axis_label_text_color = INK\np.xaxis.major_label_text_font_size = \"34pt\"\np.yaxis.major_label_text_font_size = \"34pt\"\np.xaxis.major_label_text_color = INK_SOFT\np.yaxis.major_label_text_color = INK_SOFT\n\n# Chrome — theme-adaptive tokens\np.background_fill_color = PAGE_BG\np.border_fill_color = PAGE_BG\np.outline_line_color = INK_SOFT\np.xaxis.axis_line_color = INK_SOFT\np.yaxis.axis_line_color = INK_SOFT\np.xaxis.major_tick_line_color = INK_SOFT\np.yaxis.major_tick_line_color = INK_SOFT\np.xaxis.minor_tick_line_color = None\np.yaxis.minor_tick_line_color = None\np.xgrid.grid_line_alpha = 0\np.ygrid.grid_line_alpha = 0\n\n# Decision boundary lines separating 16-QAM symbol regions\nfor level in [-2, 0, 2]:\n    p.add_layout(\n        Span(location=level, dimension=\"height\", line_color=INK_SOFT, line_dash=\"dashed\", line_width=3, line_alpha=0.4)\n    )\n    p.add_layout(\n        Span(location=level, dimension=\"width\", line_color=INK_SOFT, line_dash=\"dashed\", line_width=3, line_alpha=0.4)\n    )\n\n# Received symbols — color-coded by error magnitude using Imprint sequential cmap\nreceived_source = ColumnDataSource(\n    data={\n        \"i\": received_i,\n        \"q\": received_q,\n        \"error\": error_vectors,\n        \"ideal_i\": ideal_i[symbol_indices],\n        \"ideal_q\": ideal_q[symbol_indices],\n    }\n)\nrx_renderer = p.scatter(\n    x=\"i\", y=\"q\", source=received_source, size=18, alpha=0.55, color=transform(\"error\", color_mapper), line_color=None\n)\n\n# Ideal constellation points — Imprint matte red as semantic reference marker\nideal_source = ColumnDataSource(data={\"i\": ideal_i, \"q\": ideal_q})\nideal_renderer = p.scatter(\n    x=\"i\", y=\"q\", source=ideal_source, size=36, color=\"#AE3030\", marker=\"cross\", line_width=7, alpha=0.95\n)\n\n# Legend\nlegend = Legend(\n    items=[\n        LegendItem(label=\"Received Symbols (error magnitude)\", renderers=[rx_renderer]),\n        LegendItem(label=\"Ideal 16-QAM Points\", renderers=[ideal_renderer]),\n    ],\n    location=\"bottom_right\",\n    label_text_font_size=\"28pt\",\n    label_text_color=INK_SOFT,\n    background_fill_color=ELEVATED_BG,\n    border_line_color=INK_SOFT,\n    padding=20,\n    spacing=12,\n)\np.add_layout(legend)\n\n# EVM annotation\nevm_label = Label(\n    x=-4.3, y=-4.1, text=f\"EVM = {evm_percent:.1f}%\", text_font_size=\"32pt\", text_color=INK, text_font_style=\"bold\"\n)\np.add_layout(evm_label)\n\n# HoverTool for interactive HTML version\nhover = HoverTool(\n    renderers=[rx_renderer],\n    tooltips=[(\"I / Q\", \"@i{0.00} / @q{0.00}\"), (\"Ideal\", \"@ideal_i{0} / @ideal_q{0}\"), (\"Error\", \"@error{0.000}\")],\n)\np.add_tools(hover)\n\n# Save HTML artifact\noutput_file(f\"plot-{THEME}.html\")\nsave(p)\n\n# Screenshot via headless Chrome — Selenium 4 / Selenium Manager\n# Chrome headless subtracts ~143px from the window height for internal overhead,\n# so set window to 2543 to get a 2400×2400 viewport (and thus a 2400×2400 PNG).\nW, H = 2400, 2400\nWIN_H = H + 143\nopts = Options()\nfor arg in (\n    \"--headless=new\",\n    \"--no-sandbox\",\n    \"--disable-dev-shm-usage\",\n    \"--disable-gpu\",\n    f\"--window-size={W},{WIN_H}\",\n    \"--hide-scrollbars\",\n    \"--force-device-scale-factor=1\",\n):\n    opts.add_argument(arg)\ndriver = webdriver.Chrome(options=opts)\ndriver.set_window_size(W, WIN_H)\ndriver.get(f\"file://{Path(f'plot-{THEME}.html').resolve()}\")\ntime.sleep(3)\ndriver.save_screenshot(f\"plot-{THEME}.png\")\ndriver.quit()\n"}