1930 lines
84 KiB
Plaintext
1930 lines
84 KiB
Plaintext
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;
|
|
}
|
|
}
|
|
}
|
|
|