Search code examples
c#wpfxamllinegraphlivecharts

Using Livecharts Constant Changes example to plot points based on time, and readings from a serial port?


Okay, so basically I am wanting to make this graph to replace DateTime and plot the X axis based on a stop watch. Then I want data coming from a serial port to plot the data on the Y Axis. This should all should happen live, on the press of a button.

Here is the example I followed:

https://lvcharts.net/App/examples/v1/wpf/Constant%20Changes

The problem with it, is that it uses System Time rather than a stop watch of some sort. I am using WPF, and would love to expand the abilities of this graph.

If someone could help me with this, either via skype or something. Please let me know!

Eventually, I want to be able to take all that data from the graph and save it somewhere. So I can compare to previous points later.

Thanks!

Mainwindow.cs.xaml

<Window x:Class="TestChartProject.MainWindow"
    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:TestChartProject"
    xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
    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" Click="InjectStopOnClick">
        Inject/Stop Data
    </Button>
    <!--Here we disable tooltips and hovering to get a better performance-->
    <lvc:CartesianChart Grid.Row="1" AnimationsSpeed="0:0:1" Hoverable="True" DataTooltip="{x:Null}">
        <lvc:CartesianChart.Series>
            <lvc:LineSeries Values="{Binding ChartValues}" 
                            PointGeometry="{x:Null}" 
                            LineSmoothness="1"
                            StrokeThickness="6" 
                            Stroke="#F34336"
                            Fill="Transparent"/>
        </lvc:CartesianChart.Series>



        <lvc:CartesianChart.AxisX>


            <lvc:Axis LabelFormatter="{Binding DateTimeFormatter}" 
                      MaxValue="{Binding AxisMax}" 
                      MinValue="{Binding AxisMin}"
                      Unit="{Binding AxisUnit}">
                <lvc:Axis.Separator>
                    <lvc:Separator Step="{Binding AxisStep}" />
                </lvc:Axis.Separator>
            </lvc:Axis>




        </lvc:CartesianChart.AxisX>



    </lvc:CartesianChart>
</Grid>

MainWindow.cs

    private double _axisMax;
    private double _axisMin;
    private double _trend;

    public MainWindow()
    {
        InitializeComponent();

        //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 IChartValues instance uses this type.
        //this code ideally should only run once
        //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.Session.Ticks)   //use DateTime.Ticks as X
            .Y(model => model.Value);           //use the value property as Y

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

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

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

        //AxisStep forces the distance between each separator in the X axis
        AxisStep = TimeSpan.FromSeconds(1).Ticks;
        //AxisUnit forces lets the axis know that we are plotting seconds
        //this is not always necessary, but it can prevent wrong labeling
        AxisUnit = TimeSpan.TicksPerSecond;

        SetAxisLimits(DateTime.Now);

        //The next code simulates data changes every 300 ms

        IsReading = false;

        DataContext = this;
    }

    public ChartValues<MeasureModel> ChartValues { get; set; }
    public Func<double, string> DateTimeFormatter { get; set; }
    public double AxisStep { get; set; }
    public double AxisUnit { 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 bool IsReading { get; set; }

    private void Read()
    {
        var r = new Random();

        while (IsReading)
        {
            Thread.Sleep(1);
            var now = DateTime.Now;


            _trend = r.Next(100);

            ChartValues.Add(new MeasureModel
            {
                Session = now,
                Value = _trend
            });

            SetAxisLimits(now);

            //lets only use the last 150 values
            if (ChartValues.Count > 10) ChartValues.RemoveAt(0);
        }
    }

    private void SetAxisLimits(DateTime now)
    {
        AxisMax = now.Ticks + TimeSpan.FromSeconds(1).Ticks; // lets force the axis to be 1 second ahead
        AxisMin = now.Ticks - TimeSpan.FromSeconds(8).Ticks; // and 8 seconds behind
    }

    private void InjectStopOnClick(object sender, RoutedEventArgs e)
    {
        IsReading = !IsReading;
        if (IsReading) Task.Factory.StartNew(Read);
    }

    #region INotifyPropertyChanged implementation

    public event PropertyChangedEventHandler PropertyChanged;

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

    #endregion
}

MeasureModel

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

Solution

  • Based on what I think you need, maybe this helps you:

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
    
            //used to generate random values
            var r = new Random();
            var t = 0d;
    
            //lets instead plot elapsed milliseconds and value
            var mapper = Mappers.Xy<MeasureModel>()
                .X(x => x.ElapsedMilliseconds)
                .Y(x => x.Value);
    
            //save the mapper globally         
            Charting.For<MeasureModel>(mapper);
    
            Values = new ChartValues<MeasureModel>();
            var sw = new Stopwatch();
            sw.Start();
    
            Task.Run(() =>
            {
                while (true)
                {
                    Thread.Sleep(500);
    
                    //we add the lecture based on our StopWatch instance
                    Values.Add(new MeasureModel
                    {
                        ElapsedMilliseconds = sw.ElapsedMilliseconds,
                        Value = t += r.Next(0,10)
                    });
                }
            });
    
            DataContext = this;
        }
    
        public ChartValues<MeasureModel> Values { get; set; }
    }
    
    public class MeasureModel
    {
        public double ElapsedMilliseconds { get; set; }
        public double Value { get; set; }
    }
    

    XAML:

    <lvc:CartesianChart>
            <lvc:CartesianChart.Series>
                <lvc:LineSeries Values="{Binding Values}" />
            </lvc:CartesianChart.Series>
        </lvc:CartesianChart>