{"spec_id":"scatter-constellation-diagram","library":"altair","language":"python","code":"\"\"\" anyplot.ai\nscatter-constellation-diagram: Digital Modulation Constellation Diagram\nLibrary: altair 6.2.1 | Python 3.13.14\nQuality: 91/100 | Updated: 2026-06-18\n\"\"\"\n\nimport os\nimport sys\n\n\n# Prevent self-import: remove the script's own directory from sys.path\n# so that `import altair` resolves to the installed package, not this file.\n_thisdir = os.path.dirname(os.path.realpath(__file__))\nsys.path[:] = [p for p in sys.path if p not in (\"\", \".\") and os.path.realpath(p) != _thisdir]\n\nimport altair as alt\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\n\n\n# Theme-adaptive chrome 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 sequential colormap — low error (green) → high error (blue)\nSEQ_LOW = \"#009E73\"  # Imprint position 1\nSEQ_HIGH = \"#4467A3\"  # Imprint position 3\n\n# Ideal point color — semantic matte red (Imprint position 5: reference/error anchor)\nIDEAL_COLOR = \"#AE3030\"\n\n# Data\nnp.random.seed(42)\nideal_vals = [-3, -1, 1, 3]\nideal_i, ideal_q = np.meshgrid(ideal_vals, ideal_vals)\nideal_i = ideal_i.flatten()\nideal_q = ideal_q.flatten()\n\nn_symbols = 1000\nsymbol_indices = np.random.randint(0, 16, size=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_signal = np.sqrt(signal_power)\nevm_pct = np.sqrt(np.mean(error_vectors**2)) / rms_signal * 100\n\ndf_received = pd.DataFrame(\n    {\n        \"I\": received_i,\n        \"Q\": received_q,\n        \"Error Magnitude\": error_vectors,\n        \"Nearest I\": ideal_i[symbol_indices],\n        \"Nearest Q\": ideal_q[symbol_indices],\n    }\n)\ndf_ideal = pd.DataFrame({\"I\": ideal_i, \"Q\": ideal_q})\n\n# Decision boundaries at midpoints separating the 16-QAM symbol regions\nboundary_vals = [-4, -2, 0, 2, 4]\nboundary_h = pd.DataFrame([{\"x\": -5.2, \"x2\": 5.2, \"y\": v} for v in boundary_vals])\nboundary_v = pd.DataFrame([{\"y\": -5.2, \"y2\": 5.2, \"x\": v} for v in boundary_vals])\n\ndf_evm = pd.DataFrame({\"I\": [4.0], \"Q\": [4.5], \"label\": [f\"EVM = {evm_pct:.1f}%\"]})\n\n# Interactive selection — highlights nearest symbol on hover\nnearest = alt.selection_point(on=\"pointerover\", nearest=True, fields=[\"I\", \"Q\"], empty=False)\n\n# Equal symmetric domains for accurate constellation geometry (equal aspect ratio)\nscale_x = alt.Scale(domain=[-5.5, 5.5], nice=False)\nscale_y = alt.Scale(domain=[-5.5, 5.5], nice=False)\n\n# Layer: dashed decision boundary grid lines\nh_rules = (\n    alt.Chart(boundary_h)\n    .mark_rule(strokeDash=[8, 5], strokeWidth=1, opacity=0.45)\n    .encode(x=alt.X(\"x:Q\", scale=scale_x), x2=\"x2:Q\", y=alt.Y(\"y:Q\", scale=scale_y), color=alt.value(INK_MUTED))\n)\n\nv_rules = (\n    alt.Chart(boundary_v)\n    .mark_rule(strokeDash=[8, 5], strokeWidth=1, opacity=0.45)\n    .encode(y=alt.Y(\"y:Q\", scale=scale_y), y2=\"y2:Q\", x=alt.X(\"x:Q\", scale=scale_x), color=alt.value(INK_MUTED))\n)\n\n# Layer: received symbols, color-coded by error magnitude (imprint_seq)\nreceived_layer = (\n    alt.Chart(df_received)\n    .mark_circle(size=40)\n    .encode(\n        x=alt.X(\"I:Q\", title=\"In-Phase (I)\", scale=scale_x),\n        y=alt.Y(\"Q:Q\", title=\"Quadrature (Q)\", scale=scale_y),\n        color=alt.Color(\n            \"Error Magnitude:Q\",\n            scale=alt.Scale(range=[SEQ_LOW, SEQ_HIGH]),\n            legend=alt.Legend(\n                title=\"Error Mag.\", titleFontSize=10, labelFontSize=10, orient=\"right\", gradientLength=100\n            ),\n        ),\n        opacity=alt.condition(nearest, alt.value(0.9), alt.value(0.35)),\n        size=alt.condition(nearest, alt.value(120), alt.value(40)),\n        tooltip=[\n            alt.Tooltip(\"I:Q\", format=\".3f\"),\n            alt.Tooltip(\"Q:Q\", format=\".3f\"),\n            alt.Tooltip(\"Error Magnitude:Q\", format=\".3f\", title=\"Error\"),\n            alt.Tooltip(\"Nearest I:Q\", format=\".0f\", title=\"Ideal I\"),\n            alt.Tooltip(\"Nearest Q:Q\", format=\".0f\", title=\"Ideal Q\"),\n        ],\n    )\n    .add_params(nearest)\n)\n\n# Layer: ideal constellation points (cross markers, Imprint matte red semantic anchor)\nideal_layer = (\n    alt.Chart(df_ideal)\n    .mark_point(size=300, filled=False, strokeWidth=3.0)\n    .encode(\n        x=\"I:Q\",\n        y=\"Q:Q\",\n        color=alt.value(IDEAL_COLOR),\n        shape=alt.value(\"cross\"),\n        tooltip=[alt.Tooltip(\"I:Q\", format=\".0f\", title=\"Ideal I\"), alt.Tooltip(\"Q:Q\", format=\".0f\", title=\"Ideal Q\")],\n    )\n)\n\n# Layer: EVM annotation\nevm_label = (\n    alt.Chart(df_evm)\n    .mark_text(fontSize=13, fontWeight=\"bold\", align=\"right\", font=\"monospace\")\n    .encode(x=\"I:Q\", y=\"Q:Q\", text=\"label:N\", color=alt.value(INK))\n)\n\nchart = (\n    alt.layer(h_rules, v_rules, received_layer, ideal_layer, evm_label)\n    .properties(\n        width=460,\n        height=460,\n        background=PAGE_BG,\n        title=alt.Title(\n            \"scatter-constellation-diagram · python · altair · anyplot.ai\",\n            fontSize=17,\n            fontWeight=\"bold\",\n            anchor=\"middle\",\n            offset=10,\n        ),\n    )\n    .configure_view(fill=PAGE_BG, stroke=None)\n    .configure_axis(\n        labelFontSize=10,\n        titleFontSize=12,\n        tickSize=4,\n        domainColor=INK_SOFT,\n        tickColor=INK_SOFT,\n        labelColor=INK_SOFT,\n        titleColor=INK,\n        grid=False,\n    )\n    .configure_title(color=INK)\n    .configure_legend(fillColor=ELEVATED_BG, strokeColor=INK_SOFT, labelColor=INK_SOFT, titleColor=INK)\n)\n\n# Save PNG then pad to exact 2400×2400 square canvas\nchart.save(f\"plot-{THEME}.png\", scale_factor=4.0)\n\nTW, TH = 2400, 2400\n_img = Image.open(f\"plot-{THEME}.png\").convert(\"RGB\")\n_w, _h = _img.size\nif _w > TW or _h > TH:\n    raise SystemExit(\n        f\"altair vl-convert produced {_w}×{_h}, exceeds target {TW}×{TH}. \"\n        f\"Shrink chart .properties(width=, height=) values and re-render.\"\n    )\nif _w < TW or _h < TH:\n    _canvas = Image.new(\"RGB\", (TW, TH), PAGE_BG)\n    _canvas.paste(_img, ((TW - _w) // 2, (TH - _h) // 2))\n    _canvas.save(f\"plot-{THEME}.png\")\n\nchart.save(f\"plot-{THEME}.html\")\n"}