03-11-2015 10:44 AM
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.
03-11-2015 05:22 PM
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];
03-12-2015 03:20 AM
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:
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.
03-12-2015 10:32 AM
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.
03-12-2015 11:15 AM
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 :
best regards
03-12-2015 01:27 PM
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
.
03-13-2015 04:41 AM
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
03-13-2015 10:30 AM
If it helps, I have attached the code I used to reproduce the issue, including the two-way binding.
08-11-2015 01:19 PM
Just wanted to let you know that the annotation binding issue was fixed in the Measurement Studio 2015 release.