{"spec_id":"smith-chart-basic","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nsmith-chart-basic: Smith Chart for RF/Impedance\nLibrary: bokeh 3.9.0 | Python 3.13.13\nQuality: 91/100 | Updated: 2026-05-20\n\"\"\"\n\nimport sys\nfrom pathlib import Path\n\n\n_script_dir = str(Path(__file__).parent.absolute())\nsys.path = [p for p in sys.path if p != _script_dir and p != \"\"]\n\nimport os\nimport time\n\nimport numpy as np\nfrom bokeh.io import output_file, save\nfrom bokeh.models import ColumnDataSource, HoverTool, Label\nfrom bokeh.plotting import figure\nfrom selenium import webdriver\nfrom selenium.webdriver.chrome.options import Options\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Okabe-Ito data colors (theme-independent)\nLOCUS_COLOR = \"#009E73\"  # position 1 — brand green, impedance locus\nBOUNDARY_COLOR = \"#4467A3\"  # position 3 — blue, unit circle boundary\nMARKER_COLOR = \"#C475FD\"  # position 2 — vermillion, matched condition\n\n# Reference impedance\nZ0 = 50  # ohms\n\n# Canvas: 2400×2400 (square — symmetric Smith chart)\nW, H = 2400, 2400\n\n# Plot\np = figure(\n    width=W,\n    height=H,\n    title=\"smith-chart-basic · python · bokeh · anyplot.ai\",\n    x_axis_label=\"Real(Γ)  (dimensionless)\",\n    y_axis_label=\"Imag(Γ)  (dimensionless)\",\n    x_range=(-1.35, 1.35),\n    y_range=(-1.35, 1.35),\n    match_aspect=True,\n    toolbar_location=None,\n    min_border_bottom=160,\n    min_border_left=180,\n    min_border_top=110,\n    min_border_right=50,\n)\n\n# Font sizes (bokeh CSS pt; 1pt ≈ 1.333 source-px)\np.title.text_font_size = \"50pt\"\np.xaxis.axis_label_text_font_size = \"42pt\"\np.yaxis.axis_label_text_font_size = \"42pt\"\np.xaxis.major_label_text_font_size = \"34pt\"\np.yaxis.major_label_text_font_size = \"34pt\"\n\n# Theme-adaptive chrome\np.background_fill_color = PAGE_BG\np.border_fill_color = PAGE_BG\np.outline_line_color = INK_SOFT\np.title.text_color = INK\np.xaxis.axis_label_text_color = INK\np.yaxis.axis_label_text_color = INK\np.xaxis.major_label_text_color = INK_SOFT\np.yaxis.major_label_text_color = INK_SOFT\np.xaxis.axis_line_color = INK_SOFT\np.yaxis.axis_line_color = INK_SOFT\np.xaxis.major_tick_line_color = INK_SOFT\np.yaxis.major_tick_line_color = INK_SOFT\np.grid.visible = False\n\n# Unit circle — outer boundary |Γ| = 1\ntheta = np.linspace(0, 2 * np.pi, 500)\np.line(np.cos(theta), np.sin(theta), line_width=4, line_color=BOUNDARY_COLOR, alpha=0.9)\n\n# Constant resistance circles: r = 0.2, 0.5, 1, 2, 5\nfor r in [0.2, 0.5, 1, 2, 5]:\n    center = r / (1 + r)\n    radius = 1 / (1 + r)\n    th = np.linspace(0, 2 * np.pi, 400)\n    cx = center + radius * np.cos(th)\n    cy = radius * np.sin(th)\n    mask = cx**2 + cy**2 <= 1.001\n    if mask.sum() > 0:\n        p.line(cx[mask], cy[mask], line_width=1.5, line_color=INK_SOFT, alpha=0.30)\n\n# Constant reactance arcs: x = ±0.2, ±0.5, ±1, ±2, ±5\nfor x in [0.2, 0.5, 1, 2, 5]:\n    for sign in [1, -1]:\n        cy_center = sign / x\n        radius = 1.0 / x\n        th = np.linspace(-np.pi, np.pi, 600)\n        ax = 1.0 + radius * np.cos(th)\n        ay = cy_center + radius * np.sin(th)\n        mask = (ax**2 + ay**2 <= 1.001) & (ax >= -0.001)\n        ax_m, ay_m = ax[mask], ay[mask]\n        if len(ax_m) > 1:\n            order = np.argsort(np.arctan2(ay_m - cy_center, ax_m - 1.0))\n            p.line(ax_m[order], ay_m[order], line_width=1.5, line_color=INK_SOFT, alpha=0.30)\n\n# Real axis (pure resistance line, x = 0)\np.line([-1, 1], [0, 0], line_width=2, line_color=INK_SOFT, alpha=0.5)\n\n# VSWR=1.5 reference zone — well-matched region (|gamma| = 0.2)\nvswr_r = (1.5 - 1) / (1.5 + 1)  # = 0.2\np.ellipse(\n    x=0,\n    y=0,\n    width=2 * vswr_r,\n    height=2 * vswr_r,\n    fill_color=MARKER_COLOR,\n    fill_alpha=0.08,\n    line_color=MARKER_COLOR,\n    line_dash=\"dashed\",\n    line_width=2.5,\n    line_alpha=0.60,\n)\n\n# Data: antenna S11 sweep 1–6 GHz (series RLC resonance at 3.5 GHz, Q=5)\nnp.random.seed(42)\nn_points = 60\nfreq = np.linspace(1e9, 6e9, n_points)\nf_res = 3.5e9\nQ = 5\nR = 45 + 10 * np.exp(-((freq - f_res) ** 2) / (0.5e9) ** 2)\nX = Z0 * Q * (freq / f_res - f_res / freq) + 5 * np.sin(2 * np.pi * freq / 2e9)\n\nz_norm = (R + 1j * X) / Z0\ngamma = (z_norm - 1) / (z_norm + 1)\ngamma_real = np.real(gamma)\ngamma_imag = np.imag(gamma)\n\nsource = ColumnDataSource(data={\"gr\": gamma_real, \"gi\": gamma_imag, \"freq\": freq / 1e9, \"R\": R, \"X\": X})\n\n# HoverTool — active in the interactive HTML version\nhover = HoverTool(\n    tooltips=[\n        (\"Frequency\", \"@freq{0.2f} GHz\"),\n        (\"R\", \"@R{0.1f} Ω\"),\n        (\"X\", \"@X{+0.1f} Ω\"),\n        (\"Γ\", \"(@gr{0.3f}, @gi{+0.3f}j)\"),\n    ]\n)\np.add_tools(hover)\n\n# Impedance locus\np.line(\"gr\", \"gi\", source=source, line_width=5, line_color=LOCUS_COLOR, alpha=0.9)\np.scatter(\"gr\", \"gi\", source=source, size=14, fill_color=LOCUS_COLOR, line_color=PAGE_BG, line_width=2, alpha=0.85)\n\n# Frequency labels at key points along the locus\nlabel_indices = [0, n_points // 4, n_points // 2, 3 * n_points // 4, n_points - 1]\nfor idx in label_indices:\n    gx, gy = gamma_real[idx], gamma_imag[idx]\n    # push near-center labels clear of the Z=Z0 marker at (0.07, 0.07)\n    if abs(gx) < 0.2 and abs(gy) < 0.2:\n        lx, ly = gx, gy + 0.22\n    else:\n        lx = gx\n        ly = gy + (0.09 if gy >= 0 else -0.13)\n    p.add_layout(\n        Label(\n            x=lx,\n            y=ly,\n            text=f\"{freq[idx] / 1e9:.1f} GHz\",\n            text_font_size=\"26pt\",\n            text_color=BOUNDARY_COLOR,\n            text_font_style=\"bold\",\n        )\n    )\n\n# Matched condition marker — Γ = 0 (Z = Z₀)\np.scatter([0], [0], size=24, fill_color=MARKER_COLOR, line_color=PAGE_BG, line_width=3)\np.add_layout(Label(x=0.07, y=0.07, text=\"Z=Z₀\", text_font_size=\"26pt\", text_color=MARKER_COLOR, text_font_style=\"bold\"))\n\n# VSWR=1.5 annotation on the reference circle\np.add_layout(\n    Label(\n        x=vswr_r * np.cos(np.pi / 4) + 0.02,\n        y=vswr_r * np.sin(np.pi / 4) + 0.02,\n        text=\"VSWR=1.5\",\n        text_font_size=\"20pt\",\n        text_color=MARKER_COLOR,\n        text_font_style=\"italic\",\n    )\n)\n\n# Grid r-value labels along real axis (enlarged from previous)\nfor r in [0.2, 0.5, 1, 2]:\n    p.add_layout(\n        Label(x=r / (1 + r), y=-0.12, text=f\"r={r}\", text_font_size=\"22pt\", text_color=INK_MUTED, text_align=\"center\")\n    )\n\n# Reactance labels at chart boundary (enlarged from previous)\nfor x in [0.5, 1, 2]:\n    angle = 2 * np.arctan(1 / x)\n    lx, ly = np.cos(angle), np.sin(angle)\n    p.add_layout(Label(x=lx + 0.05, y=ly + 0.02, text=f\"x={x}\", text_font_size=\"20pt\", text_color=INK_MUTED))\n    p.add_layout(Label(x=lx + 0.05, y=-ly - 0.07, text=f\"x=−{x}\", text_font_size=\"20pt\", text_color=INK_MUTED))\n\n# Save interactive HTML\noutput_file(f\"plot-{THEME}.html\", title=\"Smith Chart – python – bokeh – anyplot.ai\")\nsave(p)\n\n# Screenshot with headless Chrome (Selenium — export_png unavailable in this env)\nopts = Options()\nfor arg in (\n    \"--headless=new\",\n    \"--no-sandbox\",\n    \"--disable-dev-shm-usage\",\n    \"--disable-gpu\",\n    f\"--window-size={W},{H}\",\n    \"--hide-scrollbars\",\n):\n    opts.add_argument(arg)\ndriver = webdriver.Chrome(options=opts)\ndriver.set_window_size(W, H)\ndriver.get(f\"file://{Path(f'plot-{THEME}.html').resolve()}\")\ntime.sleep(3)\ndriver.save_screenshot(f\"plot-{THEME}.png\")\ndriver.quit()\n"}