Measurement Studio for .NET Languages

cancel
Showing results for 
Search instead for 
Did you mean: 

WPF Graph how to highlight maximum/minimum points on Plot?

Hi,

 in our WPF / MVVM project we use NI Graph control. Is there a way to highlight Maximum/Minimum points on single plot?

 

best regards.

0 Kudos
Message 1 of 9
(6,555 Views)

One option would be creating point annotations for the minimum and maximum. The example below generates random data, does an in-place calculation of the extremes, and then updates two annotations to point to those values:


    XAML
    <ni:Graph x:Name="graph">
        <ni:Graph.Children>
            <ni:PointAnnotation x:Name="minimumAnnotation" Label="Minimum" LabelAlignment="BottomLeft" />
            <ni:PointAnnotation x:Name="maximumAnnotation" Label="Maximum" LabelAlignment="TopRight" />
        </ni:Graph.Children>
    </ni:Graph>

    Code

    // Generate data, and determine extremes.
    int minimumIndex = 0;
    int maximumIndex = 0;
    var random = new Random();
    double[] data = new double[100];
    for( int i = 0; i < data.Length; ++i ) {
        data[i] = random.NextDouble();

        if (data[i] < data[minimumIndex])
            minimumIndex = i;

        if (data[i] > data[maximumIndex])
            maximumIndex = i;
    }

    // Send data to graph, and position annotations.
    this.graph.DataSource = data;
    this.minimumAnnotation.HorizontalPosition = minimumIndex;
    this.maximumAnnotation.HorizontalPosition = maximumIndex;
    this.minimumAnnotation.VerticalPosition = data[minimumIndex];
    this.maximumAnnotation.VerticalPosition = data[maximumIndex];

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

Hi Paul,

thank you for quick response. Unfortunately this approach does not work.

What i have:

XAML

  <ni:Graph Grid.Column="0"  Name="GraphTrending" Grid.Row="2" DataSource="{Binding TrendingDataList}" PreferIndexData="False"
                                  PlotAreaBackground="White" Height="600" Width="Auto" 
                                  Background="White" HorizontalAlignment="Stretch"
                                  DefaultInteraction="Pan"
                                  Margin="10" VerticalAlignment="Top" SuppressScaleLayout="False"
                              >
                        <ni:Graph.Axes>
                            <ni:AxisDouble x:Name="Y" Orientation="Vertical"  >
                                <ni:AxisDouble.Label>
                                    <Label Content="{Binding VerticalAxisLable}" Style="{StaticResource lblF14BoldDB1}"/>
                                </ni:AxisDouble.Label>
                            </ni:AxisDouble>

                            <ni:AxisDateTime x:Name="X" Orientation="Horizontal">
                                <ni:AxisDateTime.MajorDivisions>
                                    <ni:RangeLabeledDivisions LabelPresenter="dd.MM.yyyy hh:mm:ss" />
                                </ni:AxisDateTime.MajorDivisions>
                                <ni:AxisDateTime.Label>
                                    <Label Content="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentControl}},
                                           Converter={StaticResource GlobalLanguageConverter}, ConverterParameter='Report.AuditTrail.Time'}" 
                                           Style="{StaticResource lblF14BoldDB1}"/>
                                </ni:AxisDateTime.Label>
                            </ni:AxisDateTime>
                        </ni:Graph.Axes>

                        <ni:Graph.Plots>
                            <ni:Plot x:Name="MaxArea" Visibility="{Binding MaxMinTrendVisibility, Converter={StaticResource VisibilityConverterHidden}}" >
                                <ni:AreaPlotRenderer Fill="{StaticResource MinMaxBrush}" FillBaseline="PositiveInfinity"/>
                            </ni:Plot>

                            <ni:Plot x:Name="Average">
                                <ni:PlotRendererGroup>
                                    <ni:LinePlotRenderer Stroke="{StaticResource AverageBrush}" StrokeThickness="2" StrokeDashCap="Square" LineStep="None"/>
                                    <ni:PointPlotRenderer StrokeLineJoin="Round"/>
                                </ni:PlotRendererGroup>
                            </ni:Plot>

                            <ni:Plot x:Name="DeviationRel" Visibility="{Binding SrelTrendVisibility, Converter={StaticResource VisibilityConverterHidden}}">
                                <ni:PlotRendererGroup>
                                    <ni:LinePlotRenderer Stroke="{StaticResource DeviationRelBrush}" StrokeThickness="2" StrokeDashCap="Square" LineStep="None"/>
                                    <ni:PointPlotRenderer StrokeLineJoin="Round"/>
                                </ni:PlotRendererGroup>
                            </ni:Plot>

                            <ni:Plot x:Name="SetPoint" Visibility="{Binding SetPointTrendVisibility, Converter={StaticResource VisibilityConverterHidden}}" >
                                <ni:PlotRendererGroup>
                                    <ni:LinePlotRenderer Stroke="{StaticResource SetPointBrush}" StrokeThickness="2" StrokeDashCap="Square" LineStep="None"/>
                                    <ni:PointPlotRenderer StrokeLineJoin="Round"/>
                                </ni:PlotRendererGroup>
                            </ni:Plot>

                            <ni:Plot x:Name="MinArea" Visibility="{Binding MaxMinTrendVisibility, Converter={StaticResource VisibilityConverterHidden}}">
                                <ni:AreaPlotRenderer Fill="{StaticResource MinMaxBrush}" />
                            </ni:Plot>

                        </ni:Graph.Plots>
                        <ni:Graph.Children >
                            <ni:PointAnnotation x:Name="Max" HorizontalPosition="{Binding MaxHorizontal}" VerticalPosition="{Binding MaxVertical}" 
                                                Visibility="{Binding TrendExtreamVisibility, Converter={StaticResource VisibilityConverterHidden}}" Label="Max" LabelAlignment="BottomLeft"/>
                        </ni:Graph.Children>
                        
                    </ni:Graph>

 ViewModel side:

this method generates data for 5 plots.

  private AnalogWaveform<double>[] GetTrendingDataItems(int trendingType)
        {
            if (SelectedReportItemsFilterBatch == null) return null;

            var entries = _trending.GetTrendingData(SelectedReportItemsFilterBatch.Value, trendingType, _pressStation.Number);

            if (entries.Count > 0)
            {
                var res = new double[5, entries.Count];
                int c = 0;

                MaxVertical = entries.Max(m => m.Average); // double value for vertical position
                MaxHorizontal = entries.FindIndex(e => e.Average.Equals(MaxVertical)); // int value for horizontal position

                foreach (TrendingDataItem entry in entries)
                {
                    res[0, c] = entry.Maximum;      // Maximum -> Plot top
                    res[1, c] = entry.Average;      // average trend
                    res[2, c] = entry.DeviationRel; // deviation trend
                    res[3, c] = entry.Setpoint;     // set value
                    res[4, c] = entry.Minimum;      // Minimum -> Plot bottom

                    c++;
                }

                var measurements = AnalogWaveform<double>.FromArray2D(res);
                for (int k = 0; k < 5; ++k)
                {
                    measurements[k].Timing =
                        WaveformTiming.CreateWithIrregularInterval(entries.Select(e => e.Timestamp).ToArray());
                }
                return  measurements;
            }
            
            return  null;
        } 

Following properies provides binding

//Graph data double/datetime 
public AnalogWaveform<double>[] TrendingDataList { get { return this._trendingCwcDataList; } set { if (this._trendingCwcDataList == value) return; this._trendingCwcDataList = value; OnPropertyChanged("TrendingDataList"); OnPropertyChanged("MaxVertical"); //just to force reading OnPropertyChanged("MaxHorizontal"); } } public int MaxHorizontal { get { return _maxHorizontal; } set { _maxHorizontal = value; OnPropertyChanged("MaxHorizontal"); } } public double MaxVertical { get { return _maxVertical; } set { _maxVertical = value; OnPropertyChanged("MaxVertical"); } }

 As a result i have following:

MaxMinAnnotationPoint.png

So, i see only label "Max" but no pointer as expected in position Y=8.0594454:X=0. Actually under debugger i can change value of horisontal position, for example set it in 1, it does not work.

NationalInstruments.Controls.Graphs Version 13.0.45.242

 

Once again, thank you for your help, may be you have other advise ?

 

best regards.

 

0 Kudos
Message 3 of 9
(6,535 Views)

From looking at your setup, I believe the issue is you are passing an integer index for the horizontal value (as in my example, that used an indexed array as the data source), but the axis is expecting a DateTime value. Converting an integer zero to a DateTime will result in a new DateTime(0), which is very small compared to DateTime.Now, and so will point to the left off screen. Instead, MaxHorizontal should be the entry.TimeStamp value for that index.

~ Paul H
0 Kudos
Message 4 of 9
(6,526 Views)

Hi Paul,

the reason why i can not see the annotation is in XAML

 PreferIndexData="False"

 if i set the property to "True" , the annotaion arrows are visible (somehow plot overlaps annotation arrows), but in this case horizontal labels lost formating.

Also i tried set horizontal position to DataTime value instead integer index, so i have the same result, wrong result : 

MaxMinPointAnnotation.png

Smiley Mad 

 

best regards

0 Kudos
Message 5 of 9
(6,522 Views)

After updating my local test application to more closely match your setup, I believe the issue is that the point annotation is overriding the binding before it has a chance to provide a value. I have created a task to fix this issue.


As a workaround, if you change the binding to be TwoWay, then the initial assignment by the point annotation will not clear the binding, giving it a chance to provide the correct value when the data is available. I used HorizontalPosition="{Binding MaxTimestamp, Mode=TwoWay}" and VerticalPosition="{Binding MaxVertical, Mode=TwoWay}", returning a DateTime value from the MaxTimestamp property, and had PreferIndexData set to False.

~ Paul H
0 Kudos
Message 6 of 9
(6,518 Views)

Hi Paul,

unfortunately this approach does not the trick. I suppose, at this place, binding does not work at all.

Only way to get it work is implement it in code behind, it breaks MVVM pattern but at least i can see annotations on plot.

 

        private void OnDataContextPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName.Equals("MaxVertical") || e.PropertyName.Equals("MaxHorizontal")) 
            {
                foreach (var child in _graph.Children)
                {
                    var annotaion = child as PointAnnotation;
                    if (annotaion != null)
                    {
                        var trendingItem = (TrendingViewItem) sender;
                        if (annotaion.Name.Equals("Max"))
                        {
                            annotaion.VerticalPosition = trendingItem.MaxVertical;
                            annotaion.HorizontalPosition = trendingItem.MaxHorizontal;
                        }
                    }
                }
            }
            else if (e.PropertyName.Equals("MinVertical") || e.PropertyName.Equals("MinHorizontal")) 
            {
                foreach (var child in _graph.Children)
                {
                    var annotaion = child as PointAnnotation;
                    if (annotaion != null)
                    {
                        var trendingItem = (TrendingViewItem) sender;
                        if (annotaion.Name.Equals("Min"))
                        {
                            annotaion.VerticalPosition = trendingItem.MinVertical;
                            annotaion.HorizontalPosition = trendingItem.MinHorizontal;
                        }
                    }
                }
            }
        }

 And once again, thank you.

 

best regards

0 Kudos
Message 7 of 9
(6,493 Views)

If it helps, I have attached the code I used to reproduce the issue, including the two-way binding.

~ Paul H
0 Kudos
Message 8 of 9
(6,487 Views)

Just wanted to let you know that the annotation binding issue was fixed in the Measurement Studio 2015 release.

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