Skip to content

Commit a179988

Browse files
feat(ggplot2): implement smith-chart-basic (#7453)
## Implementation: `smith-chart-basic` - r/ggplot2 Implements the **r/ggplot2** version of `smith-chart-basic`. **File:** `plots/smith-chart-basic/implementations/r/ggplot2.R` **Parent Issue:** #3792 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/26135676591)* --------- Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Markus Neusinger <2921697+MarkusNeusinger@users.noreply.github.com>
1 parent 4858879 commit a179988

2 files changed

Lines changed: 424 additions & 0 deletions

File tree

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
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

Comments
 (0)