05-11-2015 02:44 PM
My Graph is working beautifly when I use:
chartCollection.Append(measurement.GHz, measurement.dB);
But when I try to use this call in the same method, I get nothing on the graph:
double[] data = new double[256]; .... graph.DataSource = data;
I don't know if this is a hint but I declare chartCollection on the main thread and then pass it on to a different thread to gather data and plot it in real time.
I could have lived with chartCollection.Append but in this case I gather a lot of data instanteneusly and I want to assign it to Graph at once.
By the way, it seems like you need about 350ms delay between each chart.Append not to cause exception... but in my case I have to delay in any case (to get data) so it's not an issue.
Thank you!
Solved! Go to Solution.
05-12-2015 10:53 AM
First, the ChartCollection
class is not thread-safe: changes made outside of the UI thread are not guaranteed to produce correct results. Instead, updates should be queued to the UI dispatcher.
Similarly, DataSource
is a WPF dependency property, which means it can only be accessed from the UI thread. If you try to assign DataSource
on another thread, WPF will throw an InvalidOperationException
.
Threading issues aside, if you assign a double[]
to a graph, the graph should display a single plot using the values in the array. The visibility of those values will depend on the data (i.e. NaN
values will display as gaps; constant values may be confused with grid lines), the configuration of the axes (i.e. if your data goes from 0 to 100 and the axis range is fixed at 1000 to 2000, all the data will be off screen), the visibility of the plot, the renderer configuration, etc.
05-12-2015 11:06 AM
Thanks for the quick reply.
Do you have a very simple exaple on what's the best way to do this?
I definitely need to gather my measurements on a different thread because I have Thread.Sleep calls in it (for instruments to settle, etc). I guess I don't have to plot in real time if I can't mix the main UI thread and secondary thread to collect data... but I do need to plot it when I am done collectiing data.
Are you saying that I can/must stash away an array I populate on a secondary thread and then do Graph.DataSource = array after I get back to the main UI thread? Is this the only/best way to do this?
Thanks
05-12-2015 11:36 AM
I am not very clear on how you are mixing the chart/array updates. Here is an example that queues chart appends through the dispatcher (using a task and helper class as a quick way to simulate background work). You can use any delegate with the dispatcher, so you could assign DataSource
in a delegate instead of use the append method on a chart collection.
public partial class MainWindow : Window {
private readonly ChartCollection<int, double> chart = new ChartCollection<int, double>( );
public MainWindow( ) {
InitializeComponent( );
this.graph.DataSource = chart;
var worker = new Worker( this.Dispatcher, chart.Append );
Task.Factory.StartNew( worker.DoWork );
}
private class Worker {
private readonly Dispatcher dispatcher;
private readonly Action<int, double> append;
public Worker( Dispatcher dispatcher, Action<int, double> append ) {
this.dispatcher = dispatcher;
this.append = append;
}
public void DoWork( ) {
for( int i = 0; i <= 360; ++i ) {
double value = Math.Sin( i * Math.PI / 180 );
this.dispatcher.BeginInvoke( this.append, i, value );
Thread.Sleep( 100 );
}
}
}
}
05-12-2015 02:22 PM
My threading structure is very different so I have to decide what's the best approach...
This is what I have:
public void RunTest()
{
ChartCollection<double, double> chartCollection = new ChartCollection<double, double>(dataSize);
graph.Data.Add(chartCollection);
Action actionRunRfBaseFuncTest = delegate { BackgroundWorker worker = new BackgroundWorker(); worker.DoWork += delegate { GetMeasurements(chartCollection); }; worker.RunWorkerCompleted += delegate { }; worker.RunWorkerAsync(); }; MainWindowDispatcher.BeginInvoke(DispatcherPriority.Background, actionRunRfBaseFuncTest);
}
public void GetMeasurements(ChartCollection<double, double> chartCollection)
{
double GHz;
double dB;
foreach (int f = 0; f < 200; f++)
{
PNA.SetFrequency(f);
Thread.Sleep(350);
PNA.GetPower(ref double GHz, ref double dB);
chartCollection.Append(GHz, dB);
}
}
Does this look like a really bad design? I'd really appreciate your feedback. (I left out all unrelated code)
Thank you
05-12-2015 02:46 PM
Your overall design looks fine. Your GetMeasurements
method is the equivalent of the DoWork
method in my example, so making similar changes (using a dispatcher and append action instead of chartCollection
) should marshal updates to the UI thread correctly. For example:
public void GetMeasurements(Dispatcher dispatcher, Action<double, double> append)
{
double GHz;
double dB;
for (int f = 0; f < 200; f++)
{
PNA.SetFrequency(f);
Thread.Sleep(350);
PNA.GetPower(ref GHz, ref dB);
dispatcher.BeginInvoke(append, GHz, dB);
}
}
05-12-2015 05:37 PM
Nice!! This works beautifully and now I don't even need any sleep in order not to crash UI thread.
So this is a perfect example on how to use chartCollection.Append but now I have a follow up question... this would help me hugely.
I also have a function where I need to plot data at once after I collected it. I could loop through my array and use chartCollection.Append but I'm sure there's a better way... and I'm sure it's graph.DataSource = array
I'm just not sure how to "marshal" it in this case. Could you please provide this kind of example?
Thank you for all your help!
05-13-2015 09:34 AM
You just need to use a different delegate. The measurement method would look like:
public void GetMeasurements(Dispatcher dispatcher, Action<object> plot) {
...
dispatcher.BeginInvoke(plot, data);
...
}
:and you could call it with a lambda delegate:
GetMeasurements(Dispatcher, data => graph.DataSource = data)
05-13-2015 01:49 PM
Thank you!
And I'm pretty sure it's my last question on this topic.
If I have like 16 ChartCollections to append in the same method on a different thread, what's the best way to marshal it? At the moment I marshal them like you showed (basically I have 16 arguments into this method) and it works nicely. I was just wondering if there's a "smarter" way of doing it.
This thread has been extremely helpful!
05-13-2015 02:07 PM
Marshaling 16 separate Append
calls will work correctly. If you see a performance issue where updates start to lag, you could change it to work more like the second approach (marshaling one call with an array of updates, and doing chart[i].Append( time[i], value[i] )
in the delegate). The graph will behave the same for either approach.