When visualizing streaming data, the chart needs to communicate not just current values, but change over time. Semiotic provides four visual encoding features for realtime data: decay,pulse, transitions, andstaleness. These work on all streaming chart types (StreamXYFrame, StreamOrdinalFrame, and all realtime HOCs).
Decay
Older data fades out based on age in the ring buffer. This creates a visual trail that shows the history of data flow. Three modes:
- linear -- opacity decreases linearly from 1 (newest) to minOpacity (oldest)
- exponential -- opacity follows a half-life curve (more natural feel)
- step -- full opacity within a threshold, then drops to minOpacity
JSX
<StreamXYFrame ref={chartRef} chartType="scatter" runtimeMode="streaming" decay={{ type: "exponential", halfLife: 80, // buffer positions minOpacity: 0.05 // floor opacity }} />
Pulse
Recently inserted data points flash briefly with a glow effect. This draws the eye to where new data is appearing. Points get a glow ring; bars and heatmap cells get a white overlay flash.
JSX
<StreamXYFrame ref={chartRef} chartType="scatter" runtimeMode="streaming" pulse={{ duration: 600, // ms color: "rgba(16, 185, 129, 0.6)", // glow color glowRadius: 6 // extra px }} />
Staleness
When the data feed stops, the chart dims and optionally shows a LIVE/STALE badge. This communicates whether the visualization reflects current or outdated data. Try pausing the feed below:
JSX
<StreamXYFrame ref={chartRef} chartType="scatter" runtimeMode="streaming" staleness={{ threshold: 2000, // ms without data → stale dimOpacity: 0.3, // canvas alpha when stale showBadge: true, // render LIVE/STALE indicator badgePosition: "top-right" }} />
Transitions
When data changes cause scene nodes to move (e.g. bar heights changing or points repositioning), transition smoothly interpolates from old to new positions using ease-out cubic easing.
JSX
<StreamOrdinalFrame ref={chartRef} chartType="bar" runtimeMode="streaming" transition={{ duration: 300, // ms easing: "ease-out" // or "linear" }} />
Tuning for streaming cadence
Transitions run as keyframes: each data change starts a fresh ease-out curve from the current rendered position to the new target. That model has a sweet spot — setduration close to your push interval so the chart is nearly always in motion instead of snap-then-pause.
- Fast streams (pushes ≪ duration) — each new push cancels the in-flight transition and restarts from the current interpolated position. You still get smooth motion, but older pushes never fully land. Consider a shorter
duration or batching pushes on the producer side. - Pulsed streams (pushes ~ duration, e.g. 800ms pushes with 750ms transitions) — the best case. Bars essentially never stop moving; the eye reads continuous motion instead of discrete updates.
- Slow or irregular streams (pushes ≫ duration) — transitions finish long before the next push, leaving dead air. A longer
duration helps, but if cadence is genuinely unpredictable, keyframe transitions will feel jerky no matter how you tune them. For that shape of data, a continuous-chase interpolation (lerp each frame toward the latest target) would handle irregular arrivals more gracefully — not currently built in.
Aggregating HOCs (LikertChart, future density/bin charts) that re-derive their full dataset from pushes participate in the same transition system as long as they route through the frame'sreplace() API — not clear() + pushMany(), which wipes the position snapshot the transition needs.
Combining Encodings
All four features can be used together. A common pattern is decay + pulse: older data fades while new data flashes, creating a clear sense of data flow direction and recency.
JSX
<StreamXYFrame ref={chartRef} chartType="scatter" runtimeMode="streaming" decay={{ type: "linear", minOpacity: 0.1 }} pulse={{ duration: 500, color: "rgba(99, 102, 241, 0.5)" }} staleness={{ threshold: 5000, dimOpacity: 0.4, showBadge: true }} />