{"spec_id":"smith-chart-basic","library":"highcharts","language":"javascript","code":"// anyplot.ai\n// smith-chart-basic: Smith Chart for RF/Impedance\n// Library: highcharts 12.6.0 | JavaScript 22.23.2\n// Quality: 91/100 | Created: 2026-09-02\n//# anyplot-orientation: square\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Smith chart grid geometry -----------------------------------------\n// The grid lives entirely in the reflection-coefficient (Gamma) plane, where\n// constant-resistance contours and constant-reactance contours are literal\n// circles/arcs — no polar chart module needed, just Cartesian x/y series.\nconst ARC_STEPS = 240;\nconst RESISTANCE_VALUES = [0.2, 0.5, 1, 2, 5];\nconst REACTANCE_VALUES = [0.2, 0.5, 1, 2, 5];\n\nconst resistanceCircle = (r, steps) => {\n  const cx = r / (1 + r);\n  const radius = 1 / (1 + r);\n  const points = [];\n  for (let i = 0; i <= steps; i++) {\n    const theta = (2 * Math.PI * i) / steps;\n    points.push([cx + radius * Math.cos(theta), radius * Math.sin(theta)]);\n  }\n  return points;\n};\n\n// Reactance arcs pass through (1, 0) and only the portion inside |Gamma| <= 1\n// is drawn — the circle's other side always sits outside the chart boundary.\nconst reactanceArc = (x, steps) => {\n  const cx = 1;\n  const cy = 1 / x;\n  const radius = Math.abs(1 / x);\n  const inside = [];\n  for (let i = 0; i <= steps; i++) {\n    const theta = (2 * Math.PI * i) / steps;\n    const px = cx + radius * Math.cos(theta);\n    const py = cy + radius * Math.sin(theta);\n    if (px * px + py * py <= 1 + 1e-6) {\n      inside.push({ theta, xy: [px, py] });\n    }\n  }\n  inside.sort((a, b) => a.theta - b.theta);\n  return inside.map((p) => p.xy);\n};\n\nconst boundaryCircle = resistanceCircle(0, ARC_STEPS);\nconst realAxis = [\n  [-1, 0],\n  [1, 0],\n];\nconst resistanceGrid = RESISTANCE_VALUES.map((r) => resistanceCircle(r, ARC_STEPS));\nconst reactanceGrid = REACTANCE_VALUES.flatMap((x) => [reactanceArc(x, ARC_STEPS), reactanceArc(-x, ARC_STEPS)]);\nconst resistanceAxisLabels = RESISTANCE_VALUES.map((r) => ({\n  x: (r - 1) / (r + 1),\n  y: 0,\n  name: String(r),\n}));\n\n// Each constant-reactance arc leaves the boundary circle at the point where\n// resistance = 0 (pure reactance, z = jx) — the conventional spot to label it.\nconst reactanceBoundaryPoint = (x) => {\n  const denom = 1 + x * x;\n  return { gammaReal: (x * x - 1) / denom, gammaImag: (2 * x) / denom };\n};\nconst REACTANCE_LABEL_OFFSET_PX = 14;\nconst reactanceLabels = REACTANCE_VALUES.flatMap((x) =>\n  [x, -x].map((signedX) => {\n    const { gammaReal, gammaImag } = reactanceBoundaryPoint(signedX);\n    const norm = Math.hypot(gammaReal, gammaImag) || 1;\n    return {\n      x: gammaReal,\n      y: gammaImag,\n      name: `${signedX > 0 ? \"+\" : \"-\"}j${Math.abs(signedX)}`,\n      dataLabels: {\n        // push the label radially outward past the boundary circle so it\n        // never sits on top of the arc/boundary line it identifies\n        x: (gammaReal / norm) * REACTANCE_LABEL_OFFSET_PX,\n        y: -(gammaImag / norm) * REACTANCE_LABEL_OFFSET_PX,\n      },\n    };\n  })\n);\n\n// --- Impedance locus: simplified series-RLC antenna feedpoint sweep -----\nconst z0 = 50;\nconst seriesResistanceOhm = 40;\nconst inductanceH = 3e-9;\nconst capacitanceF = 6.893e-13; // tuned so reactance crosses zero near 3.5 GHz\nconst freqStartHz = 2e9;\nconst freqEndHz = 5e9;\nconst freqPoints = 40;\n\nconst frequenciesHz = Array.from(\n  { length: freqPoints },\n  (_, i) => freqStartHz + ((freqEndHz - freqStartHz) * i) / (freqPoints - 1)\n);\n\nconst locusData = frequenciesHz.map((f) => {\n  const omega = 2 * Math.PI * f;\n  const reactanceOhm = omega * inductanceH - 1 / (omega * capacitanceF);\n  const zr = seriesResistanceOhm / z0;\n  const zi = reactanceOhm / z0;\n  const denom = (zr + 1) * (zr + 1) + zi * zi;\n  const gammaReal = (zr * zr - 1 + zi * zi) / denom;\n  const gammaImag = (2 * zi) / denom;\n  return { x: gammaReal, y: gammaImag, freqGHz: f / 1e9 };\n});\n\nconst labelIndices = [0, 8, 16, 24, 32, 39];\nconst labeledPoints = labelIndices.map((i) => ({\n  x: locusData[i].x,\n  y: locusData[i].y,\n  name: `${locusData[i].freqGHz.toFixed(1)} GHz`,\n}));\n\n// --- Chart ---------------------------------------------------------------\nHighcharts.chart(\"container\", {\n  chart: {\n    type: \"line\",\n    backgroundColor: \"transparent\",\n    animation: false,\n    style: { fontFamily: \"inherit\" },\n    events: {\n      // Keep resistance circles circular: force equal Gamma-units-per-pixel\n      // on both axes regardless of how title/legend margins shape the plot area.\n      load: function () {\n        const chart = this;\n        const xAxis = chart.xAxis[0];\n        const yAxis = chart.yAxis[0];\n        const xRange = xAxis.max - xAxis.min;\n        const yRange = yAxis.max - yAxis.min;\n        if (chart.plotWidth > chart.plotHeight) {\n          const targetRange = yRange * (chart.plotWidth / chart.plotHeight);\n          const mid = (xAxis.max + xAxis.min) / 2;\n          xAxis.setExtremes(mid - targetRange / 2, mid + targetRange / 2, false);\n        } else if (chart.plotHeight > chart.plotWidth) {\n          const targetRange = xRange * (chart.plotHeight / chart.plotWidth);\n          const mid = (yAxis.max + yAxis.min) / 2;\n          yAxis.setExtremes(mid - targetRange / 2, mid + targetRange / 2, false);\n        }\n        chart.redraw();\n      },\n    },\n  },\n  credits: { enabled: false },\n  legend: { enabled: false },\n  title: {\n    text: \"smith-chart-basic · javascript · highcharts · anyplot.ai\",\n    style: { color: t.ink, fontSize: \"22px\", fontWeight: \"600\" },\n  },\n  subtitle: {\n    text: `Antenna feedpoint S11, 2–5 GHz · Z₀ = ${z0} Ω`,\n    style: { color: t.inkSoft, fontSize: \"14px\" },\n  },\n  xAxis: {\n    min: -1.15,\n    max: 1.15,\n    lineWidth: 0,\n    tickLength: 0,\n    gridLineWidth: 0,\n    labels: { enabled: false },\n    title: { text: null },\n  },\n  yAxis: {\n    min: -1.15,\n    max: 1.15,\n    lineWidth: 0,\n    tickLength: 0,\n    gridLineWidth: 0,\n    labels: { enabled: false },\n    title: { text: null },\n  },\n  plotOptions: {\n    series: { animation: false, enableMouseTracking: false, showInLegend: false },\n  },\n  series: [\n    // r=1 is the most diagnostically important resistance circle (matches Z0\n    // on the real axis at both ends), so it renders heavier than the rest.\n    ...resistanceGrid.map((data, i) => ({\n      type: \"line\",\n      data,\n      color: RESISTANCE_VALUES[i] === 1 ? t.inkSoft : t.grid,\n      lineWidth: RESISTANCE_VALUES[i] === 1 ? 2 : 1,\n      marker: { enabled: false },\n    })),\n    ...reactanceGrid.map((data) => ({\n      type: \"line\",\n      data,\n      color: t.grid,\n      lineWidth: 1,\n      marker: { enabled: false },\n    })),\n    {\n      type: \"line\",\n      data: realAxis,\n      color: t.inkSoft,\n      lineWidth: 2,\n      marker: { enabled: false },\n    },\n    {\n      type: \"line\",\n      data: boundaryCircle,\n      color: t.inkSoft,\n      lineWidth: 2,\n      marker: { enabled: false },\n    },\n    {\n      type: \"scatter\",\n      data: resistanceAxisLabels,\n      color: t.inkSoft,\n      marker: { enabled: false },\n      dataLabels: {\n        enabled: true,\n        format: \"{point.name}\",\n        align: \"center\",\n        y: 16,\n        style: { color: t.inkSoft, fontSize: \"12px\", textOutline: \"none\" },\n      },\n    },\n    {\n      type: \"scatter\",\n      data: reactanceLabels,\n      color: t.inkSoft,\n      marker: { enabled: false },\n      enableMouseTracking: false,\n      dataLabels: {\n        enabled: true,\n        format: \"{point.name}\",\n        align: \"center\",\n        verticalAlign: \"middle\",\n        style: { color: t.inkSoft, fontSize: \"11px\", textOutline: \"none\" },\n      },\n    },\n    {\n      // matched condition: Z = Z0, so Gamma = 0 at the chart's center — the\n      // \"1\" resistance-circle label already occupies the space below this\n      // point, so the Z0 label sits above it instead to avoid colliding\n      type: \"scatter\",\n      data: [{ x: 0, y: 0, name: \"Z₀\" }],\n      color: t.inkSoft,\n      enableMouseTracking: false,\n      marker: { symbol: \"circle\", radius: 4, fillColor: t.pageBg, lineColor: t.inkSoft, lineWidth: 1.5 },\n      dataLabels: {\n        enabled: true,\n        format: \"{point.name}\",\n        align: \"center\",\n        y: -14,\n        style: { color: t.inkSoft, fontSize: \"11px\", fontStyle: \"italic\", textOutline: \"none\" },\n      },\n    },\n    {\n      type: \"line\",\n      name: \"S11 locus\",\n      data: locusData,\n      color: t.palette[0],\n      lineWidth: 3,\n      marker: { enabled: false },\n    },\n    {\n      type: \"scatter\",\n      name: \"Frequency\",\n      data: labeledPoints,\n      color: t.palette[0],\n      marker: { symbol: \"circle\", radius: 6, fillColor: t.palette[0], lineColor: t.pageBg, lineWidth: 1.5 },\n      dataLabels: {\n        enabled: true,\n        format: \"{point.name}\",\n        y: -14,\n        style: { color: t.ink, fontSize: \"13px\", fontWeight: \"500\", textOutline: \"none\" },\n      },\n    },\n  ],\n});\n"}