Spectrum Monitor
A live scrolling spectrum and heat band: a canvas waterfall of band intensities drawn frame by frame, with pause, hover readouts and automatic pausing when off screen.
This is a WebberUI Pro component
The install command below needs a licence to run; without one it returns 401.
Turn continuously changing band intensities into an instrument panel: the top shows the current frame as spectrum bars, each with a slowly falling peak-hold mark; below is a waterfall where every frame adds a new row of pixels at the top and the whole image scrolls down, colored by intensity, so you can tell at a glance which band is always ringing and which comes and goes. It suits vibration monitoring, audio analysis, network traffic or any live "band × time" data. Two canvases are drawn frame by frame and stop automatically when off screen or when the tab is hidden, so nothing spins in the background.
Bring this into your project
Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.
npx webberui@latest add "https://webberui.com/r/spectrum-monitor.en.json?t=<install token>"Playground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<SpectrumMonitor/>
The complete example includes the preview layout and data. Install the component first, then paste this file into your project.
Installation
npx webberui@latest add "https://webberui.com/r/spectrum-monitor.en.json?t=<install token>"Or, once registries are configured in components.json, install it as @webberui/spectrum-monitor.
Usage
import { SpectrumMonitor } from "@/components/ui/spectrum-monitor";
// Without a data source, the built-in deterministic generator is used
<SpectrumMonitor title="Spectrum monitor" bins={64} minHz={20} maxHz={20000} />;
// Your own data: return an array of bins values in 0–1
<SpectrumMonitor
title="Sensor A3"
bins={96}
minHz={10}
maxHz={5000}
source={(t, bins) => readLatestBands(bins)}
onPausedChange={(paused) => console.log(paused ? "paused" : "resumed")}
/>;Props
| Prop | Type | Default | Description |
|---|---|---|---|
bins | number | 64 | Number of bands (spectrum bars and waterfall pixels per row), 4–512 |
source | (t: number, bins: number) => number[] | — | Custom data source: given simulated time t (seconds) and the band count, return 0–1 intensities; defaults to a built-in deterministic generator (a few sine peaks + noise hashed from the frame index) |
frames | number[][] | — | Static playback; each element is one frame, looped in order; takes precedence over source |
fps | number | 30 | Frames produced per second, throttled by accumulating requestAnimationFrame time, 1–120 |
history | number | 120 | Rows kept in the waterfall (one row per frame), 10–600 |
colorStops | string[] | — | Intensity color ramp (low to high), any CSS colors; defaults to the --wb-spectrum-monitor-low / -mid / -high / -peak variables, topping out at white |
paused | boolean | — | Controlled paused state; managed internally when omitted |
defaultPaused | boolean | false | Initial paused state in uncontrolled mode |
onPausedChange | (paused: boolean) => void | — | Called when the paused state changes |
minHz | number | 20 | Lower frequency bound (Hz), used only for the scale and readouts |
maxHz | number | 20000 | Upper frequency bound (Hz) |
logScale | boolean | true | Spread bands on a logarithmic scale |
labels | string[] | — | Custom bottom scale labels (evenly spaced); generated from minHz / maxHz when omitted |
height | number | 320 | Total plot height (px); about 32% goes to the spectrum bars, the rest to the waterfall |
title | string | "Spectrum monitor" | Status bar title |
surface | "dark" | "auto" | "dark" | Frame colors: dark is a fixed dark instrument, auto follows the site light/dark theme; the plot is always a dark screen |
label | string | — | aria-label of the plot's role="img"; generated from the title, band count and frequency range when omitted |
className | string | — | Passed through to the outermost container |
Also exports getSpectrumBinHz(i, bins, minHz, maxHz, logScale) (the center frequency of band i, handy for aligning a custom source to frequencies) and the SpectrumMonitorProps and SpectrumMonitorSurface types.
CSS variables
| Variable | Default | Description |
|---|---|---|
--wb-spectrum-monitor-low | #171717 | Bottom of the ramp (neutral-900) |
--wb-spectrum-monitor-mid | #0f766e | Lower-middle of the ramp (teal-700) |
--wb-spectrum-monitor-high | #2dd4bf | Upper-middle of the ramp (teal-400) |
--wb-spectrum-monitor-peak | #fcd34d | Top of the ramp (amber-300), then up to white |
--wb-spectrum-monitor-pulse | 1.6s | LIVE light breathing period |
How it works
- Two canvases: the spectrum canvas is resized to the container times
min(devicePixelRatio, 2)(ResizeObserver); the waterfall canvas resolution is the data resolution (bins×history), which the browser smooths up into a heat band - The waterfall never redraws in full: each new frame first shifts the canvas down one row onto itself with
drawImage, then paints only the newest row; the color ramp is precomputed into a 256-color lookup table - Throttling: each rAF tick accumulates elapsed time and produces a frame only once
1 / fpsseconds have passed; a single tick is capped at 0.25 seconds when you return to the tab, so it never catches up too many frames at once - Peak hold: the white mark on each bar takes the larger of the current value and the previous peak × 0.96, so it falls slowly
- Readouts: hovering the spectrum or waterfall shows a crosshair and a readout card — frequency, level (dB via 20·log10, plus percent) and how many seconds ago that row was; while paused the image is frozen but readouts still work
- Auto stop: rAF is cancelled when IntersectionObserver reports the plot off screen or
visibilitychangereports the tab hidden; unmounting cancels rAF and disconnects every observer - Initial snapshot:
historyframes are pre-run on mount, so the waterfall starts full and hover has data right away
Accessibility
- The plot is
role="img"with anaria-labeldescribing the title, band count and frequency range; both canvases arearia-hidden - A
sr-onlyaria-live="polite"region updates every 5 seconds with the current peak, such as "目前峰值 1.2 kHz,-12 dB" (Current peak 1.2 kHz, -12 dB), and stops while paused - The pause button is
type="button"witharia-pressed; the plot is focusable with Tab (focus-visiblering), Space toggles pause while focus is inside the component, and clicking the plot also pauses or resumes - When the user has reduce motion enabled, nothing autoplays: a static snapshot of
historypre-run frames is drawn and the LIVE light does not breathe; playback starts only after pressing play, at no more than 10 fps
Paired Column Chart
A paired column chart comparing two series side by side: switching periods reorders and resizes the bars with a layout animation, and hovering shows the gap between the pair.
Spreadsheet Grid
An editable spreadsheet grid: arrow-key selection, double-click or Enter to edit, a formula bar showing raw input, simple formulas, and a frozen header row and column.