09-23-2012 10:03 PM
Hi,all.
I'm learning the new WPF Graph and meet a strange problem. I put 9 plots in a WPF graph and hoped to refresh the Plots[0] at every tick, but the graph just kept in its initial state. Is there any property I have to set to let the graph keep up with the datasource or any other suggestions ?
I'm using Microsoft Visual Studio 2010 with Measurement Studio.
herr is the xaml file:
<Window x:Class="NIGraphTest.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ni="http://schemas.ni.com/controls/2009/xaml/presentation" Title="MainWindow" Height="480" Width="640" > <Grid> <Grid.RowDefinitions> <RowDefinition Height="400*" /> <RowDefinition Height="41*" /> </Grid.RowDefinitions> <ni:Graph Name="m_Graph" Grid.Row="0" Grid.RowSpan="1" Interactions="Pan, Zoom, ZoomIn, ZoomOut" DataContext="{Binding}"> <ni:Graph.Axes > <ni:AxisDouble x:Name="xAxis" Orientation="Horizontal" /> <ni:AxisDouble x:Name="yAxis" Orientation="Vertical" Range="0,2048"/> </ni:Graph.Axes> <ni:Graph.Plots> <ni:Plot Label="Plots[0]" > <ni:LinePlotRenderer Stroke="Blue" /> </ni:Plot> <ni:Plot Label="Plots[1]"> <ni:LinePlotRenderer Stroke="Green"/> </ni:Plot> <ni:Plot Label="Plot 2" /> <ni:Plot Label="Plot 3" /> <ni:Plot Label="Plot 4" /> <ni:Plot Label="Plot 5" /> <ni:Plot Label="Plot 6" /> <ni:Plot Label="Plot 7" /> <ni:Plot Label="Plot 8" /> </ni:Graph.Plots> </ni:Graph> <Button Content="Button" Grid.Row="1" Name="button1" Width="75" Click="button1_Click" /> </Grid> </Window>
and here is the code behind:
namespace NIGraphTest { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { private readonly DispatcherTimer timer = new DispatcherTimer(); Random ra = new Random(); public MainWindow() { InitializeComponent(); timer.Tick += OnTimerTick; timer.Interval = TimeSpan.FromMilliseconds(1000); timer.Start(); _dataList = new ObservableCollection<Point>[9]; int k; for (int i = 0; i <9; i++) { _dataList[i] = new ObservableCollection<Point>(); for (int j = 0; j < 200; j++) { k = i * 10 + j; _dataList[i].Add(new Point(j, k)); } } m_Graph.DataSource = _dataList; } private ObservableCollection<Point>[] _dataList; public ObservableCollection<Point>[] DataList { get { return this._dataList; } set { if (this._dataList != value) { this._dataList = value; } } } void OnTimerTick(object sender, EventArgs e) { ObservableCollection<Point> data = new ObservableCollection<Point>(); int i = ra.Next(8); int k; for (int j = 0; j < 200; j++) { k = i * 10 - j; data.Add(new Point(j, k)); } _dataList[0] = data; } private void button1_Click(object sender, RoutedEventArgs e) { if (!timer.IsEnabled) timer.Start(); else timer.Stop(); } } }
Thanks!
Solved! Go to Solution.
09-24-2012 10:22 AM
The main issue is that the data given to the graph is not changing; instead, you are creating new data and not notifying the graph to re-check its source. There are a few ways to solve this:
1) You can add a call to m_Graph.Refresh()
to the end of the OnTimerTick
method.
— This will tell the graph to re-examine the array assigned to its DataSource
property, where it will find a new observable collection at element zero.
2) You can also change _dataList
itself from an array to an observable collection, so that it will automatically notify the graph when a new data collection is replaced.
— Unlike arrays, observable collections provide a data change event that graphs can listen to, so changes to the list of data collections will be displayed automatically.
3) You can use the existing collection already referenced by the graph. To do this, remove the data = new ObservableCollection<Point>()
in OnTimerTick
and add the lines var data = this._dataList[i]; data.Clear();
after retrieving the index i
.
— Similar to option 2, but allows you to edit individual plots without having to re-allocate a new data collection each time.
A few other notes:
Plots
collection is completely optional. Auto-generated plots will automatically have their Label
property initialized with a readable value like "Plot 6". Since only the first two plots in the collection have a custom renderer and the other plots are not referenced elsewhere, you could leave the other plots unspecified and the graph would behave the same.Random.Next(int)
is an exclusive upper bound, so ra.Next(8)
will return a value in the range [0,7]
. Since you have nine data collections in the _dataList
array, you would want ra.Next(9)
to cover the whole array.DispatcherTimer.IsEnabled
property is mutable, so you can change the body of button1_Click
to timer.IsEnabled = !timer.IsEnabled
to toggle the timer on and off.09-25-2012 02:05 AM
Thanks for your precious reply,Paul.
I used solution 3 and the graph can refresh now. But another problem came out. When I changed the length of _dataList[0] from 200 to 2000 and the timer.Interval from 1000ms to 100ms, I found the refresh interval is about 1000ms. How can I improve the refresh frequency?
The modified code behind file is :
public partial class MainWindow : Window { private readonly DispatcherTimer timer = new DispatcherTimer(); Random ra = new Random(); public MainWindow() { InitializeComponent(); timer.Tick += OnTimerTick; timer.Interval = TimeSpan.FromMilliseconds(1000); timer.Start(); int k; for (int i = 0; i <9; i++) { _dataList[i] = new ObservableCollection<Point>(); for (int j = 0; j < 2000; j++) { k = i * 10 + j; _dataList[i].Add(new Point(j, k)); } } m_Graph.DataSource = _dataList; } private ObservableCollection<Point>[] _dataList=new ObservableCollection<Point>[9]; public ObservableCollection<Point>[] DataList { get { return this._dataList; } set { if (this._dataList != value) { this._dataList = value; } } } void OnTimerTick(object sender, EventArgs e) { int i = ra.Next(9); var data = this._dataList[0]; data.Clear(); int k; for (int j = 0; j < 2000; j++) { k = i * 10 - j; data.Add(new Point(j, k)); } _dataList[0] = data; } private void button1_Click(object sender, RoutedEventArgs e) { timer.IsEnabled = !timer.IsEnabled; } } }
09-25-2012 10:42 AM
I believe the issue here is the use of observable collections. Since ObservableCollection<T>
raises an event for every single Add
operation, the graph is receiving 2 000 events every timer tick. To fix this, we want to reduce the number of events the graph observes.
A) We could go with option 2 from before, but make the data list an ObservableCollection<Point[]>
, creating a new Point[]
for every tick:
const int Size = 2000;
...
// (in OnTimerTick method)
int i = ra.Next(9);
var points = new Point[Size];
for( int j = 0; j < Size; ++j ) {
int k = i * 10 - j;
points[j] = new Point(j, k);
}
_dataList[i] = points;
Here the graph will see a single replace event when the array of points is assigned to the data list observable collection.
B) Another option would be to use a ChartCollection<int>
instead of an ObservableCollection<Point>
for each element in the data list:
// (in MainWindow constructor)
_dataList[i] = new ChartCollection<int>(capacity: Size);
...
// (in OnTimerTick method)
int i = ra.Next(9);
var data = this._dataList[i];
data.Clear();
var ks = new int[Size];
for( int j = 0; j < Size; ++j )
ks[j] = i * 10 - j;
data.Append(ks);
Here the graph receives only two events (one for Clear
, and one for Append
). However, this does have the additional constraint that the index values must be increasing (since your code was using the index variable j
, I used the simpler chart collection that automatically assigns the index values; you could also use ChartCollection<int,int>
to provide your own index values, but it will not allow the scatter data that is possible with Point
).
09-25-2012 09:11 PM
I tried both solutions and the first one seems to response more quickly.
Thank you Paul, my problem was solved.