03-19-2018 04:31 AM
Hi all,
as mentioned before in this topic I use an ObservableCollection of DataPoints[] as DataSource of my Graph, which are implemented as follows:
namespace HansDataAnalyzer { [DataTypeDescriptor( typeof( DataPointDescriptor ) )] public struct DataPoint { public const int PropertyCount = 4; // ID, tan_delta, U_rms public int ID { get; set; } public double U_scaled { get; set; } public double tan_delta { get; set; }
public double I_rms { get; set; } public DataPoint(MeasurementValue value, double dividerFactor) : this() { this.ID = value.ID; this.U_scaled = value.U_rms * dividerFactor / 1000; //Anzeige in *10^(-3); this.tan_delta = value.tan_delta * 1000;
this.I_rms = value.I_rms; } } public struct DataPointDescriptor : IOpMultiDimensional<DataPoint> { private static readonly ReadOnlyCollection<Type> _dimensionDataTypes = new ReadOnlyCollection<Type>( Enumerable.Repeat( typeof( double ), DataPoint.PropertyCount ).ToArray( ) ); public ReadOnlyCollection<Type> GetDimensionDataTypes( DataPoint value, Trait decomposeOption ) { throw new NotImplementedException( ); } public DataPoint GetDefaultValue( ) { return default( DataPoint ); } public IList<IBuffer> DecomposeValues( Buffer<DataPoint> values, Trait decomposeOption ) { int size = values.Size; double[] U_scaled = new double[size]; double[] tan_delta = new double[size]; int[] ID = new int[size];
double[] I_rms = new double[size]; for( int i = 0; i < size; ++i ) { DataPoint value = values[i]; U_scaled[i] = value.U_scaled; tan_delta[i] = value.tan_delta;
I_rms[i] = value.I_rms; ID[i] = value.ID; } return new IBuffer[] { // Dimensions rendered by graph are listed first. BufferPool.Default.GetBuffer(U_scaled, Unit.None), BufferPool.Default.GetBuffer(tan_delta, Unit.None), // Other values are saved by graph, and displayed by cursor. BufferPool.Default.GetBuffer(ID, Unit.None),
BufferPool.Default.GetBuffer(I_rms, Unit.None) }; } public Buffer<DataPoint> ComposeValues( IList<IBuffer> dimensionValues, Trait composeOption ) { throw new NotSupportedException( ); } } }
As phansen recommended once, I could add more properties for the DataPoint structure. What I would like to do here:
Change the displayed data from U_scaled on x-axis and tan_delta on y-axis to e.g. U_scaled on x-axis and I_rms on y-axis. Is there a nice way to do this? Somehow change the order of returning the single data or...? As mentioned in the topic above, I still do not understand, where the graph "knows" from, which Property to actually put in the plot, when linked to a DataSource.
Of course I could just add another ni:Graph and implement another DataPoint structure but this seems a bit odd, as my DataPoint-struct already includes all the data that I need to plot (and can be modified with other data...).
Thank you already
Solved! Go to Solution.
03-19-2018 01:37 PM
Change the displayed data from U_scaled on x-axis and tan_delta on y-axis to e.g. U_scaled on x-axis and I_rms on y-axis. Is there a nice way to do this? Somehow change the order of returning the single data or...? As mentioned in the topic above, I still do not understand, where the graph "knows" from, which Property to actually put in the plot, when linked to a DataSource.
Of course I could just add another ni:Graph and implement another DataPoint structure but this seems a bit odd, as my DataPoint-struct already includes all the data that I need to plot (and can be modified with other data...).
First, the graph "knows" how to manage data from two places: the descriptor for the data type (which provides all dimensions available), and the renderer data requirements (which declares the dimensions it will display). You can see samples of these in the original example (in the DataPointDescriptor
and CustomRenderer
types, respectively).
Unfortunately, unlike renderers, the graph cursor was built with an assumption that the first two dimensions are always the most important. So while we could update the plot renderer to draw 0
and 2
from { 0: U_scaled, 1: tan_delta, 2: I_rms }
, the cursor would still snap to 0
and 1
.
So the better solution is probably to update the data source. You can use the same graph instance, and just specify a new data source. A completely new "DataPoint2" type should not be needed: all that is required is a new parameter on the existing DataPoint
type to control the order of buffers returned by the descriptor. For example, you could include an integer array, and use that to sort the buffers before they are returned from the DecomposeValues
method:
public IList<IBuffer> DecomposeValues( Buffer<DataPoint> values, Trait decomposeOption ) { // (create buffer array as before) int size = values.Size; double[] U_scaled = ... for( int i = 0; i < size; ++i ) { DataPoint value = values[i]; U_scaled[i] = ... } var buffers = new IBuffer[] { ... }; // If the new "Reorder" property is set, then sort the buffer array with the Reorder keys before return. int[] reorder = values[0].Reorder; if( reorder != null ) Array.Sort( reorder, buffers ); return buffers; }
This does require updating the data points with a reorder array when you want to change the display (e.g. new DataPoint( ... ) { Reorder = new int[] { 0, 2, 1 } }
; and possibly updating the order of cursor labels, too), but otherwise all the existing rendering logic would remain the same.
03-20-2018 07:24 AM - edited 03-20-2018 07:25 AM
Alright, thank you Paul, I will see how I can implement this (esp. the part with the cursor label reordering).
I was looking around already but am wondering why nobody else seems to have this problem. Is it really unusual to get a set of different data (containing e.g. different measurement parameters) , which should be displayed in different ways in 2D or even 3D plots, or is it only me, possibly being on the wrong track?
Thanks again for your effort!
03-20-2018 09:36 AM
Hi again Paul,
once again I'm stuck. I managed to extend the datapoint structure and also to change the order of the displayed data (by reordering it hardcoded in my DataPoint constructor), but it seems that if I change the Reorder array during runtime, either the UI does not recognize it (once again, I have a ObservableCollection<DataPoint[]> as my graphs' datasource), or the graph itself needs to update somehow. I can see at least, that the Reorder array changed.
I can also debug into it and see, that the DecomposeValues-List (?) of the DataPointDescriptor is not getting updated, if I change the Reorder-array during runtime...
Can you tell me, what I missed here?
03-20-2018 10:09 AM
I was looking around already but am wondering why nobody else seems to have this problem. Is it really unusual to get a set of different data (containing e.g. different measurement parameters) , which should be displayed in different ways in 2D or even 3D plots, or is it only me, possibly being on the wrong track?
I would not say you are on the wrong track. You seem to have a compelling use case, but it does happen to be unique — you are the first one to require this kind of dynamic data reinterpretation that I have come across, at least 🙂
I managed to extend the datapoint structure and also to change the order of the displayed data (by reordering it hardcoded in my DataPoint constructor), but it seems that if I change the Reorder array during runtime, either the UI does not recognize it (once again, I have a ObservableCollection<DataPoint[]> as my graphs' datasource), or the graph itself needs to update somehow. I can see at least, that the Reorder array changed.
Ah, sorry for the confusion. I don't want to head off on a long tangent, so suffice it to say that mutating a struct does not automatically trigger any sort of change notification. In order for a WPF control like the graph to observe the Reorder change, either the observable collection will need to raise an event (e.g. temporarily removing then adding back the array of modified values), or the graph needs to be notified directly by a call to the Refresh
method.
(What I had done locally, but failed to communicate, was the first option: I created a new data array with the Reorder property set, and replaced the existing data in the observable collection.)
04-05-2018 04:25 AM
Alright, the Refresh at the end of the reordering does the trick, thank you!
04-05-2018 09:16 AM
Okay, sorry for reposting on this, but I still find some things a bit confusing here: The Refresh() for the ni:Graph module works, if I have only one array of DataPoint in my ObservableCollection<DataPoint[]> (or more precisely: it only works with the first array of DataPoints), which is the DataSource of my ni:Graph. The second (and all other) linegraph(s) in my ni:Graph stays with the recent (not "reordered") values. I can debug into it and recognize, that all my DataPoints in all arrays of the DataSource of the ni:Graph are getting the new Reorder array changed, as you mentioned before. After this, I call the Refresh()-method of the graph and if I debug into this, I recognize that DecomposeValues is called for every item in my ObservableCollection<DataPoint[]> and only in the first call, the values[0].Reorder contains the values as set before for the whole DataSource.
I am also thinking about some kind of other solution - maybe some kind of global variable with the Reorder array? - because the way the data is plotted stays the same over all DataPoints, so there is actually no need to set this for every single DataPoint...
I hope you can understand my problem, otherwise please let me know, what else you need to know about this.
04-05-2018 11:08 AM
Unfortunately, I could not reproduce the issue in my test application: after updating the first point in all the arrays and calling Refresh
, all of the lines redrew.
That said, there is nothing wrong with changing the Reorder
helper to a static member. It does mean the value will apply globally to all graphs throughout your application (though if you only have the one graph using this data, then that should not be an issue).
04-09-2018 03:29 AM
Okay, now I know at least, what went wrong. I have an array variable, which I tried to use from different functions within the programm. The Array.Sort-method now sorts the elements within the value array appropriate to the key array, but also sorts the key array and that is why my other following graphs got this sorted array as input.