Skip to content

Commit abec69c

Browse files
feat(matplotlib): implement smith-chart-basic (#3868)
## Implementation: `smith-chart-basic` - matplotlib Implements the **matplotlib** version of `smith-chart-basic`. **File:** `plots/smith-chart-basic/implementations/matplotlib.py` **Parent Issue:** #3792 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/21047488873)* --------- 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 34d5c44 commit abec69c

File tree

2 files changed

+383
-0
lines changed

2 files changed

+383
-0
lines changed
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
""" pyplots.ai
2+
smith-chart-basic: Smith Chart for RF/Impedance
3+
Library: matplotlib 3.10.8 | Python 3.13.11
4+
Quality: 91/100 | Created: 2026-01-15
5+
"""
6+
7+
import matplotlib.pyplot as plt
8+
import numpy as np
9+
10+
11+
# Reference impedance
12+
Z0 = 50 # ohms
13+
14+
# Generate example data: antenna impedance sweep from 1 GHz to 6 GHz
15+
np.random.seed(42)
16+
frequency = np.linspace(1e9, 6e9, 50) # 50 frequency points
17+
18+
# Simulate realistic antenna impedance variation with frequency
19+
# This creates a spiral-like pattern typical of antenna impedance measurements
20+
z_real = 50 + 30 * np.sin(2 * np.pi * (frequency - 1e9) / 2e9) + 10 * np.cos(4 * np.pi * (frequency - 1e9) / 5e9)
21+
z_imag = 20 * np.cos(2 * np.pi * (frequency - 1e9) / 1.5e9) + 15 * np.sin(3 * np.pi * (frequency - 1e9) / 5e9)
22+
23+
# Normalize impedance
24+
z_norm = (z_real + 1j * z_imag) / Z0
25+
26+
# Convert to reflection coefficient (gamma)
27+
gamma = (z_norm - 1) / (z_norm + 1)
28+
29+
# Create square figure for Smith chart
30+
fig, ax = plt.subplots(figsize=(12, 12))
31+
ax.set_aspect("equal")
32+
33+
# Colors
34+
grid_color = "#888888"
35+
circle_color = "#306998"
36+
data_color = "#FFD43B"
37+
38+
# Draw constant resistance circles
39+
r_values = [0, 0.2, 0.5, 1, 2, 5]
40+
theta = np.linspace(0, 2 * np.pi, 500)
41+
42+
for r in r_values:
43+
# Center and radius of constant resistance circle
44+
center = r / (r + 1)
45+
radius = 1 / (r + 1)
46+
x_circle = center + radius * np.cos(theta)
47+
y_circle = radius * np.sin(theta)
48+
# Clip to unit circle
49+
mask = x_circle**2 + y_circle**2 <= 1.001
50+
x_clipped = np.where(mask, x_circle, np.nan)
51+
y_clipped = np.where(mask, y_circle, np.nan)
52+
ax.plot(x_clipped, y_clipped, color=grid_color, linewidth=1, alpha=0.6)
53+
# Label resistance circles
54+
if r > 0:
55+
label_x = center + radius
56+
if label_x <= 1:
57+
ax.text(label_x + 0.02, 0.02, f"{r}", fontsize=12, color=grid_color, ha="left", va="bottom")
58+
59+
# Draw constant reactance arcs
60+
x_values = [0.2, 0.5, 1, 2, 5]
61+
62+
for x in x_values:
63+
# Positive reactance (inductive - upper half)
64+
center_y = 1 / x
65+
radius = 1 / x
66+
arc_theta = np.linspace(0, np.pi, 500)
67+
x_arc = 1 + radius * np.cos(arc_theta + np.pi)
68+
y_arc = center_y + radius * np.sin(arc_theta + np.pi)
69+
# Clip to unit circle
70+
mask = x_arc**2 + y_arc**2 <= 1.001
71+
x_clipped = np.where(mask, x_arc, np.nan)
72+
y_clipped = np.where(mask, y_arc, np.nan)
73+
ax.plot(x_clipped, y_clipped, color=grid_color, linewidth=1, alpha=0.6)
74+
75+
# Negative reactance (capacitive - lower half)
76+
y_arc_neg = -center_y + radius * np.sin(arc_theta)
77+
mask = x_arc**2 + y_arc_neg**2 <= 1.001
78+
x_clipped = np.where(mask, x_arc, np.nan)
79+
y_clipped = np.where(mask, y_arc_neg, np.nan)
80+
ax.plot(x_clipped, y_clipped, color=grid_color, linewidth=1, alpha=0.6)
81+
82+
# Label reactance arcs
83+
# Find intersection with unit circle
84+
if x <= 1:
85+
angle = 2 * np.arctan(1 / x)
86+
label_x = np.cos(angle)
87+
label_y = np.sin(angle)
88+
ax.text(label_x, label_y + 0.05, f"+j{x}", fontsize=11, color=grid_color, ha="center", va="bottom")
89+
ax.text(label_x, -label_y - 0.05, f"-j{x}", fontsize=11, color=grid_color, ha="center", va="top")
90+
91+
# Draw horizontal axis (real axis, x=0)
92+
ax.axhline(y=0, color=grid_color, linewidth=1, alpha=0.6)
93+
94+
# Draw unit circle (boundary)
95+
unit_theta = np.linspace(0, 2 * np.pi, 500)
96+
ax.plot(np.cos(unit_theta), np.sin(unit_theta), color=circle_color, linewidth=2.5)
97+
98+
# Draw center point (matched condition)
99+
ax.plot(0, 0, "o", color=circle_color, markersize=10, label="Matched (Z=Z₀)")
100+
101+
# Plot impedance locus
102+
ax.plot(gamma.real, gamma.imag, color=data_color, linewidth=3, label="Impedance Locus", zorder=5)
103+
ax.scatter(gamma.real, gamma.imag, c=data_color, s=60, edgecolors="black", linewidths=0.5, zorder=6)
104+
105+
# Add frequency labels at key points
106+
freq_label_indices = [0, 12, 24, 36, 49] # Start, middle points, end
107+
for idx in freq_label_indices:
108+
freq_ghz = frequency[idx] / 1e9
109+
x_pos = gamma.real[idx]
110+
y_pos = gamma.imag[idx]
111+
# Offset labels to avoid overlap with data points
112+
offset_x = 0.08 if x_pos < 0.5 else -0.08
113+
offset_y = 0.08 if y_pos >= 0 else -0.08
114+
ax.annotate(
115+
f"{freq_ghz:.1f} GHz",
116+
(x_pos, y_pos),
117+
xytext=(x_pos + offset_x, y_pos + offset_y),
118+
fontsize=14,
119+
fontweight="bold",
120+
color="black",
121+
arrowprops={"arrowstyle": "->", "color": "black", "lw": 1.5},
122+
zorder=10,
123+
)
124+
125+
# Draw VSWR circles (optional - constant reflection coefficient magnitude)
126+
vswr_values = [1.5, 2, 3]
127+
vswr_angles = [np.pi / 4, np.pi / 3, 5 * np.pi / 12] # Different angles for each label
128+
for vswr, label_angle in zip(vswr_values, vswr_angles, strict=True):
129+
gamma_mag = (vswr - 1) / (vswr + 1)
130+
vswr_theta = np.linspace(0, 2 * np.pi, 200)
131+
ax.plot(
132+
gamma_mag * np.cos(vswr_theta),
133+
gamma_mag * np.sin(vswr_theta),
134+
"--",
135+
color="#CC4444",
136+
linewidth=1.5,
137+
alpha=0.7,
138+
label=f"VSWR={vswr}" if vswr == 1.5 else "",
139+
)
140+
# Label VSWR circles at different angles to avoid overlap
141+
label_x = gamma_mag * np.cos(label_angle)
142+
label_y = gamma_mag * np.sin(label_angle)
143+
ax.text(label_x, label_y + 0.06, f"VSWR={vswr}", fontsize=11, color="#CC4444", alpha=0.9, ha="center")
144+
145+
# Styling
146+
ax.set_xlim(-1.25, 1.25)
147+
ax.set_ylim(-1.25, 1.25)
148+
ax.set_xlabel("Real(Γ)", fontsize=20)
149+
ax.set_ylabel("Imag(Γ)", fontsize=20)
150+
ax.set_title("smith-chart-basic · matplotlib · pyplots.ai", fontsize=24)
151+
ax.tick_params(axis="both", labelsize=16)
152+
ax.legend(fontsize=14, loc="upper left")
153+
154+
# Remove axis ticks for cleaner Smith chart appearance
155+
ax.set_xticks([])
156+
ax.set_yticks([])
157+
158+
# Add axis labels on chart edge
159+
ax.text(1.15, 0, "Open\n(Γ=1)", fontsize=12, ha="center", va="center", color=circle_color)
160+
ax.text(-1.15, 0, "Short\n(Γ=-1)", fontsize=12, ha="center", va="center", color=circle_color)
161+
ax.text(0, 1.15, "+jX\n(Inductive)", fontsize=12, ha="center", va="center", color=grid_color)
162+
ax.text(0, -1.15, "-jX\n(Capacitive)", fontsize=12, ha="center", va="center", color=grid_color)
163+
164+
plt.tight_layout()
165+
plt.savefig("plot.png", dpi=300, bbox_inches="tight")
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
library: matplotlib
2+
specification_id: smith-chart-basic
3+
created: '2026-01-15T21:52:42Z'
4+
updated: '2026-01-15T21:55:17Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 21047488873
7+
issue: 3792
8+
python_version: 3.13.11
9+
library_version: 3.10.8
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/smith-chart-basic/matplotlib/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/smith-chart-basic/matplotlib/plot_thumb.png
12+
preview_html: null
13+
quality_score: 91
14+
review:
15+
strengths:
16+
- Excellent implementation of a complex specialized chart type from scratch
17+
- All Smith chart grid elements (resistance circles, reactance arcs) are mathematically
18+
correct with proper clipping to unit circle
19+
- Comprehensive labeling including Open/Short/Inductive/Capacitive annotations at
20+
chart boundaries
21+
- Optional VSWR circles add engineering value beyond the basic spec requirements
22+
- Clean, well-organized code with clear comments explaining each section
23+
- Realistic antenna impedance data that creates a visually interesting spiral pattern
24+
weaknesses:
25+
- Grid alpha at 0.6 is slightly too prominent; 0.3-0.4 would provide subtler background
26+
- Does not leverage distinctive matplotlib features (uses only basic plot, scatter,
27+
text, annotate)
28+
image_description: The plot displays a Smith chart for RF/impedance visualization.
29+
The outer boundary is a solid blue circle representing |Γ|=1 (total reflection).
30+
Inside, gray constant resistance circles (labeled 0.2, 0.5, 1, 2, 5) are drawn
31+
centered along the horizontal axis, and constant reactance arcs curve from the
32+
right edge. The chart shows inductive (+jX) on the upper half and capacitive (-jX)
33+
on the lower half, with labels at the cardinal positions. A yellow impedance locus
34+
curve traces the antenna impedance sweep from 1.0 GHz to 6.0 GHz with circular
35+
markers at each frequency point. Key frequencies (1.0, 2.2, 3.4, 4.7, 6.0 GHz)
36+
are labeled with black arrows. Three red dashed VSWR circles (1.5, 2, 3) are drawn
37+
concentrically from the center. The matched condition (Z=Z₀) is marked at the
38+
center with a blue dot. Corner labels indicate "Open (Γ=1)", "Short (Γ=-1)", "+jX
39+
(Inductive)", and "-jX (Capacitive)". The legend in the upper left shows "Matched
40+
(Z=Z₀)", "Impedance Locus", and "VSWR=1.5".
41+
criteria_checklist:
42+
visual_quality:
43+
score: 37
44+
max: 40
45+
items:
46+
- id: VQ-01
47+
name: Text Legibility
48+
score: 10
49+
max: 10
50+
passed: true
51+
comment: Title at 24pt, axis labels at 20pt, frequency labels at 14pt bold,
52+
all perfectly readable at full size
53+
- id: VQ-02
54+
name: No Overlap
55+
score: 7
56+
max: 8
57+
passed: true
58+
comment: Minor overlap of some reactance arc labels with other elements, but
59+
main content readable
60+
- id: VQ-03
61+
name: Element Visibility
62+
score: 8
63+
max: 8
64+
passed: true
65+
comment: Yellow locus with s=60 markers and linewidth=3 is clearly visible
66+
against gray grid
67+
- id: VQ-04
68+
name: Color Accessibility
69+
score: 5
70+
max: 5
71+
passed: true
72+
comment: Blue/yellow/red/gray palette is colorblind-safe with good contrast
73+
- id: VQ-05
74+
name: Layout Balance
75+
score: 5
76+
max: 5
77+
passed: true
78+
comment: Square 12x12 figure fills canvas well with balanced margins
79+
- id: VQ-06
80+
name: Axis Labels
81+
score: 1
82+
max: 2
83+
passed: true
84+
comment: Real(Γ) and Imag(Γ) are descriptive but dimensionless quantities
85+
- id: VQ-07
86+
name: Grid & Legend
87+
score: 1
88+
max: 2
89+
passed: true
90+
comment: Grid at alpha=0.6 is slightly prominent; legend is well-placed
91+
spec_compliance:
92+
score: 25
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 constant resistance circles and constant
101+
reactance arcs
102+
- id: SC-02
103+
name: Data Mapping
104+
score: 5
105+
max: 5
106+
passed: true
107+
comment: Complex impedance correctly converted to reflection coefficient Γ
108+
and plotted
109+
- id: SC-03
110+
name: Required Features
111+
score: 5
112+
max: 5
113+
passed: true
114+
comment: 'All spec features present: resistance circles, reactance arcs, impedance
115+
locus, frequency labels, VSWR circles'
116+
- id: SC-04
117+
name: Data Range
118+
score: 3
119+
max: 3
120+
passed: true
121+
comment: All data points visible within the unit circle boundary
122+
- id: SC-05
123+
name: Legend Accuracy
124+
score: 2
125+
max: 2
126+
passed: true
127+
comment: Legend correctly identifies matched condition, impedance locus, and
128+
VSWR reference
129+
- id: SC-06
130+
name: Title Format
131+
score: 2
132+
max: 2
133+
passed: true
134+
comment: 'Uses correct format: smith-chart-basic · matplotlib · pyplots.ai'
135+
data_quality:
136+
score: 19
137+
max: 20
138+
items:
139+
- id: DQ-01
140+
name: Feature Coverage
141+
score: 8
142+
max: 8
143+
passed: true
144+
comment: Shows spiral impedance locus typical of antenna measurements, both
145+
inductive and capacitive regions traversed
146+
- id: DQ-02
147+
name: Realistic Context
148+
score: 6
149+
max: 7
150+
passed: true
151+
comment: Antenna impedance sweep from 1-6 GHz is a plausible RF engineering
152+
scenario
153+
- id: DQ-03
154+
name: Appropriate Scale
155+
score: 5
156+
max: 5
157+
passed: true
158+
comment: Impedance values around 50Ω with ±30Ω variation is realistic for
159+
antenna measurements
160+
code_quality:
161+
score: 10
162+
max: 10
163+
items:
164+
- id: CQ-01
165+
name: KISS Structure
166+
score: 3
167+
max: 3
168+
passed: true
169+
comment: Follows imports → data → plot → save structure without functions/classes
170+
- id: CQ-02
171+
name: Reproducibility
172+
score: 3
173+
max: 3
174+
passed: true
175+
comment: Uses np.random.seed(42) for reproducible results
176+
- id: CQ-03
177+
name: Clean Imports
178+
score: 2
179+
max: 2
180+
passed: true
181+
comment: Only matplotlib.pyplot and numpy are imported, both are used
182+
- id: CQ-04
183+
name: No Deprecated API
184+
score: 1
185+
max: 1
186+
passed: true
187+
comment: No deprecated matplotlib API used
188+
- id: CQ-05
189+
name: Output Correct
190+
score: 1
191+
max: 1
192+
passed: true
193+
comment: Saves as plot.png with dpi=300 and bbox_inches=tight
194+
library_features:
195+
score: 0
196+
max: 5
197+
items:
198+
- id: LF-01
199+
name: Distinctive Features
200+
score: 0
201+
max: 5
202+
passed: false
203+
comment: Manually draws all Smith chart elements; does not showcase distinctive
204+
matplotlib features beyond basic plotting
205+
verdict: APPROVED
206+
impl_tags:
207+
dependencies: []
208+
techniques:
209+
- annotations
210+
- patches
211+
- manual-ticks
212+
patterns:
213+
- data-generation
214+
- iteration-over-groups
215+
dataprep: []
216+
styling:
217+
- alpha-blending
218+
- edge-highlighting

0 commit comments

Comments
 (0)