{"spec_id":"bode-basic","library":"muix","language":"javascript","code":"// anyplot.ai\n// bode-basic: Bode Plot for Frequency Response\n// Library: muix 7.29.1 | JavaScript 22.22.3\n// Quality: 84/100 | Created: 2026-06-17\n\nimport { LineChart } from \"@mui/x-charts/LineChart\";\nimport { ChartsReferenceLine } from \"@mui/x-charts/ChartsReferenceLine\";\nimport Box from \"@mui/material/Box\";\nimport Typography from \"@mui/material/Typography\";\n\nconst t = window.ANYPLOT_TOKENS;\n\n// Refined chart appearance: subtle axis lines, grid at 0.15 opacity, thicker data lines\nconst chartSx = {\n  \"& .MuiChartsAxis-line\": { stroke: t.inkSoft, strokeWidth: 0.7 },\n  \"& .MuiChartsAxis-tick\": { stroke: t.inkSoft, strokeWidth: 0.7 },\n  \"& .MuiChartsGrid-root line\": { stroke: t.grid },\n  \"& .MuiLineElement-root\": { strokeWidth: 2.5 },\n};\n\n// --- Data: G(s) = 20 / ((s/1+1)(s/10+1)(s/100+1)) — 3-pole open-loop TF ---\n// Pole frequencies: ω₁=1, ω₂=10, ω₃=100 rad/s  (0.16 Hz, 1.59 Hz, 15.9 Hz)\nconst K = 20;\nconst w1 = 1;\nconst w2 = 10;\nconst w3 = 100;\nconst N = 250;\n\nconst freqs: number[] = [];\nconst mags: number[] = [];\nconst phases: number[] = [];\n\nfor (let i = 0; i < N; i++) {\n  const f = Math.pow(10, -2 + (i / (N - 1)) * 4); // 0.01 to 100 Hz\n  const omega = 2 * Math.PI * f;\n  const gain =\n    K /\n    Math.sqrt(\n      (1 + (omega / w1) ** 2) *\n        (1 + (omega / w2) ** 2) *\n        (1 + (omega / w3) ** 2)\n    );\n  freqs.push(f);\n  mags.push(20 * Math.log10(gain));\n  phases.push(\n    -(\n      Math.atan(omega / w1) +\n      Math.atan(omega / w2) +\n      Math.atan(omega / w3)\n    ) *\n      (180 / Math.PI)\n  );\n}\n\n// Gain crossover: magnitude crosses 0 dB going downward\nlet gcFreq = freqs[0];\nlet phaseAtGC = phases[0];\nfor (let i = 1; i < N; i++) {\n  if (mags[i - 1] > 0 && mags[i] <= 0) {\n    gcFreq = freqs[i];\n    phaseAtGC = phases[i];\n    break;\n  }\n}\nconst phaseMargin = phaseAtGC + 180;\n\n// Phase crossover: phase crosses -180° going downward\nlet pcFreq = freqs[N - 1];\nlet magAtPC = mags[N - 1];\nlet hasCrossover = false;\nfor (let i = 1; i < N; i++) {\n  if (phases[i - 1] > -180 && phases[i] <= -180) {\n    pcFreq = freqs[i];\n    magAtPC = mags[i];\n    hasCrossover = true;\n    break;\n  }\n}\nconst gainMargin = hasCrossover ? -magAtPC : null;\n\n// --- Chart component --------------------------------------------------------\nexport default function Chart() {\n  const { width, height } = window.ANYPLOT_SIZE;\n  const titleH = 56;\n  const panelH = Math.floor((height - titleH) / 2);\n\n  return (\n    <Box\n      sx={{\n        width,\n        height,\n        bgcolor: t.pageBg,\n        display: \"flex\",\n        flexDirection: \"column\",\n      }}\n    >\n      <Box\n        sx={{\n          height: titleH,\n          display: \"flex\",\n          alignItems: \"center\",\n          justifyContent: \"center\",\n        }}\n      >\n        <Typography sx={{ fontSize: 23, fontWeight: 600, letterSpacing: 0.3, color: t.ink }}>\n          bode-basic · javascript · muix · anyplot.ai\n        </Typography>\n      </Box>\n\n      {/* Magnitude panel */}\n      <LineChart\n        width={width}\n        height={panelH}\n        colors={[t.palette[0]]}\n        skipAnimation\n        xAxis={[\n          {\n            data: freqs,\n            scaleType: \"log\",\n            tickSize: 0,\n            tickLabelStyle: { display: \"none\" },\n          },\n        ]}\n        yAxis={[\n          {\n            label: \"Magnitude (dB)\",\n            labelStyle: { fontSize: 15 },\n            tickLabelStyle: { fontSize: 13 },\n          },\n        ]}\n        series={[{ data: mags, showMark: false, label: \"Magnitude\" }]}\n        slotProps={{ legend: { hidden: true } }}\n        grid={{ horizontal: true, vertical: false }}\n        margin={{ left: 80, right: 50, top: 20, bottom: 8 }}\n        sx={chartSx}\n      >\n        <ChartsReferenceLine\n          y={0}\n          lineStyle={{\n            stroke: t.inkSoft,\n            strokeDasharray: \"6 4\",\n            strokeWidth: 1.5,\n          }}\n          label=\"0 dB\"\n          labelStyle={{ fontSize: 12 }}\n          labelAlign=\"end\"\n        />\n        {hasCrossover && (\n          <ChartsReferenceLine\n            x={pcFreq}\n            lineStyle={{\n              stroke: t.palette[3],\n              strokeDasharray: \"6 4\",\n              strokeWidth: 1.5,\n            }}\n            label={`GM = ${gainMargin!.toFixed(1)} dB`}\n            labelStyle={{ fontSize: 12, fill: t.palette[3] }}\n            labelAlign=\"start\"\n          />\n        )}\n      </LineChart>\n\n      {/* Phase panel */}\n      <LineChart\n        width={width}\n        height={panelH}\n        colors={[t.palette[1]]}\n        skipAnimation\n        xAxis={[\n          {\n            data: freqs,\n            scaleType: \"log\",\n            label: \"Frequency (Hz)\",\n            labelStyle: { fontSize: 15 },\n            tickLabelStyle: { fontSize: 13 },\n          },\n        ]}\n        yAxis={[\n          {\n            label: \"Phase (°)\",\n            labelStyle: { fontSize: 15 },\n            tickLabelStyle: { fontSize: 13 },\n          },\n        ]}\n        series={[{ data: phases, showMark: false, label: \"Phase\" }]}\n        slotProps={{ legend: { hidden: true } }}\n        grid={{ horizontal: true, vertical: false }}\n        margin={{ left: 80, right: 50, top: 5, bottom: 60 }}\n        sx={chartSx}\n      >\n        <ChartsReferenceLine\n          y={-180}\n          lineStyle={{\n            stroke: t.inkSoft,\n            strokeDasharray: \"6 4\",\n            strokeWidth: 1.5,\n          }}\n          label=\"-180°\"\n          labelStyle={{ fontSize: 12 }}\n          labelAlign=\"end\"\n        />\n        <ChartsReferenceLine\n          x={gcFreq}\n          lineStyle={{\n            stroke: t.palette[2],\n            strokeDasharray: \"6 4\",\n            strokeWidth: 1.5,\n          }}\n          label={`PM = ${phaseMargin.toFixed(1)}°`}\n          labelStyle={{ fontSize: 12, fill: t.palette[2] }}\n          labelAlign=\"start\"\n        />\n      </LineChart>\n    </Box>\n  );\n}\n"}