{"spec_id":"bode-basic","library":"echarts","language":"javascript","code":"// anyplot.ai\n// bode-basic: Bode Plot for Frequency Response\n// Library: echarts 5.5.1 | JavaScript 22.22.3\n// Quality: 90/100 | Created: 2026-06-17\n//# anyplot-orientation: landscape\n\nconst t = window.ANYPLOT_TOKENS;\n\n// Open-loop transfer function: G(f) = K / ((1 + jf/f1)(1 + jf/f2)(1 + jf/f3))\n// Three poles at f1=10 Hz, f2=100 Hz, f3=1000 Hz; DC gain K=30 (~29.5 dB)\nconst K_tf = 30, pole1 = 10, pole2 = 100, pole3 = 1000;\nconst N = 600;\nconst magData = [], phaseData = [];\n\nfor (let i = 0; i < N; i++) {\n    const f = Math.pow(10, -1 + 5 * i / (N - 1));\n    const r1 = f / pole1, r2 = f / pole2, r3 = f / pole3;\n    const magLin = K_tf / (Math.sqrt(1 + r1 * r1) * Math.sqrt(1 + r2 * r2) * Math.sqrt(1 + r3 * r3));\n    const magDb  = 20 * Math.log10(magLin);\n    const phase  = -(Math.atan(r1) + Math.atan(r2) + Math.atan(r3)) * 180 / Math.PI;\n    magData.push([f, +magDb.toFixed(3)]);\n    phaseData.push([f, +phase.toFixed(3)]);\n}\n\n// Interpolate gain crossover (|G| = 0 dB) and phase crossover (phase = -180°)\nlet gcFreq = null, gcPhase = null, pcFreq = null, pcMagDb = null;\nfor (let i = 0; i < N - 1; i++) {\n    if (magData[i][1] >= 0 && magData[i + 1][1] < 0 && gcFreq === null) {\n        const a = -magData[i][1] / (magData[i + 1][1] - magData[i][1]);\n        gcFreq  = Math.pow(10, Math.log10(magData[i][0]) + a * Math.log10(magData[i + 1][0] / magData[i][0]));\n        gcPhase = phaseData[i][1] + a * (phaseData[i + 1][1] - phaseData[i][1]);\n    }\n    if (phaseData[i][1] >= -180 && phaseData[i + 1][1] < -180 && pcFreq === null) {\n        const a = (-180 - phaseData[i][1]) / (phaseData[i + 1][1] - phaseData[i][1]);\n        pcFreq  = Math.pow(10, Math.log10(phaseData[i][0]) + a * Math.log10(phaseData[i + 1][0] / phaseData[i][0]));\n        pcMagDb = magData[i][1] + a * (magData[i + 1][1] - magData[i][1]);\n    }\n}\n\nconst gainMarginDb   = pcMagDb  !== null ? +(-pcMagDb).toFixed(1)     : null;\nconst phaseMarginDeg = gcPhase  !== null ? +(180 + gcPhase).toFixed(1) : null;\n\nconst chart = echarts.init(document.getElementById(\"container\"));\n\nconst LAVENDER = t.palette[1];\nconst BLUE     = t.palette[2];\nconst RED      = t.palette[4];\n\nchart.setOption({\n    animation: false,\n    color: t.palette,\n    backgroundColor: \"transparent\",\n\n    title: {\n        text: \"bode-basic · javascript · echarts · anyplot.ai\",\n        left: \"center\",\n        top: 16,\n        textStyle: { color: t.ink, fontSize: 22, fontWeight: \"bold\" }\n    },\n\n    grid: [\n        { left: 95, right: 55, top: 65,  height: \"38%\" },\n        { left: 95, right: 55, bottom: 68, height: \"29%\" }\n    ],\n\n    xAxis: [\n        {\n            gridIndex: 0, type: \"log\", min: 0.1, max: 10000,\n            axisLabel: { show: false },\n            axisLine: { lineStyle: { color: t.inkSoft } },\n            axisTick: { lineStyle: { color: t.inkSoft } },\n            minorTick: { show: true, splitNumber: 9, lineStyle: { color: t.grid } },\n            splitLine: { lineStyle: { color: t.grid, width: 1 } },\n            minorSplitLine: { show: true, lineStyle: { color: t.grid, width: 0.5 } }\n        },\n        {\n            gridIndex: 1, type: \"log\", min: 0.1, max: 10000,\n            name: \"Frequency  (Hz)\",\n            nameLocation: \"center\", nameGap: 44,\n            nameTextStyle: { color: t.inkSoft, fontSize: 14 },\n            axisLabel: {\n                color: t.inkSoft, fontSize: 13,\n                formatter: (v) => ({ 0.1: \"0.1\", 1: \"1\", 10: \"10\", 100: \"100\", 1000: \"1k\", 10000: \"10k\" }[v] || \"\")\n            },\n            axisLine: { lineStyle: { color: t.inkSoft } },\n            axisTick: { lineStyle: { color: t.inkSoft } },\n            minorTick: { show: true, splitNumber: 9, lineStyle: { color: t.grid } },\n            splitLine: { lineStyle: { color: t.grid, width: 1 } },\n            minorSplitLine: { show: true, lineStyle: { color: t.grid, width: 0.5 } }\n        }\n    ],\n\n    yAxis: [\n        {\n            gridIndex: 0, type: \"value\", min: -70, max: 40, interval: 20,\n            name: \"Magnitude  (dB)\",\n            nameLocation: \"center\", nameGap: 60,\n            nameTextStyle: { color: t.inkSoft, fontSize: 14 },\n            axisLabel: { color: t.inkSoft, fontSize: 13 },\n            axisLine: { lineStyle: { color: t.inkSoft } },\n            axisTick: { lineStyle: { color: t.inkSoft } },\n            splitLine: { lineStyle: { color: t.grid, width: 1 } }\n        },\n        {\n            gridIndex: 1, type: \"value\", min: -270, max: 0, interval: 90,\n            name: \"Phase  (°)\",\n            nameLocation: \"center\", nameGap: 60,\n            nameTextStyle: { color: t.inkSoft, fontSize: 14 },\n            axisLabel: { color: t.inkSoft, fontSize: 13, formatter: (v) => v + \"°\" },\n            axisLine: { lineStyle: { color: t.inkSoft } },\n            axisTick: { lineStyle: { color: t.inkSoft } },\n            splitLine: { lineStyle: { color: t.grid, width: 1 } }\n        }\n    ],\n\n    series: [\n        // Magnitude response — Imprint palette position 1 (brand green)\n        {\n            name: \"Magnitude\",\n            type: \"line\",\n            xAxisIndex: 0, yAxisIndex: 0,\n            data: magData,\n            lineStyle: { color: t.palette[0], width: 3 },\n            symbol: \"none\",\n            markLine: {\n                silent: true,\n                symbol: [\"none\", \"none\"],\n                data: [\n                    {\n                        yAxis: 0,\n                        lineStyle: { color: t.inkSoft, type: \"dashed\", width: 1.5, opacity: 0.6 },\n                        label: { show: true, position: \"insideEndTop\",\n                                 formatter: \"0 dB\", color: t.inkSoft, fontSize: 12 }\n                    },\n                    ...(gcFreq !== null ? [{\n                        xAxis: gcFreq,\n                        lineStyle: { color: LAVENDER, type: \"dashed\", width: 1.5, opacity: 0.8 },\n                        label: { show: false }\n                    }] : []),\n                    ...(pcFreq !== null ? [{\n                        xAxis: pcFreq,\n                        lineStyle: { color: RED, type: \"dashed\", width: 1.5, opacity: 0.8 },\n                        label: { show: false }\n                    }] : [])\n                ]\n            },\n            markPoint: {\n                symbol: \"circle\",\n                symbolSize: 12,\n                data: pcFreq !== null ? [{\n                    coord: [pcFreq, pcMagDb],\n                    itemStyle: { color: RED, borderColor: t.pageBg, borderWidth: 2 },\n                    label: {\n                        show: true,\n                        position: \"top\",\n                        distance: 10,\n                        formatter: `GM = ${gainMarginDb} dB`,\n                        color: RED,\n                        fontSize: 14,\n                        fontWeight: \"bold\",\n                        backgroundColor: t.elevatedBg,\n                        padding: [4, 8],\n                        borderRadius: 4\n                    }\n                }] : []\n            }\n        },\n        // Phase response — Imprint palette position 3 (blue)\n        {\n            name: \"Phase\",\n            type: \"line\",\n            xAxisIndex: 1, yAxisIndex: 1,\n            data: phaseData,\n            lineStyle: { color: BLUE, width: 3 },\n            symbol: \"none\",\n            markLine: {\n                silent: true,\n                symbol: [\"none\", \"none\"],\n                data: [\n                    {\n                        yAxis: -180,\n                        lineStyle: { color: t.inkSoft, type: \"dashed\", width: 1.5, opacity: 0.6 },\n                        label: { show: true, position: \"insideEndTop\",\n                                 formatter: \"−180°\", color: t.inkSoft, fontSize: 12 }\n                    },\n                    ...(gcFreq !== null ? [{\n                        xAxis: gcFreq,\n                        lineStyle: { color: LAVENDER, type: \"dashed\", width: 1.5, opacity: 0.8 },\n                        label: { show: false }\n                    }] : []),\n                    ...(pcFreq !== null ? [{\n                        xAxis: pcFreq,\n                        lineStyle: { color: RED, type: \"dashed\", width: 1.5, opacity: 0.8 },\n                        label: { show: false }\n                    }] : [])\n                ]\n            },\n            markPoint: {\n                symbol: \"circle\",\n                symbolSize: 12,\n                data: gcFreq !== null ? [{\n                    coord: [gcFreq, gcPhase],\n                    itemStyle: { color: LAVENDER, borderColor: t.pageBg, borderWidth: 2 },\n                    label: {\n                        show: true,\n                        position: \"top\",\n                        distance: 10,\n                        formatter: `PM = ${phaseMarginDeg}°`,\n                        color: LAVENDER,\n                        fontSize: 14,\n                        fontWeight: \"bold\",\n                        backgroundColor: t.elevatedBg,\n                        padding: [4, 8],\n                        borderRadius: 4\n                    }\n                }] : []\n            }\n        }\n    ]\n});\n"}