{"spec_id":"bode-basic","library":"ggplot2","language":"r","code":"#' anyplot.ai\n#' bode-basic: Bode Plot for Frequency Response\n#' Library: ggplot2 3.5.1 | R 4.4.1\n#' Quality: 92/100 | Created: 2026-06-17\n\nlibrary(ggplot2)\nlibrary(ragg)\n\n# Theme tokens (Imprint palette — see prompts/default-style-guide.md)\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\"\nGRID_COLOR  <- adjustcolor(INK, alpha.f = 0.12)\n\nIMPRINT_PALETTE <- c(\n  \"#009E73\",  # 1 — brand green (first series)\n  \"#C475FD\",  # 2 — lavender\n  \"#4467A3\",  # 3 — blue\n  \"#BD8233\",  # 4 — ochre\n  \"#AE3030\",  # 5 — matte red\n  \"#2ABCCD\",  # 6 — cyan\n  \"#954477\",  # 7 — rose\n  \"#99B314\"   # 8 — lime\n)\n\n# Transfer function G(s) = 10 / (s(s+1)(s+5)) — type-1 system with two real poles\n# Phase crossover at omega = sqrt(5), gain margin ~9.54 dB\nfreq_hz <- 10^seq(log10(0.005), log10(50), length.out = 500)\nomega   <- 2 * pi * freq_hz\n\n# Analytical magnitude and phase (avoids Arg() wrapping for high-order systems)\nmagnitude_db <- 20 * log10(10 / (omega * sqrt(1 + omega^2) * sqrt(25 + omega^2)))\nphase_deg    <- -90 - atan(omega) * (180 / pi) - atan(omega / 5) * (180 / pi)\n\n# Gain crossover frequency: |G| crosses 0 dB downward\ngc_idx    <- which(diff(sign(magnitude_db)) < 0)[1]\ngc_freq   <- 10^(log10(freq_hz[gc_idx]) +\n                 (0 - magnitude_db[gc_idx]) *\n                 (log10(freq_hz[gc_idx + 1]) - log10(freq_hz[gc_idx])) /\n                 (magnitude_db[gc_idx + 1] - magnitude_db[gc_idx]))\ngc_phase  <- approx(freq_hz, phase_deg, xout = gc_freq)$y\nphase_margin <- 180 + gc_phase\n\n# Phase crossover frequency: phase crosses -180° downward\npc_idx    <- which(diff(sign(phase_deg + 180)) < 0)[1]\npc_freq   <- 10^(log10(freq_hz[pc_idx]) +\n                 (-180 - phase_deg[pc_idx]) *\n                 (log10(freq_hz[pc_idx + 1]) - log10(freq_hz[pc_idx])) /\n                 (phase_deg[pc_idx + 1] - phase_deg[pc_idx]))\npc_mag    <- approx(freq_hz, magnitude_db, xout = pc_freq)$y\ngain_margin <- -pc_mag\n\n# Long-format data for dual-panel faceted plot\npanels <- c(\"Magnitude (dB)\", \"Phase (°)\")\ndf <- tibble::tibble(\n  freq  = c(freq_hz, freq_hz),\n  value = c(magnitude_db, phase_deg),\n  panel = factor(rep(panels, each = 500), levels = panels)\n)\n\n# Per-facet reference lines: 0 dB (magnitude) and -180° (phase)\nref_df <- tibble::tibble(\n  panel = factor(panels, levels = panels),\n  yint  = c(0, -180)\n)\n\n# Stability zone shading: vertical band between gain and phase crossover frequencies\nshade_df <- tibble::tibble(\n  panel = factor(panels, levels = panels),\n  xmin  = min(gc_freq, pc_freq),\n  xmax  = max(gc_freq, pc_freq),\n  ymin  = -Inf,\n  ymax  = Inf\n)\n\n# Stability margin annotations: place in each panel near the relevant crossover\nann_df <- tibble::tibble(\n  freq  = c(pc_freq * 2.5, gc_freq * 4.0),\n  value = c(pc_mag / 2, (gc_phase - 180) / 2),\n  label = c(sprintf(\"GM = %.1f dB\", gain_margin),\n            sprintf(\"PM = %.1f°\", phase_margin)),\n  panel = factor(panels, levels = panels)\n)\n\ntitle_str  <- \"bode-basic · r · ggplot2 · anyplot.ai\"\ntitle_size <- max(8, round(12 * min(1.0, 67 / nchar(title_str))))\n\np <- ggplot(df, aes(x = freq, y = value)) +\n  # Stability zone: vertical band between crossover frequencies\n  geom_rect(\n    data = shade_df,\n    aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax),\n    inherit.aes = FALSE,\n    fill = adjustcolor(IMPRINT_PALETTE[1], alpha.f = 0.07),\n    color = NA\n  ) +\n  # Reference lines per facet (0 dB and -180°)\n  geom_hline(\n    data = ref_df, aes(yintercept = yint),\n    color = INK_MUTED, linetype = \"dashed\", linewidth = 0.6\n  ) +\n  # Gain crossover frequency marker (both panels)\n  geom_vline(\n    xintercept = gc_freq,\n    color = IMPRINT_PALETTE[3], linetype = \"dotted\", linewidth = 0.7\n  ) +\n  # Phase crossover frequency marker (both panels)\n  geom_vline(\n    xintercept = pc_freq,\n    color = IMPRINT_PALETTE[5], linetype = \"dotted\", linewidth = 0.7\n  ) +\n  # Frequency response curves\n  geom_line(color = IMPRINT_PALETTE[1], linewidth = 1.0) +\n  # Stability margin annotations\n  geom_label(\n    data = ann_df,\n    aes(x = freq, y = value, label = label),\n    color = INK, fill = ELEVATED_BG,\n    label.size = 0.25, size = 3.5, label.r = unit(0.15, \"lines\")\n  ) +\n  scale_x_log10(\n    breaks       = 10^(-2:1),\n    labels       = c(\"0.01\", \"0.1\", \"1\", \"10\"),\n    minor_breaks = as.vector(outer(1:9, 10^(-3:2)))\n  ) +\n  facet_wrap(~ panel, ncol = 1, scales = \"free_y\") +\n  labs(\n    title = title_str,\n    x     = \"Frequency (Hz)\",\n    y     = NULL\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_line(color = GRID_COLOR, linewidth = 0.4),\n    panel.grid.minor = element_line(color = GRID_COLOR, linewidth = 0.2),\n    panel.border     = element_blank(),\n    axis.line        = element_line(color = INK_SOFT,  linewidth = 0.4),\n    axis.title.x     = element_text(color = INK,      size = 10),\n    axis.text        = element_text(color = INK_SOFT,  size = 8),\n    plot.title       = element_text(color = INK,      size = title_size, face = \"bold\"),\n    strip.text       = element_text(color = INK,      size = 10, face = \"bold\"),\n    strip.background = element_rect(fill = ELEVATED_BG, color = NA),\n    plot.margin      = margin(10, 15, 10, 10, unit = \"pt\")\n  )\n\nggsave(\n  filename = sprintf(\"plot-%s.png\", THEME),\n  plot     = p,\n  device   = ragg::agg_png,\n  width    = 8,\n  height   = 4.5,\n  units    = \"in\",\n  dpi      = 400\n)\n"}