{"spec_id":"scatter-constellation-diagram","library":"ggplot2","language":"r","code":"#' anyplot.ai\n#' scatter-constellation-diagram: Digital Modulation Constellation Diagram\n#' Library: ggplot2 3.5.1 | R 4.4.1\n#' Quality: 89/100 | Created: 2026-06-18\n\nlibrary(ggplot2)\nlibrary(ragg)\n\nset.seed(42)\n\n# --- Theme tokens ---\nTHEME       <- Sys.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG     <- if (THEME == \"light\") \"#FAF8F1\" else \"#1A1A17\"\nELEVATED_BG <- if (THEME == \"light\") \"#FFFDF6\" else \"#242420\"\nINK         <- if (THEME == \"light\") \"#1A1A17\" else \"#F0EFE8\"\nINK_SOFT    <- if (THEME == \"light\") \"#4A4A44\" else \"#B8B7B0\"\nINK_MUTED   <- if (THEME == \"light\") \"#6B6A63\" else \"#A8A79F\"\n\nIMPRINT_PALETTE <- c(\n  \"#009E73\",  # 1 brand green — received symbols\n  \"#C475FD\",  # 2 lavender\n  \"#4467A3\",  # 3 blue\n  \"#BD8233\",  # 4 ochre\n  \"#AE3030\",  # 5 matte red — ideal constellation reference points\n  \"#2ABCCD\",  # 6 cyan\n  \"#954477\",  # 7 rose\n  \"#99B314\"   # 8 lime\n)\n\n# --- Data ---\n# 16-QAM: 16 ideal symbol positions on a 4x4 grid at +/-1, +/-3\nqam_levels <- c(-3, -1, 1, 3)\nideal_grid <- expand.grid(ideal_i = qam_levels, ideal_q = qam_levels)\n\n# SNR ~20 dB: signal_power = mean(i^2 + q^2) = 10 across 16-QAM grid\n# noise_power = signal_power / 100 = 0.1 → per-component sigma = sqrt(0.05)\nn_per_point <- 100   # 1600 received symbols total\nsigma       <- sqrt(0.05)\n\nreceived <- do.call(rbind, lapply(seq_len(nrow(ideal_grid)), function(idx) {\n  data.frame(\n    i       = ideal_grid$ideal_i[idx] + rnorm(n_per_point, 0, sigma),\n    q       = ideal_grid$ideal_q[idx] + rnorm(n_per_point, 0, sigma),\n    ideal_i = ideal_grid$ideal_i[idx],\n    ideal_q = ideal_grid$ideal_q[idx]\n  )\n}))\n\n# EVM: rms error vector magnitude / rms ideal signal amplitude × 100%\nerror_rms  <- sqrt(mean((received$i - received$ideal_i)^2 +\n                        (received$q - received$ideal_q)^2))\nsignal_rms <- sqrt(mean(ideal_grid$ideal_i^2 + ideal_grid$ideal_q^2))\nevm_label  <- sprintf(\"EVM = %.1f%%\", error_rms / signal_rms * 100)\n\n# Decision boundaries fall midway between symbol rows and columns\nboundaries <- c(-2, 0, 2)\naxis_lim   <- c(-4.5, 4.5)\n\n# --- Plot ---\n# Title character count: ~56 < 67 baseline → default title size of 12pt\nplot_title <- \"scatter-constellation-diagram · r · ggplot2 · anyplot.ai\"\n\np <- ggplot() +\n  # Dashed decision-region boundaries\n  geom_vline(xintercept = boundaries,\n             linetype = \"dashed\", color = INK_SOFT, linewidth = 0.45, alpha = 0.65) +\n  geom_hline(yintercept = boundaries,\n             linetype = \"dashed\", color = INK_SOFT, linewidth = 0.45, alpha = 0.65) +\n  # Received symbols — semi-transparent to reveal density clusters\n  geom_point(data = received, aes(x = i, y = q, color = \"Received Symbols\"),\n             size = 0.9, alpha = 0.30, shape = 16) +\n  # Ideal constellation points — prominent cross markers (engineering convention)\n  geom_point(data = ideal_grid, aes(x = ideal_i, y = ideal_q, color = \"Ideal Points\"),\n             size = 4.5, shape = 3, stroke = 2.0) +\n  # EVM annotation (bottom-right corner)\n  annotate(\"text\",\n           x = 4.25, y = -4.1,\n           label = evm_label,\n           color = INK_SOFT, size = 4.0, hjust = 1) +\n  scale_color_manual(\n    values = c(\"Received Symbols\" = IMPRINT_PALETTE[1],\n               \"Ideal Points\"     = IMPRINT_PALETTE[5]),\n    breaks = c(\"Received Symbols\", \"Ideal Points\"),\n    name   = NULL\n  ) +\n  guides(color = guide_legend(\n    override.aes = list(\n      shape  = c(16, 3),\n      size   = c(2.5, 3.5),\n      alpha  = c(0.8, 1.0),\n      stroke = c(0.0, 1.5)\n    )\n  )) +\n  scale_x_continuous(limits = axis_lim, breaks = qam_levels) +\n  scale_y_continuous(limits = axis_lim, breaks = qam_levels) +\n  coord_fixed() +\n  labs(\n    title = plot_title,\n    x     = \"In-Phase (I)\",\n    y     = \"Quadrature (Q)\"\n  ) +\n  theme_minimal(base_size = 8) +\n  theme(\n    plot.background   = element_rect(fill = PAGE_BG, color = PAGE_BG),\n    panel.background  = element_rect(fill = PAGE_BG, color = NA),\n    panel.grid.major  = element_blank(),\n    panel.grid.minor  = element_blank(),\n    panel.border      = element_blank(),\n    axis.line         = element_line(color = INK_SOFT, linewidth = 0.5),\n    axis.ticks        = element_line(color = INK_SOFT, linewidth = 0.4),\n    axis.title        = element_text(color = INK, size = 10),\n    axis.text         = element_text(color = INK_SOFT, size = 8),\n    plot.title        = element_text(color = INK, size = 12, hjust = 0),\n    plot.margin       = margin(14, 18, 14, 14, \"pt\"),\n    legend.background = element_rect(fill = NA, color = NA),\n    legend.key        = element_rect(fill = NA, color = NA),\n    legend.text       = element_text(color = INK_SOFT, size = 8),\n    legend.title      = element_blank(),\n    legend.position   = \"bottom\"\n  )\n\n# --- Save ---\nggsave(\n  filename = sprintf(\"plot-%s.png\", THEME),\n  plot     = p,\n  device   = ragg::agg_png,\n  width    = 6,\n  height   = 6,\n  units    = \"in\",\n  dpi      = 400\n)\n"}