|
| 1 | +#' anyplot.ai |
| 2 | +#' smith-chart-basic: Smith Chart for RF/Impedance |
| 3 | +#' Library: ggplot2 3.5.1 | R 4.4.1 |
| 4 | +#' Quality: 89/100 | Created: 2026-05-20 |
| 5 | + |
| 6 | +library(ggplot2) |
| 7 | +library(ragg) |
| 8 | + |
| 9 | +# --- Theme tokens ----------------------------------------------------------- |
| 10 | +THEME <- Sys.getenv("ANYPLOT_THEME", "light") |
| 11 | +PAGE_BG <- if (THEME == "light") "#FAF8F1" else "#1A1A17" |
| 12 | +INK <- if (THEME == "light") "#1A1A17" else "#F0EFE8" |
| 13 | +INK_SOFT <- if (THEME == "light") "#4A4A44" else "#B8B7B0" |
| 14 | +OKABE_ITO <- c("#009E73", "#D55E00", "#0072B2", "#CC79A7", |
| 15 | + "#E69F00", "#56B4E9", "#F0E442") |
| 16 | + |
| 17 | +# --- Smith chart grid ------------------------------------------------------- |
| 18 | +theta <- seq(0, 2 * pi, length.out = 361) |
| 19 | + |
| 20 | +# Outer unit circle (|Gamma| = 1 boundary) |
| 21 | +outer_circle <- data.frame(x = cos(theta), y = sin(theta)) |
| 22 | + |
| 23 | +# Constant resistance circles: r/(1+r) center, 1/(1+r) radius |
| 24 | +r_vals <- c(0, 0.2, 0.5, 1, 2, 5) |
| 25 | +r_circles <- do.call(rbind, lapply(r_vals, function(r) { |
| 26 | + cx <- r / (1 + r) |
| 27 | + rad <- 1 / (1 + r) |
| 28 | + data.frame( |
| 29 | + x = cx + rad * cos(theta), |
| 30 | + y = rad * sin(theta), |
| 31 | + grp = paste0("r", r) |
| 32 | + ) |
| 33 | +})) |
| 34 | + |
| 35 | +# Constant reactance arcs: center (1, 1/x), radius 1/|x|, clipped to unit disc |
| 36 | +x_vals <- c(0.2, 0.5, 1, 2, 5) |
| 37 | +x_arcs <- do.call(rbind, lapply(c(x_vals, -x_vals), function(x) { |
| 38 | + cy <- 1 / x |
| 39 | + rad <- abs(1 / x) |
| 40 | + pts_x <- 1 + rad * cos(theta) |
| 41 | + pts_y <- cy + rad * sin(theta) |
| 42 | + inside <- (pts_x^2 + pts_y^2) <= 1.001 |
| 43 | + if (sum(inside) < 3) return(NULL) |
| 44 | + data.frame(x = pts_x[inside], y = pts_y[inside], grp = paste0("x", x)) |
| 45 | +})) |
| 46 | + |
| 47 | +# VSWR = 2 reference circle (|Gamma| = 1/3) |
| 48 | +vswr_circle <- data.frame( |
| 49 | + x = (1 / 3) * cos(theta), |
| 50 | + y = (1 / 3) * sin(theta) |
| 51 | +) |
| 52 | + |
| 53 | +# --- Impedance locus: RLC antenna resonant near 2 GHz ---------------------- |
| 54 | +set.seed(42) |
| 55 | +Z0 <- 50 |
| 56 | +freqs_ghz <- seq(1, 3, length.out = 80) |
| 57 | +omega <- 2 * pi * freqs_ghz * 1e9 |
| 58 | + |
| 59 | +R_ant <- 45 |
| 60 | +L_ant <- 5e-9 |
| 61 | +C_ant <- 1 / ((2 * pi * 2e9)^2 * L_ant) # resonance exactly at 2 GHz |
| 62 | + |
| 63 | +zr <- R_ant / Z0 |
| 64 | +zx <- (omega * L_ant - 1 / (omega * C_ant)) / Z0 |
| 65 | + |
| 66 | +# Reflection coefficient Gamma = (z - 1) / (z + 1) |
| 67 | +denom <- (zr + 1)^2 + zx^2 |
| 68 | +gamma_re <- ((zr - 1) * (zr + 1) + zx^2) / denom |
| 69 | +gamma_im <- 2 * zx / denom |
| 70 | + |
| 71 | +locus <- data.frame(gre = gamma_re, gim = gamma_im, freq = freqs_ghz) |
| 72 | + |
| 73 | +# Labels at 1 GHz, 2 GHz, 3 GHz |
| 74 | +label_freqs <- c(1, 2, 3) |
| 75 | +label_idx <- sapply(label_freqs, function(f) which.min(abs(locus$freq - f))) |
| 76 | +label_pts <- locus[label_idx, ] |
| 77 | +label_pts$lbl <- c("1 GHz", "2 GHz\n(res.)", "3 GHz") |
| 78 | + |
| 79 | +# Directional arrow at 3 GHz end to show sweep direction |
| 80 | +n_loc <- nrow(locus) |
| 81 | +dx <- locus$gre[n_loc] - locus$gre[n_loc - 5] |
| 82 | +dy <- locus$gim[n_loc] - locus$gim[n_loc - 5] |
| 83 | +norm <- sqrt(dx^2 + dy^2) |
| 84 | +arr_len <- 0.055 |
| 85 | +arr_x0 <- locus$gre[n_loc] - (dx / norm) * arr_len |
| 86 | +arr_y0 <- locus$gim[n_loc] - (dy / norm) * arr_len |
| 87 | + |
| 88 | +# --- Plot ------------------------------------------------------------------- |
| 89 | +p <- ggplot() + |
| 90 | + # Grid: outer circle |
| 91 | + geom_path(data = outer_circle, aes(x = x, y = y), |
| 92 | + color = INK_SOFT, linewidth = 0.7) + |
| 93 | + # Grid: constant-r circles |
| 94 | + geom_path(data = r_circles, aes(x = x, y = y, group = grp), |
| 95 | + color = INK_SOFT, linewidth = 0.22, alpha = 0.65) + |
| 96 | + # Grid: constant-x arcs |
| 97 | + geom_path(data = x_arcs, aes(x = x, y = y, group = grp), |
| 98 | + color = INK_SOFT, linewidth = 0.22, alpha = 0.65) + |
| 99 | + # Real axis |
| 100 | + geom_segment(aes(x = -1, xend = 1, y = 0, yend = 0), |
| 101 | + color = INK_SOFT, linewidth = 0.3) + |
| 102 | + # VSWR = 2 reference circle |
| 103 | + geom_path(data = vswr_circle, aes(x = x, y = y), |
| 104 | + color = OKABE_ITO[3], linewidth = 0.5, linetype = "dashed") + |
| 105 | + annotate("text", x = -0.38, y = 0.06, |
| 106 | + label = "VSWR = 2", size = 3.0, color = OKABE_ITO[3]) + |
| 107 | + # Matched-load centre marker |
| 108 | + geom_point(aes(x = 0, y = 0), |
| 109 | + color = INK_SOFT, size = 1.8, shape = 3) + |
| 110 | + # Impedance locus |
| 111 | + geom_path(data = locus, aes(x = gre, y = gim), |
| 112 | + color = OKABE_ITO[1], linewidth = 1.4) + |
| 113 | + # Directional arrow at 3 GHz end showing sweep direction |
| 114 | + annotate("segment", |
| 115 | + x = arr_x0, xend = locus$gre[n_loc], |
| 116 | + y = arr_y0, yend = locus$gim[n_loc], |
| 117 | + color = OKABE_ITO[1], linewidth = 1.4, |
| 118 | + arrow = arrow(length = unit(0.1, "inches"), type = "closed")) + |
| 119 | + # Start (1 GHz) and end (3 GHz) markers |
| 120 | + geom_point(data = locus[1, ], aes(x = gre, y = gim), |
| 121 | + color = OKABE_ITO[1], size = 3.5, shape = 16) + |
| 122 | + geom_point(data = locus[nrow(locus), ], aes(x = gre, y = gim), |
| 123 | + color = OKABE_ITO[2], size = 3.5, shape = 17) + |
| 124 | + # Frequency labels |
| 125 | + geom_text(data = label_pts, aes(x = gre, y = gim, label = lbl), |
| 126 | + color = INK, size = 3.2, hjust = -0.15, lineheight = 0.9) + |
| 127 | + # Resistance value labels along real axis |
| 128 | + annotate("text", |
| 129 | + x = (r_vals - 1) / (r_vals + 1), |
| 130 | + y = -0.06, |
| 131 | + label = as.character(r_vals), |
| 132 | + size = 3.0, color = INK_SOFT, vjust = 1) + |
| 133 | + coord_fixed(xlim = c(-1.15, 1.35), ylim = c(-1.15, 1.15)) + |
| 134 | + labs( |
| 135 | + title = "smith-chart-basic · r · ggplot2 · anyplot.ai", |
| 136 | + x = "Re(Γ)", |
| 137 | + y = "Im(Γ)" |
| 138 | + ) + |
| 139 | + theme_minimal(base_size = 8) + |
| 140 | + theme( |
| 141 | + plot.background = element_rect(fill = PAGE_BG, color = PAGE_BG), |
| 142 | + panel.background = element_rect(fill = PAGE_BG, color = NA), |
| 143 | + panel.grid.major = element_blank(), |
| 144 | + panel.grid.minor = element_blank(), |
| 145 | + panel.border = element_blank(), |
| 146 | + axis.title = element_text(color = INK, size = 10), |
| 147 | + axis.text = element_text(color = INK_SOFT, size = 8), |
| 148 | + plot.title = element_text(color = INK, size = 12), |
| 149 | + axis.line = element_blank() |
| 150 | + ) |
| 151 | + |
| 152 | +# --- Save ------------------------------------------------------------------- |
| 153 | +ggsave( |
| 154 | + filename = sprintf("plot-%s.png", THEME), |
| 155 | + plot = p, |
| 156 | + device = ragg::agg_png, |
| 157 | + width = 6, |
| 158 | + height = 6, |
| 159 | + units = "in", |
| 160 | + dpi = 400 |
| 161 | +) |
0 commit comments