React Bode Plot
The standard view for filters and control loops — and the one that answers the question that matters, which is not "what does it do" but "is it stable".
Two-pole loop response — 40 dB DC gain
10Hz – 10MHz · DC gain 40.0 dB
- −3 dB corner
- 997 Hz
- Unity gain
- 57k Hz
- Phase margin
- 35.9°
Installation
Props
| Prop | Type | Description |
|---|---|---|
| transfer | (freqHz: number) => Complex | Your transfer function, evaluated at a frequency |
| fMin fMax | number | Sweep range in Hz |
| resolution | number | Samples per decade (default 60) |
| height | number | SVG height (default 340) |
Phase margin is the output
Magnitude is 20·log10|H|, phase is atan2(im, re) unwrapped across the sweep — without unwrapping, a two-pole response jumps from −179° to +179° and the phase margin comes out nonsense.
The three reported figures are found by interpolating in log-frequency across the sampled sweep: the −3 dB corner (relative to DC gain), the unity-gain crossover, and the phase margin, which is 180° + phase at crossover. The margin is colour-coded — green at ≥45°, amber at ≥30°, red below — because that is the number that decides whether a loop is damped, rings, or oscillates.
The demo is a two-pole loop with 40 dB DC gain, poles at 1 kHz and 40 kHz. It checks out against the analytic values: exactly 40.000 dB at DC, 36.99 dB at the first pole (the expected −3 dB), −46.4° phase there (−45° from the dominant pole plus −1.4° from the second), −133.6° at the second pole, and a −180° asymptote. Phase margin lands at 35.9° — amber, which is a loop that will ring on a step.
Pass any transfer you like: measured VNA data, a filter prototype, or a closed-loop expression. It only needs to return a complex number.
New components every week
Get the week's new Kelvin UI components and templates in one short email. No spam, unsubscribe anytime.