Measurement Studio for .NET Languages

cancel
Showing results for 
Search instead for 
Did you mean: 

Changing plotted data during runtime with DataPoint structure

Solved!
Go to solution

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

0 Kudos
Message 1 of 9
(2,720 Views)

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.

~ Paul H
0 Kudos
Message 2 of 9
(2,693 Views)

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!

0 Kudos
Message 3 of 9
(2,681 Views)

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?

0 Kudos
Message 4 of 9
(2,671 Views)
Solution
Accepted by topic author mrpip
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.)

~ Paul H
0 Kudos
Message 5 of 9
(2,667 Views)

Alright, the Refresh at the end of the reordering does the trick, thank you!

0 Kudos
Message 6 of 9
(2,614 Views)

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.

0 Kudos
Message 7 of 9
(2,608 Views)

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).

~ Paul H
0 Kudos
Message 8 of 9
(2,605 Views)

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.

0 Kudos
Message 9 of 9
(2,582 Views)