{"spec_id":"smith-chart-basic","library":"altair","language":"python","code":"\"\"\" anyplot.ai\nsmith-chart-basic: Smith Chart for RF/Impedance\nLibrary: altair 6.1.0 | Python 3.13.13\nQuality: 92/100 | Updated: 2026-05-20\n\"\"\"\n\nimport importlib\nimport os\nimport sys\n\n\n# Drop script directory from sys.path so the `altair` package resolves, not this file\nsys.path[:] = [p for p in sys.path if os.path.abspath(p or \".\") != os.path.dirname(os.path.abspath(__file__))]\nalt = importlib.import_module(\"altair\")\nnp = importlib.import_module(\"numpy\")\npd = importlib.import_module(\"pandas\")\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\n\nBRAND = \"#009E73\"  # Okabe-Ito position 1 — impedance locus\nVSWR_COLOR = \"#C475FD\"  # Okabe-Ito position 2 — VSWR circles\n\n# Reference impedance\nZ0 = 50  # ohms\n\n# Generate Smith chart grid — constant resistance circles\ntheta = np.linspace(0, 2 * np.pi, 200)\nresistance_circles = []\nresistance_values = [0, 0.2, 0.5, 1.0, 2.0, 5.0]\nfor r in resistance_values:\n    center_x = r / (r + 1)\n    radius = 1 / (r + 1)\n    x = center_x + radius * np.cos(theta)\n    y = radius * np.sin(theta)\n    mask = x**2 + y**2 <= 1.0\n    for i in range(len(x)):\n        if mask[i]:\n            resistance_circles.append({\"x\": x[i], \"y\": y[i], \"group\": f\"r_{r}\", \"idx\": i})\nresistance_df = pd.DataFrame(resistance_circles)\n\n# Generate constant reactance arcs\nreactance_arcs = []\nreactance_values = [0.2, 0.5, 1.0, 2.0, 5.0]\narc_theta = np.linspace(-np.pi, np.pi, 200)\nfor x_val in reactance_values:\n    center_y = 1 / x_val\n    radius = 1 / x_val\n    x = 1 + radius * np.cos(arc_theta)\n    y = center_y + radius * np.sin(arc_theta)\n    mask = (x**2 + y**2 <= 1.0) & (x >= -0.01)\n    y_neg = -center_y - radius * np.sin(arc_theta)\n    for i in range(len(x)):\n        if mask[i]:\n            reactance_arcs.append({\"x\": x[i], \"y\": y[i], \"group\": f\"x_pos_{x_val}\", \"idx\": i})\n            reactance_arcs.append({\"x\": x[i], \"y\": y_neg[i], \"group\": f\"x_neg_{x_val}\", \"idx\": i})\n\n# Zero reactance line (horizontal axis)\nx_line = np.linspace(-1, 1, 50)\nfor i, xi in enumerate(x_line):\n    reactance_arcs.append({\"x\": xi, \"y\": 0, \"group\": \"x_zero\", \"idx\": i})\nreactance_df = pd.DataFrame(reactance_arcs)\n\n# VSWR circles — constant reflection coefficient magnitude\nvswr_theta = np.linspace(0, 2 * np.pi, 200)\nvswr_circles = []\nvswr_entries = [(1.5, (1.5 - 1) / (1.5 + 1)), (2.0, 1 / 3), (3.0, 0.5)]\nfor vswr_val, gamma_mag in vswr_entries:\n    x = gamma_mag * np.cos(vswr_theta)\n    y = gamma_mag * np.sin(vswr_theta)\n    for i in range(len(x)):\n        vswr_circles.append({\"x\": x[i], \"y\": y[i], \"group\": f\"vswr_{vswr_val}\", \"idx\": i})\nvswr_df = pd.DataFrame(vswr_circles)\n\n# VSWR labels at 45° (upper-right of each circle, away from impedance curve)\nvswr_labels_data = [\n    {\"x\": v * np.cos(np.pi / 4), \"y\": v * np.sin(np.pi / 4), \"label\": f\"VSWR {w}\"} for w, v in vswr_entries\n]\nvswr_labels_df = pd.DataFrame(vswr_labels_data)\n\n# Unit circle boundary\nunit_theta = np.linspace(0, 2 * np.pi, 200)\nunit_circle_df = pd.DataFrame({\"x\": np.cos(unit_theta), \"y\": np.sin(unit_theta), \"idx\": range(len(unit_theta))})\n\n# Antenna impedance sweep 1–6 GHz\nnp.random.seed(42)\nn_points = 50\nfrequency = np.linspace(1e9, 6e9, n_points)\nt = np.linspace(0, 2.5 * np.pi, n_points)\nz_real = 50 * (1 - 0.7 * np.exp(-t / 3))\nz_imag = 40 * np.sin(t) * np.exp(-t / 4)\nz_norm = (z_real + 1j * z_imag) / Z0\ngamma = (z_norm - 1) / (z_norm + 1)\n\nimpedance_df = pd.DataFrame(\n    {\n        \"x\": gamma.real,\n        \"y\": gamma.imag,\n        \"frequency_ghz\": frequency / 1e9,\n        \"z_real\": z_real,\n        \"z_imag\": z_imag,\n        \"idx\": range(n_points),\n    }\n)\n\n# Frequency labels — per-label dx/dy offsets; 4.7 GHz skipped (converges with endpoint)\nlabel_configs = [(0, 18, -18), (12, 18, -18), (24, 18, -18), (49, 18, -20)]\nlabel_layers = []\nfor row_idx, dx, dy in label_configs:\n    row = impedance_df.iloc[[row_idx]].copy()\n    row[\"label\"] = f\"{row['frequency_ghz'].values[0]:.1f} GHz\"\n    label_layers.append(\n        alt.Chart(row)\n        .mark_text(fontSize=14, fontWeight=\"bold\", color=INK, dx=dx, dy=dy)\n        .encode(\n            x=alt.X(\"x:Q\", scale=alt.Scale(domain=[-1.2, 1.2])),\n            y=alt.Y(\"y:Q\", scale=alt.Scale(domain=[-1.2, 1.2])),\n            text=\"label:N\",\n        )\n    )\n\nscale_x = alt.Scale(domain=[-1.2, 1.2])\nscale_y = alt.Scale(domain=[-1.2, 1.2])\n\n# Unit circle boundary\nboundary = (\n    alt.Chart(unit_circle_df)\n    .mark_line(color=INK, strokeWidth=3, opacity=0.8)\n    .encode(x=alt.X(\"x:Q\", scale=scale_x), y=alt.Y(\"y:Q\", scale=scale_y), order=\"idx:O\")\n)\n\n# Resistance circles\nres_circles = (\n    alt.Chart(resistance_df)\n    .mark_line(strokeWidth=1.0, opacity=0.3)\n    .encode(\n        x=alt.X(\"x:Q\", scale=scale_x),\n        y=alt.Y(\"y:Q\", scale=scale_y),\n        detail=\"group:N\",\n        order=\"idx:O\",\n        color=alt.value(INK_SOFT),\n    )\n)\n\n# Reactance arcs\nreact_arcs = (\n    alt.Chart(reactance_df)\n    .mark_line(strokeWidth=1.0, opacity=0.3)\n    .encode(\n        x=alt.X(\"x:Q\", scale=scale_x),\n        y=alt.Y(\"y:Q\", scale=scale_y),\n        detail=\"group:N\",\n        order=\"idx:O\",\n        color=alt.value(INK_SOFT),\n    )\n)\n\n# VSWR circles (dashed, subtle)\nvswr_layer = (\n    alt.Chart(vswr_df)\n    .mark_line(strokeWidth=1.2, opacity=0.4, strokeDash=[5, 4])\n    .encode(\n        x=alt.X(\"x:Q\", scale=scale_x),\n        y=alt.Y(\"y:Q\", scale=scale_y),\n        detail=\"group:N\",\n        order=\"idx:O\",\n        color=alt.value(VSWR_COLOR),\n    )\n)\n\n# VSWR labels\nvswr_labels = (\n    alt.Chart(vswr_labels_df)\n    .mark_text(fontSize=11, color=VSWR_COLOR, fontStyle=\"italic\", dx=6, dy=-8)\n    .encode(x=alt.X(\"x:Q\", scale=scale_x), y=alt.Y(\"y:Q\", scale=scale_y), text=\"label:N\")\n)\n\n# Impedance locus curve\nimpedance_line = (\n    alt.Chart(impedance_df)\n    .mark_line(strokeWidth=4, color=BRAND)\n    .encode(x=alt.X(\"x:Q\", scale=scale_x), y=alt.Y(\"y:Q\", scale=scale_y), order=\"idx:O\")\n)\n\n# Impedance data points with interactive tooltips\nimpedance_points = (\n    alt.Chart(impedance_df)\n    .mark_circle(size=80, color=BRAND, stroke=PAGE_BG, strokeWidth=1)\n    .encode(\n        x=alt.X(\"x:Q\", scale=scale_x),\n        y=alt.Y(\"y:Q\", scale=scale_y),\n        tooltip=[\n            alt.Tooltip(\"frequency_ghz:Q\", title=\"Frequency (GHz)\", format=\".2f\"),\n            alt.Tooltip(\"z_real:Q\", title=\"R (Ω)\", format=\".1f\"),\n            alt.Tooltip(\"z_imag:Q\", title=\"X (Ω)\", format=\".1f\"),\n        ],\n    )\n)\n\n# Center point marker (matched condition Z = Z₀)\ncenter_df = pd.DataFrame({\"x\": [0], \"y\": [0]})\ncenter_point = (\n    alt.Chart(center_df)\n    .mark_point(size=200, shape=\"cross\", color=INK, strokeWidth=3)\n    .encode(x=alt.X(\"x:Q\", scale=scale_x), y=alt.Y(\"y:Q\", scale=scale_y))\n)\n\n# Resistance value labels along real axis\nr_labels_data = [\n    {\"x\": 0.0, \"y\": 0.08, \"label\": \"0\"},\n    {\"x\": 0.17, \"y\": 0.08, \"label\": \"0.2\"},\n    {\"x\": 0.33, \"y\": 0.08, \"label\": \"0.5\"},\n    {\"x\": 0.5, \"y\": 0.08, \"label\": \"1\"},\n    {\"x\": 0.67, \"y\": 0.08, \"label\": \"2\"},\n    {\"x\": 0.83, \"y\": 0.08, \"label\": \"5\"},\n]\nr_labels_df = pd.DataFrame(r_labels_data)\nr_labels = (\n    alt.Chart(r_labels_df)\n    .mark_text(fontSize=12, fontWeight=\"bold\")\n    .encode(x=alt.X(\"x:Q\", scale=scale_x), y=alt.Y(\"y:Q\", scale=scale_y), text=\"label:N\", color=alt.value(INK_SOFT))\n)\n\n# Compose all layers\nchart = (\n    alt.layer(\n        res_circles,\n        react_arcs,\n        vswr_layer,\n        vswr_labels,\n        boundary,\n        center_point,\n        impedance_line,\n        impedance_points,\n        *label_layers,\n        r_labels,\n    )\n    .properties(\n        width=600,\n        height=600,\n        background=PAGE_BG,\n        title=alt.Title(\n            \"smith-chart-basic · python · altair · anyplot.ai\",\n            fontSize=16,\n            anchor=\"middle\",\n            color=INK,\n            subtitle=\"Antenna Impedance Sweep (1–6 GHz, Z₀ = 50 Ω)\",\n            subtitleFontSize=12,\n            subtitleColor=INK_SOFT,\n        ),\n    )\n    .configure_view(fill=PAGE_BG, stroke=INK_SOFT, strokeWidth=0)\n    .configure_axis(grid=False, domain=False, labels=False, ticks=False, title=None)\n    .interactive()\n)\n\nchart.save(f\"plot-{THEME}.png\", scale_factor=4.0)\nchart.save(f\"plot-{THEME}.html\")\n"}