2

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);
}
Mdarende
  • 469
  • 3
  • 13
  • At every cycle, you add new value to existing datasets.data array and invoke the update() method of the chart instance. I see Data is an array of string but it's weird, I guess it should be an array of numbers – user2057925 Oct 28 '22 at 10:02
  • @user2057925 Ok, but the problem is there is not update() method for Chart. Normally chart.js is supporting chart update. Do I have to add any code to the Chart cunction for updating? – Mdarende Oct 28 '22 at 10:42
  • You don't have the update method as static in Chart object but you have the update method for each chart instance: https://www.chartjs.org/docs/latest/developers/api.html#update-mode. You wrote: "new Chart(ctx, config);" but if you assign to a const, like "const myChart = new Chart(ctx, config);", you have the instance of the chart and you can perform myChart.update(). You have to retrieve "myChart" instance. – user2057925 Oct 28 '22 at 11:25
  • @user2057925 Last question if you allow: I have updated my chart.js (see abow last part). I have passed to chart.js additional par. like "update_chart". If that is true then chart should be updated. Now I have to care that on each data update I call setup with arg. chart_update=true. Is this approach OK? – Mdarende Oct 28 '22 at 12:43
  • having a look to "setup", if invoked, you create new chart every time and I don't think it's good. The setup function should at least return the chart instance. Without that, you cannot update the chart. – user2057925 Oct 28 '22 at 12:52
  • @user2057925 Excuse me so much but could you explain "The setup function should at least return the chart instance" a little bit more with a short example? – Mdarende Oct 28 '22 at 14:30
  • When you are doing "new Chart(ctx, config);", you are creating new instance of chart. This instance is needed if you want to update the chart, changing the data instead of create new chart every time. Therefore I'm expecting that "setup" function will end with "return new Chart(ctx, config);" in order that the caller of setup can receive the chart instance and stores it in a variable (or constant) to sue for the other purposes – user2057925 Oct 31 '22 at 07:19

1 Answers1

2

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();
    }
}
Tomato
  • 102
  • 1
  • 11