{"spec_id":"smith-chart-basic","library":"echarts","language":"javascript","code":"// anyplot.ai\n// smith-chart-basic: Smith Chart for RF/Impedance\n// Library: echarts 6.1.0 | JavaScript 22.23.2\n// Quality: 91/100 | Created: 2026-09-02\n\n//# anyplot-orientation: square\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Smith chart grid: constant-resistance circles + constant-reactance arcs ---\n// All grid curves live in the reflection-coefficient (gamma) plane, where the\n// chart boundary |gamma| = 1 is itself the r = 0 resistance circle.\nconst SEGMENTS = 120;\n\nfunction resistanceCircle(r) {\n  const cx = r / (1 + r);\n  const cr = 1 / (1 + r);\n  const pts = [];\n  for (let i = 0; i <= SEGMENTS; i++) {\n    const theta = (i / SEGMENTS) * 2 * Math.PI;\n    pts.push([cx + cr * Math.cos(theta), cr * Math.sin(theta)]);\n  }\n  return pts;\n}\n\nfunction reactanceArc(x) {\n  const cy = 1 / x;\n  const cr = Math.abs(1 / x);\n  const pts = [];\n  for (let i = 0; i <= SEGMENTS; i++) {\n    const theta = (i / SEGMENTS) * 2 * Math.PI;\n    const px = 1 + cr * Math.cos(theta);\n    const py = cy + cr * Math.sin(theta);\n    // Keep only the stretch inside the unit disk — the rest of the full\n    // parametric circle lies outside the chart and is dropped as a gap.\n    pts.push(px * px + py * py <= 1.0005 ? [px, py] : null);\n  }\n  return pts;\n}\n\nconst resistanceValues = [0.2, 0.5, 1, 2, 5];\nconst reactanceValues = [0.2, 0.5, 1, 2, 5];\n\nconst boundaryCircle = resistanceCircle(0);\nconst resistanceCircles = resistanceValues.map(resistanceCircle);\nconst reactanceArcsPos = reactanceValues.map(reactanceArc);\nconst reactanceArcsNeg = reactanceValues.map((x) => reactanceArc(-x));\nconst zeroReactanceLine = [\n  [-1, 0],\n  [1, 0],\n];\n\n// --- Grid value labels: top of each resistance circle, boundary crossing of\n// each reactance arc. Rendered later via the `graphic` component once the\n// coordinate system exists, so positions are exact pixel conversions rather\n// than approximations.\nconst resistanceLabelData = resistanceValues.map((r) => ({\n  text: String(r),\n  point: [r / (1 + r), 1 / (1 + r)],\n}));\n\nfunction reactanceEdgePoint(x) {\n  const theta = 2 * Math.atan(1 / x);\n  return [Math.cos(theta), Math.sin(theta)];\n}\n\nconst reactanceLabelData = [\n  ...reactanceValues.map((x) => {\n    const [px, py] = reactanceEdgePoint(x);\n    return { text: `j${x}`, point: [px * 1.07, py * 1.07] };\n  }),\n  ...reactanceValues.map((x) => {\n    const [px, py] = reactanceEdgePoint(x);\n    return { text: `-j${x}`, point: [px * 1.07, -py * 1.07] };\n  }),\n];\n\n// --- Impedance locus: series R-L-C antenna feed, swept 1-5 GHz -------------\nconst z0 = 50;\nconst inductanceH = 4e-9; // 4 nH series feed inductance\nconst capacitanceF = 1e-12; // 1 pF series feed capacitance\nconst numPoints = 13;\nconst freqStartHz = 1e9;\nconst freqEndHz = 5e9;\n\nconst locus = [];\nfor (let i = 0; i < numPoints; i++) {\n  const freqHz = freqStartHz + ((freqEndHz - freqStartHz) * i) / (numPoints - 1);\n  const resistance = 20 + 3 * Math.sqrt(freqHz / 1e9); // skin-effect-like rise\n  const reactance =\n    2 * Math.PI * freqHz * inductanceH - 1 / (2 * Math.PI * freqHz * capacitanceF);\n  const zRe = resistance / z0;\n  const zIm = reactance / z0;\n\n  // gamma = (z - 1) / (z + 1), complex division\n  const a = zRe - 1;\n  const b = zIm;\n  const c = zRe + 1;\n  const d = zIm;\n  const denomSq = c * c + d * d;\n  const gammaRe = (a * c + b * d) / denomSq;\n  const gammaIm = (b * c - a * d) / denomSq;\n\n  locus.push({ freqGHz: freqHz / 1e9, point: [gammaRe, gammaIm] });\n}\n\nconst locusPoints = locus.map((d) => d.point);\nconst labeledIndices = [0, 4, 8, numPoints - 1];\nconst freqLabelData = labeledIndices.map((idx) => {\n  const { freqGHz, point } = locus[idx];\n  const [x, y] = point;\n  const norm = Math.sqrt(x * x + y * y) || 1;\n  return {\n    value: point,\n    label: {\n      show: true,\n      formatter: `${freqGHz.toFixed(1)} GHz`,\n      color: t.ink,\n      fontSize: 17,\n      offset: [(x / norm) * 46, -(y / norm) * 46],\n    },\n  };\n});\n\n// --- Init ---------------------------------------------------------------\nconst chart = echarts.init(document.getElementById(\"container\"));\n\n// --- Option ---------------------------------------------------------------\nconst gridLineStyle = { color: t.grid, width: 1 };\nconst gridSeriesBase = {\n  type: \"line\",\n  symbol: \"none\",\n  smooth: false,\n  silent: true,\n  connectNulls: false,\n  z: 1,\n};\n\nconst gridSeries = [\n  ...resistanceCircles.map((data) => ({ ...gridSeriesBase, data, lineStyle: gridLineStyle })),\n  ...reactanceArcsPos.map((data) => ({ ...gridSeriesBase, data, lineStyle: gridLineStyle })),\n  ...reactanceArcsNeg.map((data) => ({ ...gridSeriesBase, data, lineStyle: gridLineStyle })),\n  { ...gridSeriesBase, data: zeroReactanceLine, lineStyle: gridLineStyle },\n  {\n    ...gridSeriesBase,\n    data: boundaryCircle,\n    lineStyle: { color: t.inkSoft, width: 2 },\n    z: 2,\n  },\n];\n\nchart.setOption({\n  animation: false,\n  backgroundColor: \"transparent\",\n  title: {\n    text: \"smith-chart-basic · javascript · echarts · anyplot.ai\",\n    subtext: \"Antenna feed impedance, 1-5 GHz · normalized to Z0 = 50 Ω\",\n    left: \"center\",\n    textStyle: { color: t.ink, fontSize: 22 },\n    subtextStyle: { color: t.inkSoft, fontSize: 15 },\n  },\n  grid: { left: \"8%\", right: \"8%\", top: \"12%\", bottom: \"4%\" },\n  xAxis: {\n    type: \"value\",\n    min: -1.15,\n    max: 1.15,\n    show: false,\n  },\n  yAxis: {\n    type: \"value\",\n    min: -1.15,\n    max: 1.15,\n    show: false,\n  },\n  series: [\n    ...gridSeries,\n    {\n      type: \"scatter\",\n      name: \"Matched\",\n      data: [[0, 0]],\n      symbolSize: 8,\n      itemStyle: { color: t.inkSoft },\n      label: {\n        show: true,\n        formatter: \"Z0\",\n        position: \"top\",\n        color: t.inkSoft,\n        fontSize: 15,\n      },\n      silent: true,\n      z: 3,\n    },\n    {\n      type: \"line\",\n      name: \"S11 locus\",\n      data: locusPoints,\n      symbol: \"circle\",\n      symbolSize: 11,\n      lineStyle: { color: t.palette[0], width: 4 },\n      itemStyle: { color: t.palette[0] },\n      z: 5,\n    },\n    {\n      type: \"scatter\",\n      name: \"Frequency labels\",\n      data: freqLabelData,\n      symbolSize: 0,\n      silent: true,\n      z: 6,\n    },\n  ],\n});\n\n// --- Grid value labels via the `graphic` component -------------------------\n// Placed after the first setOption so convertToPixel resolves exact pixel\n// coordinates from the grid's data space, rather than approximating with\n// percentage offsets.\nconst gridLabelStyle = { fill: t.inkSoft, fontSize: 12, textAlign: \"center\" };\nconst gridLabelElements = [\n  ...resistanceLabelData.map(({ text, point }) => {\n    const [x, y] = chart.convertToPixel({ xAxisIndex: 0, yAxisIndex: 0 }, point);\n    return {\n      type: \"text\",\n      x,\n      y,\n      silent: true,\n      z: 4,\n      style: { ...gridLabelStyle, text, textVerticalAlign: \"bottom\" },\n    };\n  }),\n  ...reactanceLabelData.map(({ text, point }) => {\n    const [x, y] = chart.convertToPixel({ xAxisIndex: 0, yAxisIndex: 0 }, point);\n    return {\n      type: \"text\",\n      x,\n      y,\n      silent: true,\n      z: 4,\n      style: { ...gridLabelStyle, text, textVerticalAlign: \"middle\" },\n    };\n  }),\n];\n\nchart.setOption({ graphic: { elements: gridLabelElements } });\n"}