{"spec_id":"smith-chart-basic","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nsmith-chart-basic: Smith Chart for RF/Impedance\nLibrary: pygal 3.1.0 | Python 3.13.13\nQuality: 83/100 | Updated: 2026-05-20\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pygal\nfrom pygal.style import Style\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# Canonical Okabe-Ito order: data series take palette positions 1-4 (added first),\n# structural chart elements use positions 5-8 (added after data).\nCHART_COLORS = (\n    \"#009E73\",  # pos 1: \"Antenna Z(f)\" ← brand green — first data series\n    \"#C475FD\",  # pos 2: \"1 GHz\" (Okabe-Ito vermillion)\n    \"#4467A3\",  # pos 3: \"3.5 GHz\" (Okabe-Ito blue)\n    \"#BD8233\",  # pos 4: \"6 GHz\" (Okabe-Ito pink/purple)\n    \"#AE3030\",  # pos 5: unit circle boundary (Okabe-Ito amber)\n    \"#2ABCCD\",  # pos 6: resistance circles (Okabe-Ito sky blue)\n    INK_MUTED,  # pos 7: reactance arcs (theme-adaptive muted)\n    INK_SOFT,  # pos 8: real axis (theme-adaptive subtle)\n)\n\nZ0 = 50  # reference impedance (ohms)\nR_CIRCLES = [0, 0.2, 0.5, 1, 2, 5]\nX_ARCS = [0.2, 0.5, 1, 2, 5]\n\n# Style\ncustom_style = Style(\n    background=PAGE_BG,\n    plot_background=PAGE_BG,\n    foreground=INK,\n    foreground_strong=INK,\n    foreground_subtle=INK_MUTED,\n    colors=CHART_COLORS,\n    title_font_size=66,\n    label_font_size=56,\n    major_label_font_size=44,\n    legend_font_size=44,\n    value_font_size=36,\n    stroke_width=3,\n)\n\n# Chart — square format; truncate_label=-1 prevents pygal from clipping tick text\nchart = pygal.XY(\n    width=2400,\n    height=2400,\n    style=custom_style,\n    title=\"smith-chart-basic · python · pygal · anyplot.ai\",\n    show_legend=True,\n    legend_at_bottom=False,\n    show_x_guides=False,\n    show_y_guides=False,\n    x_title=\"Reflection Coefficient Real Part\",\n    y_title=\"Reflection Coefficient Imaginary Part\",\n    dots_size=8,\n    range=(-1.15, 1.15),\n    xrange=(-1.15, 1.15),\n    margin=120,\n    truncate_label=-1,\n)\n\n# Explicit tick marks at clean round values to prevent floating-point label truncation\nchart.x_labels = [-1.0, -0.5, 0.0, 0.5, 1.0]\n\n# Data — antenna impedance sweep 1–6 GHz\n# Added FIRST so data series take palette positions 1-4 (brand green at pos 1)\nnp.random.seed(42)\nn = 50\nfreqs = np.linspace(1e9, 6e9, n)\n\nr_base = 25 + 50 * np.exp(-freqs / 3e9)\nx_base = 30 * np.sin(2 * np.pi * freqs / 2e9) + 20 * np.cos(freqs / 1e9)\nz_r = r_base + np.random.randn(n) * 3\nz_i = x_base + np.random.randn(n) * 5\n\nz_c = z_r + 1j * z_i\nz_norm = z_c / Z0\ngamma = (z_norm - 1) / (z_norm + 1)\n\n# Impedance locus: pos 1 = #009E73 brand green\nlocus = [(float(g.real), float(g.imag)) for g in gamma]\nchart.add(\"Antenna Z(f)\", locus, show_dots=True, stroke_width=5, dots_size=6)\n\n# Frequency markers (stroke=False = dots only, no connecting line): pos 2-4\nchart.add(\"1 GHz\", [(float(gamma[0].real), float(gamma[0].imag))], show_dots=True, dots_size=22, stroke=False)\nchart.add(\n    \"3.5 GHz\", [(float(gamma[n // 2].real), float(gamma[n // 2].imag))], show_dots=True, dots_size=22, stroke=False\n)\nchart.add(\"6 GHz\", [(float(gamma[-1].real), float(gamma[-1].imag))], show_dots=True, dots_size=22, stroke=False)\n\n# Structural series — added AFTER data to take palette positions 5-8.\n# Named labels eliminate the unlabeled colored squares that caused legend confusion.\n# Unit circle boundary (|Γ| = 1): pos 5 = #AE3030 amber\ntheta = np.linspace(0, 2 * np.pi, 200)\nunit_circle = [(float(np.cos(t)), float(np.sin(t))) for t in theta]\nchart.add(\"Unit Circle\", unit_circle, show_dots=False, stroke_width=5)\n\n# Constant resistance circles: pos 6 = #2ABCCD sky blue\nr_grid = []\nfor r in R_CIRCLES:\n    x_vals = np.concatenate([np.linspace(-50, -0.01, 100), np.linspace(0.01, 50, 100)])\n    for x in x_vals:\n        z_n = complex(r, x)\n        g = (z_n - 1) / (z_n + 1)\n        if abs(g) <= 1.001:\n            r_grid.append((float(g.real), float(g.imag)))\n    r_grid.append((None, None))\nchart.add(\"Resistance Grid\", r_grid, show_dots=False, stroke_width=1.5)\n\n# Constant reactance arcs: pos 7 = INK_MUTED gray\nx_grid = []\nfor x in X_ARCS:\n    for r in np.linspace(0.001, 50, 100):\n        z_n = complex(r, x)\n        g = (z_n - 1) / (z_n + 1)\n        if abs(g) <= 1.001:\n            x_grid.append((float(g.real), float(g.imag)))\n    x_grid.append((None, None))\n    for r in np.linspace(0.001, 50, 100):\n        z_n = complex(r, -x)\n        g = (z_n - 1) / (z_n + 1)\n        if abs(g) <= 1.001:\n            x_grid.append((float(g.real), float(g.imag)))\n    x_grid.append((None, None))\nchart.add(\"Reactance Grid\", x_grid, show_dots=False, stroke_width=1.5)\n\n# Real axis: pos 8 = INK_SOFT gray\nchart.add(\"Real Axis\", [(-1.0, 0.0), (1.0, 0.0)], show_dots=False, stroke_width=2)\n\n# Save\nchart.render_to_file(f\"plot-{THEME}.html\")\nchart.render_to_png(f\"plot-{THEME}.png\")\n"}