Bode plot
measured coplanar-waveguide ring-slot antenna (VNA, W-band)
Example from the compendium of canonical charts
Python Code
"""Bode plot — measured coplanar-waveguide ring-slot antenna (VNA, W-band)."""
from pathlib import Path
# ── Palette + theme (matching the Plotly Studio gallery these charts ship in) ──
VIOLET, TEAL, GREEN, PINK, ORANGE = "#845EEE", "#52B3D0", "#55B685", "#DA5597", "#E9A23B"
PRIMARY, SECONDARY = VIOLET, TEAL
COLORWAY = [VIOLET, TEAL, GREEN, PINK, ORANGE]
BG, TEXT, GRID, MUTED = "#ffffff", "#1c2024", "#d9d9e0", "#60646c"
FONT = "Inter, -apple-system, BlinkMacSystemFont, sans-serif"
COLORSCALE = [[0, "rgba(132, 94, 238, 0.05)"], [1, "rgba(132, 94, 238, 0.9)"]]
def apply_theme(fig):
"""Light gallery theme: white background, Inter font, soft gridlines."""
fig.update_layout(
paper_bgcolor=BG, plot_bgcolor=BG, colorway=COLORWAY,
font=dict(family=FONT, color=TEXT, size=12),
legend=dict(font=dict(color=TEXT)),
hoverlabel=dict(bgcolor="#f0f0f3", font=dict(color=TEXT, family=FONT), bordercolor=GRID),
)
fig.update_xaxes(gridcolor=GRID, linecolor=GRID, zerolinecolor=GRID)
fig.update_yaxes(gridcolor=GRID, linecolor=GRID, zerolinecolor=GRID)
def fetch_csv(url, **kwargs):
import io
import pandas as pd
import requests
r = requests.get(url, timeout=60)
r.raise_for_status()
return pd.read_csv(io.StringIO(r.text), **kwargs)
def fetch_json(url):
import requests
r = requests.get(url, timeout=60)
r.raise_for_status()
return r.json()
import numpy as np
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import requests, warnings
# Real vector-network-analyzer sweep of a CPW ring-slot antenna, 75–110 GHz
# (W-band), shipped as example data with scikit-rf. Touchstone .s2p, real/imag
# S-parameters referenced to 50 Ω. S11 = return loss (how well the antenna is
# matched), S21 = transmission through the two-port fixture.
URL = "https://raw.githubusercontent.com/scikit-rf/scikit-rf/master/skrf/data/ring%20slot.s2p"
def generate():
print("fetching measured ring-slot antenna S-parameters (scikit-rf) …")
warnings.filterwarnings("ignore")
requests.packages.urllib3.disable_warnings()
r = requests.get(URL, verify=False, timeout=30)
r.raise_for_status()
# Touchstone: skip '!' comments and the '#' option line; data rows start
# with a digit. Columns: freq ReS11 ImS11 ReS21 ImS21 ReS12 ImS12 ReS22 ImS22
rows = [ln.split() for ln in r.text.splitlines() if ln[:1].isdigit()]
a = np.array(rows, dtype=float)
f = a[:, 0] # frequency, GHz (header declares "# GHz")
print(f" parsed {len(f)} frequency points, {f[0]:.0f}–{f[-1]:.0f} GHz")
def db_phase(re, im):
z = re + 1j * im
mag = 20 * np.log10(np.abs(z))
ph = np.unwrap(np.angle(z)) * 180 / np.pi
return mag, ph
s11_db, s11_ph = db_phase(a[:, 1], a[:, 2])
s21_db, s21_ph = db_phase(a[:, 3], a[:, 4])
# Resonance: deepest return loss (best match) — where the antenna radiates.
res_i = int(np.argmin(s11_db))
res_f, res_db = f[res_i], s11_db[res_i]
fig = make_subplots(
rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.07,
subplot_titles=("Magnitude (dB)", "Phase (°)"),
)
traces = [
("S₁₁ return loss", VIOLET, s11_db, s11_ph),
("S₂₁ transmission ", TEAL, s21_db, s21_ph),
]
for name, color, mag, ph in traces:
fig.add_trace(go.Scatter(
x=f, y=mag, mode="lines", name=name,
line=dict(color=color, width=2.5), legendgroup=name,
hovertemplate="%{x:.2f} GHz<br>%{y:.2f} dB<extra></extra>",
), row=1, col=1)
fig.add_trace(go.Scatter(
x=f, y=ph, mode="lines", name=name,
line=dict(color=color, width=2.5), legendgroup=name,
showlegend=False,
hovertemplate="%{x:.2f} GHz<br>%{y:.1f}°<extra></extra>",
), row=2, col=1)
# Mark the return-loss resonance.
fig.add_vline(x=res_f, line=dict(color=MUTED, width=1, dash="dot"), row=1, col=1)
fig.add_vline(x=res_f, line=dict(color=MUTED, width=1, dash="dot"), row=2, col=1)
fig.add_trace(go.Scatter(
x=[res_f], y=[res_db], mode="markers+text",
marker=dict(color=VIOLET, size=11, symbol="circle", line=dict(color="white", width=2)),
text=[f" resonance {res_f:.1f} GHz · {res_db:.1f} dB"],
textposition="middle right", textfont=dict(color=MUTED, size=11),
showlegend=False,
hovertemplate=f"Resonance: {res_f:.1f} GHz, {res_db:.1f} dB<extra></extra>",
), row=1, col=1)
fig.update_xaxes(type="log", row=1, col=1)
fig.update_xaxes(type="log", title_text="Frequency (GHz)", row=2, col=1)
fig.update_yaxes(title_text="Magnitude (dB)", row=1, col=1)
fig.update_yaxes(title_text="Phase (°)", row=2, col=1)
fig.update_layout(
title=dict(text="Bode Plot — Measured Ring-Slot Antenna (VNA, W-band)", x=0.5),
legend=dict(orientation="h", y=1.08),
margin=dict(t=70, b=50, l=70, r=40),
height=540,
)
apply_theme(fig)
return fig
fig = generate()
fig.show()
Made with Plotly