DualWindowMeter
React dual-window meter chart — two time windows compared in one compact meter. Tiny accessible SVG microchart; RSC-safe static or /interactive.
DualWindowMeter answers "is the level compliant against its target both right now and on average — momentary spikes vs sustained drift?". From one raw series it computes two rolling means: a thin fast window that reacts instantly and a thick slow window that carries the sustained read, both against a target line. It is the general form of any noisy metric with a compliance target.
Install
import { DualWindowMeter } from "@microcharts/react/dual-window-meter";// samples — real values under “Sample data” below<DualWindowMeter data={samples} target={-23} format={{ maximumFractionDigits: 1 }} title="Loudness"/>Sample data
const samples = Array.from( { length: 60 }, (_, i) => -22 + Math.sin(i / 3) * 4 + Math.sin(i / 11) * 2 - (i > 40 ? 2 : 0),);Try it
When to use it
- Good for — loudness / LUFS metering, latency SLO or CPU-headroom compliance.
- Avoid for — a single series (Sparkline) or anything without a target to compare against.
Sizing
Variants
With a locale, the right-edge readings and the accessible summary follow that locale's decimal mark — the fast reading
above renders "-23,9", not "-23.9", and the accessible name reads "Slow window -25 vs target -23; fast -23,9."
Edge cases
Four homes
Why this default
Thin-fast, thick-slow because the sustained read should carry more ink — a momentary spike on the fast trace matters
less than a slow-window drift away from target. The window sizes are part of the reading, not a hidden implementation
detail — windows is an explicit prop, and the examples that change it state both sizes. Both traces always share one
domain; a trace begins only where its window has filled, never faking a partial-window value.
Accessibility
The accessible name leads with the sustained read — "Slow window -25 vs target -23; fast -23,9." The interactive entry roves the samples with ←/→, announcing the fast value, the slow value, and the target at each point.
The interactive entry follows the shared interaction contract:
arrow keys rove between units on both axes, Home and End jump to the ends, and a click, tap, Enter or
Space selects a unit — pinning its readout so it survives blur, until you select it again or press Escape.
On touch, a tap pins and a drag scrubs.
Props
| Prop | Type | Notes |
|---|---|---|
| data* | number[] | Raw samples; two rolling means are computed. |
| target* | number | The compliance line — required. |
| windows | [number, number] | Fast/slow integration windows (samples). |
| band | [number, number] | A compliance corridor instead of one line. |
| domain | [number, number] | Fix the vertical scale instead of auto-fitting both traces. |
| animate | boolean | interactiveOpt-in entrance motion when the chart mounts client-side — add import "@microcharts/react/motion" once. Inert on the server, on hydrated server HTML, and under prefers-reduced-motion. |
Plus the shared grammar — data, domain, color, title, summary, format — and the layout props (width, height, className, style) that every chart accepts. Interactive entries also share animate and live, and — wherever a chart has more than one navigable unit — onActive, onSelect, selectedIndex and defaultSelectedIndex; and — wherever the chart shows a hover value — readout. See the shared grammar.