{"spec_id":"smith-chart-basic","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\nsmith-chart-basic: Smith Chart for RF/Impedance\nLibrary: letsplot 4.9.0 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-05-20\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom lets_plot import (\n    LetsPlot,\n    aes,\n    coord_fixed,\n    element_rect,\n    element_text,\n    geom_path,\n    geom_point,\n    geom_text,\n    ggplot,\n    ggsize,\n    labs,\n    layer_tooltips,\n    theme,\n    theme_void,\n)\nfrom lets_plot.export import ggsave\n\n\nLetsPlot.setup_html()\n\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\"\nBRAND = \"#009E73\"\nCOLOR_START = \"#4467A3\"  # Okabe-Ito position 3\nCOLOR_END = \"#C475FD\"  # Okabe-Ito position 2\n\nZ0 = 50  # Reference impedance (ohms)\n\n# Simulated antenna impedance sweep 1–6 GHz\nnp.random.seed(42)\nn_points = 60\nfreq = np.linspace(1e9, 6e9, n_points)\nt = np.linspace(0, 2.5 * np.pi, n_points)\nr_base = 20 + 80 * (1 - np.exp(-t / 2.5))\nx_base = 50 * np.sin(t) * np.exp(-t / 6)\nz_real = r_base + 3 * np.random.randn(n_points)\nz_imag = x_base + 2 * np.random.randn(n_points)\n\nz_norm = (z_real + 1j * z_imag) / Z0\ngamma = (z_norm - 1) / (z_norm + 1)\ngamma_real = np.real(gamma)\ngamma_imag = np.imag(gamma)\n\ndf_locus = pd.DataFrame(\n    {\n        \"gamma_real\": gamma_real,\n        \"gamma_imag\": gamma_imag,\n        \"freq_ghz\": freq / 1e9,\n        \"z_real\": z_real,\n        \"z_imag\": z_imag,\n        \"vswr\": (1 + np.abs(gamma)) / (1 - np.abs(gamma)),\n    }\n)\n\n# Smith chart grid — constant resistance circles\n# Circle centered at (r/(r+1), 0) with radius 1/(r+1)\ngrid_data = []\nr_values = [0, 0.2, 0.5, 1, 2, 5]\nfor r in r_values:\n    cx = r / (r + 1)\n    rad = 1 / (r + 1)\n    theta = np.linspace(0, 2 * np.pi, 120)\n    x = cx + rad * np.cos(theta)\n    y = rad * np.sin(theta)\n    mask = (x**2 + y**2) <= 1.001\n    for i in np.where(mask)[0]:\n        grid_data.append({\"x\": x[i], \"y\": y[i], \"type\": \"resistance\", \"group\": f\"r_{r}\"})\n\n# Constant reactance arcs\n# Arc centered at (1, 1/x_val) with radius |1/x_val|\nxv_values = [0.2, 0.5, 1, 2, 5]\nfor xv in xv_values:\n    for sign in [1, -1]:\n        x_val = sign * xv\n        cy = 1 / x_val\n        rad = abs(1 / x_val)\n        theta = np.linspace(0, 2 * np.pi, 120)\n        x = 1 + rad * np.cos(theta)\n        y = cy + rad * np.sin(theta)\n        mask = (x**2 + y**2) <= 1.001\n        for i in np.where(mask)[0]:\n            grid_data.append({\"x\": x[i], \"y\": y[i], \"type\": \"reactance\", \"group\": f\"x_{x_val}\"})\n\ndf_grid = pd.DataFrame(grid_data)\n\ntheta_circle = np.linspace(0, 2 * np.pi, 200)\ndf_boundary = pd.DataFrame({\"x\": np.cos(theta_circle), \"y\": np.sin(theta_circle)})\ndf_axis = pd.DataFrame({\"x\": [-1, 1], \"y\": [0, 0]})\n\n# Resistance labels at leftmost point of each r-circle on the real axis\ndf_r_labels = pd.DataFrame([{\"x\": (r - 1) / (r + 1), \"y\": 0, \"label\": str(r)} for r in r_values])\n\n# Reactance labels at unit-circle boundary intersection\n# For xv: boundary at x2=(xv²-1)/(xv²+1), y2=xv*(1-x2)\nxv_label_rows = []\nfor xv in xv_values:\n    x2 = (xv**2 - 1) / (xv**2 + 1)\n    y2 = xv * (1 - x2)\n    scale = 1.12\n    xv_label_rows.append({\"x\": x2 * scale, \"y\": y2 * scale, \"label\": f\"+j{xv}\"})\n    xv_label_rows.append({\"x\": x2 * scale, \"y\": -y2 * scale, \"label\": f\"-j{xv}\"})\ndf_x_labels = pd.DataFrame(xv_label_rows)\n\nlabel_indices = [0, n_points // 2, n_points - 1]\n# Per-label nudge to avoid crowding: 1.0 GHz left+up, 3.5 GHz upper-left, 6.0 GHz lower-right\nlabel_nudges = [(-0.18, 0.13), (-0.12, 0.22), (0.26, -0.22)]\ndf_freq_labels = pd.DataFrame(\n    {\n        \"x\": [gamma_real[i] + nx for i, (nx, ny) in zip(label_indices, label_nudges, strict=True)],\n        \"y\": [gamma_imag[i] + ny for i, (nx, ny) in zip(label_indices, label_nudges, strict=True)],\n        \"label\": [f\"{freq[i] / 1e9:.1f} GHz\" for i in label_indices],\n    }\n)\n\ndf_start = df_locus.iloc[[0]]\ndf_end = df_locus.iloc[[-1]]\ndf_center = pd.DataFrame({\"x\": [0], \"y\": [0]})\n\ndf_legend = pd.DataFrame(\n    {\n        \"x\": [1.15, 1.15, 1.15],\n        \"y\": [0.25, 0.05, -0.15],\n        \"grp\": [\"locus\", \"start\", \"end\"],\n        \"label\": [\"Impedance locus\", \"Start (1.0 GHz)\", \"End (6.0 GHz)\"],\n    }\n)\n\nplot = (\n    ggplot()\n    # Outer boundary\n    + geom_path(aes(x=\"x\", y=\"y\"), data=df_boundary, color=INK, size=2.0)\n    # Real axis\n    + geom_path(aes(x=\"x\", y=\"y\"), data=df_axis, color=INK_SOFT, size=0.8)\n    # Resistance circles\n    + geom_path(\n        aes(x=\"x\", y=\"y\", group=\"group\"),\n        data=df_grid[df_grid[\"type\"] == \"resistance\"],\n        color=INK_SOFT,\n        size=0.5,\n        alpha=0.5,\n    )\n    # Reactance arcs\n    + geom_path(\n        aes(x=\"x\", y=\"y\", group=\"group\"),\n        data=df_grid[df_grid[\"type\"] == \"reactance\"],\n        color=INK_SOFT,\n        size=0.5,\n        alpha=0.5,\n    )\n    # Resistance labels along real axis\n    + geom_text(aes(x=\"x\", y=\"y\", label=\"label\"), data=df_r_labels, size=8, nudge_y=-0.08, color=INK_SOFT)\n    # Reactance labels at chart boundary\n    + geom_text(aes(x=\"x\", y=\"y\", label=\"label\"), data=df_x_labels, size=9, color=INK_SOFT)\n    # Impedance locus path\n    + geom_path(aes(x=\"gamma_real\", y=\"gamma_imag\"), data=df_locus, color=BRAND, size=3.2)\n    # Interactive hover points\n    + geom_point(\n        aes(x=\"gamma_real\", y=\"gamma_imag\"),\n        data=df_locus,\n        color=BRAND,\n        size=3,\n        alpha=0.8,\n        tooltips=layer_tooltips()\n        .line(\"Freq: @freq_ghz GHz\")\n        .line(\"Z: @z_real + j@z_imag Ω\")\n        .line(\"VSWR: @vswr\")\n        .format(\"z_real\", \".1f\")\n        .format(\"z_imag\", \".1f\")\n        .format(\"vswr\", \".2f\"),\n    )\n    # Start marker\n    + geom_point(aes(x=\"gamma_real\", y=\"gamma_imag\"), data=df_start, color=COLOR_START, size=10)\n    # End marker\n    + geom_point(aes(x=\"gamma_real\", y=\"gamma_imag\"), data=df_end, color=COLOR_END, size=10)\n    # Matched condition marker at chart center\n    + geom_point(aes(x=\"x\", y=\"y\"), data=df_center, color=INK_SOFT, size=6, shape=3)\n    # Frequency labels along trajectory (pre-nudged per-label to avoid crowding)\n    + geom_text(aes(x=\"x\", y=\"y\", label=\"label\"), data=df_freq_labels, size=11, color=INK)\n    # Manual legend outside chart area\n    + geom_point(aes(x=\"x\", y=\"y\"), data=df_legend[df_legend[\"grp\"] == \"locus\"], color=BRAND, size=5)\n    + geom_point(aes(x=\"x\", y=\"y\"), data=df_legend[df_legend[\"grp\"] == \"start\"], color=COLOR_START, size=7)\n    + geom_point(aes(x=\"x\", y=\"y\"), data=df_legend[df_legend[\"grp\"] == \"end\"], color=COLOR_END, size=7)\n    + geom_text(aes(x=\"x\", y=\"y\", label=\"label\"), data=df_legend, size=10, hjust=0, nudge_x=0.06, color=INK)\n    + labs(title=\"smith-chart-basic · python · letsplot · anyplot.ai\")\n    + theme_void()\n    + theme(\n        plot_title=element_text(size=16, hjust=0.5, color=INK),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG),\n    )\n    + coord_fixed(ratio=1, xlim=(-1.35, 2.1), ylim=(-1.35, 1.35))\n    + ggsize(720, 600)\n)\n\nggsave(plot, f\"plot-{THEME}.png\", path=\".\", scale=4)\nggsave(plot, f\"plot-{THEME}.html\", path=\".\")\n"}