"Live Charts" graph refresh slowing as data is processed and added to the charts WPF c#

charley picture charley · Dec 1, 2016 · Viewed 7.4k times · Source

Im using live charts to data log incoming serial data. In the long run I will have 6 different sets of variables being sent from an Arduino. Labelled from U-Z. My charts works at relatively good speed when it is simply reading and plotting the data however as soon as the 'if' statement gets involved it slows considerably. My concern is that once I incorporate another 6 pieces of data (although it is only 2 per graph so a total of 3 graphs) the programme will be running too slow to make the data logging worth while.

I have added a text box to read all incoming data and that is updating as expected. As well as this the graph also seems to slow down as more data is added to it, even though I am removing points once they are 'off screen' essentially.

Thanks!

public partial class MainWindow : Window, INotifyPropertyChanged
{

    private double _axisMax;
    private double _axisMin;
    Stopwatch stopwatch = new Stopwatch();
    SerialPort myPort;

    public MainWindow()
    {
        InitializeComponent();

        myPort = new SerialPort("COM4", 9600, Parity.None, 8, StopBits.One);
        myPort.Open();
        //To handle live data easily, in this case we built a specialized type
        //the MeasureModel class, it only contains 2 properties
        //DateTime and Value
        //We need to configure LiveCharts to handle MeasureModel class
        //The next code configures MEasureModel  globally, this means
        //that livecharts learns to plot MeasureModel and will use this config every time
        //a ChartValues instance uses this type.
        //this code ideally should only run once, when application starts is reccomended.
        //you can configure series in many ways, learn more at http://lvcharts.net/App/examples/v1/wpf/Types%20and%20Configuration


        var mapper = Mappers.Xy<MeasureModel>()
            .X(model => model.DateTime.Ticks)   //use DateTime.Ticks as X
            .Y(model => model.Value);           //use the value property as Y

        var mapper2 = Mappers.Xy<Graph1SecondVal>()
            .X(model => model.DateTime.Ticks)   //use DateTime.Ticks as X
            .Y(model => model.Value);

        //lets save the mapper globally.
        Charting.For<MeasureModel>(mapper);
        Charting.For<Graph1SecondVal>(mapper2);


        //the values property will store our values array
        ChartValues = new ChartValues<MeasureModel>();
        ChartValuesTwo = new ChartValues<Graph1SecondVal>();

        //lets set how to display the X Labels
        DateTimeFormatter = value => new DateTime((long)value).ToString("hh:mm:ss");

        AxisStep = TimeSpan.FromSeconds(1).Ticks;
        SetAxisLimits(DateTime.Now);

        //The next code simulates data changes every 300 ms
        Timer = new DispatcherTimer
        {
            Interval = TimeSpan.FromMilliseconds(10)
        };
        Timer.Tick += TimerOnTick;
        IsDataInjectionRunning = false;
        R = new Random();
        DataContext = this;
    }
    public ChartValues<Graph1SecondVal> ChartValuesTwo { get; set; }
    public ChartValues<MeasureModel> ChartValues { get; set; }
    public Func<double, string> DateTimeFormatter { get; set; }

    public double AxisStep { get; set; }

    public double AxisMax
    {
        get { return _axisMax; }
        set
        {
            _axisMax = value;
            OnPropertyChanged("AxisMax");
        }
    }
    public double AxisMin
    {
        get { return _axisMin; }
        set
        {
            _axisMin = value;
            OnPropertyChanged("AxisMin");
        }
    }

    public DispatcherTimer Timer { get; set; }
    public bool IsDataInjectionRunning { get; set; }
    public Random R { get; set; }

    private void RunDataOnClick(object sender, RoutedEventArgs e)
    {
        if (IsDataInjectionRunning)
        {
            stopwatch.Stop();
            Timer.Stop();
            IsDataInjectionRunning = false;
        }
        else
        {
            stopwatch.Start();
            Timer.Start();


            IsDataInjectionRunning = true;
        }
    }

    private void TimerOnTick(object sender, EventArgs eventArgs) // Class is referencing from here!
    {
        var now = DateTime.Now;
        ProcessData();
        SetAxisLimits(DateTime.Now);
        //lets only use the last 30 values
        if (ChartValues.Count > 50) ChartValues.RemoveAt(0);
    }

    private void SetAxisLimits(DateTime now)
    {
        AxisMax = now.Ticks + TimeSpan.FromSeconds(1).Ticks; // lets force the axis to be 100ms ahead
        AxisMin = now.Ticks - TimeSpan.FromSeconds(8).Ticks; //we only care about the last 8 seconds
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName = null)
    {
        if (PropertyChanged != null) // if subrscribed to event
            PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    void ProcessData()
    {
        int NumberInt = 0;
        string IncomingSerial = myPort.ReadLine(); // Read incomming serial data
        string StrIncomingSerial = IncomingSerial.ToString(); // convert this data to workable string

        if (StrIncomingSerial.Contains("Z"))
        {
                string Number = myPort.ReadLine(); // Read Serialport
                double Num; // Create variable "Num"
                bool isNum = double.TryParse(Number, out Num); // Is the incomming serial data a number?
                if (isNum) // If it is a number...
                {
                    NumberInt = Convert.ToInt16(Number); // convert string to int
                    textBox1.Text = NumberInt.ToString() + "," + textBox1.Text;

            }

            myPort.DiscardInBuffer();
            ChartValues.Add(new MeasureModel
            {
                DateTime = DateTime.Now,
                Value = NumberInt

            });
        }

        if (StrIncomingSerial.Contains("Y"))
        {
                string Number = myPort.ReadLine(); // Read Serialport
                double Num; // Create variable "Num"
                bool isNum = double.TryParse(Number, out Num); // Is the incomming serial data a number?
                if (isNum) // If it is a number...
                {
                    NumberInt = Convert.ToInt16(Number); // convert string to int
                    textBox1.Text = NumberInt.ToString() + "," + textBox1.Text;
                }

            myPort.DiscardInBuffer();
            ChartValuesTwo.Add(new Graph1SecondVal
            {
                DateTime = DateTime.Now,
                Value = NumberInt
            });
        }

        SetAxisLimits(DateTime.Now);

    }

    private void ReadSerial_Click(object sender, RoutedEventArgs e)
    {
        for (int i = 0; i < 10; i++)
        {
            string Number = myPort.ReadLine(); // Read Serialport
            textBox1.Text = Number.ToString() + "," + textBox1.Text;
        }

        myPort.DiscardInBuffer();

    }
}

<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:graph_test_6"
    xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
    xmlns:geared="clr-namespace:LiveCharts.Geared;assembly=LiveCharts.Geared"
    xmlns:chart="http://mindfusion.eu/charting/wpf" x:Class="graph_test_6.MainWindow"
    mc:Ignorable="d"
    Title="MainWindow" Height="350" Width="525">
    <Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"></RowDefinition>
        <RowDefinition Height="*"></RowDefinition>
    </Grid.RowDefinitions>
    <Button Grid.Row="0" Height="30" Click="RunDataOnClick">
        Inject/Stop Data
    </Button>
    <lvc:CartesianChart Grid.Row="1" AnimationsSpeed="0:0:0" Hoverable="False">
        <lvc:CartesianChart.Series>
            <lvc:LineSeries Values="{Binding ChartValues}" PointGeometrySize="5" StrokeThickness="1" />
            <lvc:LineSeries Values="{Binding ChartValues2}" PointGeometrySize="5" StrokeThickness="1" />
        </lvc:CartesianChart.Series>
        <lvc:CartesianChart.AxisX>
            <lvc:Axis LabelFormatter="{Binding DateTimeFormatter}" 
                      MaxValue="{Binding AxisMax}" 
                      MinValue="{Binding AxisMin}"
                      DisableAnimations="True">
                <lvc:Axis.Separator>
                    <lvc:Separator Step="{Binding AxisStep}"></lvc:Separator>
                </lvc:Axis.Separator>
            </lvc:Axis>
        </lvc:CartesianChart.AxisX>
    </lvc:CartesianChart>
    <TextBox x:Name="textBox" HorizontalAlignment="Left" Height="23" Margin="323,-71,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"/>
    <TextBox x:Name="textBox1" HorizontalAlignment="Left" Height="189" Margin="43,299,0,-199" Grid.Row="1" TextWrapping="Wrap" Text="0" VerticalAlignment="Top" Width="203"/>
    <Button x:Name="ReadSerial" Content="Button" HorizontalAlignment="Left" Height="54" Margin="43,10,0,0" Grid.Row="1" VerticalAlignment="Top" Width="75" Click="ReadSerial_Click"/>
</Grid>

    public class MeasureModel
{
    public DateTime DateTime { get; set; }
    public double Value { get; set; }
}



    public class Graph1SecondVal
{
    public DateTime DateTime { get; set; }
    public double Value { get; set; }
}

Answer

Pulsar79 picture Pulsar79 · Jun 28, 2017

Read this:

https://lvcharts.net/App/examples/v1/wpf/Performance%20Tips

If you use these attributes, you can get more speed:

<lvc:CartesianChart Hoverable="False" DataTooltip="{x:Null}" />

The attibute 'DataTooltip' influences very much.