Waterfall
addWaterfall(plot, opts) — a spectrogram that streams. Each pushed column becomes the newest row at the top and the history slides one row down, while the y axis reads as elapsed time.
const wf = addWaterfall(plot, {
extent: [0, 160_000], // the band one row spans
cols: 512, rows: 400, // cells across × rows of history
rowSeconds: 0.08, // seconds per row → 32s on screen
domain: [-68, -6], // fix it, or the colours breathe every push
colormap: "plasma", name: "power (dB)",
timeFormat: "hh:mm:ss", // or "mm:ss.mmm", or (s) => your own label
timeTitle: "time",
});
wf.push(psdInDb); // one column per step
wf.setTimeAxis({ format: "mm:ss.mmm" }); // relabel liveTwo things a hand-rolled version keeps getting wrong are handled inside: ticks are handed over as a fresh array on every push (an axis caches its resolved ticks while the domain and config hold still, and a waterfall's domain never moves — so a generator gets called once and the labels sit there while the image scrolls under them), and the clock starts one row negative so the first push lands on startTime exactly instead of leaving the top edge of the empty history a row above its own tick.
A column longer than cols is reduced by block maximum, so a peak two bins wide survives fitting a 200k-bin spectrum into a few hundred cells. history opens on a pre-computed grid instead of an empty one. Clock labels are wider than plain numbers and the left margin is not measured from them — pass margin: { left: 72 }.
Pure and unit tested: waterfallTimeTicks, formatDuration, niceTimeStep, blockMax.
Wrappers: a Waterfall component (react/vue/solid) and a waterfall series (svelte/gea/wc). Live streaming still goes through the handle on a core Plot, as with every other streaming layer.
Gallery
examples/vanilla gains a Signal tab — a 409.6 kHz receiver as a 204 800-point live spectrum over the waterfall, both full width on a shared frequency axis, with start/stop frequency boxes and a time-label picker. Its streaming updaters are now grouped per tab, so switching away from Dynamic no longer leaves those 50 panels running behind it.