From Friday, April 19th (11:00 PM CDT) through Saturday, April 20th (2:00 PM CDT), 2024, ni.com will undergo system upgrades that may result in temporary service interruption.

We appreciate your patience as we improve our online experience.

Measurement Studio for .NET Languages

cancel
Showing results for 
Search instead for 
Did you mean: 

Cursor display and events

Solved!
Go to solution

Hi all,

 

I have a cursor implemented like the example from phansen (SqlDataExample.zip), but I don't seem to have quite understood, what exactly is happening there at some points. As recommended, 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 = 3; // ID, tan_delta, U_rms

        public int ID { get; set; }
        public double U_scaled { get; set; }
        public double tan_delta { 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;
        }
    }

    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];

            for( int i = 0; i < size; ++i ) {
                DataPoint value = values[i];
                U_scaled[i] = value.U_scaled;
                tan_delta[i] = value.tan_delta;
                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)
            };
        }

        public Buffer<DataPoint> ComposeValues( IList<IBuffer> dimensionValues, Trait composeOption ) {
            throw new NotSupportedException( );
        }
    }
}

(Btw: Where does the Graph "know" from, which values of a DataPoint are plotted? As from the comment above (Dimensions rendered by graph are listed first) it seems clear, but if I maybe want to save more data in one point without displaying it in the cursor this will not work (Deleting the line "BufferPool.Default.GetBuffer(ID, Unit.None)" leads to plots where tan delta is plotted over ID but shown as U_scaled...))

 

The cursor with the XAML code:

 

<ni:Cursor Name="tandCursor" PositionChanged="tandCursor_PositionChanged">
    <ni:Cursor.ValuePresenter>
        <local:CustomValueFormatterGroup>
            <ni:ValueFormatterGroup Separator="&#x0A;">
                <ni:GeneralValueFormatter Format="U_scaled={0:0.00} [kV]" />
                <ni:GeneralValueFormatter Format="tan delta={0:0.00} [10⁻³]" />
                <ni:GeneralValueFormatter Format="ID: {0}" />
            </ni:ValueFormatterGroup>
        </local:CustomValueFormatterGroup>
    </ni:Cursor.ValuePresenter>
</ni:Cursor>

And the according implementation of the CustomValueFormatterGroup:

namespace HansDataAnalyzer
{
    [ContentProperty( "Group" )]
    public class CustomValueFormatterGroup : ValuePresenter {
        public ValueFormatterGroup Group { get; set; }

        protected override UIElement VisualizeCore<TData>( TData value, ValuePresenterArgs args, UIElement existingVisual ) {
                        
            var element = (TextBlock)Group.Visualize<TData>( value, args, existingVisual );
            // Align numeric values on the right side of the cursor's value label.
            element.TextAlignment = TextAlignment.Right;

            return element;
        }

        protected override Freezable CreateInstanceCore( ) {
            return new CustomValueFormatterGroup( );
        }
    }
}

Every time the cursor is moved, I need to do some stuff with the ID "saved" in the according datapoint (Get the whole line out of a database with this ID and plot some data from there). My problem is: I don't know how I can access the "actual" datapoint from within my main window via e.g. the PositionChanged event, nor can I access any method of my window, when the CustomValueFormatterGroup is updated (?) via the VisualizeCore call.
(I can get the ID by splitting element.Text, but I do not understand, how I could pass this on to my main programme). I hope you can understand what I mean...

 

Any advice would be welcome, thanks!

 

0 Kudos
Message 1 of 3
(2,856 Views)
Solution
Accepted by topic author mrpip

First, thanks for including all of the example code; it made it easy to understand the current state of your program! (It would also be good to point out the question that the example file came from, as the forum doesn't seem to record what post an attachment refers to.)




Every time the cursor is moved, I need to do some stuff with the ID "saved" in the according datapoint (Get the whole line out of a database with this ID and plot some data from there). My problem is: I don't know how I can access the "actual" datapoint from within my main window via e.g. the PositionChanged event, nor can I access any method of my window, when the CustomValueFormatterGroup is updated (?) via the VisualizeCore call.

The data associated with the cursor's current position is stored in the Value property. Based on your code, here is an example using the PositionChanged event to respond when the associated ID changes:

 

private int _selectedID = -1;
private int SelectedID {
    get { return _selectedID; }
    set {
        if( _selectedID != value ) {
            _selectedID = value;
            // Perform database logic here.
        }
    }
}

private void OnCursorPositionChanged( object sender, EventArgs e ) {
    var cursor = (Cursor)sender;
    IList value = cursor.Value;

    if( value.Count > 2 ) {
        object idValue = value[2];
        int id = Convert.ToInt32( idValue );
        SelectedID = id;
    }
}


Where does the Graph "know" from, which values of a DataPoint are plotted? As from the comment above (Dimensions rendered by graph are listed first) it seems clear, but if I maybe want to save more data in one point without displaying it in the cursor this will not work

Good question: there is nothing hard-coded into the graph that says "the first two dimensions are what gets rendered" (though, for a 2D graph, those dimensions are a little special). The graph processes data according to the data requirements declared by the renderer for a plot. For example, a line plot renderer only cares about X and Y position, but an intensity plot renderer cares about X, Y, and Z values (rendered as a color at an X/Y position). This is a more advanced topic (so we have not spent much time creating formal documentation), but this feature allows for deep customization of how the graph processes and renders data.

 


(Deleting the line "BufferPool.Default.GetBuffer(ID, Unit.None)" leads to plots where tan delta is plotted over ID but shown as U_scaled...)

That is unexpected; off-hand, I do not know why the graph would end up mapping the data in the way you describe. The graph does assume that a given data type will always have the same set of dimensions (i.e. a Point is defined by WPF as a struct with X and Y properties, and you can't add new properties or remove existing ones at run-time). So to record "no ID" for a particular data point, you would need to use a sentinel to distinguish it (e.g. "-1", if that is an invalid ID value).

~ Paul H
0 Kudos
Message 2 of 3
(2,835 Views)

Aww man, it's sometimes just too easy. Thanks for that!

0 Kudos
Message 3 of 3
(2,799 Views)