1

I am currently using liveChart to plot a real time graph of 3 values: a position, a load and a deformation. The program is based on the Doli.DoPE library (a proprietary dll)

In MainForm.cs, there is an event that is triggered everytime the sensor records a new value (every millisecond or so).

public void Initialisation()
{
   //...    
   MyEdc.Eh.OnDataHdlr += new DoPE.OnDataHdlr(OnData)
   //...
}

with

private int OnData(ref DoPE.OnData Data, object Parameter)
{
    DoPE.Data Sample = Data.Data;
    if (Data.DoPError == DoPE.ERR.NOERROR)
    {
        Int32 Time = Environment.TickCount;
        if ((Time - LastTime) >= 250 /*ms*/)
        {
            // Send the data from the ondata handler inside of a global list
            ListData.time.Add(Sample.Time);
            ListData.position.Add(Sample.Sensor[(int)DoPE.SENSOR.SENSOR_S]);
            ListData.load.Add(Sample.Sensor[(int)DoPE.SENSOR.SENSOR_F]);
            ListData.extend.Add(Sample.Sensor[(int)DoPE.SENSOR.SENSOR_E]);

            Thread ThForUpdateChart = new Thread(() =>
            {
                if (NewINstanceOfChart != null)
                { NewINstanceOfChart.UpdateValues(ListData.time.Last(), ListData.position.Last(),ListData.load.Last(), ListData.extend.Last()); }
            });
            ThForUpdateChart.Start();
            LastTime = Time;
        }
    }
    return 0;
}

The function UpdateValues is part of a second form RealTimeChart.cs called in the MainForm through a button click event:

private void btnGraph_Click(object sender, EventArgs e)
{
    var thread = new Thread(() =>
    {
        NewINstanceOfChart = new RealTimeChart(ListData);
        NewINstanceOfChart.Show();
    });
    thread.Start();
}

the form RealTimeCharts.cs is initalised this way:

public RealTimeChart(Globals ListData)
{
    InitializeComponent();

    //measures = ListData;

    ListPosition = new ChartValues<ObservablePoint>();
    for (int i = 0; i < measures.load.Count(); i++)
    {
        ListPosition.Add(new ObservablePoint
        {
            X = measures.time[i],
            Y = measures.position[i]
        });
    }
    ListLoad = new ChartValues<ObservablePoint>();
    for (int i = 0; i < measures.load.Count(); i++)
    {
        ListLoad.Add(new ObservablePoint
        {
            X = measures.time[i],
            Y = measures.load[i]
        });
    }

    ListExtend = new ChartValues<ObservablePoint>();
    for (int i = 0; i < measures.load.Count(); i++)
    {
        ListExtend.Add(new ObservablePoint
        {
            X = measures.time[i],
            Y = measures.extend[i]
        });
    }

    resultChart.Series.Add(new LineSeries
    {
        LineSmoothness = 0,
        Values = ListPosition,
        PointGeometrySize = 2,
        StrokeThickness = 4
    });


    SetXAxisLimits();

}

And the UpdateValues function is defined as followed:

        public void UpdateValues(double time, double position, double load, double extend)
        {
            measures.time.Add(time-measures.TareTime);
            measures.position.Add(position);
            measures.load.Add(load);
            measures.extend.Add(extend);

            UpdateEnabledSequencialPartToTrue();

        }



        public void UpdateEnabledSequencialPartToTrue()
        {
            if (this.InvokeRequired)
                BeginInvoke(new System.Action(() => this.InternalUpdateEnabledSequencialPartToTrue()));
            else
                InternalUpdateEnabledSequencialPartToTrue();
        }
        private void InternalUpdateEnabledSequencialPartToTrue()
        {
            try
            {
                ListPosition.Add(new ObservablePoint
                {
                    X = measures.time.Last(),
                    Y = measures.position.Last()
                });

                ListLoad.Add(new ObservablePoint
                {
                    X = measures.time.Last(),
                    Y = measures.load.Last()
                });

                ListExtend.Add(new ObservablePoint
                {
                    X = measures.time.Last(),
                    Y = measures.extend.Last()
                });

                //LineSeries plot = new LineSeries();
                SetXAxisLimits();

                // lets only use the last 14400 values (1h long recording, 14400 values at frequency of 1 record very 250ms, see OnData function MainForm
                if (measures.time.Count > 14400)
                {
                    ListPosition.RemoveAt(0);
                    ListLoad.RemoveAt(0);
                    ListExtend.RemoveAt(0);
                }
            }
            catch (NullReferenceException) { }
        }

After a minute, the programme starts to be really laggy. I tried putting the second winform (RealTimeCharts) on another thread so the MainForm does not lag (it is piloting a machine, it has to be responsive), but no success.

I would like to know if the whole thing is laggy because the code is way too bad, or if it is liveChart that reached its (free) limits. Would you advice another way to plot real time data ?

MaximeS
  • 79
  • 7

1 Answers1

2

In MainForm.cs, there is an event that is triggered everytime the sensor records a new value (every millisecond or so).

That is natturally way higher then what Winforms Drawing can take. See, drawing a GUI is expensive. If you only do it once per user-triggered event, you will never notice that. But do it from a loop - including sampling a sensor every MS - and you can quickly overlord the UI. My first Multithreading tests actually appeared to have failed on big numbers, becaus I ended up sending so many updates I plain overloaded the GUI thread. Since then I know not to go past progress bars.

You can add data to a background collection as quickly as you can sample them, but you can not draw that quickly. And honestly drawing more often then 30-60 times/second (every ~17 ms) is not really going to help anyone anyway. Usually you can not use a timer, as the Tick might happen more often then it can be processed - again, a GUI Thread with a overflowing Event Queue.

I do not have any rate limiting code for WindowsForms. But I would guess an Event that re-queues itself at the end of the EventQueue after finishing the work would work.

Christopher
  • 8,956
  • 2
  • 14
  • 31
  • Thank you for your quick response ! I entirely agree with you on the none necessity of plotting values every ms. However in the OnData function, the UpdateValues function is trigerred every 250ms, is that still too high ? As long as I don't start the graph, the programme could run for hours with no problem, yet the GUI of MainForm is being updated at the same rate (did not put the code for that interface up there, should I ?) – MaximeS Feb 12 '20 at 16:55
  • @MaximeS If it takes 251ms or longer to draw, it is indeed to high. You will have change events starting to stack up in the EventQueue. If it takes 500 ms to draw, you will get 2 stacked up Draw Opeartions every second. And it takes equally long to work them off. | And drawing speed can be non-static too. Sometimes it might take longer, as you are dealing with a Garbage Collector too. That is why I prefer rate limiting: I draw only as fast as is actually possible, but never faster then X. – Christopher Feb 12 '20 at 17:10
  • Oh, ok, I see what you mean. It may be worth for me to switch to WPF then. I know it is not that easy, but I have been struggling for way too long with this. I have read quite a few nice tutorials to build live charts from scratch using WPF. I'll test, and I'll try to use a rate limiting function to avoid the same problem. – MaximeS Feb 12 '20 at 17:32
  • 1
    WPF should be faster at the drawing. However it is designed for a unique pattern called MVVM. As long as it is only output, it should work fine. Rate llimiting can be done by not using binding and just adding explicit polling instead. I wrote a intro into MVVM that should still help you get started: https://social.msdn.microsoft.com/Forums/vstudio/en-US/b1a8bf14-4acd-4d77-9df8-bdb95b02dbe2/lets-talk-about-mvvm?forum=wpf – Christopher Feb 12 '20 at 17:42
  • I'll start with your guide then. I am not going to close the question yet though, since this solution of rewritting the code is kind of extrem. I have been wanting to try WPF for a while now, but someone with a similar problem, and a bigger code may not consider that a good option. I think it is only fair to wait for a little bit. Thank you very much for your inputs and your time though ! – MaximeS Feb 12 '20 at 17:51
  • @MaximeS Oh yes, a rewrite is massive. I do remember some other Options for more "direct" drawing. GDI+? But that is overall outside my area of expertise. – Christopher Feb 12 '20 at 18:13
  • Just wanted to thank you one more time for introducing me to the MVVM pattern. For sure I did not know what I was going into, but boy that journey is pleasant. I have learnt sooo much. From simply learning about WPF, how the xaml binds to the code, to actually keeping the MVVM pattern through the use of commands instead of events. Got me into interface development for certain aspects as well as the implementation of ValidationRules, Converters, creation of reusable and customizable code through user controls. Cheers man ! – MaximeS Jun 11 '20 at 14:00
  • @MaximeS I had the same reaaction to MVVM. It is the pattern I always wanted for GUI, but never could imagine existed. :D – Christopher Jun 14 '20 at 11:34