using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ComponentModel; namespace DTS.Slice.PedestrianAndHeadReports { /// /// Generic class for handling graphs in reports /// public class ReportGraph : INotifyPropertyChanged { /*private string GetEngineeringUnits(DTS.Slice.Control.Event.Module.Channel channel) { if (channel is DTS.DAS.Concepts.DAS.Channel.IEngineeringUnitAware) { return (channel as DTS.DAS.Concepts.DAS.Channel.IEngineeringUnitAware).EngineeringUnits; } else { return "EU"; } }*/ /// /// colors for lines, start with blue, etc. /// private System.Drawing.Color[] COLORS = new System.Drawing.Color[] { System.Drawing.Color.Blue, System.Drawing.Color.Red, System.Drawing.Color.Green, System.Drawing.Color.Orange }; /// /// we hold onto an actual chart object if we know what it is, /// which we will once we've drawn it atleast once. /// this lets us redraw if something changes /// private C1.Win.C1Chart.C1Chart _chart = null; public C1.Win.C1Chart.C1Chart Chart { get { return _chart; } } public void Draw() { Draw(_chart); } public void Draw(C1.Win.C1Chart.C1Chart chart) { if (null == chart) { return; } if (null == _chart) { _chart = chart; } chart.ChartGroups[0].ChartData.SeriesList.Clear(); for (int i = 0; i < _channelIds.Count; i++) { chart.ChartGroups[0].ChartData.SeriesList.AddNewSeries(); GraphChannel gc = _channels[_channelIds[i]]; if (gc.Channel == null) { continue; } chart.ChartGroups[0].ChartData.SeriesList[i].Label = gc.DisplayName; chart.ChartGroups[0].ChartData.SeriesList[i].LineStyle.Color = COLORS[i]; chart.ChartGroups[0].ChartData.SeriesList[i].LineStyle.Thickness = 2; chart.ChartGroups[0].ChartData.SeriesList[i].SymbolStyle.Shape = C1.Win.C1Chart.SymbolShapeEnum.None; chart.ChartGroups[0].ChartData.SeriesList[i].PlotFilterMethod = C1.Win.C1Chart.PlotFilterMethodEnum.Alternative; chart.ChartGroups[0].ChartData.SeriesList[i].PlotFilter = 5; DTS.Slice.Control.Event.Module.Channel aic = gc.Channel.Channel as DTS.Slice.Control.Event.Module.Channel; if (null == aic) { continue; } double dCurrentTime = ((double)aic.ParentModule.StartRecordSampleNumber - (double)aic.ParentModule.TriggerSampleNumbers[0]) / (double)aic.ParentModule.SampleRateHz; double dIncrement = 1/(double)aic.ParentModule.SampleRateHz; if (TimeUnits == "ms") { dCurrentTime *= 1000; dIncrement *=1000;} List xAxis = new List(500000); List yAxis = new List(500000); double dMin = double.MaxValue; double dMax = double.MinValue; double dTimeOfMax = double.NaN; double dTimeOfMin = double.NaN; double eu; MeasurementUnit channelUnit = GetChannelUnit(); string actualUnit = GetEngineeringUnits(aic); double scaleFactor = 1D; try { scaleFactor = channelUnit.GetScalerConversionFrom(actualUnit);//GetScaleFactor(channelUnit, actualUnit); System.Diagnostics.Trace.WriteLine("Scaling " + gc.Id + " by " + scaleFactor.ToString()); } catch (System.Exception) { } for (ulong z = 0; z < aic.ParentModule.NumberOfSamples; z++) { try { if (dCurrentTime >= DomainMin && dCurrentTime <= DomainMax) { eu = aic.DataEu[Convert.ToInt32(z)]; eu *= scaleFactor; xAxis.Add(dCurrentTime); yAxis.Add(eu); if (dMax < eu) { dMax = eu; dTimeOfMax = dCurrentTime; } if (dMin > eu) { dMin = eu; dTimeOfMin = dCurrentTime; } } } catch (System.Exception ex) { DTS.Utilities.Logging.APILogger.Log("Failed to access sample at index: ", z, " of ", aic.ChannelDescriptionString, " which reports: ", aic.ParentModule.NumberOfSamples, " samples.", ex); if (z < (aic.ParentModule.NumberOfSamples - 1)) { System.Windows.Forms.MessageBox.Show("Incomplete data for " + aic.ChannelDescriptionString + " please check data file: ", ex.Message); } } dCurrentTime += dIncrement; } chart.ChartGroups[0].ChartData.SeriesList[i].X.CopyDataIn(xAxis.ToArray()); chart.ChartGroups[0].ChartData.SeriesList[i].Y.CopyDataIn(yAxis.ToArray()); if (double.MinValue != dMax) { if (aic is DTS.Slice.Control.Event.Module.AnalogInputChannel) { System.Diagnostics.Trace.WriteLine("Max for " + gc.Id + " is " + dMax.ToString() + " scale is " + aic.ScaleFactorMv.ToString()); } gc.DataMax = dMax; gc.TimeOfMax = dTimeOfMax; } if (double.MaxValue != dMin) { gc.DataMin = dMin; gc.TimeOfMin = dTimeOfMin; } } int startIndex = chart.ChartGroups[0].ChartData.SeriesList.Count; if (ThresholdsActive) { for (int i = 0; i < _thresholds.Count; i++) { chart.ChartGroups[0].ChartData.SeriesList.AddNewSeries(); chart.ChartGroups[0].ChartData.SeriesList[startIndex].LineStyle.Color = System.Drawing.Color.Black; chart.ChartGroups[0].ChartData.SeriesList[startIndex].LineStyle.Thickness = 2; chart.ChartGroups[0].ChartData.SeriesList[startIndex].SymbolStyle.Shape = C1.Win.C1Chart.SymbolShapeEnum.None; chart.ChartGroups[0].ChartData.SeriesList[startIndex].X.CopyDataIn(new double[] { DomainMin, DomainMax }); chart.ChartGroups[0].ChartData.SeriesList[startIndex].Y.CopyDataIn(new double[] { _thresholds[i], _thresholds[i] }); startIndex++; } } if (UseRangeMin) { chart.ChartArea.AxisY.AutoMin = false; chart.ChartArea.AxisY.Min = RangeMin; } else { chart.ChartArea.AxisY.AutoMin = true; RangeMin = chart.ChartGroups[0].ChartData.MinY; } if (UseRangeMax) { chart.ChartArea.AxisY.AutoMax = false; chart.ChartArea.AxisY.Max = RangeMax; } else { chart.ChartArea.AxisY.AutoMax = true; RangeMax = chart.ChartGroups[0].ChartData.MaxY; } chart.ChartArea.AxisX.Text = string.Format("Time ({0})", TimeUnits); } /// /// since the CFC changes the channel data, we need to know when it changes and redraw accordingly /// /// public void SetCFC(string cfc, ReportBase report) { SensorDB.FilterClass fc = new DTS.SensorDB.FilterClass(cfc); var e = _channels.GetEnumerator(); while (e.MoveNext()) { if (null != e.Current.Value.Channel) { DTS.Slice.Control.Event.Module.Channel aic = e.Current.Value.Channel.Channel as DTS.Slice.Control.Event.Module.Channel; if (null == aic) { return; } switch (fc.FClass) { case DTS.SensorDB.FilterClass.FilterClassType.AdHoc: throw new NotSupportedException(); case DTS.SensorDB.FilterClass.FilterClassType.CFC10: aic.CurrentFilter = new DTS.Slice.Control.Event.Module.Channel.SaeJ211Filter(DTS.Utilities.ChannelFilter.Class10); break; case DTS.SensorDB.FilterClass.FilterClassType.CFC1000: aic.CurrentFilter = new DTS.Slice.Control.Event.Module.Channel.SaeJ211Filter(DTS.Utilities.ChannelFilter.Class1000); break; case DTS.SensorDB.FilterClass.FilterClassType.CFC180: aic.CurrentFilter = new DTS.Slice.Control.Event.Module.Channel.SaeJ211Filter(DTS.Utilities.ChannelFilter.Class180); break; case DTS.SensorDB.FilterClass.FilterClassType.CFC60: aic.CurrentFilter = new DTS.Slice.Control.Event.Module.Channel.SaeJ211Filter(DTS.Utilities.ChannelFilter.Class60); break; case DTS.SensorDB.FilterClass.FilterClassType.CFC600: aic.CurrentFilter = new DTS.Slice.Control.Event.Module.Channel.SaeJ211Filter(DTS.Utilities.ChannelFilter.Class600); break; case DTS.SensorDB.FilterClass.FilterClassType.None: aic.CurrentFilter = new DTS.Slice.Control.Event.Module.Channel.SaeJ211Filter(DTS.Utilities.ChannelFilter.Unfiltered); break; } } } report.DrawGraph(Id, _chart); //Draw(_chart); } /// /// gets the scale factor from two different units /// assumes units are linearly scaled (example G's to m/sec^2) /// /// /// /// public static double GetScaleFactor(string desiredUnits, string actualUnits) { actualUnits = actualUnits.Trim(); //1 g = 9.80665 m / s2 desiredUnits = desiredUnits.Trim().ToLower(); actualUnits = actualUnits.Trim().ToLower(); if ( desiredUnits == actualUnits) { return 1D; } else if (actualUnits == "g" && desiredUnits.Contains("m/sec") ) { return 9.80665D; } else if (actualUnits.Contains("m/sec") && desiredUnits == "g") { return 1D / 9.80665D; }//"N", "kN" else if (actualUnits == "n" && desiredUnits == "kn") { return .001D; } else if (actualUnits == "kn" && desiredUnits == "n") { return 1000D; } else { throw new NotSupportedException("unknown conversion"); } } /// /// gets a value of a channel field in the graph /// (properties of channels) /// /// channel id /// field /// value public string GetValue(string id, GraphChannel.Fields field) { return _channels[id].GetValue(field); } public string GetValueTrunc2Places(string id, GraphChannel.Fields field) { return _channels[id].GetValueTrunc2Places(field); } /// /// gets a value of a graph field /// (properties of the graph) /// /// /// public string GetValue(Fields field) { switch (field) { case Fields.DomainMax: return DomainMax.ToString(); case Fields.DomainMin: return DomainMin.ToString(); case Fields.RangeMax: return RangeMax.ToString(Properties.Settings.Default.PROTECTIONREPORT_NumberFormat); case Fields.RangeMin: return RangeMin.ToString(Properties.Settings.Default.PROTECTIONREPORT_NumberFormat); case Fields.ThresholdInUse: return ThresholdsActive.ToString(); case Fields.Thresholds: return Thresholds; case Fields.UseRangeMax: return UseRangeMax.ToString(); case Fields.UseRangeMin: return UseRangeMin.ToString(); default: throw new NotImplementedException(); } } /// /// sets the value for a field of the graph /// (properties of graph) /// /// /// public void SetValue(Fields field, string value) { switch (field) { case Fields.DomainMax: { double d; if (double.TryParse(value.Trim(), out d)) { DomainMax = d; } } break; case Fields.DomainMin: { double d; if (double.TryParse(value.Trim(), out d)) { DomainMin = d; } } break; case Fields.RangeMax: { double d; if (double.TryParse(value.Trim(), out d)) { RangeMax = d; } } break; case Fields.RangeMin: { double d; if (double.TryParse(value.Trim(), out d)) { RangeMin = d; } } break; case Fields.ThresholdInUse: { bool b; if (Boolean.TryParse(value.Trim(), out b)) { ThresholdsActive = b; } } break; case Fields.Thresholds: { Thresholds = value; } break; case Fields.UseRangeMax: { UseRangeMax = Convert.ToBoolean(value.Trim()); } break; case Fields.UseRangeMin: { UseRangeMin = Convert.ToBoolean(value.Trim()); } break; } } /// /// whether graph minimum range is constrained /// private bool _useRangeMin = false; public bool UseRangeMin { get { return _useRangeMin; } set { SetProperty(ref _useRangeMin, value, "UseRangeMin"); Draw(_chart); } } /// /// whether graph maximum range is constrained /// private bool _useRangeMax = false; public bool UseRangeMax { get { return _useRangeMax; } set { SetProperty(ref _useRangeMax, value, "UseRangeMax"); Draw(_chart); } } /// /// fields/properties of a graph /// public enum Fields { RangeMin, UseRangeMin, RangeMax, UseRangeMax, DomainMin, DomainMax, Thresholds, ThresholdInUse } /// /// time units for the graph (seconds or ms) /// private string _timeUnits = "s"; public string TimeUnits { get { return _timeUnits; } set { if (_timeUnits != value) { if (value == "ms") { DomainMin = _domainMin * 1000D; DomainMax = _domainMax * 1000D; } else { DomainMin = _domainMin / 1000D; DomainMax = _domainMax / 1000D; } SetProperty(ref _timeUnits, value, "TimeUnits"); //convert domain min/max to new units.. Draw(_chart); } } } /// /// these functions make us WPF/dependency property friendly, and also /// adds a convenient way for us to notify consumers when a property has changed /// public event PropertyChangedEventHandler PropertyChanged; protected bool SetProperty(ref T storage, T value, String propertyName) { if (object.Equals(storage, value)) return false; storage = value; this.OnPropertyChanged(propertyName); return true; } protected void OnPropertyChanged(string propertyName) { var eventHandler = this.PropertyChanged; if (eventHandler != null) { eventHandler(this, new PropertyChangedEventArgs(propertyName)); } } /// /// graph id /// private string _id; public string Id { get { return _id; } } /// /// display name for graph /// private string _displayName; public string DisplayName { get { return _displayName; } set { SetProperty(ref _displayName, value, "DisplayName"); } } /// /// the minimum range of the graph (use UseRangeMin) to see if the graph is constrained to min or not /// private double _rangeMin; public double RangeMin { get { return _rangeMin; } set { if (value != _rangeMin) { SetProperty(ref _rangeMin, value, "RangeMin"); if (UseRangeMin) { Draw(_chart); } } } } /// /// the maximum range of the graph (use UseRangeMax to determine if graph is constrained to max or not) /// private double _rangeMax; public double RangeMax { get { return _rangeMax; } set { if (value != _rangeMax) { SetProperty(ref _rangeMax, value, "RangeMax"); if (UseRangeMax) { Draw(_chart); } } } } /// /// graph is always domain constrained, this is the min x axis value /// default is -.5s /// private double _domainMin = -.5; public double DomainMin { get { return _domainMin; } set { SetProperty(ref _domainMin, value, "DomainMin"); Draw(_chart); } } /// /// max x axis value (default is .5s) /// private double _domainMax = .5; public double DomainMax { get { return _domainMax; } set { if (value != _domainMax) { SetProperty(ref _domainMax, value, "DomainMax"); Draw(_chart); } } } /// /// collection of thresholds (just horizontal lines for now) /// private List _thresholds = new List(); public string Thresholds { get { List thresholds = new List(); foreach (var d in _thresholds) { if (!thresholds.Contains(d.ToString())) { thresholds.Add(d.ToString()); } } return string.Join(",", thresholds.ToArray()); } set { string[] tokens = value.Split(new char[] { ',' }); List thresholds = new List(); foreach (string token in tokens) { double d; if (double.TryParse(token.Trim(), out d)) { if (!thresholds.Contains(d)) { thresholds.Add(d); } } } SetProperty(ref _thresholds, thresholds, "Thresholds"); if (ThresholdsActive) { Draw(_chart); } } } /// /// draw thresholds or not /// private bool _bThresholdsActive = false; public bool ThresholdsActive { get { return _bThresholdsActive; } set { SetProperty(ref _bThresholdsActive, value, "ThresholdsActive"); Draw(_chart); } } /// /// list of channel ids for channels in the graph /// private List _channelIds = new List(); private Dictionary _channels = new Dictionary(); private MeasurementUnit[] _channelUnits; public MeasurementUnit[] GetChannelUnits() { return _channelUnits; } private MeasurementUnit _channelUnit; public MeasurementUnit GetChannelUnit() { return _channelUnit; } public void SetChannelUnit(MeasurementUnit mu) { SetProperty(ref _channelUnit, mu, "ChannelUnit"); var e = _channels.GetEnumerator(); while (e.MoveNext()) { string eu = mu.MainDisplayUnit; if (null == e.Current.Value) { continue; } if (null == e.Current.Value.Channel) { continue; } if (null == e.Current.Value.Channel.Channel) { continue; } if (e.Current.Value.Channel.Channel is DTS.DAS.Concepts.DAS.Channel.IEngineeringUnitAware) { if (!(e.Current.Value.Channel.Channel is DTS.Slice.Control.Event.Module.AnalogInputChannel)) { continue; } eu = ((DTS.DAS.Concepts.DAS.Channel.IEngineeringUnitAware)e.Current.Value.Channel.Channel).EngineeringUnits; if (!mu.UnitMatches(eu)) { double scaler = mu.GetScalerConversionFrom(eu); System.Diagnostics.Trace.WriteLine("Changed scale factor for " + e.Current.Value.Channel.Channel.ChannelDescriptionString + " from " + e.Current.Value.Channel.Channel.ScaleFactorMv.ToString() + " to " + (e.Current.Value.Channel.Channel.ScaleFactorMv * scaler).ToString()); e.Current.Value.Channel.Channel.ScaleFactorMv *= scaler; ((DTS.DAS.Concepts.DAS.Channel.IEngineeringUnitAware)e.Current.Value.Channel.Channel).EngineeringUnits = mu.ToString(); } } } } public ReportGraph(string id, string displayname, MeasurementUnit [] possibleUnits, GraphChannel[] channels, string thresholds) { _id = id; DisplayName = displayname; _channelUnits = possibleUnits; if (null != possibleUnits && possibleUnits.Length >= 1) { _channelUnit = possibleUnits[0]; } foreach (var channel in channels) { _channelIds.Add(channel.Id); _channels.Add(channel.Id, channel); channel.PropertyChanged += new PropertyChangedEventHandler(channel_PropertyChanged); } Thresholds = thresholds; } /// /// pass on when a channel or graph property changes /// this lets UI code react when a property changes /// /// /// public void channel_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == "Channel") { OnPropertyChanged("Channel"); } else { GraphChannel gc = sender as GraphChannel; OnPropertyChanged("graph-x-" + Id + "-x-" + gc.Id + "-x-" + e.PropertyName); } } /// /// the reviewtestchannel is the actual analoginputdaschannel, or the actual binary data for the file /// /// /// public ReviewTestChannel GetReviewTestChannel(string graphchannel) { return _channels[graphchannel].Channel; } public void SetReviewTestChannel(string graphchannel, ReviewTestChannel channel) { if (null != channel) { if (channel.Channel is DTS.Slice.Control.Event.Module.AnalogInputChannel) { MeasurementUnit unit = GetChannelUnit(); if (null != channel && null != channel.Channel) { var channelEU = (channel.Channel as DTS.DAS.Concepts.DAS.Channel.IEngineeringUnitAware).EngineeringUnits; if (unit.UnitMatches(channelEU)) { DTS.Slice.Control.Event.Module.AnalogInputChannel analog = channel.Channel as DTS.Slice.Control.Event.Module.AnalogInputChannel; double scaler = unit.GetScalerConversionFrom(channelEU); System.Diagnostics.Trace.WriteLine("Changing " + analog.ChannelDescriptionString + " from " + analog.ScaleFactorMv.ToString() + " to " + (analog.ScaleFactorMv * scaler).ToString()); analog.ScaleFactorMv *= scaler; analog.EngineeringUnits = unit.ToString(); } var graphChannel = GetGraphChannel(graphchannel); if (graphChannel.UseOffset) { channel.Channel.UserOffsetEU = graphChannel.Offset; } else { channel.Channel.UserOffsetEU = 0D; } } } } _channels[graphchannel].Channel = channel; } public GraphChannel GetGraphChannel(string graphChannel) { return _channels[graphChannel]; } /// /// returns all viable channels to fullfill the graph channel /// /// /// /// public ReviewTestChannel[] GetAvailableChannels(string target, ReviewTest test) { List channels = new List(); if (null != test) { try { foreach (var channel in test.Channels) { bool bAdded = false; string eu = GetEngineeringUnits(channel.Channel); foreach (var mu in GetChannelUnits()) { if (bAdded) { continue; } if (mu.UnitMatches(eu.Trim())) { if (!channels.Contains(channel)) { channels.Add(channel); bAdded = true; } } } } } catch (System.Exception ex) { System.Windows.Forms.MessageBox.Show(string.Format("Test missing data\r\n[{0}]", ex.Message), "Warning", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error); } } return channels.ToArray(); } public void Clear(ReviewTest test) { var e = _channels.GetEnumerator(); while (e.MoveNext()) { if (null != e.Current.Value.Channel) { e.Current.Value.Channel.Cleanup(); } e.Current.Value.Channel = null; } } public void SetDefaults(ReviewTest test) { var e = _channels.GetEnumerator(); Dictionary lookup = new Dictionary(); while (e.MoveNext()) { var bestchoices = from channel in GetAvailableChannels(e.Current.Key, test) where (channel.Channel as Slice.Control.Event.Module.AnalogInputChannel).Description.ToLower().Contains(e.Current.Value.ChannelNameHint.ToLower()) select channel; if (null != bestchoices && bestchoices.Count() > 0 && null == e.Current.Value.Channel) { lookup.Add(e.Current.Key, bestchoices.First()); } } try { var e2 = lookup.GetEnumerator(); while (e2.MoveNext()) { SetReviewTestChannel(e2.Current.Key, e2.Current.Value); } } catch (System.Exception) { } } /// /// returns the x plot for a given channel /// /// id of channel to get /// public double[] GetXPlot(string id) { int index = _channelIds.IndexOf(id); if (null != _chart) { return _chart.ChartGroups[0].ChartData.SeriesList[index].X.Cast().ToArray(); } else return new double[0]; } /// /// returns the y plot for a given channel /// /// channel id /// public double[] GetYPlot(string id) { int index = _channelIds.IndexOf(id); if (null != _chart) { return _chart.ChartGroups[0].ChartData.SeriesList[index].Y.Cast().ToArray(); } else return new double[0]; } } }