Files
2026-04-17 14:55:32 -04:00

1930 lines
84 KiB
C#

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
using C1.WPF.C1Chart;
using DTS.Common;
using DTS.Common.Base;
using DTS.Common.Classes.Viewer.Reports;
using DTS.Common.Enums;
using DTS.Common.Enums.Sensors;
using DTS.Common.Enums.Viewer;
using DTS.Common.Events;
using DTS.Common.Interface;
using DTS.Common.Strings;
using DTS.Common.Utilities;
using DTS.Common.Utilities.Logging;
using DTS.Common.Utils;
using DTS.Viewer.Graph.Resources;
using DTS.Common.Enums.DASFactory;
using Prism.Events;
using Prism.Regions;
using Unity;
using DTS.Common.Interactivity;
using Prism.Commands;
// ReSharper disable InconsistentNaming
// ReSharper disable NotAccessedField.Local
// ReSharper disable AutoPropertyCanBeMadeGetOnly.Local
// ReSharper disable CheckNamespace
// ReSharper disable CompareOfFloatsByEqualityOperator
// ReSharper disable RedundantAssignment
// ReSharper disable UnusedAutoPropertyAccessor.Local
// ReSharper disable RedundantDefaultMemberInitializer
namespace DTS.Viewer.Graph
{
public class TestDataSeriesViewModel : BaseViewModel<ITestDataSeriesViewModel>, ITestDataSeriesViewModel
{
public ITestDataSeriesView View { get; set; }
private IBaseViewModel Parent { get; set; }
public new ITestDataSeries Model { get; set; }
private IEventAggregator _eventAggregator { get; set; }
private IUnityContainer _unityContainer { get; set; }
public InteractionRequest<Notification> NotificationRequest { get; private set; }
public new InteractionRequest<Confirmation> ConfirmationRequest { get; private set; }
private IChartOptionsModel ChartViewOptions { get; set; }
/// <summary>
/// Creates a new instance of the GraphViewModel.
/// </summary>
/// <param name="view">The GraphView interface.</param>
/// <param name="regionManager">The logical placeholder defined within the application's UI (in the shell or within views) into which views are displayed.</param>
/// <param name="eventAggregator">The EventAggregator which allows different components to publish/subscribe to events without being coupled to each other.</param>
/// <param name="unityContainer">The unityContainer.</param>
public TestDataSeriesViewModel(ITestDataSeriesView view, IRegionManager regionManager, IEventAggregator eventAggregator, IUnityContainer unityContainer)
: base(regionManager, eventAggregator, unityContainer)
{
View = view;
View.DataContext = this;
NotificationRequest = new InteractionRequest<Notification>();
ConfirmationRequest = new InteractionRequest<Confirmation>();
_eventAggregator = eventAggregator;
_unityContainer = unityContainer;
}
#region Methods
public override void Initialize()
{
}
public override void Initialize(object parameter)
{
Subscribe();
Parent = (IBaseViewModel)parameter;
GraphDataSeries.CollectionChanged += GraphDataSeries_CollectionChanged;
}
public override void Initialize(object parameter, object model)
{
Initialize(parameter);
ChartType = model.ToString();
}
private bool IsPSD = false;
private string ChartType = string.Empty;
internal void SubscribePSD()
{
IsPSD = true;
_eventAggregator.GetEvent<PSDReportSettingsChangedEvent>().Subscribe(OnPSDReportSettingsChanged);
_eventAggregator.GetEvent<SaveReportToCSVRequestedEvent>().Subscribe(OnSaveReportToCSVRequested);
_eventAggregator.GetEvent<SaveReportToPDFRequestedEvent>().Subscribe(OnSaveReportToPDFRequested);
_eventAggregator.GetEvent<ShowT0CursorEvent>().Unsubscribe(OnShowT0Cursor);
OnShowT0Cursor(false);
}
public override void Activated()
{
var fp = new FilterParameterArgs
{
Param = string.Empty,
Requester = this
};
_eventAggregator.GetEvent<FilterParameterChangedEvent>().Publish(fp);
}
#endregion
private bool _subscribed = false;
private void Subscribe()
{
if (_subscribed) return;
_subscribed = true;
_eventAggregator.GetEvent<ChartOptionsChangedEvent>().Subscribe(OnChartOptionsChanged, ThreadOption.BackgroundThread);
_eventAggregator.GetEvent<ChannelsModificationNotification>().Subscribe(OnModifiedChannelsChanged);
_eventAggregator.GetEvent<ChannelsModificationLineFitNotification>().Subscribe(OnLineFit);
_eventAggregator.GetEvent<GraphSelectedChannelsNotification>().Subscribe(OnSelectedChannelsChanged, ThreadOption.BackgroundThread);
_eventAggregator.GetEvent<ResetZoomChangedEvent>().Subscribe(OnResetZoom);
_eventAggregator.GetEvent<GraphClearNotification>().Subscribe(OnGraphClearChanged);
_eventAggregator.GetEvent<ShowT0CursorEvent>().Subscribe(OnShowT0Cursor);
_eventAggregator.GetEvent<TestModificationChangedEvent>().Subscribe(OnTestModificationChanged);
_eventAggregator.GetEvent<ChannelCodesViewChangedEvent>().Subscribe(OnChannelCodesViewChanged);
}
private readonly object saveLock = new object();
private void OnSaveReportToPDFRequested(SaveReportToPDFRequestedEventArgs args)
{
if (null == _view) return;
lock (saveLock)
{
// 35532 display save path after successful write
var output = _view.SaveReportToPDF(args.Directory) ? string.Format(StringResources.SaveReportPDFSuccess, args.Directory) : StringResources.SaveReportPDFError;
_eventAggregator.GetEvent<PageErrorEvent>().Publish(new PageErrorArg(new string[] { output }, null));
}
}
private void OnSaveReportToCSVRequested(SaveReportToCSVRequestedEventArgs args)
{
if (null == _view) return;
lock (saveLock)
{
// 35532 display save path after successful write
var output = _view.SaveReportToCSV(args.Directory) ? string.Format(StringResources.SaveReportCSVSuccess, args.Directory) : StringResources.SaveReportCSVError;
_eventAggregator.GetEvent<PageErrorEvent>().Publish(new PageErrorArg(new string[] { output }, null));
}
}
private void OnChannelCodesViewChanged(IsoViewMode obj)
{
ViewMode = obj;
}
private static int FindNearestT0Index(double[] array, double t0)
{
var idx = Array.IndexOf(array, t0);
if (idx >= 0) { return idx; }
var end = array.Length - 1;
//finds closes x axis spot that is less than t0 but
//the next index is greater, this handles when t0 is decimated
for (var i = 0; i < end; i++)
{
if (array[i] < t0 && array[i + 1] > t0)
{
return i;
}
}
return -1;
}
private void OnTestModificationChanged(ITestModificationModel model)
{
if (null == model) return;
if (_sc.Any())
{
if (!(_sc[0] is XYDataSeries xy)) { return; }
if ( null == xy.XValuesSource) { return; }
try
{
_t0sampleMS = model.T0;
var array = xy.XValuesSource.Cast<double>().ToArray();
var idx = FindNearestT0Index(array, _t0sampleMS);
if ( idx >= 0)
{
SetAndDrawMarker(idx);
return;
}
}
catch( Exception ex)
{
APILogger.Log(ex);
}
}
}
/// <summary>
/// Sets marker index and draws marker
/// </summary>
private void SetAndDrawMarker(int index)
{
_markerIndex = index;
DrawCurrentMarker();
}
private void OnShowT0Cursor(bool show)
{
if (show)
{
CurrentMarker = Markers.moveT0;
InitCurrentMarker();
}
else
{
CurrentMarker = Markers.NONE;
RemoveCurrentMarker();
}
}
#region Zoom Change Events
/// <summary>
/// Reset UI Zoom
/// </summary>
/// <param name="all">Reset all or reset X</param>
private void OnResetZoom(bool all)
{
if (all) ResetZoom();
else ResetT();
}
#endregion Zoom Change Events
#region Chart Options Changed Event
private TestDataSeriesView _view;
public void OnGraphClearChanged(GraphClearNotificationArg arg)
{
try
{
if (((GraphViewModel)Parent).Parent != arg?.ParentVM) return;
GraphDataSeries = new ObservableCollection<ITestDataSeries>();
TestChannelList = new ObservableCollection<ITestChannel>();
if (ChartViewOptions != null)
{
ChartViewOptions.CanPublishChanges = false;
ChartViewOptions.ReadData = true;
ChartViewOptions.CanPublishChanges = true;
}
Dispatcher.CurrentDispatcher.InvokeAsync(() =>
{
_view = (TestDataSeriesView)((IGraphViewModel)Parent)?.DataSeriesView;
if (_view == null) return;
_view.MainChart.MouseRightButtonDown += new MouseButtonEventHandler(c1Chart_MouseRightButtonDown);
_view.MainChart.BeginUpdate();
_sc = _view.MainChart.Data.Children;
_sc.Clear();
_view.MainChart.EndUpdate();
});
}
catch (Exception ex)
{
APILogger.LogException(ex);
}
}
/// <summary>
/// Chart Options Changed Event
/// </summary>
/// <param name="args">IChartOptionsModel</param>
public void OnChartOptionsChanged(ChartOptionsChangedEventArg arg)
{
try
{
if (((GraphViewModel)Parent).Parent != arg?.ParentVM || arg?.ChartType != ChartType) return;
ChartViewOptions = arg?.Model;
if (ChartViewOptions == null) { return; }
if (TestChannelList == null) { TestChannelList = new ObservableCollection<ITestChannel>(); }
//FB 37997 Created a local list copy to prevent "Collection was modified" exception
var testChannelList = TestChannelList.ToList();
if (!testChannelList.Any()) { ChartViewOptions.ReadData = false; return; }
if (ChartViewOptions.ReadData && (psdSettings?.ReadData ?? true))
{
ReadData(testChannelList, null, !testChannelList.Exists(ch => ch.Bridge != IEPE_BRIDGE));
ChartViewOptions.ReadData = false;
}
else
{
_eventAggregator?.GetEvent<BusyIndicatorChangeNotification>()?.Publish(true);
ApplyChartOptions(!testChannelList.Exists(ch => ch.Bridge != IEPE_BRIDGE));
_eventAggregator?.GetEvent<BusyIndicatorChangeNotification>()?.Publish(false);
}
}
catch (Exception ex)
{
APILogger.LogException(ex);
}
}
#endregion Chart Options Changed Event
#region PSD Settings Changed Event
private IPSDReportSettingsModel psdSettings { get; set; }
private void OnPSDReportSettingsChanged(PSDReportSettingsChangedEventArg arg)
{
try
{
psdSettings = arg?.Model;
if (TestChannelList == null) TestChannelList = new ObservableCollection<ITestChannel>();
//FB 37997 Created a local list copy to prevent "Collection was modified" exception
var testChannelList = TestChannelList.ToList();
if (psdSettings == null) { return; }
if (!testChannelList.Any()) { psdSettings.ReadData = false; return; }
if (!psdSettings.ReadData) { return; }
_eventAggregator?.GetEvent<BusyIndicatorChangeNotification>().Publish(true);
GraphDataSeries.Clear();
ReadData(testChannelList, null, !testChannelList.Exists(ch => ch.Bridge != IEPE_BRIDGE));
psdSettings.ReadData = false;
_eventAggregator?.GetEvent<BusyIndicatorChangeNotification>().Publish(false);
}
catch (Exception ex)
{
APILogger.LogException(ex);
}
}
#endregion
#region Graph Selection Changed event
private readonly object lockObj = new object();
private const string IEPE_BRIDGE = "IEPE";
/// <summary>
/// Read one selected channel or channels for one selected graph
/// </summary>
/// <param name="channels">one or more channels</param>
private void OnSelectedChannelsChanged(GraphSelectedChannelsNotificationArg arg)
{
try
{
if (((GraphViewModel)Parent).Parent != arg?.ParentVM) return;
var channels = arg?.SelectedChannels;
if (TestChannelList == null) TestChannelList = new ObservableCollection<ITestChannel>();
var existingAllIEPE = !TestChannelList.Any(ch => ch.Bridge != IEPE_BRIDGE);
var listRemove = TestChannelList.Where(tchl => !channels.Exists(chl => chl.Group == tchl.Group
&& chl.TestId == tchl.TestId
&& chl.ChannelId == tchl.ChannelId
&& chl.BinaryFileName == tchl.BinaryFileName
&& chl.ChannelDescriptionString == tchl.ChannelDescriptionString)).ToList();
var listAdd = channels.Where(channel => !TestChannelList.Any(chl => chl.Group == channel.Group
&& chl.TestId == channel.TestId
&& chl.ChannelId == channel.ChannelId
&& chl.BinaryFileName == channel.BinaryFileName
&& chl.ChannelDescriptionString == channel.ChannelDescriptionString
)).ToList();
lock (lockObj)
{
/* Remove unselected or unlocked channel(s) */
if (listRemove.Any())
{
foreach (var l in listRemove)
{
TestChannelList.Remove(l);
var item = GraphDataSeries.FirstOrDefault(x => x.TestGroup == l.Group && x.TestId == l.TestId && x.ChannelId == l.ChannelId);
if (item == null) continue;
GraphDataSeries.Remove(item);
}
if (listRemove.Count != listAdd.Count)
{
DisplayGraphs(GraphDataSeries);
}
UpdateChartOverlayProperties();
}
var newChannelsAreAllIEPE = !(listAdd.Exists(ch => ch.Bridge != IEPE_BRIDGE) || TestChannelList.Any(ch => ch.Bridge != IEPE_BRIDGE));
if (TestChannelList.Any() && newChannelsAreAllIEPE != existingAllIEPE)
{
//uh oh, we have to remove all the existing selected channels since the ALL IEPE status has changed
var items = TestChannelList.ToArray();
foreach (var l in items)
{
TestChannelList.Remove(l);
var item = GraphDataSeries.FirstOrDefault(x => x.TestGroup == l.Group && x.TestId == l.TestId && x.ChannelId == l.ChannelId);
if (item == null) continue;
GraphDataSeries.Remove(item);
listAdd.Add(l);
}
}
/* Add new channel(s) */
if (listAdd.Any())
{
TestChannelList.AddRange(listAdd.ToArray());
ReadData(listAdd, null, newChannelsAreAllIEPE);
}
UpdateChartOverlayProperties();
}
MarkerVisibilty = channels.Count(x => x.IsLocked && x.IsSelected) == 1 && channels.Count(x => x.IsLocked) == 1 ? Visibility.Visible : Visibility.Collapsed;
if (psdSettings != null && listRemove.Any() && !listAdd.Any())
{
// We only removed items from the graph, so publish the new list
// Added items are published in the read path
PublishGRMSValues(GraphDataSeries.ToList());
}
}
catch (Exception ex)
{
APILogger.LogException(ex);
}
}
private void OnModifiedChannelsChanged(List<ITestChannel> channels)
{
try
{
MarkerVisibilty = channels.Count == 1 ? Visibility.Visible : Visibility.Collapsed;
ReadData(channels);
}
catch (Exception ex)
{
APILogger.LogException(ex);
}
}
public string TestSetupName
{
get
{
if (GraphDataSeries.Count > 1) return string.Empty;
var firstOrDefault = GraphDataSeries.FirstOrDefault();
if (firstOrDefault != null) return firstOrDefault.TestSetupName;
return string.Empty;
}
}
public string TestId
{
get
{
if (GraphDataSeries.Count > 1) return string.Empty;
var firstOrDefault = GraphDataSeries.FirstOrDefault();
if (firstOrDefault != null) return firstOrDefault.TestId;
return string.Empty;
}
}
public string HardwareChannel
{
get
{
if (GraphDataSeries.Count > 1) return string.Empty;
var firstOrDefault = GraphDataSeries.FirstOrDefault();
if (firstOrDefault != null) return firstOrDefault.HardwareChannel;
return string.Empty;
}
}
public string Bridge
{
get
{
if (GraphDataSeries.Count > 1) return string.Empty;
var firstOrDefault = GraphDataSeries.FirstOrDefault();
if (firstOrDefault != null) return firstOrDefault.Bridge;
return string.Empty;
}
}
public string GroupName
{
get
{
if (GraphDataSeries.Count > 1) return string.Empty;
var firstOrDefault = GraphDataSeries.FirstOrDefault();
if (firstOrDefault != null) return firstOrDefault.GroupName;
return string.Empty;
}
}
public string SWAAF
{
get
{
if (GraphDataSeries.Count > 1) return string.Empty;
var firstOrDefault = GraphDataSeries.FirstOrDefault();
if (firstOrDefault != null) return firstOrDefault.SWAAF;
return string.Empty;
}
}
public string HWAAF
{
get
{
if (GraphDataSeries.Count > 1) return string.Empty;
var firstOrDefault = GraphDataSeries.FirstOrDefault();
if (firstOrDefault != null)
{
if (string.IsNullOrEmpty(firstOrDefault.Bridge) || firstOrDefault.Bridge == SensorConstants.BridgeType.DigitalInput.ToString())
{
return Strings.NotApplicable;
}
//29424 TSR AIR hardware CFC is misleading
//44307 Hardware Filter (Hz) displays "---" for Voltage Input channels
if (IsTSRAIR(firstOrDefault))
{
return Strings.Table_NA;
}
else
{
return firstOrDefault.HWAAF;
}
}
return string.Empty;
}
}
public static bool IsTSRAIR(ITestDataSeries firstOrDefault)
{
return firstOrDefault.SensorSN == SensorConstants.TEST_SPECIFIC_ANALOG_SERIAL &&
(firstOrDefault.HardwareChannel.Contains(DFConstantsAndEnums.LOWG_SERIAL_APPEND) ||
firstOrDefault.HardwareChannel.Contains(DFConstantsAndEnums.HIGHG_SERIAL_APPEND) ||
firstOrDefault.HardwareChannel.Contains(DFConstantsAndEnums.ARS_SERIAL_APPEND) ||
firstOrDefault.HardwareChannel.Contains(DFConstantsAndEnums.ATMOSPHERIC_SERIAL_APPEND));
}
public string SampleRate
{
get
{
if (GraphDataSeries.Count > 1) return string.Empty;
var firstOrDefault = GraphDataSeries.FirstOrDefault();
if (firstOrDefault != null) return firstOrDefault.SampleRate;
return string.Empty;
}
}
public string ISOCode
{
get
{
if (GraphDataSeries.Count > 1) return string.Empty;
var firstOrDefault = GraphDataSeries.FirstOrDefault();
if (firstOrDefault != null) return firstOrDefault.ISOCode;
return string.Empty;
}
}
public string ISOChannelName
{
get
{
if (GraphDataSeries.Count > 1) return string.Empty;
var firstOrDefault = GraphDataSeries.FirstOrDefault();
if (firstOrDefault != null)
{
if (!string.IsNullOrEmpty(firstOrDefault.ISOChannelName))
{
return firstOrDefault.ISOChannelName;
}
else if (!string.IsNullOrEmpty(firstOrDefault.UserChannelName))
{
//FB 18668 return UserChannelName if exists
return firstOrDefault.UserChannelName;
}
else if (!string.IsNullOrEmpty(firstOrDefault.ChannelName))
{
//FB 18668 return ChannelName2
return firstOrDefault.ChannelName;
}
else
{
return Strings.Table_NA;
}
}
return string.Empty;
}
}
public string UserCode
{
get
{
if (GraphDataSeries.Count > 1) return string.Empty;
var firstOrDefault = GraphDataSeries.FirstOrDefault();
if (firstOrDefault != null)
{
if (!string.IsNullOrEmpty(firstOrDefault.UserCode))
{
return firstOrDefault.UserCode;
}
else
{
return Strings.Table_NA;
}
}
return string.Empty;
}
}
public string UserChannelName
{
get
{
if (GraphDataSeries.Count > 1) return string.Empty;
var firstOrDefault = GraphDataSeries.FirstOrDefault();
if (firstOrDefault != null)
{
if (!string.IsNullOrEmpty(firstOrDefault.UserChannelName))
{
return firstOrDefault.UserChannelName;
}
else if (!string.IsNullOrEmpty(firstOrDefault.ChannelName))
{
//FB 18668 return ChannelName2
return firstOrDefault.ChannelName;
}
else
{
return Strings.Table_NA;
}
}
return string.Empty;
}
}
public string ChannelName
{
get
{
if (GraphDataSeries.Count > 1) return string.Empty;
var firstOrDefault = GraphDataSeries.FirstOrDefault();
if (firstOrDefault != null) return firstOrDefault.ChannelName;
return string.Empty;
}
}
public string Description
{
get
{
if (GraphDataSeries.Count > 1) return string.Empty;
var firstOrDefault = GraphDataSeries.FirstOrDefault();
if (firstOrDefault != null)
{
if (!string.IsNullOrEmpty(firstOrDefault.Description))
{
return firstOrDefault.Description;
}
else
{
return Strings.Table_NA;
}
}
return string.Empty;
}
}
public string SensorSN
{
get
{
if (GraphDataSeries.Count > 1) return string.Empty;
var firstOrDefault = GraphDataSeries.FirstOrDefault();
if (firstOrDefault != null) return firstOrDefault.SensorSNDisplay;
return string.Empty;
}
}
public string Excitation
{
get
{
if (GraphDataSeries.Count > 1) return string.Empty;
var firstOrDefault = GraphDataSeries.FirstOrDefault();
if (firstOrDefault != null) return firstOrDefault.Excitation;
return string.Empty;
}
}
public string Polarity
{
get
{
if (GraphDataSeries.Count > 1) return string.Empty;
var firstOrDefault = GraphDataSeries.FirstOrDefault();
if (firstOrDefault != null) return firstOrDefault.Polarity;
return string.Empty;
}
}
public string MinY
{
get
{
if (GraphDataSeries.Count > 1) return string.Empty;
var firstOrDefault = GraphDataSeries.FirstOrDefault();
if (firstOrDefault != null) return firstOrDefault.MinY;
return string.Empty;
}
}
public string MaxY
{
get
{
if (GraphDataSeries.Count > 1) return string.Empty;
var firstOrDefault = GraphDataSeries.FirstOrDefault();
if (firstOrDefault != null) return firstOrDefault.MaxY;
return string.Empty;
}
}
public string AvgY
{
get
{
if (GraphDataSeries.Count > 1) return string.Empty;
var firstOrDefault = GraphDataSeries.FirstOrDefault();
if (firstOrDefault != null) return firstOrDefault.AvgY;
return string.Empty;
}
}
public string StdDevY
{
get
{
if (GraphDataSeries.Count > 1) return string.Empty;
var firstOrDefault = GraphDataSeries.FirstOrDefault();
if (firstOrDefault != null) return firstOrDefault.StdDevY;
return string.Empty;
}
}
public string CursorValue
{
get
{
if (GraphDataSeries.Count > 1) { return string.Empty; }
var firstOrDefault = GraphDataSeries.FirstOrDefault();
if (firstOrDefault == null) { return string.Empty; }
if ( firstOrDefault.Xvalue == null || firstOrDefault.Xvalue.Length == 0) { return string.Empty; }
if (_markerIndex < 0) { return string.Empty; }
return $"({firstOrDefault.Xvalue[_markerIndex]},{firstOrDefault.Yvalue[_markerIndex]:0.000})";
}
}
public string T0EUValue
{
get
{
if (GraphDataSeries.Count > 1) { return string.Empty; }
var firstOrDefault = GraphDataSeries.FirstOrDefault();
if (firstOrDefault == null) { return string.Empty; }
return firstOrDefault.T0EUValue; // 14529 - Can't view ROI data if T0 not included in ROI period
}
}
/// <summary>
/// holds the frequency in hz of the frequency of highest magnitude in signal
/// 6402 Implement ability to switch to FFT live in the Review
/// </summary>
public double PeakFrequency
{
get
{
if (GraphDataSeries.Count > 1) { return double.NaN; }
var firstOrDefault = GraphDataSeries.FirstOrDefault();
if (firstOrDefault == null) { return double.NaN; }
return firstOrDefault.PeakFrequency; // 14529 - Can't view ROI data if T0 not included in ROI period
}
}
/// <summary>
/// holds the peak magnitude (dB) of frequencies in the signal data
/// 6402 Implement ability to switch to FFT live in the Review
/// </summary>
public double PeakMagnitude
{
get
{
if (GraphDataSeries.Count > 1) { return double.NaN; }
var firstOrDefault = GraphDataSeries.FirstOrDefault();
if (firstOrDefault == null) { return double.NaN; }
return firstOrDefault.PeakMagnitude; // 14529 - Can't view ROI data if T0 not included in ROI period
}
}
public bool HIC
{
get
{
if (GraphDataSeries.Count > 1) { return false; }
var firstOrDefault = GraphDataSeries.FirstOrDefault();
if (firstOrDefault == null) { return false; }
return firstOrDefault.HIC;
}
}
/// <summary>
/// holds whether the series displayed are FFT of signal data
/// 6402 Implement ability to switch to FFT live in the Review
/// </summary>
public bool FFT
{
get
{
if (GraphDataSeries.Count > 1) { return false; }
var firstOrDefault = GraphDataSeries.FirstOrDefault();
if (firstOrDefault == null) { return false; }
return firstOrDefault.FFT;
}
}
public string HICValue
{
get
{
if (GraphDataSeries.Count > 1) { return string.Empty; }
var firstOrDefault = GraphDataSeries.FirstOrDefault();
if (firstOrDefault == null) { return string.Empty; }
return firstOrDefault.HICValue;
}
}
public string T1Time
{
get
{
if (GraphDataSeries.Count > 1) { return string.Empty; }
var firstOrDefault = GraphDataSeries.FirstOrDefault();
if (firstOrDefault == null) { return string.Empty; }
return firstOrDefault.T1Time;
}
}
public string T2Time
{
get
{
if (GraphDataSeries.Count > 1) { return string.Empty; }
var firstOrDefault = GraphDataSeries.FirstOrDefault();
if (firstOrDefault == null) { return string.Empty; }
return firstOrDefault.T2Time;
}
}
public string RecordingMode
{
get
{
if (GraphDataSeries.Count > 1) return string.Empty;
var firstOrDefault = GraphDataSeries.FirstOrDefault();
if (firstOrDefault != null) return firstOrDefault.RecordingMode;
return string.Empty;
}
}
public Visibility OverlayVisibility => (GraphDataSeries.Count > 1) || ((GraphViewModel)Parent).Parent is IPSDReportMainViewModel ? Visibility.Collapsed : Visibility.Visible;
private void UpdateChartOverlayProperties()
{
OnPropertyChanged("OverlayVisibility");
OnPropertyChanged("TestSetupName");
OnPropertyChanged("TestId");
OnPropertyChanged("GroupName");
OnPropertyChanged("HardwareChannel");
OnPropertyChanged("SWAAF");
OnPropertyChanged("HWAAF");
OnPropertyChanged("SampleRate");
OnPropertyChanged("ISOCode");
OnPropertyChanged("ISOChannelName");
OnPropertyChanged("UserCode");
OnPropertyChanged("UserChannelName");
OnPropertyChanged("ChannelName");
OnPropertyChanged("Description");
OnPropertyChanged("SensorSN");
OnPropertyChanged("Excitation");
OnPropertyChanged("MinY");
OnPropertyChanged("MaxY");
OnPropertyChanged("AvgY");
OnPropertyChanged("StdDevY");
OnPropertyChanged("RecordingMode");
OnPropertyChanged("T0EUValue");
OnPropertyChanged("CursorValue");
OnPropertyChanged("Polarity");
OnPropertyChanged("PolarityVisibility");
OnPropertyChanged("HIC");
OnPropertyChanged("FFT");
OnPropertyChanged("PeakFrequency");
OnPropertyChanged("PeakMagnitude");
OnPropertyChanged("HICValue");
OnPropertyChanged("T1Time");
OnPropertyChanged("T2Time");
}
/// <summary>
/// handles line fit events, which just draws a straight line between two points
/// </summary>
/// <param name="args"></param>
private void OnLineFit(LineFitArgs args)
{
try
{
if (args?.Channel == null) { return; }
ReadData(new List<ITestChannel> { args.Channel }, args);
}
catch (Exception ex)
{
APILogger.LogException(ex);
}
}
#endregion Graph Selection Changed event
#region Read Channel Data
private bool _reading = false;
/// <summary>
/// Read channel data
/// </summary>
/// <param name="channels"></param>
/// <param name="lineFitArgs">option line fit parameters, default null. if present will perform linefit on the given channel</param>
private void ReadData(List<ITestChannel> channels, LineFitArgs lineFitArgs = null, bool bVolts = false)
{
if (channels == null || channels.Count == 0 || _reading) return;
_eventAggregator.GetEvent<GraphChannelsReadCompletedNotification>().Publish(new GraphChannelsReadCompletedNotificationArgs() { IsReadCompleted = false, GraphVM = (GraphViewModel)Parent });
_reading = true;
var dataSeries = new TestDataSeriesModel() { _eventAggregator = _eventAggregator, Parent = (IGraphViewModel)Parent }.GetTestData(channels.Where(ch => string.IsNullOrEmpty(ch.ErrorMessage)).ToList(), ChartViewOptions, bVolts, psdSettings);
#region Dispatcher
Application.Current.Dispatcher.BeginInvoke(new System.Action(() =>
{
try
{
// 26951 make sure we have at least 1 data series and make sure all values are plot-able
if (dataSeries.Any() && dataSeries.TrueForAll(ds => !Array.Exists(ds.Yvalue, y => double.IsNaN(y))))
{
if (null != lineFitArgs) { dataSeries = ApplyLineFit(dataSeries, lineFitArgs); }
/* Reloading all channels*/
if (channels.Count == TestChannelList.Count) { GraphDataSeries = new ObservableCollection<ITestDataSeries>(); }
GraphDataSeries.AddRange(dataSeries);
ChartViewOptions.CanPublishChanges = false;
if (null != psdSettings) { ApplyPSDOptions(); PublishGRMSValues(GraphDataSeries.ToList()); }
if (!ChartViewOptions.LockedT)
{
ChartViewOptions.MaxFixedT = GraphDataSeries.Where(d => d != null).Max(x => x.Xvalue.Max());
ChartViewOptions.MinFixedT = GraphDataSeries.Where(d => d != null).Min(x => x.Xvalue.Min());
}
ChartViewOptions.CanPublishChanges = true;
DisplayGraphs(GraphDataSeries);
ApplyChartOptions(bVolts);
}
else if (dataSeries.Exists(ds => Array.Exists(ds.Yvalue, y => double.IsNaN(y))))
{
// 26951 we have an un-plot-able data series. most likely a bad CFC value. check if filter frequency < 1/2 of sample rate, warn the user
var warned = false;
switch (ChartViewOptions.Filter)
{
case FilterOptionEnum.Custom:
var badCustom = dataSeries.Where(ds => ChartViewOptions.SelectedFilter.FClass != FilterClassType.Unfiltered && double.Parse(ds.SampleRate) / 2 < ChartViewOptions.SelectedFilter.Frequency)
.Select(ds => $"{ds.ChannelName}, sample rate: {ds.SampleRate}Hz").ToList();
if (badCustom.Any())
{
// the filter causing problems is the one chosen in chart options
_eventAggregator.GetEvent<PageErrorEvent>().Publish(new PageErrorArg(new[] { string.Format(StringResources.BadDataFromCustomFilter, ChartViewOptions.SelectedFilter.Frequency.ToString(), string.Join(Environment.NewLine, badCustom.ToArray())) }, null));
warned = true;
}
break;
case FilterOptionEnum.TestSetupDefault:
var filters = Enum.GetValues(typeof(ChannelFilter)).Cast<ChannelFilter>().ToList();
var badTSD = dataSeries.Where(ds => Convert.ToInt32(filters.Find(cf => cf.GetEnumDescription() == ds.SWAAF)) > 0 && int.Parse(ds.SampleRate) / 2 < Convert.ToInt32(filters.FirstOrDefault(cf => cf.GetEnumDescription() == ds.SWAAF)))
.Select(ds => $"{ds.ChannelName}, sample rate: {ds.SampleRate}Hz, filter: {Convert.ToInt32(filters.FirstOrDefault(cf => cf.GetEnumDescription() == ds.SWAAF))}Hz").ToList();
if (badTSD.Any())
{
// the call is coming from inside the house
_eventAggregator.GetEvent<PageErrorEvent>().Publish(new PageErrorArg(new[] { string.Format(StringResources.BadDataFromTestSetupDefaultFilter, string.Join(Environment.NewLine, badTSD.ToArray())) }, null));
warned = true;
}
break;
case FilterOptionEnum.Unfiltered:
default:
// no filter, not really sure how they got here
break;
}
if (!warned)
{
// either we have bad unfiltered data or the filter is already less than half of sample rate
var badChannels = dataSeries.Where(ds => Array.Exists(ds.Yvalue, y => double.IsNaN(y))).Select(ds => ds.ChannelName).ToArray();
_eventAggregator.GetEvent<PageErrorEvent>().Publish(new PageErrorArg(new[] { string.Format(StringResources.BadDataUnfilteredUnknown, string.Join(Environment.NewLine, badChannels)) }, null));
}
GraphDataSeries.Clear();
DisplayGraphs(GraphDataSeries);
}
}
catch (Exception ex)
{
_eventAggregator.GetEvent<PageErrorEvent>().Publish(new PageErrorArg(new[] { ex.Message }, null));
}
finally
{
UpdateChartOverlayProperties();
_eventAggregator.GetEvent<GraphChannelsReadCompletedNotification>().Publish(new GraphChannelsReadCompletedNotificationArgs() { IsReadCompleted = true, GraphVM = (GraphViewModel)Parent });
_reading = false;
}
}));
#endregion Dispatcher
}
/// <summary>
/// Assuming the data series passed contains
/// </summary>
/// <param name="_dataSeries"></param>
/// <returns></returns>
private void PublishGRMSValues(List<ITestDataSeries> dataSeries)
{
_eventAggregator.GetEvent<PSDReportGRMSValuesUpdatedEvent>().Publish(new PSDReportGRMSValuesUpdatedEventArg()
{
ParentVM = ((GraphViewModel)Parent).Parent,
Values = dataSeries.Select(d => new ChannelGRMSSummary() { ChannelName = d.ChannelName, SampleRate = int.Parse(d.SampleRate), GRMS = d.GRMS }).ToArray()
});
}
/// <summary>
/// Apply Test modification
/// </summary>
/// <param name="_dataSeries"></param>
/// <param name="lineFitArgs"></param>
/// <returns></returns>
private List<ITestDataSeries> ApplyLineFit(List<ITestDataSeries> _dataSeries, LineFitArgs lineFitArgs)
{
foreach (var ds in _dataSeries)
{
if (ds.ChannelId != lineFitArgs.Channel.ChannelId) continue;
if (lineFitArgs.EndIndex < (ulong)ds.Yvalue.Length)
{
//first value in the new line
var startValue = ds.Yvalue[(int)lineFitArgs.StartIndex];
//last value in the new line
var endValue = ds.Yvalue[(int)lineFitArgs.EndIndex];
//the delta between each point from start to end
var delta = (endValue - startValue) / (lineFitArgs.EndIndex - lineFitArgs.StartIndex);
var increment = 0;
//finally, linefit between start and end
for (var idx = (int)lineFitArgs.StartIndex + 1; idx < (int)lineFitArgs.EndIndex; idx++)
{
ds.Yvalue[idx] = ++increment * delta + startValue;
}
}
break;
}
return _dataSeries;
}
/// <summary>
/// Apply Report View Options
/// </summary>
private void ApplyPSDOptions()
{
ChartViewOptions.CanPublishChanges = false;
ChartViewOptions.CanPublishChanges = true;
}
#endregion
#region Properties
private Visibility _pointLabelVisibilty = Visibility.Visible;
public Visibility PointLabelVisibilty { get => _pointLabelVisibilty; set { _pointLabelVisibilty = value; OnPropertyChanged("PointLabelVisibilty"); } }
private Visibility _markerVisibilty = Visibility.Visible;
public Visibility MarkerVisibilty { get => _markerVisibilty; set { _markerVisibilty = value; OnPropertyChanged("MarkerVisibilty"); } }
public Visibility ISOVisibility => (ViewMode == IsoViewMode.ISOAndUserCode || ViewMode == IsoViewMode.ISOOnly) ? Visibility.Visible : Visibility.Collapsed;
public Visibility UserVisibility => (ViewMode == IsoViewMode.UserCodeOnly || ViewMode == IsoViewMode.ISOAndUserCode) ? Visibility.Visible : Visibility.Collapsed;
public Visibility ChannelNameOnlyVisibility => ViewMode == IsoViewMode.ChannelNameOnly ? Visibility.Visible : Visibility.Collapsed;
private Common.Enums.IsoViewMode _viewMode = IsoViewMode.ISOAndUserCode;
public Common.Enums.IsoViewMode ViewMode
{
get => _viewMode;
set
{
_viewMode = value;
OnPropertyChanged("ViewMode");
OnPropertyChanged("ISOVisibility");
OnPropertyChanged("UserVisibility");
OnPropertyChanged("ChannelNameOnlyVisibility");
}
}
public Visibility PolarityVisibility => string.IsNullOrEmpty(Polarity) ? Visibility.Collapsed : Visibility.Visible;
private ObservableCollection<ITestChannel> _testChannelList;
public ObservableCollection<ITestChannel> TestChannelList { get => _testChannelList; set { _testChannelList = value; OnPropertyChanged("TestChannelList"); } }
public ObservableCollection<ITestDataSeries> _graphDataSeries = new ObservableCollection<ITestDataSeries>();
public ObservableCollection<ITestDataSeries> GraphDataSeries { get => _graphDataSeries; set { _graphDataSeries = value; OnPropertyChanged("GraphDataSeries"); } }
#region Collection Changed Events
public void GraphDataSeries_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.OldItems != null) { foreach (INotifyPropertyChanged item in e.OldItems) { item.PropertyChanged -= GraphDataSeries_PropertyChanged; } }
if (e.NewItems != null) { foreach (INotifyPropertyChanged item in e.NewItems) { item.PropertyChanged += GraphDataSeries_PropertyChanged; } }
}
private void GraphDataSeries_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "TestSummaryList") { }
}
#endregion Collection Changed Events
#region Graph
private string _currentCursorValues = string.Empty;
public string CurrentCursorValues { get => _currentCursorValues; set { _currentCursorValues = value; ChartViewOptions.CurrentCursorValues = _currentCursorValues; OnPropertyChanged("CurrentCursorValues"); } }
private string _titleY = string.Empty;
public string TitleY { get => _titleY; set { _titleY = value; OnPropertyChanged("TitleY"); } }
private string _titleX = string.Empty;
public string TitleX { get => _titleX; set { _titleX = value; OnPropertyChanged("TitleX"); } }
public double _currentYMin = 0D;
public double CurrentYMin { get => _currentYMin; set { _currentYMin = value; if (CanPublishChanges) _eventAggregator.GetEvent<ChartAxisChangedEvent>().Publish(new ChartAxisChangedEventArg() { ParentVM = ((GraphViewModel)Parent).Parent, Axis = "Y", MinValue = _currentYMin, MaxValue = _currentYMax }); OnPropertyChanged("CurrentYMin"); } }
private double _currentYMax = 0D;
public double CurrentYMax { get => _currentYMax; set { _currentYMax = value; if (CanPublishChanges) _eventAggregator.GetEvent<ChartAxisChangedEvent>().Publish(new ChartAxisChangedEventArg() { ParentVM = ((GraphViewModel)Parent).Parent, Axis = "Y", MinValue = _currentYMin, MaxValue = _currentYMax }); OnPropertyChanged("CurrentYMax"); } }
public double _currentXMin = 0D;
public double CurrentXMin { get => _currentXMin; set { _currentXMin = value; if (CanPublishChanges) _eventAggregator.GetEvent<ChartAxisChangedEvent>().Publish(new ChartAxisChangedEventArg() { ParentVM = ((GraphViewModel)Parent).Parent, Axis = "X", MinValue = _currentXMin, MaxValue = _currentXMax }); OnPropertyChanged("CurrentXMin"); } }
private double _currentXMax = 0D;
public double CurrentXMax { get => _currentXMax; set { _currentXMax = value; if (CanPublishChanges) _eventAggregator.GetEvent<ChartAxisChangedEvent>().Publish(new ChartAxisChangedEventArg() { ParentVM = ((GraphViewModel)Parent).Parent, Axis = "X", MinValue = _currentXMin, MaxValue = _currentXMax }); OnPropertyChanged("CurrentXMax"); } }
private bool _canPublishChanges = true;
public bool CanPublishChanges { get => _canPublishChanges; set { _canPublishChanges = value; OnPropertyChanged("CanPublishChanges"); } }
#endregion Graph
private bool _isBusy = false;
public new bool IsBusy { get => _isBusy; set { _isBusy = value; OnPropertyChanged("IsBusy"); } }
public new bool IsDirty { get; } = false;
private Cursor _chartCursor = Cursors.Arrow;
public Cursor ChartCursor { get => _chartCursor; set { _chartCursor = value; OnPropertyChanged("ChartCursor"); } }
///<summary>
///Occurs when a property value changes.
///</summary>
public new event PropertyChangedEventHandler PropertyChanged;
private new void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); }
#endregion Properties
#region UI functions
public void MoveCursor(object sender, KeyEventArgs e) { }
/// <summary>
/// Apply Chart Options
/// </summary>
private void ApplyChartOptions(bool bVolts)
{
if (GraphDataSeries.Count == 0) return;
if (!Application.Current.Dispatcher.CheckAccess())
{
Application.Current.Dispatcher.BeginInvoke(new System.Action(() => { ApplyChartOptions(bVolts); }));
return;
}
ChartViewOptions.DisplayingVolts = bVolts;
_view.MainChart.View.AxisX.Max = ChartViewOptions.MaxFixedT;
_view.MainChart.View.AxisX.Min = ChartViewOptions.MinFixedT;
if (ChartViewOptions.LockedT) { _view.MainChart.View.AxisX.Scale = 1; ScaleChart(); }
else { ChartViewOptions.CanPublishChanges = false; ScaleChart(); ChartViewOptions.CanPublishChanges = true; }
SetAxisTitle(bVolts);
UpdateScrollbars();
MoveCurrentMarker(0, true);
_view.MainChart.UpdateLayout();
}
DataSeriesCollection _sc = new DataSeriesCollection();
/// <summary>
/// Display Graphs UI
/// </summary>
/// <param name="graphs">Charts to display</param>
private void DisplayGraphs(ObservableCollection<ITestDataSeries> graphs)
{
if (_view == null) return;
if (!Application.Current.Dispatcher.CheckAccess())
{
Application.Current.Dispatcher.BeginInvoke(new System.Action(() => { DisplayGraphs(graphs); }));
return;
}
_sc = _view.MainChart.Data.Children;
_view.MainChart.BeginUpdate();
_sc.Clear();
foreach (var graph in graphs)
{
if (graph == null) continue;
var ds = new XYDataSeries
{
RenderMode = RenderMode.Bitmap,
ConnectionStrokeThickness = 2,
ConnectionFill = graph.GraphColor,
XValuesSource = graph.Xvalue,
ValuesSource = graph.Yvalue,
Label = graph.ChannelId,
PointTooltipTemplate = (DataTemplate)((TestDataSeriesView)((IGraphViewModel)Parent).DataSeriesView).MainChart.Resources["PointTooltipTemplate"],
};
_sc.Add(ds);
#region marker
_markerY = new XYDataSeries()
{
RenderMode = RenderMode.Fast,
ConnectionStrokeDashes = new DoubleCollection(new double[] { 5, 3 }),
ConnectionStrokeThickness = 1D,
ConnectionStroke = Brushes.Red,
};
_markerX = new XYDataSeries()
{
RenderMode = RenderMode.Fast,
ConnectionStrokeDashes = new DoubleCollection(new double[] { 5, 3 }),
ConnectionStrokeThickness = 1D,
ConnectionStroke = Brushes.Red,
};
_sc.Add(_markerY);
_sc.Add(_markerX);
_markerSymbol = new XYDataSeries()
{
RenderMode = RenderMode.Fast,
SymbolMarker = Marker.Dot,
SymbolSize = new Size(5, 5),
SymbolStroke = Brushes.Red,
ConnectionStroke = Brushes.Red,
ConnectionStrokeThickness = 1D,
SymbolStrokeThickness = 1D,
SymbolFill = Brushes.White
};
_sc.Add(_markerSymbol);
InitCurrentMarker();
#endregion marker
}
_view.MainChart.EndUpdate();
// 37984 LTTB RAM runaway: enforce a garbage collection here. In "worst case scenario" data sets, GBs of RAM are used and then out of scope but not immediately freed
GC.Collect();
GC.WaitForPendingFinalizers();
}
private void SetAxisTitle(bool bVolts)
{
try
{
switch (ChartViewOptions.UnitType)
{
case ChartUnitTypeEnum.ADC: TitleY = "A/D Counts"; break;
case ChartUnitTypeEnum.EU:
{
if (TestChannelList.Any())
{
var val = TestChannelList[0].Eu; TitleY = TestChannelList.All(x => x.Eu == val) ? val : "EU";
}
else TitleY = Strings.Table_NA;
}
break;
case ChartUnitTypeEnum.mV: TitleY = bVolts ? "V" : "mV"; break;
case ChartUnitTypeEnum.FFT: TitleY = Strings.Magnitude; break;
case ChartUnitTypeEnum.PSD:
// 25554 add PSD charting: logarithmic Y from ~0 to 1
// 26766 Scale PDS chart Y-axis to data in graph
var max = GraphDataSeries.Max(tds => double.Parse(tds.MaxY));
var maxNextPow10 = Math.Ceiling(Math.Log10(max));
var min = GraphDataSeries.Min(tds => double.Parse(tds.MinY));
var minPrevPow10 = Math.Floor(Math.Log10(min));
TitleY = Strings.GSquaredOverHz;
_view.MainChart.View.AxisY.Max = maxNextPow10 > Constants.PSD_DEFAULT_MAX_POW10 ? Math.Pow(10, maxNextPow10) : Math.Pow(10, Constants.PSD_DEFAULT_MAX_POW10);
_view.MainChart.View.AxisY.Min = minPrevPow10 > Constants.PSD_DEFAULT_MIN_POW10 ? Math.Pow(10, minPrevPow10) : Math.Pow(10, Constants.PSD_DEFAULT_MIN_POW10);
_view.MainChart.View.AxisY.LogBase = 10;
_view.MainChart.View.AxisY.AnnoFormat = "E2";
break;
default:
throw new ArgumentOutOfRangeException();
}
//we want to make the time axis logarithmic, we have to start at 1 since 0 is undefined
//6402 Implement ability to switch to FFT live in the Review
// 25554 add PSD charting
if (ChartUnitTypeEnum.FFT == ChartViewOptions.UnitType || ChartUnitTypeEnum.PSD == ChartViewOptions.UnitType)
{
TitleX = Strings.FrequencyHz;
_view.MainChart.View.AxisX.Min = 1;
_view.MainChart.View.AxisX.LogBase = 10;
_view.MainChart.View.AxisX.AnnoFormat = "D";
}
else
{
TitleX = ChartViewOptions.TimeUnitType.ToString();
_view.MainChart.View.AxisX.LogBase = double.NaN;
}
_view.MainChart.View.AxisX.Title = TitleX;
_view.MainChart.View.AxisY.Title = TitleY;
}
catch (Exception ex)
{
APILogger.Log(ex);
}
}
public void UpdateScrollbars()
{
//((AxisScrollBar)_view.MainChart.View.AxisX.ScrollBar).Visibility = _view.MainChart.View.AxisX.Scale >= 1.0 ? Visibility.Collapsed : Visibility.Visible;
//((AxisScrollBar)_view.MainChart.View.AxisY.ScrollBar).Visibility = _view.MainChart.View.AxisY.Scale >= 1.0 ? Visibility.Collapsed : Visibility.Visible;
}
public void ResetZoom()
{
if (_view == null) return;
ChartViewOptions.CanPublishChanges = false;
ChartViewOptions.YRange = YRangeScaleEnum.AutoRange;
if (0 == GraphDataSeries.Count || ChartViewOptions.DecimateData)
{
if (ChartViewOptions.DecimateData) ChartViewOptions.LockedT = false;
ChartViewOptions.MaxFixedT = ChartViewOptions.MinFixedT = 0.0D;
}
else
{
//if we are FFT, we want to constrain the behavior of ResetZoom a little, mainly
//make sure T is always 1 or greater as 0 and negative values are undefined
//6402 Implement ability to switch to FFT live in the Review
if (ChartViewOptions.UnitType == ChartUnitTypeEnum.FFT || ChartViewOptions.UnitType == ChartUnitTypeEnum.PSD)
{
ChartViewOptions.MinFixedT = 1;
if (null != psdSettings)
{
psdSettings.CanPublishChanges = false;
psdSettings.DataStart = psdSettings.DataEnd = 0D;
psdSettings.CanPublishChanges = true;
}
}
else
{
ChartViewOptions.MinFixedT =
Math.Min(GraphDataSeries.Min(x => x.Xvalue.Min()), ChartViewOptions.MinFixedT);
}
ChartViewOptions.MaxFixedT =
Math.Max(GraphDataSeries.Max(x => x.Xvalue.Max()), ChartViewOptions.MaxFixedT);
}
ChartViewOptions.SelectedFullScaleValue = 100;
_view.MainChart.View.AxisX.Max = ChartViewOptions.MaxFixedT;
_view.MainChart.View.AxisX.Min = ChartViewOptions.MinFixedT;
ReSetScale();
ScaleChart();
UpdateScrollbars();
ChartViewOptions.CanPublishChanges = true;
ChartViewOptions.ReadData = true;
ChartViewOptions.WidthPoints = (long)(_view.MainChart.ActualWidth / _view.MainChart.View.AxisX.Scale);
if (null != psdSettings)
{
psdSettings.ReadData = true;
psdSettings.Parent.PublishChanges();
}
if (ChartViewOptions.DecimateData) ChartViewOptions.Parent.PublishChanges();
}
public void ResetT()
{
ChartViewOptions.CanPublishChanges = false;
if (!TestChannelList.Any()) { return; }
CurrentXMax = TestChannelList.Max(x => x.Xmax);
CurrentXMin = TestChannelList.Min(x => x.Xmin);
ChartViewOptions.MaxFixedT = CurrentXMax;
ChartViewOptions.MinFixedT = CurrentXMin;
SetMinMaxX(CurrentXMin, CurrentXMax);
ChartViewOptions.LockedT = false;
ReSetScaleX();
UpdateScrollbars();
ChartViewOptions.CanPublishChanges = true;
}
private void ReSetScaleX()
{
_view.MainChart.View.AxisX.Scale = 1;
_view.MainChart.View.AxisX.Value = 0.5;
}
private void ReSetScaleY()
{
_view.MainChart.View.AxisY.Scale = 1;
_view.MainChart.View.AxisY.Value = 0.5;
}
private void ReSetScale()
{
_view.MainChart.View.AxisX.Scale = 1;
_view.MainChart.View.AxisX.Value = 0.5;
_view.MainChart.View.AxisY.Scale = 1;
_view.MainChart.View.AxisY.Value = 0.5;
}
public void ScaleChart()
{
switch (ChartViewOptions.YRange)
{
case YRangeScaleEnum.Fixed:
CanPublishChanges = false;
CurrentYMax = ChartViewOptions.MaxFixedY;
CurrentYMin = ChartViewOptions.MinFixedY;
CalculateMinMax();
SetMinMaxY(CurrentYMin, CurrentYMax);
CanPublishChanges = true;
break;
case YRangeScaleEnum.FullScale:
CurrentYMax = double.MinValue; CurrentYMin = double.MaxValue;
CalculateMinMax();
SetMinMaxY(CurrentYMin, CurrentYMax);
break;
case YRangeScaleEnum.AutoRange:
CurrentYMax = double.MinValue; CurrentYMin = double.MaxValue;
CalculateMinMax();
SetMinMaxY(CurrentYMin, CurrentYMax);
ReSetScaleY();
break;
}
DrawCurrentMarker();
}
/// <summary>
/// Setting min first, otherwise Chart will ignore max (Y)
/// </summary>
/// <param name="min"></param>
/// <param name="max"></param>
private void SetMinMaxY(double min, double max)
{
if (min == double.MaxValue
&& max == double.MinValue
&& null == _view.MainChart.View.AxisY.Max
&& null == _view.MainChart.View.AxisY.Min) { return; }
if (min > _view.MainChart.View.AxisY.Max) { _view.MainChart.View.AxisY.Max = max; _view.MainChart.View.AxisY.Min = min; }
else { _view.MainChart.View.AxisY.Min = min; _view.MainChart.View.AxisY.Max = max; }
try { _view.MainChart.UpdateLayout(); }
catch { }
}
private void SetMinMaxX(double min, double max)
{
_view.MainChart.View.AxisX.Max = max;
_view.MainChart.View.AxisX.Min = min;
_view.MainChart.UpdateLayout();
}
/// <summary>
/// Calculate chart Min Max and scale
/// </summary>
private void CalculateMinMax()
{
var channels = TestChannelList.ToList();
if (!channels.Any()) { CurrentYMax = 1; CurrentYMin = 0; return; }
switch (ChartViewOptions.YRange)
{
case YRangeScaleEnum.Fixed:
CurrentYMax = ChartViewOptions.MaxFixedY;
CurrentYMin = ChartViewOptions.MinFixedY;
break;
case YRangeScaleEnum.FullScale:
switch (ChartViewOptions.UnitType)
{
case ChartUnitTypeEnum.ADC:
var actualMaxRangeAdc = channels.Max(ch => ch.ActualMaxRangeAdc);
var actualMinRangeAdc = channels.Min(ch => ch.ActualMinRangeAdc);
if (DFConstantsAndEnums.AlwaysShowUnsignedADC)
{
actualMaxRangeAdc = ushort.MaxValue;
actualMinRangeAdc = ushort.MinValue;
}
CurrentYMax = Math.Max(actualMaxRangeAdc * CalculateScalingFactor(), CurrentYMax);
CurrentYMin = Math.Min(actualMinRangeAdc * CalculateScalingFactor(), CurrentYMin);
break;
case ChartUnitTypeEnum.EU:
if (channels.TrueForAll(ch => ch.Bridge == SensorConstants.BridgeType.DigitalInput.ToString()))
{
CurrentYMax =
Math.Max(CalculateFixedRange(channels.Max(ch => ch.ActualMaxRangeEu)),
CurrentYMax);
CurrentYMin =
Math.Min(CalculateFixedRange(channels.Min(ch => ch.ActualMinRangeEu)),
CurrentYMin);
}
else
{
var proportionalToExcitation = channels.Max(ch => ch.ProportionalToExcitation);
var sensorCapacity = channels.Max(ch => ch.SensorCapacity);
if (proportionalToExcitation || sensorCapacity == 0)
{
CurrentYMax =
Math.Max(CalculateFixedRange(channels.Max(ch => ch.DesiredRange)),
CurrentYMax);
CurrentYMin =
Math.Min(
CalculateFixedRange(channels.Max(ch => ch.DesiredRange) * (-1)),
CurrentYMin);
}
else
{
CurrentYMax = Math.Max(CalculateFixedRange(sensorCapacity),
CurrentYMax);
CurrentYMin = Math.Min(CalculateFixedRange(-sensorCapacity),
CurrentYMin);
}
}
break;
case ChartUnitTypeEnum.mV:
var actualMaxRangeMv = channels.Max(ch => ch.ActualMaxRangeMv);
var actualMinRangeMv = channels.Min(ch => ch.ActualMinRangeMv);
CurrentYMax = Math.Max(actualMaxRangeMv * CalculateScalingFactor(), CurrentYMax);
CurrentYMin = Math.Min(actualMinRangeMv * CalculateScalingFactor(), CurrentYMax);
break;
}
break;
case YRangeScaleEnum.AutoRange:
if (!GraphDataSeries.Any()) return;
var minY = GraphDataSeries.Where(d => d != null).Min(x => x.Yvalue.Min());
var maxY = GraphDataSeries.Where(d => d != null).Max(x => x.Yvalue.Max());
if (channels.TrueForAll(ch => ch.Bridge == SensorConstants.BridgeType.DigitalInput.ToString())
&& ChartViewOptions.UnitType == ChartUnitTypeEnum.EU)
{
var overheadEU = Math.Abs((channels.Min(ch => ch.ActualMinRangeEu) +
channels.Max(ch => ch.ActualMaxRangeEu))
* (Constants.GRAPH_MAX_AUTOZOOM - 1.0D));
CurrentYMin = channels.Min(ch => ch.ActualMinRangeEu) - overheadEU;
CurrentYMax = channels.Max(ch => ch.ActualMaxRangeEu) + overheadEU;
}
else
{
var capacity = 0D;
switch (ChartViewOptions.UnitType)
{
case ChartUnitTypeEnum.ADC:
capacity = channels.Max(ch => ch.ActualMaxRangeAdc);
break;
case ChartUnitTypeEnum.EU:
capacity = channels.Max(ch => ch.ActualMaxRangeEu);
break;
case ChartUnitTypeEnum.mV:
capacity = channels.Max(ch => ch.ActualMaxRangeMv);
break;
//we can constrain min/max a little when we are FFT, so we do so
//6402 Implement ability to switch to FFT live in the Review
case ChartUnitTypeEnum.FFT:
capacity = channels.Max(ch => ch.ActualMaxRangeEu);
maxY = 0;
break;
}
Utils.CalculateMinMaxAutoScaling(capacity,
minY,
maxY,
Constants.GRAPH_MIN_AUTOZOOM,
Constants.GRAPH_MAX_AUTOZOOM,
out var resultMaxy,
out var resultMinY);
CurrentYMin = resultMinY;
CurrentYMax = resultMaxy;
}
break;
}
}
/// <summary>
/// Copied from DataPro
/// </summary>
/// <param name="capacity"></param>
/// <param name="dataMin"></param>
/// <param name="dataMax"></param>
/// <remarks>
/// per http://fogbugz/fogbugz/default.asp?8281 use 120%
/// The idea here is to have a moving window no smaller than +/-30% of capacity.
/// Also make sure that the top of the signal is seen so make the visible window
/// +/-120% of the signal
/// </remarks>
public double FixedRangePercentageOfFullScale = 100;
public double FixedRangeValue = 5000;
/// <summary>
/// Calculate scaling factor
/// </summary>
/// <param name="range"></param>
/// <returns></returns>
private double CalculateFixedRange(double range)
{
return (ChartViewOptions.SelectedFullScaleValue / 100.0) * range;
}
private double CalculateScalingFactor()
{
return ChartViewOptions.SelectedFullScaleValue / 100.0;
}
#endregion
#region Commands
private DelegateCommand<object> _actionEnterCommand;
public DelegateCommand<object> ActionEnterCommand => _actionEnterCommand ?? (_actionEnterCommand = new DelegateCommand<object>(ActionEnterMethod));
private DelegateCommand<object> _actionLeaveCommand;
public DelegateCommand<object> ActionLeaveCommand => _actionLeaveCommand ?? (_actionLeaveCommand = new DelegateCommand<object>(ActionLeaveMethod));
private DelegateCommand<object> _mouseUpCommand;
public DelegateCommand<object> MouseUpCommand => _mouseUpCommand ?? (_mouseUpCommand = new DelegateCommand<object>(MouseUpMethod));
private DelegateCommand<object> _mouseDownCommand;
public DelegateCommand<object> MouseDownCommand => _mouseDownCommand ?? (_mouseDownCommand = new DelegateCommand<object>(MouseDownMethod));
private DelegateCommand<object> _mouseLeftButtonDownCommand;
public DelegateCommand<object> MouseLeftButtonDownCommand => _mouseLeftButtonDownCommand ?? (_mouseLeftButtonDownCommand = new DelegateCommand<object>(MouseLeftButtonDownMethod));
private DelegateCommand<object> _mouseMoveCommand;
public DelegateCommand<object> MouseMoveCommand => _mouseMoveCommand ?? (_mouseMoveCommand = new DelegateCommand<object>(MouseMoveMethod));
private DelegateCommand<object> _gotFocusCommand;
public DelegateCommand<object> GotFocusCommand => _gotFocusCommand ?? (_gotFocusCommand = new DelegateCommand<object>(GotFocusCommandMethod));
private DelegateCommand<KeyEventArgs> _chartOnKeyUpCommand;
public DelegateCommand<KeyEventArgs> ChartOnKeyUpCommand => _chartOnKeyUpCommand ?? (_chartOnKeyUpCommand = new DelegateCommand<KeyEventArgs>(ChartOnKeyUpMethod));
private void ActionEnterMethod(object sender) { }
/// <summary>
/// Zoom end function
/// </summary>
/// <remarks>Ignore X if Locked T</remarks>
/// <param name="sender"></param>
private void ActionLeaveMethod(object sender)
{
ChartCursor = Cursors.Arrow;
// 34455 turn off publishing when setting values in order to not trigger multiple re-reads
CanPublishChanges = false;
if (ChartViewOptions.LockedT) { _view.MainChart.View.AxisX.Max = ChartViewOptions.MaxFixedT; _view.MainChart.View.AxisX.Min = ChartViewOptions.MinFixedT; _view.MainChart.View.AxisX.Scale = 1; }
else { CurrentXMax = _view.MainChart.View.AxisX.GetDispMax(); CurrentXMin = _view.MainChart.View.AxisX.GetDispMin(); }
ChartViewOptions.WidthPoints = (long)(_view.MainChart.ActualWidth / _view.MainChart.View.AxisX.Scale);
CanPublishChanges = true;
//publish changes now that values are set
_eventAggregator.GetEvent<ChartAxisChangedEvent>().Publish(new ChartAxisChangedEventArg() { ParentVM = ((GraphViewModel)Parent).Parent, Axis = "X", MinValue = _currentXMin, MaxValue = _currentXMax });
ChartViewOptions.CanPublishChanges = false;
ChartViewOptions.YRange = YRangeScaleEnum.Manual;
ChartViewOptions.ReadData = true;
ChartViewOptions.CanPublishChanges = true;
if (null != psdSettings) psdSettings.ReadData = true;
// 34455 lock T in data selector on Zoom
if ("DataSelect" == ChartType)
{
ChartViewOptions.LockedT = true;
}
CurrentYMax = _view.MainChart.View.AxisY.GetDispMax();
CurrentYMin = _view.MainChart.View.AxisY.GetDispMin();
UpdateScrollbars();
if (ChartViewOptions.DecimateData) ChartViewOptions.Parent.PublishChanges();
}
private void InitCurrentMarker()
{
MoveCurrentMarker(0, true);
}
private void RemoveCurrentMarker()
{
_markerIndex = 0;
if (_markerSymbol == null) return;
_markerSymbol.ValuesSource = new DoubleCollection();
_markerSymbol.XValuesSource = new DoubleCollection();
_markerY.ValuesSource = new DoubleCollection();
_markerY.XValuesSource = new DoubleCollection();
_markerX.ValuesSource = new DoubleCollection();
_markerX.XValuesSource = new DoubleCollection();
}
/// <summary>
/// Returns the sample index from the file based on the given milliseconds
/// </summary>
/// <param name="milliSeconds"></param>
/// <returns></returns>
private ulong GetSampleIndexFromMilliseconds(double milliSeconds)
{
var seconds = milliSeconds / 1000.0;
var selectedChannel = TestChannelList.FirstOrDefault();
if (selectedChannel != null)
{
var t0offset = (ulong)-selectedChannel.ParentModule.StartRecordSampleNumber + (ulong)(selectedChannel.ParentModule.SampleRateHz * seconds);
return selectedChannel.ParentModule.TriggerSampleNumbers.Count > 0
? selectedChannel.ParentModule.TriggerSampleNumbers[0] + t0offset
: ulong.MinValue + t0offset;
}
return ulong.MinValue;
}
private double _t0sampleMS = 0;
private void MoveCurrentMarker(int adjustment, bool isInitialization = false)
{
if (!_sc.Any()) return;
if (!(_sc[0] is XYDataSeries dataseries))
{
return;
}
var x = dataseries.XValuesSource as double[];
switch (CurrentMarker)
{
case Markers.moveT0:
//find the first index of where the time value is >= to the time requested and buck up one index
//if index is <0 (no such time entry found) then just use index 0
var idx = FindNearestT0Index(x, _t0sampleMS);
_markerIndex = idx >= 0 ? idx : -1;
break;
case Markers.LineFitT1:
_markerIndex = (int)GetSampleIndexFromMilliseconds(_t0sampleMS);
break;
case Markers.LineFitT2:
_markerIndex = (int)GetSampleIndexFromMilliseconds(_t0sampleMS);
break;
case Markers.NONE:
return;
}
// Init Case
if (null != x && 0 == adjustment && _markerIndex >= x.Length)
{
_markerIndex = x.Length - 1;
_markerSymbol.ValuesSource = new DoubleCollection();
_markerSymbol.XValuesSource = new DoubleCollection();
_markerY.ValuesSource = new DoubleCollection();
_markerY.XValuesSource = new DoubleCollection();
_markerX.XValuesSource = new DoubleCollection();
_markerX.ValuesSource = new DoubleCollection();
}
else
{
DrawCurrentMarker();
if (_markerIndex >= 0 && null != x && _markerIndex < x.Length && CurrentMarker == Markers.moveT0)
{
_eventAggregator.GetEvent<ShiftT0Event>().Publish(new ShiftT0EventArguments(x[_markerIndex], isInitialization));
}
}
}
private void DrawCurrentMarker()
{
if (!_sc.Any() || CurrentMarker == Markers.NONE) return;
if (!(_sc[0] is XYDataSeries dataseries))
{
return;
}
var x = dataseries.XValuesSource as double[];
var y = dataseries.ValuesSource as double[];
if (x == null || y == null)
{
return;
}
OnPropertyChanged("T0EUValue");
if (_markerIndex < 0)
{
_markerSymbol.ValuesSource = new DoubleCollection();
_markerSymbol.XValuesSource = new DoubleCollection();
_markerY.ValuesSource = new DoubleCollection();
_markerY.XValuesSource = new DoubleCollection();
_markerX.ValuesSource = new DoubleCollection();
_markerX.XValuesSource = new DoubleCollection();
}
else
{
_markerSymbol.ValuesSource = new DoubleCollection(new[] { y[_markerIndex] });
_markerSymbol.XValuesSource = new DoubleCollection(new[] { x[_markerIndex] });
_markerSymbol.Label = $"({x[_markerIndex]}, {y[_markerIndex]})";
_markerY.ValuesSource = new DoubleCollection(new[] { CurrentYMax, CurrentYMin });
_markerY.XValuesSource = new DoubleCollection(new[] { x[_markerIndex], x[_markerIndex] });
_markerX.ValuesSource = new DoubleCollection(new[] { y[_markerIndex], y[_markerIndex] });
_markerX.XValuesSource = new DoubleCollection(new[] { x[0], x[x.Length - 1] });
}
OnPropertyChanged("CursorValue");
//hide the T0 marker when we are FFT
//6402 Implement ability to switch to FFT live in the Review
// 25554 add PSD charting
if (ChartViewOptions.UnitType == ChartUnitTypeEnum.FFT || ChartViewOptions.UnitType == ChartUnitTypeEnum.PSD)
{
MarkerVisibilty = Visibility.Collapsed;
}
}
private enum Markers
{
NONE,
moveT0,
LineFitT1,
LineFitT2
}
private Markers CurrentMarker { get; set; } = Markers.NONE;
private int _markerIndex = 0;
private void c1Chart_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
if (CurrentMarker == Markers.NONE)
return;
double d;
var dataIndex = _view.MainChart.View.DataIndexFromPoint(e.GetPosition(_view.MainChart), 0, MeasureOption.X, out d);
if (dataIndex < 0) return;
if (!_sc.Any()) return;
try
{
var dataseries = _sc[0] as XYDataSeries;
if (null == dataseries)
{
return;
}
var x = dataseries.XValuesSource as double [];
if (null == x) { return; }
var length = x.Length;
if (dataIndex >= length)
{
dataIndex = length - 1;
}
_markerIndex = dataIndex;
DrawCurrentMarker();
switch (CurrentMarker)
{
case Markers.moveT0:
_eventAggregator.GetEvent<ShiftT0Event>().Publish(new ShiftT0EventArguments(x[dataIndex], false));
break;
case Markers.LineFitT1:
break;
case Markers.LineFitT2:
break;
}
}
catch (Exception ex)
{
APILogger.Log(ex);
}
}
private void MouseDownMethod(object sender) { }
private XYDataSeries _markerSymbol = null;
private XYDataSeries _markerX = null;
private XYDataSeries _markerY = null;
private void MouseLeftButtonDownMethod(object sender) { }
private void MouseUpMethod(object sender) { }
private void MouseMoveMethod(object sender) { }
public void ChartOnKeyUpMethod(object sender) { }
public void ChartOnKeyUpMethod(KeyEventArgs args) { }
public void ChartOnKeyUpMethod(object sender, KeyEventArgs args) { }
private void GotFocusCommandMethod(object sender) { }
#endregion Commands
}
/// <summary>
/// Extension method to remove item form ObservableCollection
/// </summary>
public static class ExtensionMethods
{
public static int Remove<T>(this ObservableCollection<T> coll, Func<T, bool> condition)
{
var itemsToRemove = coll.Where(condition).ToList();
foreach (var itemToRemove in itemsToRemove) { coll.Remove(itemToRemove); }
return itemsToRemove.Count;
}
}
}