Line-Chart gets frozen when removing objects from data source #1083

Closed
opened 2026-01-29 17:48:41 +00:00 by claunia · 2 comments
Owner

Originally created by @lausitzer on GitHub (Jan 7, 2024).

I need a rolling line chart to visualize telemetry data. I use a System.Collections.Generic.Queue for data source. New measurement objects get pushed into this queue. If the queue contains more than 10 objects after push, first objects gets removed in a FIFO manner. At last Reload() gets called for the chart.

While the queue is growing and no objects need to bee removed everything works fine. When the queue is full and the first object gets removed the chart stops moving. Value labes get removed step by step and then the chart stays frozen. Same happens when using List instead of Queue calling List's Remove method to drop the first object. The only inefficient way to work around this problem seems to be filling up the list up to the limit and then start to iterate through the list replacing each objects value by that of it successor.

Here is a example showing both methods. OnInitialized() creates a timer that calls both AddDataset Methods every second to feed the queue and the list with random datasets.

@page "/"

<div Class="rz-p-0 rz-p-md-12">
    <t3>Pushing new objects to queue while dequeuing older ones</t3>
    <RadzenChart @ref=chart1>
        <RadzenLineSeries Smooth="true" Data="@Datasets1" CategoryProperty="Ts" Title="Value 1" ValueProperty="Value1" RenderingOrder="4">
            <RadzenSeriesDataLabels Visible="true" />
        </RadzenLineSeries>
        <RadzenLineSeries Smooth="true" Data="@Datasets1" CategoryProperty="Ts" Title="Value 2" ValueProperty="Value2" RenderingOrder="3">
            <RadzenSeriesDataLabels Visible="true" />
        </RadzenLineSeries>

        <RadzenCategoryAxis Padding="20" Formatter="@FormatAsTime" />
        
        <RadzenValueAxis Min="0" Step="10">
            <RadzenGridLines Visible="true" />
            <RadzenAxisTitle Text="" />
        </RadzenValueAxis>
    </RadzenChart>
</div>

<div Class="rz-p-0 rz-p-md-12">
    <t3>Replacing objects content by copying successors content</t3>
    <RadzenChart @ref=chart2>
        <RadzenLineSeries Smooth="true" Data="@Datasets2" CategoryProperty="Ts" Title="Value 1" ValueProperty="Value1" RenderingOrder="4">
            <RadzenSeriesDataLabels Visible="true" />
        </RadzenLineSeries>
        <RadzenLineSeries Smooth="true" Data="@Datasets2" CategoryProperty="Ts" Title="Value 2" ValueProperty="Value2" RenderingOrder="3">
            <RadzenSeriesDataLabels Visible="true" />
        </RadzenLineSeries>

        <RadzenCategoryAxis Padding="20" Formatter="@FormatAsTime" />
        
        <RadzenValueAxis Min="0" Step="10">
            <RadzenGridLines Visible="true" />
            <RadzenAxisTitle Text="" />
        </RadzenValueAxis>
    </RadzenChart>
</div>

@code {
    private RadzenChart chart1 = default!;
    private RadzenChart chart2 = default!;
    private Queue<Measurement> Datasets1 = new();
    private List<Measurement> Datasets2 = new();

    const int CHARTLEN = 10;

    override protected void OnInitialized()
    {
        base.OnInitialized();


        var r = new Random();
        var timer = new System.Timers.Timer(1000);
        timer.Elapsed += (s, e) =>
        {
            var ts = DateTime.UtcNow;
            int v1 = r.Next(100);
            int v2 = r.Next(100);

            // Need to create two different instances of Measuerement to avoid interferences
            AddDataset1(new Measurement { Ts = ts, Value1 = v1, Value2 = v2 });
            AddDataset2(new Measurement { Ts = ts, Value1 = v1, Value2 = v2 });
        };
        timer.Start();
    }

    private void AddDataset1(Measurement m)
    {
        Datasets1?.Enqueue(m);

        while (Datasets1?.Count > CHARTLEN)
            Datasets1?.Dequeue();

        chart1?.Reload();
    }

    private void AddDataset2(Measurement m)
    {        
        if(Datasets2.Count < CHARTLEN)
        {
            // Fillup list to CHARTLEN Size
            Datasets2?.Add(m);
        }
        else
        {
            // Replacing content bei followers content
            for(int a = 0; a < (CHARTLEN - 1); a++)
            {
                Datasets2[a].Ts = Datasets2[a+1].Ts;
                Datasets2[a].Value1 = Datasets2[a+1].Value1;
                Datasets2[a].Value2 = Datasets2[a+1].Value2;
            }

            Datasets2[(CHARTLEN - 1)].Ts = m.Ts;
            Datasets2[(CHARTLEN - 1)].Value1 = m.Value1;
            Datasets2[(CHARTLEN - 1)].Value2 = m.Value2;
        }
        
        chart2?.Reload();
    }

    private string FormatAsTime(object o)
    {
        return (o as DateTime?)?.ToLocalTime().ToString("HH:mm:ss") ?? "??";
    }

    public class Measurement
    {
        public DateTime Ts { get; set; }
        public int Value1 { get; set; }
        public int Value2 { get; set; }
    }
}
Originally created by @lausitzer on GitHub (Jan 7, 2024). I need a rolling line chart to visualize telemetry data. I use a System.Collections.Generic.Queue for data source. New measurement objects get pushed into this queue. If the queue contains more than 10 objects after push, first objects gets removed in a FIFO manner. At last Reload() gets called for the chart. While the queue is growing and no objects need to bee removed everything works fine. When the queue is full and the first object gets removed the chart stops moving. Value labes get removed step by step and then the chart stays frozen. Same happens when using List instead of Queue calling List's Remove method to drop the first object. The only inefficient way to work around this problem seems to be filling up the list up to the limit and then start to iterate through the list replacing each objects value by that of it successor. Here is a example showing both methods. OnInitialized() creates a timer that calls both AddDataset Methods every second to feed the queue and the list with random datasets. ``` @page "/" <div Class="rz-p-0 rz-p-md-12"> <t3>Pushing new objects to queue while dequeuing older ones</t3> <RadzenChart @ref=chart1> <RadzenLineSeries Smooth="true" Data="@Datasets1" CategoryProperty="Ts" Title="Value 1" ValueProperty="Value1" RenderingOrder="4"> <RadzenSeriesDataLabels Visible="true" /> </RadzenLineSeries> <RadzenLineSeries Smooth="true" Data="@Datasets1" CategoryProperty="Ts" Title="Value 2" ValueProperty="Value2" RenderingOrder="3"> <RadzenSeriesDataLabels Visible="true" /> </RadzenLineSeries> <RadzenCategoryAxis Padding="20" Formatter="@FormatAsTime" /> <RadzenValueAxis Min="0" Step="10"> <RadzenGridLines Visible="true" /> <RadzenAxisTitle Text="" /> </RadzenValueAxis> </RadzenChart> </div> <div Class="rz-p-0 rz-p-md-12"> <t3>Replacing objects content by copying successors content</t3> <RadzenChart @ref=chart2> <RadzenLineSeries Smooth="true" Data="@Datasets2" CategoryProperty="Ts" Title="Value 1" ValueProperty="Value1" RenderingOrder="4"> <RadzenSeriesDataLabels Visible="true" /> </RadzenLineSeries> <RadzenLineSeries Smooth="true" Data="@Datasets2" CategoryProperty="Ts" Title="Value 2" ValueProperty="Value2" RenderingOrder="3"> <RadzenSeriesDataLabels Visible="true" /> </RadzenLineSeries> <RadzenCategoryAxis Padding="20" Formatter="@FormatAsTime" /> <RadzenValueAxis Min="0" Step="10"> <RadzenGridLines Visible="true" /> <RadzenAxisTitle Text="" /> </RadzenValueAxis> </RadzenChart> </div> @code { private RadzenChart chart1 = default!; private RadzenChart chart2 = default!; private Queue<Measurement> Datasets1 = new(); private List<Measurement> Datasets2 = new(); const int CHARTLEN = 10; override protected void OnInitialized() { base.OnInitialized(); var r = new Random(); var timer = new System.Timers.Timer(1000); timer.Elapsed += (s, e) => { var ts = DateTime.UtcNow; int v1 = r.Next(100); int v2 = r.Next(100); // Need to create two different instances of Measuerement to avoid interferences AddDataset1(new Measurement { Ts = ts, Value1 = v1, Value2 = v2 }); AddDataset2(new Measurement { Ts = ts, Value1 = v1, Value2 = v2 }); }; timer.Start(); } private void AddDataset1(Measurement m) { Datasets1?.Enqueue(m); while (Datasets1?.Count > CHARTLEN) Datasets1?.Dequeue(); chart1?.Reload(); } private void AddDataset2(Measurement m) { if(Datasets2.Count < CHARTLEN) { // Fillup list to CHARTLEN Size Datasets2?.Add(m); } else { // Replacing content bei followers content for(int a = 0; a < (CHARTLEN - 1); a++) { Datasets2[a].Ts = Datasets2[a+1].Ts; Datasets2[a].Value1 = Datasets2[a+1].Value1; Datasets2[a].Value2 = Datasets2[a+1].Value2; } Datasets2[(CHARTLEN - 1)].Ts = m.Ts; Datasets2[(CHARTLEN - 1)].Value1 = m.Value1; Datasets2[(CHARTLEN - 1)].Value2 = m.Value2; } chart2?.Reload(); } private string FormatAsTime(object o) { return (o as DateTime?)?.ToLocalTime().ToString("HH:mm:ss") ?? "??"; } public class Measurement { public DateTime Ts { get; set; } public int Value1 { get; set; } public int Value2 { get; set; } } } ```
Author
Owner

@akorchev commented on GitHub (Jan 8, 2024):

You should use InvokeAsync in timer related code otherwise Blazor won't refresh:

        timer.Elapsed += (s, e) =>
        {
            InvokeAsync(
                () => {
                    var ts = DateTime.UtcNow;
                    int v1 = r.Next(100);
                    int v2 = r.Next(100);

                    // Need to create two different instances of Measuerement to avoid interferences
                    AddDataset1(new Measurement { Ts = ts, Value1 = v1, Value2 = v2 });
                    AddDataset2(new Measurement { Ts = ts, Value1 = v1, Value2 = v2 });
                }
            );
        };
@akorchev commented on GitHub (Jan 8, 2024): You should use InvokeAsync in timer related code otherwise Blazor won't refresh: ``` timer.Elapsed += (s, e) => { InvokeAsync( () => { var ts = DateTime.UtcNow; int v1 = r.Next(100); int v2 = r.Next(100); // Need to create two different instances of Measuerement to avoid interferences AddDataset1(new Measurement { Ts = ts, Value1 = v1, Value2 = v2 }); AddDataset2(new Measurement { Ts = ts, Value1 = v1, Value2 = v2 }); } ); }; ```
Author
Owner

@lausitzer commented on GitHub (Jan 14, 2024):

@akorchev: Thanks for your reply. I tried your suggestion but it didn't solve my problem. I also tried making AddDataset1 asynchronous and placing AddDataset1's code in InvokeAsync but without success.

The chart updates (value labes get removed step by step) but does not move forward.

@lausitzer commented on GitHub (Jan 14, 2024): @akorchev: Thanks for your reply. I tried your suggestion but it didn't solve my problem. I also tried making AddDataset1 asynchronous and placing AddDataset1's code in InvokeAsync but without success. The chart updates (value labes get removed step by step) but does not move forward.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/radzen-blazor#1083