Search code examples
c#wpfxamlchartswpftoolkit

WPF Charting Toolkit: How to set a fixed X-axis range while adding real time data


I'm doing an application real time data charting basing on WPF Charting Toolkit. I get the datas via serial port. The code of setting chart is below:

    <chartingToolkit:Chart  Margin="10,10,10,0" ClipToBounds="True" x:Name="chart1" Title="Chart Title">
        <chartingToolkit:LineSeries IndependentValueBinding="{Binding Value1}" DependentValueBinding="{Binding Value2}" ItemsSource="{Binding}" Background="Transparent" Cursor="No">
            <chartingToolkit:LineSeries.DataPointStyle>
                <Style TargetType="{x:Type chartingToolkit:LineDataPoint}">
                    <Setter Property="Height" Value="0"/>
                    <Setter Property="Width" Value="0" />
                    <Setter Property="Background" Value="Green"/>
                </Style>
            </chartingToolkit:LineSeries.DataPointStyle>
        </chartingToolkit:LineSeries>
    </chartingToolkit:Chart>

It works good but I still need to set maximum and minimum values of X axis. The X Values (Value1) are as number of received samples and the Y axis values (Value2) are obviously as concrete values of the received samples.

My question is about the X axis range.

Currently, I'm getting minimum as 0 and maximinum as the highest number of sample which the serial port received in current moment.

But I want to set a permanent range of X axis which I want to see.

For example I want to see on the X axis range of 500 samples.

It means that when the number of samples exceeds 500, the max should be as the highest sample number and the min should be max-500.

The main difficulty is how to set it with real time data in WPF??

Can anyone help me, please??

Updated question

I'm updating my question after @jstreet advise.

I have this method which is running in separate thread within MainWindow class, likes below.

 public partial class MainWindow : Window 
  {
 public SerialPort serialPort1 = new SerialPort();
    public string rx_str = "";
    public string rx_str_copy;
    public int a;
    public double x, y;


      ObservableCollection<ChartData> chartData;
    ChartData objChartData;
    Thread myThread;

     public MainWindow()
    {
        InitializeComponent();
        string[] port = SerialPort.GetPortNames();
        foreach (string a in port)
        {
            comboPorts.Items.Add(a);
        }
        Array.Sort(port);
        comboPorts.Text = port[0];

        objChartData = new ChartData();
        chartData.Add(objChartData);
        chart1.DataContext = chartData;
        myThread = new Thread(new ThreadStart(Run));


    }

   public void Run()
       {
        while (true)
        {
            serialPort1.Write("a");
            rx_str = serialPort1.ReadTo("b");
            rx_str_copy = rx_str;
            x = a;
            y = Double.Parse(rx_str_copy,     CultureInfo.InvariantCulture);                                               

            a++;

            Dispatcher.Invoke(new Action(delegate
            {

         chartData.Add(new ChartData() { Value1 = x,         
       Value2= y             });          
            }));



        }
    }  

This Run() method is responsible for receiving datas and adding it to the chart.

In another class I have handle of reaction on comming datas and settings properties Valeu1 and Value2:

  public class ChartData : INotifyPropertyChanged
   {
    double _Value1;
    double _Value2;


    public double Value1
    {
        get
        {
            return _Value1;
        }
        set
        {
            _Value1 = value;
            OnPropertyChanged("Value1");
        }
    }

    public double Value2
    {
        get
        {
            return _Value2;
        }
        set
        {
            _Value2 = value;
            OnPropertyChanged("Value2");
        }
    }


    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new   
     PropertyChangedEventArgs(propertyName));
        }
    }
}

How can I adapt @jstreet's solution to my behind code example??


Solution

  • Create a MinValue dependency property in your view model and bind it to your axis Minimum property. Take a look:

    enter image description here

    XAML:

    <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:WpfApp31"
            xmlns:chartingToolkit="clr-namespace:System.Windows.Controls.DataVisualization.Charting;assembly=System.Windows.Controls.DataVisualization.Toolkit" 
            x:Class="WpfApp31.MainWindow"
            mc:Ignorable="d"
            Title="MainWindow" Height="350" Width="525">
        <Window.DataContext>
            <local:MyViewModel/>
        </Window.DataContext>
        <Grid>
            <chartingToolkit:Chart  Title="My Sample">
                <chartingToolkit:Chart.Axes>
                    <chartingToolkit:LinearAxis Minimum="{Binding MinValue}" Orientation="X"></chartingToolkit:LinearAxis>
                </chartingToolkit:Chart.Axes>
                <chartingToolkit:LineSeries IndependentValueBinding="{Binding Value1}" 
                                            DependentValueBinding="{Binding Value2}" 
                                            ItemsSource="{Binding Data}">
                </chartingToolkit:LineSeries>
            </chartingToolkit:Chart>
        </Grid>
    </Window>
    

    View Model:

    public class MyViewModel : DependencyObject
    {
        public int MinValue
        {
            get { return (int)GetValue(MinValueProperty); }
            set { SetValue(MinValueProperty, value); }
        }
    
        // Using a DependencyProperty as the backing store for MinValue.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty MinValueProperty =
            DependencyProperty.Register("MinValue", typeof(int), typeof(MyViewModel), new PropertyMetadata(default(int)));
    
        public ObservableCollection<MyDataModel> Data { get; set; }
    
        private Timer serialPort;
        private Random y;
        private int x;
        private int range;
    
        public MyViewModel()
        {
            range = 10;
            Data = new ObservableCollection<MyDataModel>();
            y = new Random(DateTime.Now.Millisecond);
            serialPort = new Timer(DataReceived, null, 500, 500);
        }
        private void DataReceived(object state)
        {
            Application.Current.Dispatcher.Invoke(() => {
                Data.Add(new MyDataModel { Value1 = x, Value2 = y.Next(10, 90) });
                MinValue = x < range ? 0 : x - range;
                x++;
            });
        }
    }
    

    EDIT: For the record, I would probably not write this code quite like below. I'm doing it here just so you can move forward with it.

    enter image description here

    XAML:

    <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:WpfApp1"
        xmlns:chartingToolkit="clr-namespace:System.Windows.Controls.DataVisualization.Charting;assembly=System.Windows.Controls.DataVisualization.Toolkit" 
        x:Class="WpfApp1.MainWindow"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="10*"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
        </Grid.RowDefinitions>
    
        <chartingToolkit:Chart Grid.Row="0"  Margin="10,10,10,0" ClipToBounds="True" x:Name="chart1" Title="Chart Title">
            <chartingToolkit:Chart.Axes>
                <chartingToolkit:LinearAxis Minimum="{Binding MinValue}" Orientation="X"></chartingToolkit:LinearAxis>
            </chartingToolkit:Chart.Axes>
            <chartingToolkit:LineSeries IndependentValueBinding="{Binding Value1}" DependentValueBinding="{Binding Value2}" ItemsSource="{Binding chartData}" Background="Transparent" Cursor="No">
                <chartingToolkit:LineSeries.DataPointStyle>
                    <Style TargetType="{x:Type chartingToolkit:LineDataPoint}">
                        <Setter Property="Height" Value="0"/>
                        <Setter Property="Width" Value="0" />
                        <Setter Property="Background" Value="Green"/>
                    </Style>
                </chartingToolkit:LineSeries.DataPointStyle>
            </chartingToolkit:LineSeries>
        </chartingToolkit:Chart>
    
        <Button Grid.Row="1" x:Name="btn1" Click="btn1_Click">START</Button>
    
    </Grid>
    

    CS:

    public partial class MainWindow : Window
    {
        public double MinValue
        {
            get { return (double)GetValue(MinValueProperty); }
            set { SetValue(MinValueProperty, value); }
        }
    
        // Using a DependencyProperty as the backing store for MinValue.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty MinValueProperty =
            DependencyProperty.Register("MinValue", typeof(double), typeof(MainWindow), new PropertyMetadata(default(double)));
    
        //public SerialPort serialPort1 = new SerialPort();
        //public string rx_str = "";
        //public string rx_str_copy;
        //public int a;
        public double x, y;
    
        public ObservableCollection<ChartData> chartData { get; set; }
        ChartData objChartData;
        Thread myThread;
        Random r;
        int range = 50;
    
        public MainWindow()
        {
            InitializeComponent();
    
            r = new Random();
    
            DataContext = this;
    
            /*
            string[] port = SerialPort.GetPortNames();
    
            foreach (string a in port)
            {
                comboPorts.Items.Add(a);
            }
    
            Array.Sort(port);
            comboPorts.Text = port[0];
            */
    
            objChartData = new ChartData();
            chartData = new ObservableCollection<ChartData>();
            chartData.Add(objChartData);
            //chart1.DataContext = chartData;
            myThread = new Thread(new ThreadStart(Run));
        }
    
        private void btn1_Click(object sender, RoutedEventArgs e)
        {
            myThread.Start();
        }
    
        public void Run()
        {
            while (true)
            {
                //serialPort1.Write("a");
                //rx_str = serialPort1.ReadTo("b");
                //rx_str_copy = rx_str;
    
                //x = a;
                //y = Double.Parse(rx_str_copy, CultureInfo.InvariantCulture);
    
                //a++;
    
                Dispatcher.Invoke(new Action(delegate
                {
                    chartData.Add(new ChartData()
                    {
                        Value1 = x,
                        Value2 = r.NextDouble(),
                        //Value2 = y
                    });
                    MinValue = x < range ? 0 : x - range;
                    x++;
                }));
    
                Thread.Sleep(50);
            }
        }
    }