Blazorise SVG Chart Streaming

Append live data and keep the chart viewport moving.

SVG chart streaming is configured with SvgChartStreamingOptions. Set Duration to a time window for rolling retention, or leave Duration as null for infinite retention.

Examples

Append values

Append values through the chart reference. A timer can call the same method in a live application.
1 series, 13 categories.Live latencyRight-to-left viewport with infinite retention00.20.40.60.81

Append a value or hover over a point.

@using System.Threading
@implements IAsyncDisposable

<Button Color="Color.Primary" Outline Clicked="@AppendValue">
    Append value
</Button>

<SvgLineChart @ref="chart"
              TItem="object"
              Data="@data"
              Options="@options"
              Streaming="@streaming"
              Hovered="@OnPointHovered">
    <SvgChartTitle Title='@("Live latency")' Subtitle='@("Right-to-left viewport with infinite retention")' />
    <SvgChartTooltip Enabled />
    <SvgChartValueAxis BeginAtZero TickCount="6" />
</SvgLineChart>

<Paragraph Margin="Margin.Is2.FromTop.Is0.FromBottom">@lastEvent</Paragraph>
@code {
    private static readonly TimeSpan StreamingInterval = TimeSpan.FromSeconds( 1 );

    private SvgLineChart<object> chart;

    private readonly Random random = new();

    private double currentLatency = 45;

    private string lastEvent = "Append a value or hover over a point.";

    private CancellationTokenSource streamingCancellationTokenSource;

    private Task streamingTask;

    private readonly SvgChartOptions options = new()
    {
        Height = 360,
        Legend = new() { Visible = false },
        XAxis = new()
        {
            GridLines = new() { Visible = true },
            Labels = new()
            {
                Step = 2,
                Offset = 30,
            },
        },
    };

    private readonly SvgChartStreamingOptions streaming = new()
    {
        Enabled = true,
        VisibleDataPoints = 12,
        Duration = null,
        Reverse = false,
        Animation = new()
        {
            Duration = StreamingInterval,
        },
        RefreshInterval = TimeSpan.FromMilliseconds( 500 ),
    };

    private readonly SvgChartData<double?> data = new()
    {
        Series =
        [
            new()
            {
                Name = "Latency",
                Color = Color.Primary,
            },
        ],
    };

    protected override Task OnAfterRenderAsync( bool firstRender )
    {
        if ( firstRender )
        {
            streamingCancellationTokenSource = new();
            streamingTask = RunStreamingAsync( streamingCancellationTokenSource.Token );
        }

        return Task.CompletedTask;
    }

    private async Task RunStreamingAsync( CancellationToken cancellationToken )
    {
        try
        {
            using PeriodicTimer timer = new( StreamingInterval );

            while ( await timer.WaitForNextTickAsync( cancellationToken ) )
            {
                await InvokeAsync( AppendValue );
            }
        }
        catch ( OperationCanceledException )
        {
        }
    }

    private async Task AppendValue()
    {
        string label = DateTime.Now.ToString( "HH:mm:ss" );
        currentLatency = Math.Clamp( currentLatency + random.Next( -16, 17 ), 25, 80 );

        if ( chart is not null )
            await chart.AppendValue( "Latency", label, currentLatency );
    }

    private Task OnPointHovered( SvgChartPointEventArgs eventArgs )
    {
        lastEvent = $"Hovered {eventArgs.SeriesName} / {eventArgs.Category}: {eventArgs.Value}";

        return Task.CompletedTask;
    }

    public async ValueTask DisposeAsync()
    {
        if ( streamingCancellationTokenSource is not null )
        {
            await streamingCancellationTokenSource.CancelAsync();
            streamingCancellationTokenSource.Dispose();
        }

        if ( streamingTask is not null )
            await streamingTask;
    }
}

Rolling window

Use Duration to retain only recent timestamped samples.
1 series, 11 categories.Rolling throughputRetains the last 20 seconds of samples00.20.40.60.81
@using System.Threading
@implements IAsyncDisposable

<Button Color="Color.Primary" Outline Clicked="@AppendValue">
    Append sample
</Button>

<SvgAreaChart @ref="chart"
              TItem="object"
              Data="@data"
              Options="@options">
    <SvgChartTitle Title='@("Rolling throughput")' Subtitle='@("Retains the last 20 seconds of samples")' />
    <SvgChartTooltip Enabled />
    <SvgChartStreaming Enabled
                       VisibleDataPoints="10"
                       Duration="@TimeSpan.FromSeconds( 20 )"
                       Animation="@streamingAnimation" />
    <SvgChartTimeAxis Format="HH:mm:ss" />
    <SvgChartValueAxis BeginAtZero TickCount="6" />
</SvgAreaChart>
@code {
    private static readonly TimeSpan StreamingInterval = TimeSpan.FromSeconds( 1 );

    private readonly Random random = new( 24 );

    private SvgAreaChart<object> chart;

    private double currentThroughput = 64;

    private CancellationTokenSource streamingCancellationTokenSource;

    private Task streamingTask;

    private readonly SvgChartOptions options = new()
    {
        Height = 360,
        Legend = new() { Visible = false },
        XAxis = new()
        {
            GridLines = new() { Visible = true },
            Labels = new() { Step = 2, Offset = 30 },
        },
    };

    private readonly SvgChartStreamingAnimationOptions streamingAnimation = new()
    {
        Duration = StreamingInterval,
    };

    private readonly SvgChartData<double?> data = new()
    {
        Series =
        [
            new()
            {
                Name = "Throughput",
                Color = Color.Success,
            },
        ],
    };

    protected override Task OnAfterRenderAsync( bool firstRender )
    {
        if ( firstRender )
        {
            streamingCancellationTokenSource = new();
            streamingTask = RunStreamingAsync( streamingCancellationTokenSource.Token );
        }

        return Task.CompletedTask;
    }

    private async Task RunStreamingAsync( CancellationToken cancellationToken )
    {
        try
        {
            using PeriodicTimer timer = new( StreamingInterval );

            while ( await timer.WaitForNextTickAsync( cancellationToken ) )
            {
                await InvokeAsync( AppendValue );
            }
        }
        catch ( OperationCanceledException )
        {
        }
    }

    private async Task AppendValue()
    {
        currentThroughput = Math.Clamp( currentThroughput + random.Next( -10, 11 ), 35, 95 );

        if ( chart is not null )
            await chart.AppendValue( "Throughput", DateTimeOffset.Now, currentThroughput );
    }

    public async ValueTask DisposeAsync()
    {
        if ( streamingCancellationTokenSource is not null )
        {
            await streamingCancellationTokenSource.CancelAsync();
            streamingCancellationTokenSource.Dispose();
        }

        if ( streamingTask is not null )
            await streamingTask;
    }
}

API

See the API reference for the parameters, events, methods, and related types available to the components covered on this page.

On this page