Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
MedicalChartHelper.cs
00001 //using System; 00002 //using System.Collections; 00003 //using System.Collections.Generic; 00004 //using System.ComponentModel; 00005 //using System.Linq; 00006 //using System.Text; 00007 //using System.Windows.Forms; 00008 //using Microsoft.Win32.SafeHandles; 00009 00010 namespace Maxim.MAX30101 00011 { 00012 #pragma warning disable 1574 00013 /// <summary> 00014 /// MedicalChartHelper class, an invisible "helper" class, 00015 /// helps manage an existing standard chart object. 00016 /// 00017 /// Initializes the standard 00018 /// System.Windows.Forms.DataVisualization.Charting.Chart chart 00019 /// with a maximum of 3 chart areas vertically aligned with each other. 00020 /// 00021 /// When data is appended to the chart, the chart series data 00022 /// is initially aligned with the left. Once the X axis reaches the 00023 /// right side of the screen, the chart begins scrolling so that 00024 /// newest data is aligned to the right edge. 00025 /// 00026 /// </summary> 00027 #pragma warning restore 1574 00028 public class MedicalChartHelper 00029 { 00030 private System.Windows.Forms.DataVisualization.Charting.Chart _chart; 00031 private string _chartArea1Name; 00032 private string _chartArea2Name; 00033 private string _chartArea3Name; 00034 private string _series1Name; 00035 private string _series2Name; 00036 private string _series3Name; 00037 private string _chartArea1AxisYTitle; 00038 private string _chartArea2AxisYTitle; 00039 private string _chartArea3AxisYTitle; 00040 00041 /// <summary> 00042 /// Constructor 00043 /// </summary> 00044 /// <param name="chart"></param> 00045 /// <param name="chartArea1Name"></param> 00046 /// <param name="series1Name"></param> 00047 /// <param name="chartArea1AxisYTitle"></param> 00048 /// <param name="chartArea2Name"></param> 00049 /// <param name="series2Name"></param> 00050 /// <param name="chartArea2AxisYTitle"></param> 00051 /// <param name="chartArea3Name"></param> 00052 /// <param name="series3Name"></param> 00053 /// <param name="chartArea3AxisYTitle"></param> 00054 public MedicalChartHelper(System.Windows.Forms.DataVisualization.Charting.Chart chart, 00055 string chartArea1Name = "ChartArea1Red", string series1Name = "SeriesRed", string chartArea1AxisYTitle = "Red ADC Code", 00056 string chartArea2Name = "ChartArea2IR", string series2Name = "SeriesIR", string chartArea2AxisYTitle = "IR ADC Code", 00057 string chartArea3Name = "ChartArea3Green", string series3Name = "SeriesGreen", string chartArea3AxisYTitle = "Green ADC Code", 00058 DataFormats dataFormat = DataFormats .FormatUnsigned 00059 ) 00060 { 00061 // implementation: see InitCharting() 00062 // default X axis length should be 10 seconds of data 00063 _chart = chart; 00064 _chartArea1Name = chartArea1Name; 00065 _chartArea2Name = chartArea2Name; 00066 _chartArea3Name = chartArea3Name; 00067 _series1Name = series1Name; 00068 _series2Name = series2Name; 00069 _series3Name = series3Name; 00070 _chartArea1AxisYTitle = chartArea1AxisYTitle; 00071 _chartArea2AxisYTitle = chartArea2AxisYTitle; 00072 _chartArea3AxisYTitle = chartArea3AxisYTitle; 00073 00074 DataFormat = dataFormat; 00075 00076 // VERIFY: OS24EVK-75 replace _xCount with _xCount1, _xCount2, _xCount3 00077 //_xCount = 0; 00078 _xCount1 = 0; 00079 _xCount2 = 0; 00080 _xCount3 = 0; 00081 00082 _chart.Titles[0].Visible = false; 00083 00084 _chart.ChartAreas[_chartArea2Name].AlignWithChartArea = _chartArea1Name; 00085 _chart.ChartAreas[_chartArea3Name].AlignWithChartArea = _chartArea1Name; 00086 00087 _chart.ChartAreas[_chartArea1Name].AxisY.Title = chartArea1AxisYTitle; 00088 _chart.ChartAreas[_chartArea2Name].AxisY.Title = chartArea2AxisYTitle; 00089 _chart.ChartAreas[_chartArea3Name].AxisY.Title = chartArea3AxisYTitle; 00090 00091 //_chart.ChartAreas[_chartArea1Name].AxisX.LabelStyle.Enabled = !_plotXAxisNoLabels; 00092 //_chart.ChartAreas[_chartArea2Name].AxisX.LabelStyle.Enabled = !_plotXAxisNoLabels; 00093 //_chart.ChartAreas[_chartArea3Name].AxisX.LabelStyle.Enabled = !_plotXAxisNoLabels; 00094 00095 _chart.ChartAreas[_chartArea1Name].Visible = true; // valid_Red; 00096 _chart.ChartAreas[_chartArea2Name].Visible = true; // valid_IR; 00097 _chart.ChartAreas[_chartArea3Name].Visible = true; // valid_Green; 00098 00099 _chart.Series[_series1Name].MarkerSize = 1; // Charting.Series.MarkerSize=0 makes no points visible 00100 _chart.Series[_series1Name].BorderWidth = 1; // Charting.Series.BorderWidth is actually the line thickness 00101 _chart.Series[_series1Name].XValueType = System.Windows.Forms.DataVisualization.Charting.ChartValueType.UInt32; 00102 _chart.Series[_series1Name].YValueType = System.Windows.Forms.DataVisualization.Charting.ChartValueType.Int32; 00103 00104 _chart.Series[_series2Name].MarkerSize = 1; // Charting.Series.MarkerSize=0 makes no points visible 00105 _chart.Series[_series2Name].BorderWidth = 1; // Charting.Series.BorderWidth is actually the line thickness 00106 _chart.Series[_series2Name].XValueType = System.Windows.Forms.DataVisualization.Charting.ChartValueType.UInt32; 00107 _chart.Series[_series2Name].YValueType = System.Windows.Forms.DataVisualization.Charting.ChartValueType.Int32; 00108 00109 _chart.Series[_series3Name].MarkerSize = 1; // Charting.Series.MarkerSize=0 makes no points visible 00110 _chart.Series[_series3Name].BorderWidth = 1; // Charting.Series.BorderWidth is actually the line thickness 00111 _chart.Series[_series3Name].XValueType = System.Windows.Forms.DataVisualization.Charting.ChartValueType.UInt32; 00112 _chart.Series[_series3Name].YValueType = System.Windows.Forms.DataVisualization.Charting.ChartValueType.Int32; 00113 } 00114 00115 /// <summary> 00116 /// Clear chart data, reset _xCount, reset _plotPointsToSkip 00117 /// </summary> 00118 public void Clear() 00119 { 00120 _chart.Series[_series1Name].Points.Clear(); 00121 _chart.Series[_series2Name].Points.Clear(); 00122 _chart.Series[_series3Name].Points.Clear(); 00123 // VERIFY: OS24EVK-75 replace _xCount with _xCount1, _xCount2, _xCount3 00124 //_xCount = 0; 00125 _xCount1 = 0; 00126 _xCount2 = 0; 00127 _xCount3 = 0; 00128 _plotPointsToSkip = 0; 00129 // https://jira.maxim-ic.com/browse/OS24EVK-32 replace numPlotTime with menu item Options | Plot Time 00130 _plotPoints = (int)_plotWindowTime * _SampleRate_Hz / SampleAverage_n; 00131 if (_plotPoints > _maxPlotPoints) 00132 { 00133 _plotPointsToSkip = _plotPoints / _maxPlotPoints - 1; 00134 _plotPoints = _maxPlotPoints; 00135 } 00136 } 00137 00138 /// <summary> 00139 /// Replacement for plotXAxisNoLabelsToolStripMenuItem.Checked 00140 /// </summary> 00141 public bool plotXAxisNoLabels { 00142 get { return _plotXAxisNoLabels; } 00143 set 00144 { 00145 _plotXAxisNoLabels = value; 00146 _chart.ChartAreas[_chartArea1Name].AxisX.LabelStyle.Enabled = !_plotXAxisNoLabels; 00147 _chart.ChartAreas[_chartArea2Name].AxisX.LabelStyle.Enabled = !_plotXAxisNoLabels; 00148 _chart.ChartAreas[_chartArea3Name].AxisX.LabelStyle.Enabled = !_plotXAxisNoLabels; 00149 } 00150 } 00151 private bool _plotXAxisNoLabels = true; // VERIFY: default plotXAxisNoLabelsToolStripMenuItem.Checked 00152 00153 /// <summary> 00154 /// Replacement for plotXAxisTimeToolStripMenuItem.Checked 00155 /// </summary> 00156 public bool plotXAxisTime 00157 { 00158 get { return _plotXAxisTime; } 00159 set 00160 { 00161 _plotXAxisTime = value; 00162 } 00163 } 00164 private bool _plotXAxisTime = true; // VERIFY: default plotXAxisTimeToolStripMenuItem.Checked 00165 00166 private int _SampleRate_Hz = 100; 00167 /// <summary> 00168 /// Sample rate. Replacement for int.Parse(cboSampleRate.Text) 00169 /// </summary> 00170 public int SampleRate_Hz 00171 { 00172 get { return _SampleRate_Hz; } 00173 set 00174 { 00175 _SampleRate_Hz = value; 00176 00177 //_AutoScaleEveryNSamples = 4 * _SampleRate_Hz; // 4 seconds assuming sample rate int.Parse(cboSampleRate.Text) == 100 samples per second 00178 00179 // VERIFY: OS24EVK-73 Sample Avg 2 breaks chart scrolling 00180 // update _plotPoints which depends on myMAX30101.SampleAverage_n 00181 _plotPoints = (int)_plotWindowTime * _SampleRate_Hz / SampleAverage_n; 00182 if (_plotPoints > _maxPlotPoints) 00183 { 00184 _plotPointsToSkip = _plotPoints / _maxPlotPoints - 1; 00185 _plotPoints = _maxPlotPoints; 00186 } 00187 } 00188 } 00189 00190 private int _SampleAverage_n = 1; 00191 /// <summary> 00192 /// Replacement for myMAX30101.SampleAverage_n 00193 /// </summary> 00194 public int SampleAverage_n 00195 { 00196 get { return _SampleAverage_n; } 00197 set 00198 { 00199 _SampleAverage_n = value; 00200 // VERIFY: OS24EVK-73 Sample Avg 2 breaks chart scrolling 00201 // update _plotPoints which depends on myMAX30101.SampleAverage_n 00202 _plotPoints = (int)_plotWindowTime * _SampleRate_Hz / SampleAverage_n; 00203 if (_plotPoints > _maxPlotPoints) 00204 { 00205 _plotPointsToSkip = _plotPoints / _maxPlotPoints - 1; 00206 _plotPoints = _maxPlotPoints; 00207 } 00208 } 00209 } 00210 00211 // VERIFY: IMPLEMENT OS24EVK-70 Maxim.MAX30101.MedicalChartHelper._xCount int instead of double? 00212 // VERIFY: OS24EVK-75 replace _xCount with _xCount1, _xCount2, _xCount3 00213 //public double _xCount = 0; 00214 public int _xCount1 = 0; 00215 public int _xCount2 = 0; 00216 public int _xCount3 = 0; 00217 00218 /// <summary> 00219 /// The actual number of points on the plot (<= _maxPlotPoints). The actual number of points may be less than _maxPlotPoints in order to maintain ~numPlotTime.Value of plotted data 00220 /// </summary> 00221 public int _plotPoints = 500; 00222 00223 private int _plotWindowTime = 5; // plot window in seconds 00224 public int plotWindowTime 00225 { 00226 get 00227 { 00228 return _plotWindowTime; 00229 } 00230 set 00231 { 00232 _plotWindowTime = value; 00233 _plotPoints = (int)_plotWindowTime * _SampleRate_Hz / SampleAverage_n; 00234 if (_plotPoints > _maxPlotPoints) 00235 { 00236 _plotPointsToSkip = _plotPoints / _maxPlotPoints - 1; 00237 _plotPoints = _maxPlotPoints; 00238 } 00239 } 00240 } 00241 00242 private int _maxPlotPoints = 2000; // maximum allowed number of points that can be plotted per series. Higher Fs will have more than 500 pts for a given numPlotTime.Value, so the number of samples per point will need to be increased accordingly to give maximum _maxPlotPoints 00243 00244 public int _plotPointsToSkip = 0; 00245 00246 // https://jira.maxim-ic.com/browse/OS24EVK-34 Autoscale tuning parameters 00247 // https://jira.maxim-ic.com/browse/OS24EVK-34 Change _AutoScaleTimeInterval to _AutoScaleEveryNSamples and use sampleNumber to measure time 00248 // https://jira.maxim-ic.com/browse/OS24EVK-42 Autoscale tuning (Larry 2014-11-24) _AutoScaleEveryNSamples 00249 // - Slow the Autoscale update rate every 4 seconds 00250 //private int _AutoScaleEveryNSamples = 400; // 4 seconds assuming sample rate int.Parse(cboSampleRate.Text) == 100 samples per second 00251 00252 // private double _AutoScaleMarginXLeft; // ignore old data at left of graph 00253 // private double _AutoScaleMarginXRight; // ignore new data at right of graph? probably 0 by default 00254 00255 /// <summary> 00256 /// chartMax empty area above dataMax 00257 /// </summary> 00258 private static double _AutoScaleMarginYTop = 0.125; 00259 00260 /// <summary> 00261 /// chartMin empty area below dataMin 00262 /// </summary> 00263 private static double _AutoScaleMarginYBottom = 0.125; 00264 00265 /// <summary> 00266 /// Minimum Y span: allow up to 10 counts per division (i.e. 50 LSBs) 00267 /// 00268 /// (5 * _AutoScaleYmultiples) minimum allowed span of chartMax-chartMin, 00269 /// to avoid focusing on LSB noise 00270 /// </summary> 00271 private static double _AutoScaleMinSpanY = 500; 00272 00273 /// <summary> 00274 /// Y axis: No decimals. Round to nearest multiple of 100. 00275 /// </summary> 00276 private static double _AutoScaleYmultiples = 100; 00277 00278 // Calculated initial default chart limits _AutoscaleInitialChartMaxY .. _AutoscaleInitialChartMinY 00279 int _AutoscaleInitialChartMaxY = 00280 ( 00281 (int) 00282 ((_AutoScaleMinSpanY * (1.0 + _AutoScaleMarginYTop + _AutoScaleMarginYBottom)) / _AutoScaleYmultiples) 00283 ) 00284 * (int)(_AutoScaleYmultiples); 00285 int _AutoscaleInitialChartMinY = 0; 00286 00287 /// <summary> 00288 /// Append data to chart area 1 series 1 (Optical: Red. Accelerometer: X) 00289 /// 00290 /// @post _xCount1 is updated 00291 /// </summary> 00292 /// <param name="rawIntArrayYData"></param> 00293 /// <param name="firstNewXIndex"></param> 00294 /// <param name="lastXIndex"></param> 00295 public void AppendDataChartArea1(int[] rawIntArrayYData, int firstNewXIndex, int lastXIndex) 00296 { 00297 // AppendDataChartArea(_chartArea1Name, _series1Name, rawIntArrayYData, firstNewXIndex, lastXIndex); 00298 string chartAreaName = _chartArea1Name; 00299 string seriesName = _series1Name; 00300 00301 for (int index = firstNewXIndex; index <= lastXIndex; index++) 00302 { 00303 int yData = rawIntArrayYData[index]; 00304 // VERIFY: OS24EVK-75 replace AutoScaleEvaluate dataFormatIs16bit2sComplement with if (DataFormat == DataFormats.Format16bit2sComplement) 00305 if (DataFormat == DataFormats .Format16bit2sComplement) 00306 { 00307 // VERIFY: OS24EVK-57 interpret rawX rawY rawZ as 16-bit 2's complement 00308 if (yData > 0x8000) 00309 { 00310 yData = yData - 0x10000; 00311 } 00312 } 00313 int count = _chart.Series[seriesName].Points.Count; 00314 // VERIFY: OS24EVK-75 replace _xCount with _xCount1, _xCount2, _xCount3 00315 double xCoord = _xCount1 * (_plotPointsToSkip + 1) * SampleAverage_n / (plotXAxisTime ? SampleRate_Hz : 1); 00316 00317 //_chart.Series[seriesName].Points.AddXY(xCoord, yData); 00318 _chart.Series[seriesName].Points.AddY(yData); 00319 _chart.ResetAutoValues(); 00320 00321 while (count > _plotPoints) 00322 { 00323 _chart.Series[seriesName].Points.RemoveAt(0); 00324 count = _chart.Series[seriesName].Points.Count; 00325 } 00326 // VERIFY: OS24EVK-75 replace _xCount with _xCount1, _xCount2, _xCount3 00327 _xCount1++; 00328 } 00329 //AutoScaleEvaluate(e.rawRedData, e.sampleNumberOffset, numSamples - 1, _chart, chartAreaName); 00330 } 00331 00332 /// <summary> 00333 /// Append data to chart area 2 series 2 (Optical: IR. Accelerometer: Y) 00334 /// 00335 /// @post _xCount2 is updated 00336 /// </summary> 00337 /// <param name="rawIntArrayYData"></param> 00338 /// <param name="firstNewXIndex"></param> 00339 /// <param name="lastXIndex"></param> 00340 public void AppendDataChartArea2(int[] rawIntArrayYData, int firstNewXIndex, int lastXIndex) 00341 { 00342 // AppendDataChartArea(_chartArea2Name, _series2Name, rawIntArrayYData, firstNewXIndex, lastXIndex); 00343 string chartAreaName = _chartArea2Name; 00344 string seriesName = _series2Name; 00345 00346 for (int index = firstNewXIndex; index <= lastXIndex; index++) 00347 { 00348 int yData = rawIntArrayYData[index]; 00349 // VERIFY: OS24EVK-75 replace AutoScaleEvaluate dataFormatIs16bit2sComplement with if (DataFormat == DataFormats.Format16bit2sComplement) 00350 if (DataFormat == DataFormats .Format16bit2sComplement) 00351 { 00352 // VERIFY: OS24EVK-57 interpret rawX rawY rawZ as 16-bit 2's complement 00353 if (yData > 0x8000) 00354 { 00355 yData = yData - 0x10000; 00356 } 00357 } 00358 int count = _chart.Series[seriesName].Points.Count; 00359 // VERIFY: OS24EVK-75 replace _xCount with _xCount1, _xCount2, _xCount3 00360 double xCoord = _xCount2 * (_plotPointsToSkip + 1) * SampleAverage_n / (plotXAxisTime ? SampleRate_Hz : 1); 00361 00362 _chart.Series[seriesName].Points.AddXY(xCoord, yData); 00363 _chart.ResetAutoValues(); 00364 00365 while (count > _plotPoints) 00366 { 00367 _chart.Series[seriesName].Points.RemoveAt(0); 00368 count = _chart.Series[seriesName].Points.Count; 00369 } 00370 // VERIFY: OS24EVK-75 replace _xCount with _xCount1, _xCount2, _xCount3 00371 _xCount2++; 00372 } 00373 //AutoScaleEvaluate(e.rawRedData, e.sampleNumberOffset, numSamples - 1, _chart, chartAreaName); 00374 } 00375 00376 /// <summary> 00377 /// Append data to chart area 3 series 3 (Optical: Green. Accelerometer: Z) 00378 /// 00379 /// @post _xCount3 is updated 00380 /// </summary> 00381 /// <param name="rawIntArrayYData"></param> 00382 /// <param name="firstNewXIndex"></param> 00383 /// <param name="lastXIndex"></param> 00384 public void AppendDataChartArea3(int[] rawIntArrayYData, int firstNewXIndex, int lastXIndex) 00385 { 00386 // AppendDataChartArea(_chartArea3Name, _series3Name, rawIntArrayYData, firstNewXIndex, lastXIndex); 00387 string chartAreaName = _chartArea3Name; 00388 string seriesName = _series3Name; 00389 00390 for (int index = firstNewXIndex; index <= lastXIndex; index++) 00391 { 00392 int yData = rawIntArrayYData[index]; 00393 // VERIFY: OS24EVK-75 replace AutoScaleEvaluate dataFormatIs16bit2sComplement with if (DataFormat == DataFormats.Format16bit2sComplement) 00394 if (DataFormat == DataFormats .Format16bit2sComplement) 00395 { 00396 // VERIFY: OS24EVK-57 interpret rawX rawY rawZ as 16-bit 2's complement 00397 if (yData > 0x8000) 00398 { 00399 yData = yData - 0x10000; 00400 } 00401 } 00402 int count = _chart.Series[seriesName].Points.Count; 00403 // VERIFY: OS24EVK-75 replace _xCount with _xCount1, _xCount2, _xCount3 00404 double xCoord = _xCount3 * (_plotPointsToSkip + 1) * SampleAverage_n / (plotXAxisTime ? SampleRate_Hz : 1); 00405 00406 _chart.Series[seriesName].Points.AddXY(xCoord, yData); 00407 _chart.ResetAutoValues(); 00408 00409 while (count > _plotPoints) 00410 { 00411 _chart.Series[seriesName].Points.RemoveAt(0); 00412 count = _chart.Series[seriesName].Points.Count; 00413 } 00414 // VERIFY: OS24EVK-75 replace _xCount with _xCount1, _xCount2, _xCount3 00415 _xCount3++; 00416 } 00417 //AutoScaleEvaluate(e.rawRedData, e.sampleNumberOffset, numSamples - 1, _chart, chartAreaName); 00418 } 00419 00420 #if PROFILER 00421 //// TODO1: OS24EVK-54 profiling: capture max interval, number of intervals, cumulative interval 00422 System.Diagnostics.Stopwatch _profilingStopwatchAutoScaleEvaluate = new System.Diagnostics.Stopwatch(); // cumulative timing 00423 int _profilingStopwatchAutoScaleEvaluate_NumIntervals; 00424 #endif // PROFILER 00425 00426 public void AutoScaleEvaluate1(int[] rawIntArrayYData, int firstNewXIndex, int lastXIndex) 00427 { 00428 AutoScaleEvaluate(rawIntArrayYData, firstNewXIndex, lastXIndex, _chartArea1Name); 00429 } 00430 00431 public void AutoScaleEvaluate2(int[] rawIntArrayYData, int firstNewXIndex, int lastXIndex) 00432 { 00433 AutoScaleEvaluate(rawIntArrayYData, firstNewXIndex, lastXIndex, _chartArea2Name); 00434 } 00435 00436 public void AutoScaleEvaluate3(int[] rawIntArrayYData, int firstNewXIndex, int lastXIndex) 00437 { 00438 AutoScaleEvaluate(rawIntArrayYData, firstNewXIndex, lastXIndex, _chartArea3Name); 00439 } 00440 00441 // TODO1: OS24EVK-57 OS24EVK-54 AutoScaleEvaluate(int[] rawIntArrayYData,...) Calculate autoscale min/max/SampleVariance from the raw integer data. This should be much faster as it doesn't involve arbitrarily converting into floating-point numbers or excessive indexing. 00442 public void AutoScaleEvaluate(int[] rawIntArrayYData, 00443 int firstNewXIndex, 00444 int lastXIndex, 00445 /*Chart chart,*/ string chartAreaName 00446 // TODO1: OS24EVK-75 replace AutoScaleEvaluate dataFormatIs16bit2sComplement with if (DataFormat == DataFormats.Format16bit2sComplement) 00447 //bool dataFormatIs16bit2sComplement = false 00448 //string seriesName, 00449 //int sampleNumber, 00450 //ref int sampleNumberPreviousAutoscale 00451 ) 00452 { 00453 #if PROFILER 00454 // TODO1: OS24EVK-54 profiling: capture max interval, number of intervals, cumulative interval 00455 _profilingStopwatchAutoScaleEvaluate.Start(); 00456 _profilingStopwatchAutoScaleEvaluate_NumIntervals = _profilingStopwatchAutoScaleEvaluate_NumIntervals + 1; 00457 #endif // PROFILER 00458 00459 // TODO1: OS24EVK-57 OS24EVK-54 Calculate autoscale min/max/SampleVariance from the raw integer data. This should be much faster as it doesn't involve arbitrarily converting into floating-point numbers or excessive indexing. 00460 00461 // https://jira.maxim-ic.com/browse/OS24EVK-34 AutoScaleEvaluate() use _AutoScaleMarginXLeft; // ignore old data at left of graph 00462 // https://jira.maxim-ic.com/browse/OS24EVK-34 AutoScaleEvaluate() use _AutoScaleMarginXRight; // ignore new data at right of graph? probably 0 by default 00463 //const int firstPointIndex = 0; 00464 // advance the left and right X limits 00465 // https://jira.maxim-ic.com/browse/OS24EVK-48 Graph X axis - Replace hidden chkPlotTime with a menu option plotXAxisTimeToolStripMenuItem.Checked 00466 // https://jira.maxim-ic.com/browse/OS24EVK-47 Graph scrolling needs to be smooth progression like a paper tape chart even when (plotXAxisTimeToolStripMenuItem.Checked is true) 00467 // per Larry Skrenes 2014-11-24: graph scrolling needs to be smooth progression like a paper tape chart. 00468 // When (plotXAxisTimeToolStripMenuItem.Checked is false) graph scrolling is already smooth. 00469 // When (plotXAxisTimeToolStripMenuItem.Checked is true) graph scrolling moves in staccato 1-second steps. Larry hates this. 00470 int xWidth = _plotPoints * (_plotPointsToSkip + 1) * SampleAverage_n / (plotXAxisTime ? SampleRate_Hz : 1); 00471 // VERIFY: OS24EVK-73 Sample Avg 2 breaks chart scrolling -- xLastXIndex = lastXIndex * myMAX30101.SampleAverage_n 00472 int xLastXIndex = lastXIndex * SampleAverage_n / (plotXAxisTime ? SampleRate_Hz : 1); 00473 //int xWidth = _plotPoints * (_plotPointsToSkip + 1) * _cboSampleAvg / (false ? int.Parse(cboSampleRate.Text) : 1); 00474 //int xWidth = _plotPoints * (_plotPointsToSkip + 1) * _cboSampleAvg / (1); 00475 //int xWidth = _plotPoints * (_plotPointsToSkip + 1) * _cboSampleAvg; 00476 // https://jira.maxim-ic.com/browse/OS24EVK-15 Fix plot X axis jumping around (_plotWindowTime) 00477 // 0.0 --> 0 1 2 3 4 5 00478 // 0.0 --> 0_ 1 2 3 4 5 00479 // 0.0 --> 0__1 2 3 4 5 00480 // 0.0 --> 0__1_ 2 3 4 5 00481 // 0.0 --> 0__1__2 3 4 5 00482 // 0.0 --> 0__1__2_ 3 4 5 00483 // 0.0 --> 0__1__2__3 4 5 00484 // 0.0 --> 0__1__2__3_ 4 5 00485 // 0.0 --> 0__1__2__3__4 5 00486 // 0.0 --> 0__1__2__3__4_ 5 00487 // 0.0 --> 0__1__2__3__4__5 00488 // 0.5 --> 1__2__3__4__5_ 6 00489 // 1.0 --> 1__2__3__4__5__6 00490 // 1.5 --> 2__3__4__5__6_ 7 00491 // 2.0 --> 2__3__4__5__6__7 00492 // https://jira.maxim-ic.com/browse/OS24EVK-47 Graph scrolling needs to be smooth progression like a paper tape chart even when (plotXAxisTimeToolStripMenuItem.Checked is true) 00493 // per Larry Skrenes 2014-11-24: graph scrolling needs to be smooth progression like a paper tape chart. 00494 // When (plotXAxisTimeToolStripMenuItem.Checked is false) graph scrolling is already smooth. 00495 // When (plotXAxisTimeToolStripMenuItem.Checked is true) graph scrolling moves in staccato 1-second steps. Larry hates this. 00496 // VERIFY: OS24EVK-76 Autoscale delay time is affected by Sample Average - firstVisiblePointIndex 00497 //int firstVisiblePointIndex = lastXIndex - (_plotPoints * (_plotPointsToSkip + 1) * SampleAverage_n); 00498 int firstVisiblePointIndex = lastXIndex - (_plotPoints * (_plotPointsToSkip + 1)); 00499 if (firstVisiblePointIndex < 0) { firstVisiblePointIndex = 0; } 00500 // 00501 // VERIFY: OS24EVK-73 Sample Avg 2 breaks chart scrolling -- xLastXIndex instead of lastXIndex 00502 int firstVisiblePointX = xLastXIndex - xWidth; // (int)chart.Series[0].Points[firstPointIndex].XValue; 00503 if (firstVisiblePointX < 0) { firstVisiblePointX = 0; } 00504 //int firstPointX = (int)System.Math.Ceiling(chart.Series[seriesName].Points[firstPointIndex].XValue); 00505 //if ((chart.Series[seriesName].Points[firstPointIndex].XValue) < 0.1) 00506 //{ 00507 // firstPointX = (int)System.Math.Floor(chart.Series[seriesName].Points[firstPointIndex].XValue); 00508 //} 00509 _chart.ChartAreas[chartAreaName].AxisX.Minimum = firstVisiblePointX; 00510 // https://jira.maxim-ic.com/browse/OS24EVK-47 Graph scrolling needs to be smooth progression like a paper tape chart even when (plotXAxisTimeToolStripMenuItem.Checked is true) 00511 //chart.ChartAreas[0].AxisX.Maximum = (int)System.Math.Ceiling((double)firstPointX + xWidth); 00512 _chart.ChartAreas[chartAreaName].AxisX.Maximum = firstVisiblePointX + xWidth; 00513 00514 if (lastXIndex /* chart.Series[seriesName].Points.Count */ < 1) 00515 { 00516 //int iMaxY_chart_default = (_AutoScaleMinSpanY * (1.0 + _AutoScaleMarginYTop + _AutoScaleMarginYBottom)) / _AutoScaleYmultiples; 00517 //int _AutoscaleInitialChartMaxY = iMaxY_chart_default * _AutoScaleYmultiples; 00518 //int _AutoscaleInitialChartMaxY = 00519 // ( 00520 // (int) 00521 // ((_AutoScaleMinSpanY * (1.0 + _AutoScaleMarginYTop + _AutoScaleMarginYBottom)) / _AutoScaleYmultiples) 00522 // ) 00523 // * (int)(_AutoScaleYmultiples); 00524 //int _AutoscaleInitialChartMinY = 0; 00525 _chart.ChartAreas[chartAreaName].AxisY.Maximum = _AutoscaleInitialChartMaxY; 00526 _chart.ChartAreas[chartAreaName].AxisY.Minimum = _AutoscaleInitialChartMinY; 00527 #if PROFILER 00528 // TODO1: OS24EVK-54 profiling: capture max interval, number of intervals, cumulative interval 00529 _profilingStopwatchAutoScaleEvaluate.Stop(); 00530 #endif // PROFILER 00531 return; 00532 } 00533 00534 // // TODO1: OS24EVK-57 OS24EVK-54 scan new data rawIntArrayYData[firstNewIndex .. lastIndex]. As is this code only triggers if the last point is extreme. 00535 // int newestPointIndex = lastXIndex; // chart.Series[seriesName].Points.Count - 1; 00536 // int newestPointY = rawIntArrayYData[lastXIndex]; // (int)chart.Series[seriesName].Points[newestPointIndex].YValues[0]; 00537 // if ((newestPointY < chart.ChartAreas[chartAreaName].AxisY.Maximum) 00538 // && (newestPointY > chart.ChartAreas[chartAreaName].AxisY.Minimum) 00539 // ) 00540 // { 00541 // // https://jira.maxim-ic.com/browse/OS24EVK-34 always re-evaluate chart when new data exceeds Minimum or Maximum 00542 // // But if we just return at this point, then autoscale will never contract to fit smaller data series. 00543 // // 00544 // // https://jira.maxim-ic.com/browse/OS24EVK-34 Change _AutoScaleTimeInterval to _AutoScaleEveryNSamples and use sampleNumber to measure time 00545 // // https://jira.maxim-ic.com/browse/OS24EVK-34 AutoScaleEvaluate() after every _AutoScaleEveryNSamples, re-scan data and fit chart to data 00546 // // if ( interval has been less than _AutoScaleEveryNSamples ) { return; } 00547 // if ((sampleNumber - sampleNumberPreviousAutoscale) < _AutoScaleEveryNSamples) 00548 // { 00549 //#if PROFILER 00550 // // TODO1: OS24EVK-54 profiling: capture max interval, number of intervals, cumulative interval 00551 // _profilingStopwatchAutoScaleEvaluate.Stop(); 00552 //#endif // PROFILER 00553 // return; 00554 // } 00555 // } 00556 00557 int chartMinYValue = (int)_chart.ChartAreas[chartAreaName].AxisY.Minimum; // rawIntArrayYData[firstVisiblePointX] - 1; 00558 int chartMaxYValue = (int)_chart.ChartAreas[chartAreaName].AxisY.Maximum; // rawIntArrayYData[firstVisiblePointX] + 1; 00559 // VERIFY: OS24EVK-73 Sample Avg 2 breaks chart scrolling - firstVisiblePointIndex instead of firstVisiblePointX 00560 int dataMinYValue = rawIntArrayYData[firstVisiblePointIndex] - 1; 00561 int dataMaxYValue = rawIntArrayYData[firstVisiblePointIndex] + 1; 00562 for (int index = firstVisiblePointIndex; index <= lastXIndex; index++) 00563 { 00564 int Y = rawIntArrayYData[index]; 00565 // VERIFY: OS24EVK-57 OS24EVK-54 improve chart throughput: AutoScaleEvaluate() dataFormatIs16bit2sComplement=true for Accelerometer X Y Z data 00566 // VERIFY: OS24EVK-75 replace AutoScaleEvaluate dataFormatIs16bit2sComplement with if (DataFormat == DataFormats.Format16bit2sComplement) 00567 if (DataFormat == DataFormats.Format16bit2sComplement) 00568 { 00569 // VERIFY: OS24EVK-57 interpret rawX rawY rawZ as 16-bit 2's complement 00570 if (Y > 0x8000) 00571 { 00572 Y = Y - 0x10000; 00573 } 00574 } 00575 //else 00576 //{ 00577 // Y = Y + 0; // debug breakpoint for code path verification 00578 //} 00579 if (dataMinYValue > Y) 00580 { 00581 dataMinYValue = Y; 00582 } 00583 if (dataMaxYValue < Y) 00584 { 00585 dataMaxYValue = Y; 00586 } 00587 } 00588 // rawIntArrayYData[firstIndex..lastIndex] spans the range from dataMinYValue .. dataMaxYValue 00589 double dataSpanY = (dataMaxYValue - dataMinYValue); 00590 double dataCenterY = (dataMaxYValue + dataMinYValue) / 2; 00591 00592 bool isAllDataVisible = ( 00593 (chartMinYValue < dataMaxYValue) && (dataMaxYValue < chartMaxYValue) 00594 && 00595 (chartMinYValue < dataMinYValue) && (dataMinYValue < chartMaxYValue) 00596 ); 00597 if (isAllDataVisible) 00598 { 00599 // all data is within the chart limits, but is the chart sufficiently zoomed in? 00600 double relativeDataSpan = (double)dataSpanY / (chartMaxYValue - chartMinYValue); 00601 if (relativeDataSpan > 0.65) 00602 { 00603 // the current chart scale is good enough, do not update 00604 #if PROFILER 00605 // TODO1: OS24EVK-54 profiling: capture max interval, number of intervals, cumulative interval 00606 _profilingStopwatchAutoScaleEvaluate.Stop(); 00607 #endif // PROFILER 00608 return; 00609 } 00610 } 00611 // https://jira.maxim-ic.com/browse/OS24EVK-34 AutoScaleEvaluate() use _AutoScaleMinSpanY; // minimum allowed span of chartMax-chartMin, to avoid focusing on LSB noise 00612 if (dataSpanY < _AutoScaleMinSpanY) 00613 { 00614 // https://jira.maxim-ic.com/browse/OS24EVK-34 Autoscale - Minimum span (in case dataMax-dataMin is too small) to avoid focusing on LSB noise 00615 dataSpanY = _AutoScaleMinSpanY; 00616 } 00617 else 00618 { 00619 // https://jira.maxim-ic.com/browse/OS24EVK-34 Autoscale - Apply a Top margin - Bottom margin = 12% .. 88% when mapping Chart max-min to Data max-min 00620 // https://jira.maxim-ic.com/browse/OS24EVK-34 AutoScaleEvaluate() use _AutoScaleMarginYTop; // chartMax empty area above dataMax 00621 // https://jira.maxim-ic.com/browse/OS24EVK-34 AutoScaleEvaluate() use _AutoScaleMarginYBottom; // chartMin empty area below dataMin 00622 // dataSpanY = dataSpanY * 1.25; 00623 // dataSpanY = dataSpanY * (1.0 + 0.125 + 0.125); 00624 dataSpanY = dataSpanY * (1.0 + _AutoScaleMarginYTop + _AutoScaleMarginYBottom); 00625 00626 // https://jira.maxim-ic.com/browse/OS24EVK-42 Autoscale tuning (Larry 2014-11-24) Round to nearest multiple of 100. 00627 // - Y axis: No decimals. Round to nearest multiple of 100. 00628 // private double _AutoScaleYmultiples = 100; 00629 //int tempdataSpanY = (int)(dataSpanY / _AutoScaleYmultiples); 00630 //dataSpanY = tempdataSpanY * _AutoScaleYmultiples; 00631 //dataSpanY = (double)((int)dataSpanY / _AutoScaleYmultiples) * _AutoScaleYmultiples; 00632 //dataCenterY = (double)((int)dataCenterY / _AutoScaleYmultiples) * _AutoScaleYmultiples; 00633 } 00634 // https://jira.maxim-ic.com/browse/OS24EVK-34 Autoscale - There are 5 vertical divisions; keep the minor axis tick values integer 00635 //int YaxisDivisions = (int)System.Math.Ceiling(dataSpanY / 6.0); 00636 //dataSpanY = YaxisDivisions * 6; 00637 // https://jira.maxim-ic.com/browse/OS24EVK-42 Autoscale tuning (Larry 2014-11-24) Round to nearest multiple of 100. 00638 // - Y axis: No decimals. Round to nearest multiple of 100. 00639 // private double _AutoScaleYmultiples = 100; 00640 //int tempdataCenterY = (int)(dataCenterY / (_AutoScaleYmultiples*2)); 00641 //dataCenterY = tempdataCenterY * (_AutoScaleYmultiples*2); 00642 00643 // chart.ChartAreas[0].AxisY.Maximum = dataMaxYValue; 00644 // chart.ChartAreas[0].AxisY.Minimum = dataMinYValue; 00645 //chart.ChartAreas[0].AxisY.Maximum = System.Math.Ceiling(dataCenterY + (dataSpanY / 2)); // dataMaxYValue; 00646 //chart.ChartAreas[0].AxisY.Minimum = System.Math.Floor(dataCenterY - (dataSpanY / 2)); // dataMinYValue; 00647 // https://jira.maxim-ic.com/browse/OS24EVK-42 Autoscale tuning (Larry 2014-11-24) Round to nearest multiple of 100. 00648 // - Y axis: No decimals. Round to nearest multiple of 100. 00649 // private double _AutoScaleYmultiples = 100; 00650 int iMaxY_target = (int)((dataCenterY + (dataSpanY / 2)) / _AutoScaleYmultiples); 00651 int iMinY_target = (int)((dataCenterY - (dataSpanY / 2)) / _AutoScaleYmultiples); 00652 00653 double dampingConstantK = 0.2; 00654 int iMaxY_damped = iMaxY_target; 00655 int iMinY_damped = iMinY_target; 00656 if (double.IsNaN(_chart.ChartAreas[chartAreaName].AxisY.Maximum) == false) 00657 { 00658 int iMaxY_chart = (int)(_chart.ChartAreas[chartAreaName].AxisY.Maximum / _AutoScaleYmultiples); 00659 int iMinY_chart = (int)(_chart.ChartAreas[chartAreaName].AxisY.Minimum / _AutoScaleYmultiples); 00660 iMaxY_damped = (int)((dampingConstantK * (double)iMaxY_target) + ((1 - dampingConstantK) * (double)iMaxY_chart) + 0.5); 00661 iMinY_damped = (int)((dampingConstantK * (double)iMinY_target) + ((1 - dampingConstantK) * (double)iMinY_chart)); 00662 } 00663 double maxY = iMaxY_target * _AutoScaleYmultiples; 00664 double minY = iMinY_target * _AutoScaleYmultiples; 00665 _chart.ChartAreas[chartAreaName].AxisY.Maximum = maxY; 00666 _chart.ChartAreas[chartAreaName].AxisY.Minimum = minY; 00667 // if chart.ChartAreas[chartAreaName].AxisY.Maximum is NaN then just assign maxY 00668 //if (double.IsNaN(chart.ChartAreas[chartAreaName].AxisY.Maximum)) 00669 //{ 00670 // chart.ChartAreas[chartAreaName].AxisY.Maximum = maxY; 00671 // chart.ChartAreas[chartAreaName].AxisY.Minimum = minY; 00672 //} 00673 //else 00674 //{ 00675 // chart.ChartAreas[chartAreaName].AxisY.Maximum = (dampingConstantK * maxY) + ((1 - dampingConstantK) * chart.ChartAreas[chartAreaName].AxisY.Maximum); 00676 // chart.ChartAreas[chartAreaName].AxisY.Minimum = (dampingConstantK * minY) + ((1 - dampingConstantK) * chart.ChartAreas[chartAreaName].AxisY.Minimum); 00677 //} 00678 00679 #if PROFILER 00680 // TODO1: OS24EVK-54 profiling: capture max interval, number of intervals, cumulative interval 00681 _profilingStopwatchAutoScaleEvaluate.Stop(); 00682 #endif // PROFILER 00683 } 00684 00685 /// <summary> 00686 /// Raw data format 00687 /// </summary> 00688 public DataFormats DataFormat = DataFormats .FormatUnsigned; 00689 00690 public enum DataFormats 00691 { 00692 /// <summary> 00693 /// Interpret raw data as unsigned values 00694 /// </summary> 00695 FormatUnsigned, 00696 00697 /// <summary> 00698 /// Interpret raw data as 16-bit, signed 2's complement values 00699 /// </summary> 00700 Format16bit2sComplement, 00701 } 00702 00703 } // public class MedicalChartHelper 00704 00705 } // namespace Maxim.MAX30101
Generated on Tue Jul 12 2022 21:52:39 by
1.7.2