Skip to content

Commit 34d5c44

Browse files
feat(plotnine): implement smith-chart-basic (#3866)
## Implementation: `smith-chart-basic` - plotnine Implements the **plotnine** version of `smith-chart-basic`. **File:** `plots/smith-chart-basic/implementations/plotnine.py` **Parent Issue:** #3792 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/21047489075)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent f8ebcf3 commit 34d5c44

2 files changed

Lines changed: 376 additions & 0 deletions

File tree

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
""" pyplots.ai
2+
smith-chart-basic: Smith Chart for RF/Impedance
3+
Library: plotnine 0.15.2 | Python 3.13.11
4+
Quality: 90/100 | Created: 2026-01-15
5+
"""
6+
7+
import numpy as np
8+
import pandas as pd
9+
from plotnine import (
10+
aes,
11+
annotate,
12+
coord_fixed,
13+
element_blank,
14+
element_rect,
15+
element_text,
16+
geom_path,
17+
geom_point,
18+
geom_text,
19+
ggplot,
20+
labs,
21+
scale_x_continuous,
22+
scale_y_continuous,
23+
theme,
24+
)
25+
26+
27+
# Reference impedance
28+
Z0 = 50
29+
30+
# Generate Smith chart grid - constant resistance circles
31+
# For resistance r, circle center at (r/(r+1), 0) with radius 1/(r+1)
32+
r_values = [0, 0.2, 0.5, 1, 2, 5]
33+
theta = np.linspace(0, 2 * np.pi, 200)
34+
35+
r_circle_data = []
36+
for r in r_values:
37+
cx = r / (r + 1)
38+
radius = 1 / (r + 1)
39+
x = cx + radius * np.cos(theta)
40+
y = radius * np.sin(theta)
41+
# Clip to unit circle
42+
mask = x**2 + y**2 <= 1.001
43+
r_circle_data.append(pd.DataFrame({"x": x[mask], "y": y[mask], "type": "r_circle", "value": f"r={r}"}))
44+
45+
r_circles_df = pd.concat(r_circle_data, ignore_index=True)
46+
47+
# Constant reactance arcs
48+
# For reactance x, circle center at (1, 1/x) with radius 1/|x|
49+
x_values = [0.2, 0.5, 1, 2, 5]
50+
reactance_data = []
51+
52+
for xv in x_values:
53+
# Positive reactance (upper half)
54+
radius_x = 1 / abs(xv)
55+
cy_pos = 1 / xv
56+
t = np.linspace(0, 2 * np.pi, 500)
57+
arc_x = 1 + radius_x * np.cos(t)
58+
arc_y = cy_pos + radius_x * np.sin(t)
59+
# Keep only points inside unit circle
60+
mask = (arc_x**2 + arc_y**2 <= 1.001) & (arc_x >= -0.01)
61+
if np.any(mask):
62+
reactance_data.append(pd.DataFrame({"x": arc_x[mask], "y": arc_y[mask], "type": "x_arc", "value": f"x={xv}"}))
63+
64+
# Negative reactance (lower half)
65+
cy_neg = -1 / xv
66+
arc_y_neg = cy_neg + radius_x * np.sin(t)
67+
mask_neg = (arc_x**2 + arc_y_neg**2 <= 1.001) & (arc_x >= -0.01)
68+
if np.any(mask_neg):
69+
reactance_data.append(
70+
pd.DataFrame({"x": arc_x[mask_neg], "y": arc_y_neg[mask_neg], "type": "x_arc", "value": f"x=-{xv}"})
71+
)
72+
73+
reactance_df = pd.concat(reactance_data, ignore_index=True)
74+
75+
# Outer boundary circle (|gamma| = 1)
76+
boundary_theta = np.linspace(0, 2 * np.pi, 200)
77+
boundary_df = pd.DataFrame(
78+
{"x": np.cos(boundary_theta), "y": np.sin(boundary_theta), "type": "boundary", "value": "boundary"}
79+
)
80+
81+
# Horizontal axis (real axis)
82+
axis_df = pd.DataFrame({"x": [-1, 1], "y": [0, 0], "type": "axis", "value": "axis"})
83+
84+
# Example impedance data: antenna S11 measurement from 1-6 GHz
85+
np.random.seed(42)
86+
n_points = 50
87+
freq_ghz = np.linspace(1, 6, n_points)
88+
89+
# Simulate antenna impedance variation with frequency
90+
# Creates realistic spiral pattern on Smith chart
91+
z_real = 50 + 30 * np.sin(2 * np.pi * freq_ghz / 2.5) + np.random.randn(n_points) * 3
92+
z_imag = 20 * np.cos(2 * np.pi * freq_ghz / 3) + 15 * (freq_ghz - 3) + np.random.randn(n_points) * 2
93+
94+
# Normalize impedance
95+
z_norm = (z_real + 1j * z_imag) / Z0
96+
97+
# Convert to reflection coefficient (gamma) for Smith chart coordinates
98+
gamma = (z_norm - 1) / (z_norm + 1)
99+
gamma_real = np.real(gamma)
100+
gamma_imag = np.imag(gamma)
101+
102+
impedance_df = pd.DataFrame({"x": gamma_real, "y": gamma_imag, "freq": freq_ghz, "type": "impedance"})
103+
104+
# Select frequency labels at key points
105+
label_indices = [0, 12, 24, 36, 49]
106+
labels_df = impedance_df.iloc[label_indices].copy()
107+
labels_df["label"] = [f"{f:.1f} GHz" for f in labels_df["freq"]]
108+
labels_df["y_offset"] = labels_df["y"] + 0.08
109+
110+
# Combine grid data
111+
grid_df = pd.concat([r_circles_df, reactance_df, boundary_df, axis_df], ignore_index=True)
112+
113+
# Create plot
114+
plot = (
115+
ggplot()
116+
# Grid lines
117+
+ geom_path(
118+
aes(x="x", y="y", group="value"),
119+
data=grid_df[grid_df["type"] == "r_circle"],
120+
color="#CCCCCC",
121+
size=0.5,
122+
alpha=0.8,
123+
)
124+
+ geom_path(
125+
aes(x="x", y="y", group="value"), data=grid_df[grid_df["type"] == "x_arc"], color="#CCCCCC", size=0.5, alpha=0.8
126+
)
127+
# Boundary circle
128+
+ geom_path(aes(x="x", y="y"), data=boundary_df, color="#333333", size=1)
129+
# Real axis
130+
+ geom_path(aes(x="x", y="y"), data=axis_df, color="#333333", size=0.5)
131+
# Impedance locus curve
132+
+ geom_path(aes(x="x", y="y"), data=impedance_df, color="#306998", size=1.5)
133+
# Data points
134+
+ geom_point(aes(x="x", y="y"), data=impedance_df, color="#306998", size=2, alpha=0.7)
135+
# Frequency labels
136+
+ geom_text(aes(x="x", y="y_offset", label="label"), data=labels_df, color="#FFD43B", size=12, fontweight="bold")
137+
# Center point marker (Z = Z0)
138+
+ annotate("point", x=0, y=0, color="#E74C3C", size=4)
139+
+ annotate("text", x=0.12, y=-0.08, label="Z=Z₀", color="#E74C3C", size=11)
140+
# Styling
141+
+ coord_fixed(ratio=1, xlim=(-1.3, 1.3), ylim=(-1.3, 1.3))
142+
+ scale_x_continuous(breaks=[])
143+
+ scale_y_continuous(breaks=[])
144+
+ labs(title="smith-chart-basic · plotnine · pyplots.ai", x="", y="")
145+
+ theme(
146+
figure_size=(12, 12),
147+
plot_title=element_text(size=24, ha="center", weight="bold"),
148+
panel_background=element_rect(fill="white"),
149+
panel_grid_major=element_blank(),
150+
panel_grid_minor=element_blank(),
151+
axis_text=element_blank(),
152+
axis_ticks=element_blank(),
153+
plot_background=element_rect(fill="white"),
154+
)
155+
)
156+
157+
# Save
158+
plot.save("plot.png", dpi=300)
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
library: plotnine
2+
specification_id: smith-chart-basic
3+
created: '2026-01-15T21:51:40Z'
4+
updated: '2026-01-15T21:54:21Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 21047489075
7+
issue: 3792
8+
python_version: 3.13.11
9+
library_version: 0.15.2
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/smith-chart-basic/plotnine/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/smith-chart-basic/plotnine/plot_thumb.png
12+
preview_html: null
13+
quality_score: 90
14+
review:
15+
strengths:
16+
- Excellent implementation of Smith chart using plotnine grammar of graphics approach
17+
- Mathematically correct calculation of constant resistance circles and reactance
18+
arcs
19+
- Proper conversion from complex impedance to reflection coefficient coordinates
20+
- Clean layered composition with grid, boundary, axis, data locus, and annotations
21+
- Realistic RF engineering data scenario with antenna S11 measurements
22+
- Good use of coord_fixed for maintaining circular aspect ratio
23+
weaknesses:
24+
- Frequency labels in yellow may have lower contrast against white background than
25+
ideal
26+
- Grid line labels (r=0, r=0.2, x=0.5, etc.) not shown - would enhance educational
27+
value
28+
- Data point markers could be slightly larger for better visibility at full resolution
29+
image_description: The plot displays a Smith chart with the correct circular layout.
30+
The outer boundary is a black circle representing |gamma|=1 (total reflection).
31+
Inside, light gray constant resistance circles are drawn (centered along the horizontal
32+
axis) and constant reactance arcs curve from the right edge. A horizontal real
33+
axis line passes through the center. The impedance locus is drawn as a connected
34+
blue (#306998) path with small markers showing an antenna S11 measurement sweep
35+
from 1-6 GHz. Five frequency labels (1.0 GHz, 2.2 GHz, 3.4 GHz, 4.7 GHz, 6.0 GHz)
36+
appear in yellow/gold text positioned above their corresponding data points. A
37+
red dot marks the center point (Z=Z₀) with a red label. The chart uses a clean
38+
white background with no axis labels or tick marks (appropriate for a Smith chart).
39+
The title "smith-chart-basic · plotnine · pyplots.ai" appears centered at the
40+
top in bold black text.
41+
criteria_checklist:
42+
visual_quality:
43+
score: 36
44+
max: 40
45+
items:
46+
- id: VQ-01
47+
name: Text Legibility
48+
score: 9
49+
max: 10
50+
passed: true
51+
comment: Title is large and bold, frequency labels are readable though could
52+
be slightly larger
53+
- id: VQ-02
54+
name: No Overlap
55+
score: 8
56+
max: 8
57+
passed: true
58+
comment: No overlapping text elements
59+
- id: VQ-03
60+
name: Element Visibility
61+
score: 7
62+
max: 8
63+
passed: true
64+
comment: Grid lines visible with appropriate alpha, data points could be slightly
65+
larger
66+
- id: VQ-04
67+
name: Color Accessibility
68+
score: 5
69+
max: 5
70+
passed: true
71+
comment: Blue/yellow/red color scheme is colorblind-safe with good contrast
72+
- id: VQ-05
73+
name: Layout Balance
74+
score: 5
75+
max: 5
76+
passed: true
77+
comment: Chart is well-centered with balanced margins, uses square 1:1 aspect
78+
ratio appropriately
79+
- id: VQ-06
80+
name: Axis Labels
81+
score: 0
82+
max: 2
83+
passed: true
84+
comment: N/A for Smith chart (no conventional axes), but spec-appropriate
85+
- id: VQ-07
86+
name: Grid & Legend
87+
score: 2
88+
max: 2
89+
passed: true
90+
comment: Grid lines are subtle with appropriate alpha, no legend needed
91+
spec_compliance:
92+
score: 24
93+
max: 25
94+
items:
95+
- id: SC-01
96+
name: Plot Type
97+
score: 8
98+
max: 8
99+
passed: true
100+
comment: Correct Smith chart with resistance circles and reactance arcs
101+
- id: SC-02
102+
name: Data Mapping
103+
score: 5
104+
max: 5
105+
passed: true
106+
comment: Complex impedance correctly converted to reflection coefficient (gamma)
107+
coordinates
108+
- id: SC-03
109+
name: Required Features
110+
score: 5
111+
max: 5
112+
passed: true
113+
comment: 'All required features present: grid, normalized impedance, connected
114+
locus curve, frequency labels, center marker'
115+
- id: SC-04
116+
name: Data Range
117+
score: 3
118+
max: 3
119+
passed: true
120+
comment: All data visible within unit circle boundary
121+
- id: SC-05
122+
name: Legend Accuracy
123+
score: 1
124+
max: 2
125+
passed: true
126+
comment: No legend needed, but labels could better explain the grid values
127+
- id: SC-06
128+
name: Title Format
129+
score: 2
130+
max: 2
131+
passed: true
132+
comment: 'Correct format: smith-chart-basic · plotnine · pyplots.ai'
133+
data_quality:
134+
score: 18
135+
max: 20
136+
items:
137+
- id: DQ-01
138+
name: Feature Coverage
139+
score: 7
140+
max: 8
141+
passed: true
142+
comment: Shows impedance variation with frequency creating realistic spiral
143+
pattern, visits different regions of Smith chart
144+
- id: DQ-02
145+
name: Realistic Context
146+
score: 7
147+
max: 7
148+
passed: true
149+
comment: Antenna S11 measurement from 1-6 GHz is a realistic RF engineering
150+
scenario
151+
- id: DQ-03
152+
name: Appropriate Scale
153+
score: 4
154+
max: 5
155+
passed: true
156+
comment: Impedance values around 50 ohms reference are realistic, frequency
157+
range is typical for antenna design
158+
code_quality:
159+
score: 9
160+
max: 10
161+
items:
162+
- id: CQ-01
163+
name: KISS Structure
164+
score: 3
165+
max: 3
166+
passed: true
167+
comment: 'Flat structure: imports, data generation, plot construction, save'
168+
- id: CQ-02
169+
name: Reproducibility
170+
score: 3
171+
max: 3
172+
passed: true
173+
comment: Uses np.random.seed(42)
174+
- id: CQ-03
175+
name: Clean Imports
176+
score: 2
177+
max: 2
178+
passed: true
179+
comment: All imports are used
180+
- id: CQ-04
181+
name: No Deprecated API
182+
score: 1
183+
max: 1
184+
passed: true
185+
comment: Uses current plotnine API
186+
- id: CQ-05
187+
name: Output Correct
188+
score: 0
189+
max: 1
190+
passed: false
191+
comment: 'Minor: could use higher dpi for 4800x2700 target'
192+
library_features:
193+
score: 3
194+
max: 5
195+
items:
196+
- id: LF-01
197+
name: Distinctive Features
198+
score: 3
199+
max: 5
200+
passed: true
201+
comment: Uses plotnine grammar of graphics with layered geom_path and geom_point,
202+
coord_fixed for aspect ratio, proper theming
203+
verdict: APPROVED
204+
impl_tags:
205+
dependencies: []
206+
techniques:
207+
- annotations
208+
- layer-composition
209+
- patches
210+
patterns:
211+
- data-generation
212+
- iteration-over-groups
213+
- matrix-construction
214+
dataprep:
215+
- normalization
216+
styling:
217+
- minimal-chrome
218+
- alpha-blending

0 commit comments

Comments
 (0)