Skip to content

Streaming

xkqg edited this page Apr 23, 2026 · 2 revisions

Streaming & Realtime

v1.4.0 — First-class live data support. Dashboards and telemetry feeds can append data points without rebuilding the figure.


Quick Start

using MatPlotLibNet;
using MatPlotLibNet.Models.Streaming;

// 1. Build a streaming figure
StreamingLineSeries? series = null;
var sf = Plt.Create()
    .WithTitle("Live Telemetry")
    .AddSubPlot(1, 1, 1, ax => { series = ax.StreamingPlot(capacity: 5000); })
    .BuildStreaming(TimeSpan.FromMilliseconds(33)); // 30fps throttle

// 2. Subscribe to render events (Avalonia example)
// sf.RenderRequested += () => Dispatcher.UIThread.Post(InvalidateVisual);

// 3. Append data from any thread
for (int i = 0; i < 1000; i++)
{
    series!.AppendPoint(i, Math.Sin(i * 0.1));
    await Task.Delay(10);
}

sf.Dispose();

Architecture

Data source (any thread)     Render timer          UI thread
       │                          │                    │
  AppendPoint(x, y)               │                    │
  [write lock: ns]                │                    │
  _version++                      │                    │
       │                    check DataVersion          │
       │                    vs lastRendered            │
       │                          │                    │
       │                    fire RenderRequested ─────►│
       │                          │            marshal │
       │                          │                    ▼
       │                          │          ApplyAxisScaling()
       │                          │          CreateSnapshot()
       │                          │          ChartRenderer.Render()

Key types

Type Description
DoubleRingBuffer Fixed-capacity circular buffer. ReaderWriterLockSlim. Never allocates on append.
StreamingSnapshot Immutable point-in-time copy (double[] X, double[] Y, long Version).
IStreamingSeries Contract: AppendPoint, AppendPoints, Clear, Version, Count, Capacity, CreateSnapshot.
StreamingSeriesBase Abstract base with twin ring buffers, version counter, ComputeDataRange.
StreamingFigure Wraps Figure. Render timer, version tracking, ApplyAxisScaling(), RenderRequested event.
AxisScaleMode Sealed record hierarchy: Fixed, AutoScale, SlidingWindow(size), StickyRight(size).

Streaming Series Types

StreamingLineSeries

Ring-buffer-backed line chart. Default capacity 10,000.

var series = ax.StreamingPlot(capacity: 10_000, configure: s =>
{
    s.Color = Colors.Blue;
    s.LineWidth = 2.0;
    s.Label = "Sensor A";
});

series.AppendPoint(timestamp, value);

StreamingScatterSeries

Ring-buffer-backed scatter plot. Default capacity 10,000.

var series = ax.StreamingScatter(configure: s =>
{
    s.Color = Colors.Orange;
    s.MarkerSize = 4;
});

StreamingSignalSeries

Y-only storage. X computed from SampleRate + offset. Optimized for oscilloscope / audio / telemetry. Default capacity 100,000.

var signal = ax.StreamingSignal(capacity: 100_000, sampleRate: 44100.0, configure: s =>
{
    s.Color = Colors.Green;
});

signal.AppendSample(amplitude);
signal.AppendSamples(buffer); // batch append

StreamingCandlestickSeries

Four parallel ring buffers (O/H/L/C). BarAppended event for indicator auto-attach. Default capacity 5,000.

var candles = ax.StreamingCandlestick(capacity: 5000);
candles.AppendBar(open: 100, high: 110, low: 95, close: 105);
candles.AppendBar(new OhlcBar(105, 115, 100, 110));

Axis Scale Modes

Control how axes auto-scale when streaming data arrives.

sf.DefaultConfig = new StreamingAxesConfig(
    new AxisScaleMode.SlidingWindow(100.0),  // X: show last 100 units
    new AxisScaleMode.AutoScale());           // Y: fit all visible data
Mode Behavior
Fixed User-set limits, never auto-adjusted
AutoScale Fit all data every render
SlidingWindow(size) Show last N X-units, scroll as data arrives
StickyRight(size) Like SlidingWindow but only scroll when at the right edge

Streaming Indicators

11 incremental technical indicators, all O(1) per append. Auto-attach to StreamingCandlestickSeries via the BarAppended event. Each indicator owns its own StreamingLineSeries output — zero renderer changes.

var candles = ax.StreamingCandlestick(5000);
candles.WithStreamingSma(axes, 20);            // auto-appends SMA line
candles.WithStreamingBollinger(axes, 20, 2.0); // auto-appends 3 band lines
candles.WithStreamingRsi(axes, 14);            // auto-appends RSI line
Indicator Output Complexity
StreamingSma Rolling average O(1)
StreamingEma Exponential average O(1)
StreamingRsi Wilder's RSI (0–100) O(1)
StreamingBollinger 3 series: mid, upper, lower O(1)
StreamingMacd 3 series: MACD, signal, histogram O(1)
StreamingObv Cumulative volume O(1)
StreamingAtr Wilder's true range O(1)
StreamingStochastic 2 series: %K, %D O(1) amortized
StreamingWilliamsR Rolling min/max O(1) amortized
StreamingCci Mean deviation O(n/period)
StreamingVwap Cumulative price*vol / vol O(1)

Platform Integration

All 5 UI hosts consume StreamingFigure through the same pattern: subscribe to RenderRequested, marshal to UI thread, invalidate.

Host Control Marshal
Avalonia MplStreamingChartControl Dispatcher.UIThread.Post
Uno MplStreamingChartElement DispatcherQueue.TryEnqueue
MAUI MplStreamingChartView MainThread.BeginInvokeOnMainThread
Blazor MplStreamingChart InvokeAsync + StateHasChanged
ASP.NET Core StreamingChartSession IChartPublisher.PublishSvgAsync

Avalonia example

<avalonia:MplStreamingChartControl StreamingFigure="{Binding LiveChart}" />

Blazor example

<MplStreamingChart StreamingFigure="@_streamingFigure" />

Server-push (ASP.NET Core)

services.AddMatPlotLibNetSignalR();

// In a background service:
var sf = new StreamingFigure(figure);
registry.RegisterStreaming("dashboard-1", sf);
// sf.RenderRequested auto-publishes SVG to all connected clients

Rx Integration

Connect IObservable<T> sources to streaming series without System.Reactive dependency:

using MatPlotLibNet.Data;

IObservable<StreamingPoint> sensorStream = ...;  // StreamingPoint(double X, double Y)
using var subscription = series.SubscribeTo(sensorStream);

IObservable<OhlcBar> priceStream = ...;
using var ohlcSub = candles.SubscribeTo(priceStream);

SVG Diff Engine

For Blazor/web scenarios, the SvgDiffEngine computes minimal patches between renders — typically 10x smaller than full SVG:

var patch = SvgDiffEngine.Compute(previousSvg, currentSvg);
if (patch.IsFullReplace)
    SendFullSvg(currentSvg);
else
    SendPatches(patch.Patches); // only changed series groups

Performance Tuning

Scenario Buffer Throttle Memory
Dashboard (1/sec) 1,000 1000ms 16 KB
Telemetry (100/sec) 10,000 33ms 640 KB
Oscilloscope (10K/sec) 100,000 16ms 800 KB
Trading (OHLC, 10/sec) 5,000 100ms 160 KB

Tips:

  • Set MinRenderInterval to match your target FPS
  • Use StreamingSignalSeries (Y-only) for uniform sample rates — saves 50% memory vs XY
  • Ring buffer never allocates on append; CreateSnapshot() allocates once per render
  • For >100K points, consider LTTB downsampling on the snapshot (existing MaxDisplayPoints respected)

Clone this wiki locally