Chart & Visualisation
RoidRunnerChart — a React component that wraps lightweight-charts to render a candlestick series, up to 12 indicator line overlays, optional stacked panes, and pattern arrow markers — with support for both full batch-load and incremental live-append.
Core Types
BarDataexport interface BarData {
symbol: string; // e.g. "AAPL"
t: number; // Unix timestamp
o: number; // open
h: number; // high
l: number; // low
c: number; // close
v: number; // volume
}OutputPointexport interface OutputPoint {
node: string; // Channel label, e.g. "sma" or "ichi.conv"
t: number; // Unix timestamp (must align with bar times)
value: number; // Indicator value
}Props
| Prop | Type | Description |
|---|---|---|
| theme | 'light' | 'dark' | Drives chart background, grid, and axis colours. Changing theme destroys and recreates the chart instance. |
| bars | BarData[] | Full OHLCV history for the selected roid. Triggers a setData() bulk-load when changed. |
| lastBar | BarData | null | Most recent bar received from a live roid_output frame. Triggers an update() incremental append. |
| outputs | Map<string, OutputPoint[]> | Full indicator output history. Triggers a full series rebuild (remove all → re-create) when changed. |
| pendingOutputs | React.RefObject<OutputPoint[]> | Ref-backed queue of live output points to append. Drained on each outputTick change without triggering a full rebuild. |
| outputTick | number | Incremented by the parent each time new live output points are queued. Triggers the drain effect. |
| hiddenChannels | Set<string> | Labels whose line series have visible: false. Effect applies options without rebuilding series. |
| stackedChannels | Set<string> | Node names whose series are placed in a dedicated stacked IPaneApi. Triggers a full series rebuild. |
Internal Refs
| Ref | Type | Role |
|---|---|---|
| containerRef | HTMLDivElement | Mount point passed to createChart(). Uses autoSize: true so no explicit width/height is needed. |
| chartRef | IChartApi | The lightweight-charts instance. Created in the theme effect; destroyed on theme change or unmount. |
| seriesRef | ISeriesApi<'Candlestick'> | The single candlestick series. All OHLCV data is set/updated via this reference. |
| lineSeriesRef | Map<label, ISeriesApi<'Line'>> | All active indicator line series, keyed by channel label. Rebuilt whenever outputs or stackedChannels changes. |
| markersPluginRef | createSeriesMarkers result | The markers plugin instance (from lightweight-charts). Created on first marker application; reused via setMarkers() on subsequent calls. |
| stackedPanesRef | Map<nodeName, IPaneApi<Time>> | Dedicated panes for stacked nodes. Populated in the first pass of the outputs rebuild effect. |
| markerDataRef | Map<prefix, Map<timestamp, MarkerVals>> | Accumulates pattern signal/confidence values indexed by node prefix and timestamp. Used to build arrow markers. |
useEffect Pipeline
The component uses five isolated effects that each respond to a specific prop change, minimising unnecessary work during high-frequency streaming.
deps: [theme]Chart creation / destructionCreates the chart with theme-appropriate colours (background, grid, crosshair, axis borders). Adds the CandlestickSeries with green/red candle colours. Returns a cleanup that calls chart.remove() and clears all refs.
deps: [bars]Full OHLCV loadSorts bars by timestamp, maps to CandlestickData, calls seriesRef.setData(). When bars is empty, clears all markers and calls setData([]). After a full load, calls timeScale().fitContent() to show all history.
deps: [lastBar]Incremental bar appendCalls seriesRef.update() with the single latest bar. This is the hot path during live streaming — does not re-sort or re-map the full array.
deps: [outputs, stackedChannels]Full series rebuildMarks all stacked panes for auto-removal (setPreserveEmptyPane(false)), removes all existing line series (triggering pane destruction), then recreates everything in two passes: (1) create one stacked pane per stacked node; (2) create a LineSeries for each non-pattern channel, in the appropriate pane. Pattern channels (signal/confidence/timestamp) are accumulated into markerDataRef instead. Calls applyMarkers() at the end.
deps: [hiddenChannels]Visibility toggleIterates lineSeriesRef and calls applyOptions({ visible }) for each series. Also calls applyMarkers(buildMarkers()) in case a signal channel was toggled. Intentionally excluded from the outputs effect to avoid full rebuilds on every eye-click.
deps: [outputTick]Live output drainDrains pendingOutputs.current. For pattern channels, accumulates into markerDataRef and calls applyMarkers(). For regular channels, calls lineSeriesRef.get(label)?.update(). Clears the pending array after draining.
Stacked Panes
When a node name is in stackedChannels, all its channel series are placed in a separate price scale pane created via chart.addPane(). Each pane is stored in stackedPanesRef keyed by node name.
Creation
In the first pass of the outputs rebuild effect, one addPane() call is made per stacked node (skipping pattern channels). setPreserveEmptyPane(true) is called immediately to keep the pane alive while series are being added.
Destruction
Before rebuilding, each stacked pane has setPreserveEmptyPane(false) called. When all series are removed from the pane (via chart.removeSeries()), the library automatically destroys the pane.
Pattern Arrow Markers
Candlestick pattern nodes emit three channels: signal, confidence, and timestamp. These are not rendered as line series — instead, buildMarkers() converts them into SeriesMarker objects applied to the candlestick series via the createSeriesMarkers plugin.
| signal value | position | shape | colour | text |
|---|---|---|---|---|
| 1 (bullish) | belowBar | arrowUp | #ffcc00 | ✚ {confidence}% |
| -1 (bearish) | aboveBar | arrowDown | #ff4444 | ▼ {confidence}% |
Markers are sorted by timestamp before being applied. If the signal channel label is in hiddenChannels, no markers are emitted for that node prefix.
Chart Configuration
Candlestick colours
Chart options
autoSize: true — fills the container div
crosshair.mode: 0 — normal crosshair mode
timeScale.timeVisible: true
timeScale.secondsVisible: false