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 { 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 NotificationRequest { get; private set; } public new InteractionRequest ConfirmationRequest { get; private set; } private IChartOptionsModel ChartViewOptions { get; set; } /// /// Creates a new instance of the GraphViewModel. /// /// The GraphView interface. /// The logical placeholder defined within the application's UI (in the shell or within views) into which views are displayed. /// The EventAggregator which allows different components to publish/subscribe to events without being coupled to each other. /// The unityContainer. public TestDataSeriesViewModel(ITestDataSeriesView view, IRegionManager regionManager, IEventAggregator eventAggregator, IUnityContainer unityContainer) : base(regionManager, eventAggregator, unityContainer) { View = view; View.DataContext = this; NotificationRequest = new InteractionRequest(); ConfirmationRequest = new InteractionRequest(); _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().Subscribe(OnPSDReportSettingsChanged); _eventAggregator.GetEvent().Subscribe(OnSaveReportToCSVRequested); _eventAggregator.GetEvent().Subscribe(OnSaveReportToPDFRequested); _eventAggregator.GetEvent().Unsubscribe(OnShowT0Cursor); OnShowT0Cursor(false); } public override void Activated() { var fp = new FilterParameterArgs { Param = string.Empty, Requester = this }; _eventAggregator.GetEvent().Publish(fp); } #endregion private bool _subscribed = false; private void Subscribe() { if (_subscribed) return; _subscribed = true; _eventAggregator.GetEvent().Subscribe(OnChartOptionsChanged, ThreadOption.BackgroundThread); _eventAggregator.GetEvent().Subscribe(OnModifiedChannelsChanged); _eventAggregator.GetEvent().Subscribe(OnLineFit); _eventAggregator.GetEvent().Subscribe(OnSelectedChannelsChanged, ThreadOption.BackgroundThread); _eventAggregator.GetEvent().Subscribe(OnResetZoom); _eventAggregator.GetEvent().Subscribe(OnGraphClearChanged); _eventAggregator.GetEvent().Subscribe(OnShowT0Cursor); _eventAggregator.GetEvent().Subscribe(OnTestModificationChanged); _eventAggregator.GetEvent().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().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().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().ToArray(); var idx = FindNearestT0Index(array, _t0sampleMS); if ( idx >= 0) { SetAndDrawMarker(idx); return; } } catch( Exception ex) { APILogger.Log(ex); } } } /// /// Sets marker index and draws marker /// 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 /// /// Reset UI Zoom /// /// Reset all or reset X 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(); TestChannelList = new ObservableCollection(); 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); } } /// /// Chart Options Changed Event /// /// IChartOptionsModel 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(); } //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()?.Publish(true); ApplyChartOptions(!testChannelList.Exists(ch => ch.Bridge != IEPE_BRIDGE)); _eventAggregator?.GetEvent()?.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(); //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().Publish(true); GraphDataSeries.Clear(); ReadData(testChannelList, null, !testChannelList.Exists(ch => ch.Bridge != IEPE_BRIDGE)); psdSettings.ReadData = false; _eventAggregator?.GetEvent().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"; /// /// Read one selected channel or channels for one selected graph /// /// one or more channels private void OnSelectedChannelsChanged(GraphSelectedChannelsNotificationArg arg) { try { if (((GraphViewModel)Parent).Parent != arg?.ParentVM) return; var channels = arg?.SelectedChannels; if (TestChannelList == null) TestChannelList = new ObservableCollection(); 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 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 } } /// /// holds the frequency in hz of the frequency of highest magnitude in signal /// 6402 Implement ability to switch to FFT live in the Review /// 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 } } /// /// holds the peak magnitude (dB) of frequencies in the signal data /// 6402 Implement ability to switch to FFT live in the Review /// 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; } } /// /// holds whether the series displayed are FFT of signal data /// 6402 Implement ability to switch to FFT live in the Review /// 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"); } /// /// handles line fit events, which just draws a straight line between two points /// /// private void OnLineFit(LineFitArgs args) { try { if (args?.Channel == null) { return; } ReadData(new List { args.Channel }, args); } catch (Exception ex) { APILogger.LogException(ex); } } #endregion Graph Selection Changed event #region Read Channel Data private bool _reading = false; /// /// Read channel data /// /// /// option line fit parameters, default null. if present will perform linefit on the given channel private void ReadData(List channels, LineFitArgs lineFitArgs = null, bool bVolts = false) { if (channels == null || channels.Count == 0 || _reading) return; _eventAggregator.GetEvent().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(); } 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().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().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().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().Publish(new PageErrorArg(new[] { string.Format(StringResources.BadDataUnfilteredUnknown, string.Join(Environment.NewLine, badChannels)) }, null)); } GraphDataSeries.Clear(); DisplayGraphs(GraphDataSeries); } } catch (Exception ex) { _eventAggregator.GetEvent().Publish(new PageErrorArg(new[] { ex.Message }, null)); } finally { UpdateChartOverlayProperties(); _eventAggregator.GetEvent().Publish(new GraphChannelsReadCompletedNotificationArgs() { IsReadCompleted = true, GraphVM = (GraphViewModel)Parent }); _reading = false; } })); #endregion Dispatcher } /// /// Assuming the data series passed contains /// /// /// private void PublishGRMSValues(List dataSeries) { _eventAggregator.GetEvent().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() }); } /// /// Apply Test modification /// /// /// /// private List ApplyLineFit(List _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; } /// /// Apply Report View Options /// 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 _testChannelList; public ObservableCollection TestChannelList { get => _testChannelList; set { _testChannelList = value; OnPropertyChanged("TestChannelList"); } } public ObservableCollection _graphDataSeries = new ObservableCollection(); public ObservableCollection 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().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().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().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().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"); } } /// ///Occurs when a property value changes. /// 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) { } /// /// Apply Chart Options /// 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(); /// /// Display Graphs UI /// /// Charts to display private void DisplayGraphs(ObservableCollection 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(); } /// /// Setting min first, otherwise Chart will ignore max (Y) /// /// /// 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(); } /// /// Calculate chart Min Max and scale /// 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; } } /// /// Copied from DataPro /// /// /// /// /// /// 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 /// public double FixedRangePercentageOfFullScale = 100; public double FixedRangeValue = 5000; /// /// Calculate scaling factor /// /// /// private double CalculateFixedRange(double range) { return (ChartViewOptions.SelectedFullScaleValue / 100.0) * range; } private double CalculateScalingFactor() { return ChartViewOptions.SelectedFullScaleValue / 100.0; } #endregion #region Commands private DelegateCommand _actionEnterCommand; public DelegateCommand ActionEnterCommand => _actionEnterCommand ?? (_actionEnterCommand = new DelegateCommand(ActionEnterMethod)); private DelegateCommand _actionLeaveCommand; public DelegateCommand ActionLeaveCommand => _actionLeaveCommand ?? (_actionLeaveCommand = new DelegateCommand(ActionLeaveMethod)); private DelegateCommand _mouseUpCommand; public DelegateCommand MouseUpCommand => _mouseUpCommand ?? (_mouseUpCommand = new DelegateCommand(MouseUpMethod)); private DelegateCommand _mouseDownCommand; public DelegateCommand MouseDownCommand => _mouseDownCommand ?? (_mouseDownCommand = new DelegateCommand(MouseDownMethod)); private DelegateCommand _mouseLeftButtonDownCommand; public DelegateCommand MouseLeftButtonDownCommand => _mouseLeftButtonDownCommand ?? (_mouseLeftButtonDownCommand = new DelegateCommand(MouseLeftButtonDownMethod)); private DelegateCommand _mouseMoveCommand; public DelegateCommand MouseMoveCommand => _mouseMoveCommand ?? (_mouseMoveCommand = new DelegateCommand(MouseMoveMethod)); private DelegateCommand _gotFocusCommand; public DelegateCommand GotFocusCommand => _gotFocusCommand ?? (_gotFocusCommand = new DelegateCommand(GotFocusCommandMethod)); private DelegateCommand _chartOnKeyUpCommand; public DelegateCommand ChartOnKeyUpCommand => _chartOnKeyUpCommand ?? (_chartOnKeyUpCommand = new DelegateCommand(ChartOnKeyUpMethod)); private void ActionEnterMethod(object sender) { } /// /// Zoom end function /// /// Ignore X if Locked T /// 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().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(); } /// /// Returns the sample index from the file based on the given milliseconds /// /// /// 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().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().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 } /// /// Extension method to remove item form ObservableCollection /// public static class ExtensionMethods { public static int Remove(this ObservableCollection coll, Func condition) { var itemsToRemove = coll.Where(condition).ToList(); foreach (var itemToRemove in itemsToRemove) { coll.Remove(itemToRemove); } return itemsToRemove.Count; } } }