import { RealtimeHistogram } from "semiotic"
RealtimeHistogram renders a streaming temporal histogram. Incoming data points are binned by time interval and rendered as bars that scroll across the chart. It wrapsStreamXYFrame withchartType="bar" and supports both simple and stacked (categorical) modes. Edge bins that only partially fall within the visible time window are rendered at proportionally narrower widths.
Quick Start
Create a ref, push data points on an interval, and RealtimeHistogram bins and renders them as bars. ThebinSize prop defines the time interval for aggregation.
JSX
import { RealtimeHistogram } from "semiotic" import { useRef, useEffect } from "react" function StreamingBars() { const chartRef = useRef() const indexRef = useRef(0) useEffect(() => { const id = setInterval(() => { chartRef.current?.push({ time: indexRef.current++, value: Math.floor(Math.random() * 39) + 1 }) }, 30) return () => clearInterval(id) }, []) return ( <RealtimeHistogram ref={chartRef} binSize={20} fill="#007bff" windowSize={200} showAxes={true} /> ) }
Examples
Stacked Bars by Category
Use categoryAccessor and colors to stack bars by category within each bin. The stack order follows the key order of the colors object.
JSX
const categories = ["errors", "warnings", "info"] useEffect(() => { const id = setInterval(() => { const cat = categories[indexRef.current % 3] chartRef.current?.push({ time: Math.floor(indexRef.current++ / 3), value: Math.floor(Math.random() * 10) + 1, category: cat }) }, 30) return () => clearInterval(id) }, []) <RealtimeHistogram ref={chartRef} binSize={20} categoryAccessor="category" colors={{ errors: "#dc3545", warnings: "#fd7e14", info: "#007bff" }} windowSize={200} />
Custom Bar Styling
Control the appearance with fill, stroke, and gap to create distinct visual styles.
JSX
useEffect(() => { const id = setInterval(() => { const i = indexRef.current++ chartRef.current?.push({ time: i, value: Math.floor(Math.abs(Math.sin(i * 0.04)) * 50) + 5 }) }, 30) return () => clearInterval(id) }, []) <RealtimeHistogram ref={chartRef} binSize={20} fill="#28a745" stroke="#1e7e34" gap={2} windowSize={200} />
Brushable Selection
Static Data with Brush
Toggle between static data and streaming push API. In static mode, 100 pre-generated points are rendered with brush="x". The selected time range is displayed below the chart.
JSX
<RealtimeHistogram data={staticData} binSize={10} fill="#007bff" brush="x" onBrush={(extent) => console.log(extent)} />
Streaming Brush with Bin Snapping
In streaming mode, the brush tracks with the data. When selected bins scroll off the left edge, the brush shrinks. When all selected bins are evicted, the brush clears automatically. Usesnap: "bin" to snap to bin boundaries on mouse-up.
Drag on the chart to brush. The selection tracks and shrinks as data scrolls.
JSX
<RealtimeHistogram ref={chartRef} binSize={20} fill="#6f42c1" brush={{ dimension: "x", snap: "bin" }} onBrush={(extent) => setBrushExtent(extent)} />
Cross-Chart Brush Filtering
Wrap both charts in <LinkedCharts>. The histogram writes its brush extent to the "timeRange" selection vialinkedBrush. A child component reads it withuseFilteredData. The base layer always renders all data as a thin faded line; when a brush is active, a bold gradient-filled area for the filtered subset is overlaid on top.
Histogram (brush to filter)
Line Chart (filtered by brush)
JSX
import { LinkedCharts, RealtimeHistogram, LineChart, AreaChart, useFilteredData } from "semiotic" // Consumer component — must be inside <LinkedCharts> function FilteredOverlay({ allData, width }) { const filtered = useFilteredData(allData, "timeRange") const hasBrush = filtered.length < allData.length return ( <div style={{ position: "relative" }}> {/* Base: always-faded thin line, stable across brush on/off */} <LineChart data={allData} xAccessor="time" yAccessor="value" color="#c4cdd8" lineWidth={1} width={width} height={200} frameProps={{ xExtent, yExtent, showAxes: true }} /> {/* Overlay: bold gradient-filled area for brushed range. background: "transparent" keeps the base layer visible. */} {hasBrush && filtered.length > 1 && ( <div style={{ position: "absolute", top: 0, left: 0, pointerEvents: "none" }}> <AreaChart data={filtered} xAccessor="time" yAccessor="value" color="#007bff" gradientFill areaOpacity={0.35} showLine lineWidth={2} width={width} height={200} frameProps={{ xExtent, yExtent, showAxes: false, background: "transparent" }} /> </div> )} </div> ) } // Dashboard <LinkedCharts> <RealtimeHistogram ref={histRef} binSize={20} brush={{ dimension: "x", snap: "bin" }} linkedBrush="timeRange" /> <FilteredOverlay allData={allData} width={600} /> </LinkedCharts>
Stacked Histogram → Multi-Line Filtering
A stacked histogram with three categories (errors,warnings, info) drives a line chart that splits into three colored lines. The base layer always renders all categories with a faded colorScheme; when a brush is active, a bold overlay using the full-opacity scheme is drawn on top. A fixed margin on both charts keeps them aligned.
Stacked Histogram (brush to filter)
Per-Category Lines (filtered by brush)
JSX
import { LinkedCharts, RealtimeHistogram, LineChart, useFilteredData } from "semiotic" const COLORS = { errors: "#dc3545", warnings: "#fd7e14", info: "#007bff" } const SCHEME = Object.values(COLORS) const SCHEME_FADED = [ "rgba(220, 53, 69, 0.25)", "rgba(253, 126, 20, 0.25)", "rgba(0, 123, 255, 0.25)", ] const MARGIN = { top: 10, right: 110, bottom: 40, left: 50 } function FilteredMultiLineOverlay({ allData, width }) { const filtered = useFilteredData(allData, "catBrush") const hasBrush = filtered.length < allData.length return ( <div style={{ position: "relative" }}> {/* Base: always-faded colored lines + axes + legend */} <LineChart data={allData} xAccessor="time" yAccessor="value" lineBy="category" colorBy="category" colorScheme={SCHEME_FADED} lineWidth={1} width={width} height={220} margin={MARGIN} showLegend showGrid frameProps={{ xExtent, yExtent, showAxes: true }} /> {/* Overlay: bold colored lines for brushed range. background: "transparent" keeps the base layer visible. */} {hasBrush && filtered.length > 1 && ( <div style={{ position: "absolute", top: 0, left: 0, pointerEvents: "none" }}> <LineChart data={filtered} xAccessor="time" yAccessor="value" lineBy="category" colorBy="category" colorScheme={SCHEME} lineWidth={3} width={width} height={220} margin={MARGIN} showLegend={false} frameProps={{ xExtent, yExtent, showAxes: false, background: "transparent" }} /> </div> )} </div> ) } <LinkedCharts> <RealtimeHistogram ref={histRef} binSize={20} categoryAccessor="category" colors={COLORS} brush={{ dimension: "x", snap: "bin" }} linkedBrush="catBrush" /> <FilteredMultiLineOverlay allData={allData} width={600} /> </LinkedCharts>
Props
| Prop | Type | Required | Default | Description |
|---|
binSize | number | Yes | — | Time interval for binning data points into bars. Points within the same bin are aggregated. |
data | array | — | [] | Controlled data array. Each object should contain fields matched by timeAccessor and valueAccessor. |
timeAccessor | string | function | — | "time" | Field name or function to access the time value from each data point. |
valueAccessor | string | function | — | "value" | Field name or function to access the numeric value from each data point. |
size | [number, number] | — | [500, 300] | Chart dimensions as [width, height]. |
margin | object | — | — | Chart margins: { top, right, bottom, left }. |
arrowOfTime | "left" | "right" | — | "right" | Direction that time flows across the chart. |
windowMode | "sliding" | "growing" | — | "sliding" | Data retention strategy. "sliding" discards old points beyond windowSize. |
windowSize | number | — | 200 | Ring buffer capacity when using sliding window mode. |
timeExtent | [number, number] | — | — | Fixed time domain. Defaults to auto-fit. |
valueExtent | [number, number] | — | — | Fixed value domain. Defaults to auto-fit. |
extentPadding | number | — | — | Padding factor applied to auto-computed extents. |
categoryAccessor | string | function | — | — | Category accessor for stacked bars. When provided, bars are stacked by category within each bin. |
colors | object | — | — | Category-to-color map for stacked bars. Keys also determine stack order. |
fill | string | — | — | Bar fill color in non-stacked mode. |
stroke | string | — | — | Bar stroke (outline) color. |
strokeWidth | number | — | — | Bar stroke width. |
gap | number | — | — | Gap between bars in pixels. |
showAxes | boolean | — | true | Show canvas-drawn axes. |
background | string | — | — | Background fill color for the chart area. |
enableHover | boolean | object | — | — | Enable hover annotations on bars. |
tooltipContent | function | — | — | Custom tooltip render function. Receives hover data. |
onHover | function | — | — | Callback fired on hover. Receives hover data or null. |
annotations | array | — | — | Array of annotation objects rendered on the chart. |
svgAnnotationRules | function | — | — | Custom SVG annotation render function. |
tickFormatTime | function | — | — | Custom formatter for time axis tick labels. |
tickFormatValue | function | — | — | Custom formatter for value axis tick labels. |
className | string | — | — | CSS class name for the chart container. |
brush | boolean | "x" | object | — | — | Brush configuration. `true` defaults to `{ dimension: "x", snap: "bin" }`. Object form accepts `dimension` ("x"|"y"|"xy") and `snap` ("continuous"|"bin"). |
onBrush | function | — | — | Callback when brush selection changes. Receives `{ x: [min, max], y: [min, max] }` or `null` when cleared. |
linkedBrush | string | object | — | — | Linked brush for cross-chart coordination via LinkedCharts. String shorthand sets the selection name. |
Graduating to the Frame
When you need more control — custom bin aggregation, mixed chart types, or advanced annotation logic — graduate toStreamXYFrame directly. Every RealtimeHistogram is just a configuredStreamXYFrame under the hood.
Chart (simple)
JSX
import { RealtimeHistogram } from "semiotic" <RealtimeHistogram ref={chartRef} data={eventStream} binSize={5000} timeAccessor="time" valueAccessor="count" fill="#007bff" gap={2} enableHover />
Frame (full control)
JSX
import { StreamXYFrame } from "semiotic" <StreamXYFrame ref={frameRef} chartType="bar" data={eventStream} binSize={5000} timeAccessor="time" valueAccessor="count" barStyle={{ fill: "#007bff", gap: 2 }} hoverAnnotation={true} showAxes={true} size={[500, 300]} />
The categoryAccessor and colors props on RealtimeHistogram map directly to categoryAccessor andbarColors on StreamXYFrame for stacked bar support.