Search code examples
c#chart.jsblazorblazor-server-sideblazor-jsinterop

I am using chart.js in my Blazor app with JSInterop. How can I update my chart when there is a new data in the data set?


I have a Blazor server app where I have used chart.js library for my chart with JSInterop. In the line chart I am showing 20 measuring values The chart is working fine when all the 20 values are present and I open the chart. The chart draws the curve for 20 values.

But I want to use that chart as live chart. That means when every second a new data value comes into the data array, I want to update the chart. How is this possible?

Here my code:

My razor page where the chart is

@page "/wmi_performance2"
@inject TagService TagService
@using System.IO
@inject IJSRuntime JSRuntime
<Chart Id="bar1" Type="@Chart.ChartType.Bar"
   Data="@CPU_Load_array"
   
   Labels="@Measuring_points_array">
</Chart>

My Chart.razor in the Shared/Components folder

@using System.IO
@inject IJSRuntime JSRuntime
@using Microsoft.JSInterop;
@inject IJSRuntime jsInvoker

<canvas id="@Id"></canvas>
@code {
public enum ChartType    
    {
        Pie,
        Bar
    }
    [Parameter]
    public string Id { get; set; }
    [Parameter]
    public ChartType Type { get; set; }
    [Parameter]
    public string[] Data { get; set; }
    [Parameter]
    public string[] BackgroundColor { get; set; }
    [Parameter]
    public string[] Labels { get; set; }
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        // Here we create an anonymous type with all the options
        // that need to be sent to Chart.js
        
        try{

        var config = new
        {
            Type = Type.ToString().ToLower(),
            Options = new
            {
                Responsive = true,
                Scales = new
                {
                    YAxes = new[]
                {
                    new { Ticks = new {
                        BeginAtZero=true
                    } }
            }
                }
            },
            Data = new
            {
                Datasets = new[]
            {
                new { Data = Data, BackgroundColor = BackgroundColor}
        },
                Labels = Labels
            }
        };

        await JSRuntime.InvokeVoidAsync("setup", Id, config);
        
        }

        catch(Exception e)
        {
        using (StreamWriter sw = File.AppendText((Pages.CommonClass.error_path)))
        {
            sw.WriteLine("Chart.JS Error: " + e + Environment.NewLine);
        }
        }
        
        
    }
}

My chart.js under wwwroot

window.setup = (id, config) => {
var ctx = document.getElementById(id).getContext('2d');
new Chart(ctx, config);
}

Solution

  • Normally you use JSRuntime's InvokeAsnyc or InvokeVoidAsync methods as for all other JS interop calls. Here is a simple example.

    JavaScript part:

    window.setupChart = (id, config) => {
        var ctx = document.getElementById(id).getContext('2d');
        new Chart(ctx, config);
    }
    

    Blazor C# part:

    [Inject]
    public IJSRuntime JSRunTime { get; set; }
    public string id = "chartCanvas";  // ID for canvas: <canvas id="@Id"></canvas>
    
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            var config = new
            {
                Type = "line",
                Options = new { ... },
                Data = new { ... }
            }
            await JSRuntime.InvokeVoidAsync("setupChart", id, config);
        }
    }
    
    

    It works perfectly, but you can't update the chart or cannot add anything to it.

    The solution is IJSObjectReference in Blazor. This is exactly what this function is for.

    You need to change the previous code to the followings:

    JavaScript part:

    window.setupChart = (id, config) => {
        var ctx = document.getElementById(id).getContext('2d');
        return new Chart(ctx, config);
    },
    window.updateChart = (chartRef, visible) => {
        // Example: make visible/hidden the 4th dataset in the chart
        chartRef.data.datasets[3].hidden = visible;
        chartRef.update();
    }
    

    Blazor C# part:

    [Inject]
    public IJSRuntime JSRunTime { get; set; }
    private IJSObjectReference? chartInstance;
    public string id = "chartCanvas";
    
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            var config = new
            {
                Type = "line",
                Options = new { ... },
                Data = new { ... }
            }
            chartInstance = await JSRuntime.InvokeAsync<IJSObjectReference>("setupChart", id, config);
        }
    }
    public async Task UpdateChart()
    {
        await JSRuntime.InvokeVoidAsync("updateChart", chartInstance, false);
    }
    
    

    And that's it, you can update your existing chart.

    And don't forget to implement @implements IAsyncDisposable in your razor component, as well as to dispose it:

    async ValueTask IAsyncDisposable.DisposeAsync()
    {
        if (chartInstance is not null)
        {
            await chartInstance.DisposeAsync();
        }
    }