This is the latest working repository used in our demo video for the Maxim to display temperature readings on Bluetooth
hspguisourcev301/HspGuiSourceV301/HSPGui/MedicalChartHelper.cs
- Committer:
- darienf
- Date:
- 2021-05-02
- Revision:
- 5:bc128a16232f
- Parent:
- 3:36de8b9e4b1a
File content as of revision 5:bc128a16232f:
//using System; //using System.Collections; //using System.Collections.Generic; //using System.ComponentModel; //using System.Linq; //using System.Text; //using System.Windows.Forms; //using Microsoft.Win32.SafeHandles; namespace Maxim.MAX30101 { #pragma warning disable 1574 /// <summary> /// MedicalChartHelper class, an invisible "helper" class, /// helps manage an existing standard chart object. /// /// Initializes the standard /// System.Windows.Forms.DataVisualization.Charting.Chart chart /// with a maximum of 3 chart areas vertically aligned with each other. /// /// When data is appended to the chart, the chart series data /// is initially aligned with the left. Once the X axis reaches the /// right side of the screen, the chart begins scrolling so that /// newest data is aligned to the right edge. /// /// </summary> #pragma warning restore 1574 public class MedicalChartHelper { private System.Windows.Forms.DataVisualization.Charting.Chart _chart; private string _chartArea1Name; private string _chartArea2Name; private string _chartArea3Name; private string _series1Name; private string _series2Name; private string _series3Name; private string _chartArea1AxisYTitle; private string _chartArea2AxisYTitle; private string _chartArea3AxisYTitle; /// <summary> /// Constructor /// </summary> /// <param name="chart"></param> /// <param name="chartArea1Name"></param> /// <param name="series1Name"></param> /// <param name="chartArea1AxisYTitle"></param> /// <param name="chartArea2Name"></param> /// <param name="series2Name"></param> /// <param name="chartArea2AxisYTitle"></param> /// <param name="chartArea3Name"></param> /// <param name="series3Name"></param> /// <param name="chartArea3AxisYTitle"></param> public MedicalChartHelper(System.Windows.Forms.DataVisualization.Charting.Chart chart, string chartArea1Name = "ChartArea1Red", string series1Name = "SeriesRed", string chartArea1AxisYTitle = "Red ADC Code", string chartArea2Name = "ChartArea2IR", string series2Name = "SeriesIR", string chartArea2AxisYTitle = "IR ADC Code", string chartArea3Name = "ChartArea3Green", string series3Name = "SeriesGreen", string chartArea3AxisYTitle = "Green ADC Code", DataFormats dataFormat = DataFormats.FormatUnsigned ) { // implementation: see InitCharting() // default X axis length should be 10 seconds of data _chart = chart; _chartArea1Name = chartArea1Name; _chartArea2Name = chartArea2Name; _chartArea3Name = chartArea3Name; _series1Name = series1Name; _series2Name = series2Name; _series3Name = series3Name; _chartArea1AxisYTitle = chartArea1AxisYTitle; _chartArea2AxisYTitle = chartArea2AxisYTitle; _chartArea3AxisYTitle = chartArea3AxisYTitle; DataFormat = dataFormat; // VERIFY: OS24EVK-75 replace _xCount with _xCount1, _xCount2, _xCount3 //_xCount = 0; _xCount1 = 0; _xCount2 = 0; _xCount3 = 0; _chart.Titles[0].Visible = false; _chart.ChartAreas[_chartArea2Name].AlignWithChartArea = _chartArea1Name; _chart.ChartAreas[_chartArea3Name].AlignWithChartArea = _chartArea1Name; _chart.ChartAreas[_chartArea1Name].AxisY.Title = chartArea1AxisYTitle; _chart.ChartAreas[_chartArea2Name].AxisY.Title = chartArea2AxisYTitle; _chart.ChartAreas[_chartArea3Name].AxisY.Title = chartArea3AxisYTitle; //_chart.ChartAreas[_chartArea1Name].AxisX.LabelStyle.Enabled = !_plotXAxisNoLabels; //_chart.ChartAreas[_chartArea2Name].AxisX.LabelStyle.Enabled = !_plotXAxisNoLabels; //_chart.ChartAreas[_chartArea3Name].AxisX.LabelStyle.Enabled = !_plotXAxisNoLabels; _chart.ChartAreas[_chartArea1Name].Visible = true; // valid_Red; _chart.ChartAreas[_chartArea2Name].Visible = true; // valid_IR; _chart.ChartAreas[_chartArea3Name].Visible = true; // valid_Green; _chart.Series[_series1Name].MarkerSize = 1; // Charting.Series.MarkerSize=0 makes no points visible _chart.Series[_series1Name].BorderWidth = 1; // Charting.Series.BorderWidth is actually the line thickness _chart.Series[_series1Name].XValueType = System.Windows.Forms.DataVisualization.Charting.ChartValueType.UInt32; _chart.Series[_series1Name].YValueType = System.Windows.Forms.DataVisualization.Charting.ChartValueType.Int32; _chart.Series[_series2Name].MarkerSize = 1; // Charting.Series.MarkerSize=0 makes no points visible _chart.Series[_series2Name].BorderWidth = 1; // Charting.Series.BorderWidth is actually the line thickness _chart.Series[_series2Name].XValueType = System.Windows.Forms.DataVisualization.Charting.ChartValueType.UInt32; _chart.Series[_series2Name].YValueType = System.Windows.Forms.DataVisualization.Charting.ChartValueType.Int32; _chart.Series[_series3Name].MarkerSize = 1; // Charting.Series.MarkerSize=0 makes no points visible _chart.Series[_series3Name].BorderWidth = 1; // Charting.Series.BorderWidth is actually the line thickness _chart.Series[_series3Name].XValueType = System.Windows.Forms.DataVisualization.Charting.ChartValueType.UInt32; _chart.Series[_series3Name].YValueType = System.Windows.Forms.DataVisualization.Charting.ChartValueType.Int32; } /// <summary> /// Clear chart data, reset _xCount, reset _plotPointsToSkip /// </summary> public void Clear() { _chart.Series[_series1Name].Points.Clear(); _chart.Series[_series2Name].Points.Clear(); _chart.Series[_series3Name].Points.Clear(); // VERIFY: OS24EVK-75 replace _xCount with _xCount1, _xCount2, _xCount3 //_xCount = 0; _xCount1 = 0; _xCount2 = 0; _xCount3 = 0; _plotPointsToSkip = 0; // https://jira.maxim-ic.com/browse/OS24EVK-32 replace numPlotTime with menu item Options | Plot Time _plotPoints = (int)_plotWindowTime * _SampleRate_Hz / SampleAverage_n; if (_plotPoints > _maxPlotPoints) { _plotPointsToSkip = _plotPoints / _maxPlotPoints - 1; _plotPoints = _maxPlotPoints; } } /// <summary> /// Replacement for plotXAxisNoLabelsToolStripMenuItem.Checked /// </summary> public bool plotXAxisNoLabels { get { return _plotXAxisNoLabels; } set { _plotXAxisNoLabels = value; _chart.ChartAreas[_chartArea1Name].AxisX.LabelStyle.Enabled = !_plotXAxisNoLabels; _chart.ChartAreas[_chartArea2Name].AxisX.LabelStyle.Enabled = !_plotXAxisNoLabels; _chart.ChartAreas[_chartArea3Name].AxisX.LabelStyle.Enabled = !_plotXAxisNoLabels; } } private bool _plotXAxisNoLabels = true; // VERIFY: default plotXAxisNoLabelsToolStripMenuItem.Checked /// <summary> /// Replacement for plotXAxisTimeToolStripMenuItem.Checked /// </summary> public bool plotXAxisTime { get { return _plotXAxisTime; } set { _plotXAxisTime = value; } } private bool _plotXAxisTime = true; // VERIFY: default plotXAxisTimeToolStripMenuItem.Checked private int _SampleRate_Hz = 100; /// <summary> /// Sample rate. Replacement for int.Parse(cboSampleRate.Text) /// </summary> public int SampleRate_Hz { get { return _SampleRate_Hz; } set { _SampleRate_Hz = value; //_AutoScaleEveryNSamples = 4 * _SampleRate_Hz; // 4 seconds assuming sample rate int.Parse(cboSampleRate.Text) == 100 samples per second // VERIFY: OS24EVK-73 Sample Avg 2 breaks chart scrolling // update _plotPoints which depends on myMAX30101.SampleAverage_n _plotPoints = (int)_plotWindowTime * _SampleRate_Hz / SampleAverage_n; if (_plotPoints > _maxPlotPoints) { _plotPointsToSkip = _plotPoints / _maxPlotPoints - 1; _plotPoints = _maxPlotPoints; } } } private int _SampleAverage_n = 1; /// <summary> /// Replacement for myMAX30101.SampleAverage_n /// </summary> public int SampleAverage_n { get { return _SampleAverage_n; } set { _SampleAverage_n = value; // VERIFY: OS24EVK-73 Sample Avg 2 breaks chart scrolling // update _plotPoints which depends on myMAX30101.SampleAverage_n _plotPoints = (int)_plotWindowTime * _SampleRate_Hz / SampleAverage_n; if (_plotPoints > _maxPlotPoints) { _plotPointsToSkip = _plotPoints / _maxPlotPoints - 1; _plotPoints = _maxPlotPoints; } } } // VERIFY: IMPLEMENT OS24EVK-70 Maxim.MAX30101.MedicalChartHelper._xCount int instead of double? // VERIFY: OS24EVK-75 replace _xCount with _xCount1, _xCount2, _xCount3 //public double _xCount = 0; public int _xCount1 = 0; public int _xCount2 = 0; public int _xCount3 = 0; /// <summary> /// 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 /// </summary> public int _plotPoints = 500; private int _plotWindowTime = 5; // plot window in seconds public int plotWindowTime { get { return _plotWindowTime; } set { _plotWindowTime = value; _plotPoints = (int)_plotWindowTime * _SampleRate_Hz / SampleAverage_n; if (_plotPoints > _maxPlotPoints) { _plotPointsToSkip = _plotPoints / _maxPlotPoints - 1; _plotPoints = _maxPlotPoints; } } } 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 public int _plotPointsToSkip = 0; // https://jira.maxim-ic.com/browse/OS24EVK-34 Autoscale tuning parameters // https://jira.maxim-ic.com/browse/OS24EVK-34 Change _AutoScaleTimeInterval to _AutoScaleEveryNSamples and use sampleNumber to measure time // https://jira.maxim-ic.com/browse/OS24EVK-42 Autoscale tuning (Larry 2014-11-24) _AutoScaleEveryNSamples // - Slow the Autoscale update rate every 4 seconds //private int _AutoScaleEveryNSamples = 400; // 4 seconds assuming sample rate int.Parse(cboSampleRate.Text) == 100 samples per second // private double _AutoScaleMarginXLeft; // ignore old data at left of graph // private double _AutoScaleMarginXRight; // ignore new data at right of graph? probably 0 by default /// <summary> /// chartMax empty area above dataMax /// </summary> private static double _AutoScaleMarginYTop = 0.125; /// <summary> /// chartMin empty area below dataMin /// </summary> private static double _AutoScaleMarginYBottom = 0.125; /// <summary> /// Minimum Y span: allow up to 10 counts per division (i.e. 50 LSBs) /// /// (5 * _AutoScaleYmultiples) minimum allowed span of chartMax-chartMin, /// to avoid focusing on LSB noise /// </summary> private static double _AutoScaleMinSpanY = 500; /// <summary> /// Y axis: No decimals. Round to nearest multiple of 100. /// </summary> private static double _AutoScaleYmultiples = 100; // Calculated initial default chart limits _AutoscaleInitialChartMaxY .. _AutoscaleInitialChartMinY int _AutoscaleInitialChartMaxY = ( (int) ((_AutoScaleMinSpanY * (1.0 + _AutoScaleMarginYTop + _AutoScaleMarginYBottom)) / _AutoScaleYmultiples) ) * (int)(_AutoScaleYmultiples); int _AutoscaleInitialChartMinY = 0; /// <summary> /// Append data to chart area 1 series 1 (Optical: Red. Accelerometer: X) /// /// @post _xCount1 is updated /// </summary> /// <param name="rawIntArrayYData"></param> /// <param name="firstNewXIndex"></param> /// <param name="lastXIndex"></param> public void AppendDataChartArea1(int[] rawIntArrayYData, int firstNewXIndex, int lastXIndex) { // AppendDataChartArea(_chartArea1Name, _series1Name, rawIntArrayYData, firstNewXIndex, lastXIndex); string chartAreaName = _chartArea1Name; string seriesName = _series1Name; for (int index = firstNewXIndex; index <= lastXIndex; index++) { int yData = rawIntArrayYData[index]; // VERIFY: OS24EVK-75 replace AutoScaleEvaluate dataFormatIs16bit2sComplement with if (DataFormat == DataFormats.Format16bit2sComplement) if (DataFormat == DataFormats.Format16bit2sComplement) { // VERIFY: OS24EVK-57 interpret rawX rawY rawZ as 16-bit 2's complement if (yData > 0x8000) { yData = yData - 0x10000; } } int count = _chart.Series[seriesName].Points.Count; // VERIFY: OS24EVK-75 replace _xCount with _xCount1, _xCount2, _xCount3 double xCoord = _xCount1 * (_plotPointsToSkip + 1) * SampleAverage_n / (plotXAxisTime ? SampleRate_Hz : 1); //_chart.Series[seriesName].Points.AddXY(xCoord, yData); _chart.Series[seriesName].Points.AddY(yData); _chart.ResetAutoValues(); while (count > _plotPoints) { _chart.Series[seriesName].Points.RemoveAt(0); count = _chart.Series[seriesName].Points.Count; } // VERIFY: OS24EVK-75 replace _xCount with _xCount1, _xCount2, _xCount3 _xCount1++; } //AutoScaleEvaluate(e.rawRedData, e.sampleNumberOffset, numSamples - 1, _chart, chartAreaName); } /// <summary> /// Append data to chart area 2 series 2 (Optical: IR. Accelerometer: Y) /// /// @post _xCount2 is updated /// </summary> /// <param name="rawIntArrayYData"></param> /// <param name="firstNewXIndex"></param> /// <param name="lastXIndex"></param> public void AppendDataChartArea2(int[] rawIntArrayYData, int firstNewXIndex, int lastXIndex) { // AppendDataChartArea(_chartArea2Name, _series2Name, rawIntArrayYData, firstNewXIndex, lastXIndex); string chartAreaName = _chartArea2Name; string seriesName = _series2Name; for (int index = firstNewXIndex; index <= lastXIndex; index++) { int yData = rawIntArrayYData[index]; // VERIFY: OS24EVK-75 replace AutoScaleEvaluate dataFormatIs16bit2sComplement with if (DataFormat == DataFormats.Format16bit2sComplement) if (DataFormat == DataFormats.Format16bit2sComplement) { // VERIFY: OS24EVK-57 interpret rawX rawY rawZ as 16-bit 2's complement if (yData > 0x8000) { yData = yData - 0x10000; } } int count = _chart.Series[seriesName].Points.Count; // VERIFY: OS24EVK-75 replace _xCount with _xCount1, _xCount2, _xCount3 double xCoord = _xCount2 * (_plotPointsToSkip + 1) * SampleAverage_n / (plotXAxisTime ? SampleRate_Hz : 1); _chart.Series[seriesName].Points.AddXY(xCoord, yData); _chart.ResetAutoValues(); while (count > _plotPoints) { _chart.Series[seriesName].Points.RemoveAt(0); count = _chart.Series[seriesName].Points.Count; } // VERIFY: OS24EVK-75 replace _xCount with _xCount1, _xCount2, _xCount3 _xCount2++; } //AutoScaleEvaluate(e.rawRedData, e.sampleNumberOffset, numSamples - 1, _chart, chartAreaName); } /// <summary> /// Append data to chart area 3 series 3 (Optical: Green. Accelerometer: Z) /// /// @post _xCount3 is updated /// </summary> /// <param name="rawIntArrayYData"></param> /// <param name="firstNewXIndex"></param> /// <param name="lastXIndex"></param> public void AppendDataChartArea3(int[] rawIntArrayYData, int firstNewXIndex, int lastXIndex) { // AppendDataChartArea(_chartArea3Name, _series3Name, rawIntArrayYData, firstNewXIndex, lastXIndex); string chartAreaName = _chartArea3Name; string seriesName = _series3Name; for (int index = firstNewXIndex; index <= lastXIndex; index++) { int yData = rawIntArrayYData[index]; // VERIFY: OS24EVK-75 replace AutoScaleEvaluate dataFormatIs16bit2sComplement with if (DataFormat == DataFormats.Format16bit2sComplement) if (DataFormat == DataFormats.Format16bit2sComplement) { // VERIFY: OS24EVK-57 interpret rawX rawY rawZ as 16-bit 2's complement if (yData > 0x8000) { yData = yData - 0x10000; } } int count = _chart.Series[seriesName].Points.Count; // VERIFY: OS24EVK-75 replace _xCount with _xCount1, _xCount2, _xCount3 double xCoord = _xCount3 * (_plotPointsToSkip + 1) * SampleAverage_n / (plotXAxisTime ? SampleRate_Hz : 1); _chart.Series[seriesName].Points.AddXY(xCoord, yData); _chart.ResetAutoValues(); while (count > _plotPoints) { _chart.Series[seriesName].Points.RemoveAt(0); count = _chart.Series[seriesName].Points.Count; } // VERIFY: OS24EVK-75 replace _xCount with _xCount1, _xCount2, _xCount3 _xCount3++; } //AutoScaleEvaluate(e.rawRedData, e.sampleNumberOffset, numSamples - 1, _chart, chartAreaName); } #if PROFILER //// TODO1: OS24EVK-54 profiling: capture max interval, number of intervals, cumulative interval System.Diagnostics.Stopwatch _profilingStopwatchAutoScaleEvaluate = new System.Diagnostics.Stopwatch(); // cumulative timing int _profilingStopwatchAutoScaleEvaluate_NumIntervals; #endif // PROFILER public void AutoScaleEvaluate1(int[] rawIntArrayYData, int firstNewXIndex, int lastXIndex) { AutoScaleEvaluate(rawIntArrayYData, firstNewXIndex, lastXIndex, _chartArea1Name); } public void AutoScaleEvaluate2(int[] rawIntArrayYData, int firstNewXIndex, int lastXIndex) { AutoScaleEvaluate(rawIntArrayYData, firstNewXIndex, lastXIndex, _chartArea2Name); } public void AutoScaleEvaluate3(int[] rawIntArrayYData, int firstNewXIndex, int lastXIndex) { AutoScaleEvaluate(rawIntArrayYData, firstNewXIndex, lastXIndex, _chartArea3Name); } // 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. public void AutoScaleEvaluate(int[] rawIntArrayYData, int firstNewXIndex, int lastXIndex, /*Chart chart,*/ string chartAreaName // TODO1: OS24EVK-75 replace AutoScaleEvaluate dataFormatIs16bit2sComplement with if (DataFormat == DataFormats.Format16bit2sComplement) //bool dataFormatIs16bit2sComplement = false //string seriesName, //int sampleNumber, //ref int sampleNumberPreviousAutoscale ) { #if PROFILER // TODO1: OS24EVK-54 profiling: capture max interval, number of intervals, cumulative interval _profilingStopwatchAutoScaleEvaluate.Start(); _profilingStopwatchAutoScaleEvaluate_NumIntervals = _profilingStopwatchAutoScaleEvaluate_NumIntervals + 1; #endif // PROFILER // 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. // https://jira.maxim-ic.com/browse/OS24EVK-34 AutoScaleEvaluate() use _AutoScaleMarginXLeft; // ignore old data at left of graph // https://jira.maxim-ic.com/browse/OS24EVK-34 AutoScaleEvaluate() use _AutoScaleMarginXRight; // ignore new data at right of graph? probably 0 by default //const int firstPointIndex = 0; // advance the left and right X limits // https://jira.maxim-ic.com/browse/OS24EVK-48 Graph X axis - Replace hidden chkPlotTime with a menu option plotXAxisTimeToolStripMenuItem.Checked // 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) // per Larry Skrenes 2014-11-24: graph scrolling needs to be smooth progression like a paper tape chart. // When (plotXAxisTimeToolStripMenuItem.Checked is false) graph scrolling is already smooth. // When (plotXAxisTimeToolStripMenuItem.Checked is true) graph scrolling moves in staccato 1-second steps. Larry hates this. int xWidth = _plotPoints * (_plotPointsToSkip + 1) * SampleAverage_n / (plotXAxisTime ? SampleRate_Hz : 1); // VERIFY: OS24EVK-73 Sample Avg 2 breaks chart scrolling -- xLastXIndex = lastXIndex * myMAX30101.SampleAverage_n int xLastXIndex = lastXIndex * SampleAverage_n / (plotXAxisTime ? SampleRate_Hz : 1); //int xWidth = _plotPoints * (_plotPointsToSkip + 1) * _cboSampleAvg / (false ? int.Parse(cboSampleRate.Text) : 1); //int xWidth = _plotPoints * (_plotPointsToSkip + 1) * _cboSampleAvg / (1); //int xWidth = _plotPoints * (_plotPointsToSkip + 1) * _cboSampleAvg; // https://jira.maxim-ic.com/browse/OS24EVK-15 Fix plot X axis jumping around (_plotWindowTime) // 0.0 --> 0 1 2 3 4 5 // 0.0 --> 0_ 1 2 3 4 5 // 0.0 --> 0__1 2 3 4 5 // 0.0 --> 0__1_ 2 3 4 5 // 0.0 --> 0__1__2 3 4 5 // 0.0 --> 0__1__2_ 3 4 5 // 0.0 --> 0__1__2__3 4 5 // 0.0 --> 0__1__2__3_ 4 5 // 0.0 --> 0__1__2__3__4 5 // 0.0 --> 0__1__2__3__4_ 5 // 0.0 --> 0__1__2__3__4__5 // 0.5 --> 1__2__3__4__5_ 6 // 1.0 --> 1__2__3__4__5__6 // 1.5 --> 2__3__4__5__6_ 7 // 2.0 --> 2__3__4__5__6__7 // 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) // per Larry Skrenes 2014-11-24: graph scrolling needs to be smooth progression like a paper tape chart. // When (plotXAxisTimeToolStripMenuItem.Checked is false) graph scrolling is already smooth. // When (plotXAxisTimeToolStripMenuItem.Checked is true) graph scrolling moves in staccato 1-second steps. Larry hates this. // VERIFY: OS24EVK-76 Autoscale delay time is affected by Sample Average - firstVisiblePointIndex //int firstVisiblePointIndex = lastXIndex - (_plotPoints * (_plotPointsToSkip + 1) * SampleAverage_n); int firstVisiblePointIndex = lastXIndex - (_plotPoints * (_plotPointsToSkip + 1)); if (firstVisiblePointIndex < 0) { firstVisiblePointIndex = 0; } // // VERIFY: OS24EVK-73 Sample Avg 2 breaks chart scrolling -- xLastXIndex instead of lastXIndex int firstVisiblePointX = xLastXIndex - xWidth; // (int)chart.Series[0].Points[firstPointIndex].XValue; if (firstVisiblePointX < 0) { firstVisiblePointX = 0; } //int firstPointX = (int)System.Math.Ceiling(chart.Series[seriesName].Points[firstPointIndex].XValue); //if ((chart.Series[seriesName].Points[firstPointIndex].XValue) < 0.1) //{ // firstPointX = (int)System.Math.Floor(chart.Series[seriesName].Points[firstPointIndex].XValue); //} _chart.ChartAreas[chartAreaName].AxisX.Minimum = firstVisiblePointX; // 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) //chart.ChartAreas[0].AxisX.Maximum = (int)System.Math.Ceiling((double)firstPointX + xWidth); _chart.ChartAreas[chartAreaName].AxisX.Maximum = firstVisiblePointX + xWidth; if (lastXIndex /* chart.Series[seriesName].Points.Count */ < 1) { //int iMaxY_chart_default = (_AutoScaleMinSpanY * (1.0 + _AutoScaleMarginYTop + _AutoScaleMarginYBottom)) / _AutoScaleYmultiples; //int _AutoscaleInitialChartMaxY = iMaxY_chart_default * _AutoScaleYmultiples; //int _AutoscaleInitialChartMaxY = // ( // (int) // ((_AutoScaleMinSpanY * (1.0 + _AutoScaleMarginYTop + _AutoScaleMarginYBottom)) / _AutoScaleYmultiples) // ) // * (int)(_AutoScaleYmultiples); //int _AutoscaleInitialChartMinY = 0; _chart.ChartAreas[chartAreaName].AxisY.Maximum = _AutoscaleInitialChartMaxY; _chart.ChartAreas[chartAreaName].AxisY.Minimum = _AutoscaleInitialChartMinY; #if PROFILER // TODO1: OS24EVK-54 profiling: capture max interval, number of intervals, cumulative interval _profilingStopwatchAutoScaleEvaluate.Stop(); #endif // PROFILER return; } // // TODO1: OS24EVK-57 OS24EVK-54 scan new data rawIntArrayYData[firstNewIndex .. lastIndex]. As is this code only triggers if the last point is extreme. // int newestPointIndex = lastXIndex; // chart.Series[seriesName].Points.Count - 1; // int newestPointY = rawIntArrayYData[lastXIndex]; // (int)chart.Series[seriesName].Points[newestPointIndex].YValues[0]; // if ((newestPointY < chart.ChartAreas[chartAreaName].AxisY.Maximum) // && (newestPointY > chart.ChartAreas[chartAreaName].AxisY.Minimum) // ) // { // // https://jira.maxim-ic.com/browse/OS24EVK-34 always re-evaluate chart when new data exceeds Minimum or Maximum // // But if we just return at this point, then autoscale will never contract to fit smaller data series. // // // // https://jira.maxim-ic.com/browse/OS24EVK-34 Change _AutoScaleTimeInterval to _AutoScaleEveryNSamples and use sampleNumber to measure time // // https://jira.maxim-ic.com/browse/OS24EVK-34 AutoScaleEvaluate() after every _AutoScaleEveryNSamples, re-scan data and fit chart to data // // if ( interval has been less than _AutoScaleEveryNSamples ) { return; } // if ((sampleNumber - sampleNumberPreviousAutoscale) < _AutoScaleEveryNSamples) // { //#if PROFILER // // TODO1: OS24EVK-54 profiling: capture max interval, number of intervals, cumulative interval // _profilingStopwatchAutoScaleEvaluate.Stop(); //#endif // PROFILER // return; // } // } int chartMinYValue = (int)_chart.ChartAreas[chartAreaName].AxisY.Minimum; // rawIntArrayYData[firstVisiblePointX] - 1; int chartMaxYValue = (int)_chart.ChartAreas[chartAreaName].AxisY.Maximum; // rawIntArrayYData[firstVisiblePointX] + 1; // VERIFY: OS24EVK-73 Sample Avg 2 breaks chart scrolling - firstVisiblePointIndex instead of firstVisiblePointX int dataMinYValue = rawIntArrayYData[firstVisiblePointIndex] - 1; int dataMaxYValue = rawIntArrayYData[firstVisiblePointIndex] + 1; for (int index = firstVisiblePointIndex; index <= lastXIndex; index++) { int Y = rawIntArrayYData[index]; // VERIFY: OS24EVK-57 OS24EVK-54 improve chart throughput: AutoScaleEvaluate() dataFormatIs16bit2sComplement=true for Accelerometer X Y Z data // VERIFY: OS24EVK-75 replace AutoScaleEvaluate dataFormatIs16bit2sComplement with if (DataFormat == DataFormats.Format16bit2sComplement) if (DataFormat == DataFormats.Format16bit2sComplement) { // VERIFY: OS24EVK-57 interpret rawX rawY rawZ as 16-bit 2's complement if (Y > 0x8000) { Y = Y - 0x10000; } } //else //{ // Y = Y + 0; // debug breakpoint for code path verification //} if (dataMinYValue > Y) { dataMinYValue = Y; } if (dataMaxYValue < Y) { dataMaxYValue = Y; } } // rawIntArrayYData[firstIndex..lastIndex] spans the range from dataMinYValue .. dataMaxYValue double dataSpanY = (dataMaxYValue - dataMinYValue); double dataCenterY = (dataMaxYValue + dataMinYValue) / 2; bool isAllDataVisible = ( (chartMinYValue < dataMaxYValue) && (dataMaxYValue < chartMaxYValue) && (chartMinYValue < dataMinYValue) && (dataMinYValue < chartMaxYValue) ); if (isAllDataVisible) { // all data is within the chart limits, but is the chart sufficiently zoomed in? double relativeDataSpan = (double)dataSpanY / (chartMaxYValue - chartMinYValue); if (relativeDataSpan > 0.65) { // the current chart scale is good enough, do not update #if PROFILER // TODO1: OS24EVK-54 profiling: capture max interval, number of intervals, cumulative interval _profilingStopwatchAutoScaleEvaluate.Stop(); #endif // PROFILER return; } } // https://jira.maxim-ic.com/browse/OS24EVK-34 AutoScaleEvaluate() use _AutoScaleMinSpanY; // minimum allowed span of chartMax-chartMin, to avoid focusing on LSB noise if (dataSpanY < _AutoScaleMinSpanY) { // https://jira.maxim-ic.com/browse/OS24EVK-34 Autoscale - Minimum span (in case dataMax-dataMin is too small) to avoid focusing on LSB noise dataSpanY = _AutoScaleMinSpanY; } else { // 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 // https://jira.maxim-ic.com/browse/OS24EVK-34 AutoScaleEvaluate() use _AutoScaleMarginYTop; // chartMax empty area above dataMax // https://jira.maxim-ic.com/browse/OS24EVK-34 AutoScaleEvaluate() use _AutoScaleMarginYBottom; // chartMin empty area below dataMin // dataSpanY = dataSpanY * 1.25; // dataSpanY = dataSpanY * (1.0 + 0.125 + 0.125); dataSpanY = dataSpanY * (1.0 + _AutoScaleMarginYTop + _AutoScaleMarginYBottom); // https://jira.maxim-ic.com/browse/OS24EVK-42 Autoscale tuning (Larry 2014-11-24) Round to nearest multiple of 100. // - Y axis: No decimals. Round to nearest multiple of 100. // private double _AutoScaleYmultiples = 100; //int tempdataSpanY = (int)(dataSpanY / _AutoScaleYmultiples); //dataSpanY = tempdataSpanY * _AutoScaleYmultiples; //dataSpanY = (double)((int)dataSpanY / _AutoScaleYmultiples) * _AutoScaleYmultiples; //dataCenterY = (double)((int)dataCenterY / _AutoScaleYmultiples) * _AutoScaleYmultiples; } // https://jira.maxim-ic.com/browse/OS24EVK-34 Autoscale - There are 5 vertical divisions; keep the minor axis tick values integer //int YaxisDivisions = (int)System.Math.Ceiling(dataSpanY / 6.0); //dataSpanY = YaxisDivisions * 6; // https://jira.maxim-ic.com/browse/OS24EVK-42 Autoscale tuning (Larry 2014-11-24) Round to nearest multiple of 100. // - Y axis: No decimals. Round to nearest multiple of 100. // private double _AutoScaleYmultiples = 100; //int tempdataCenterY = (int)(dataCenterY / (_AutoScaleYmultiples*2)); //dataCenterY = tempdataCenterY * (_AutoScaleYmultiples*2); // chart.ChartAreas[0].AxisY.Maximum = dataMaxYValue; // chart.ChartAreas[0].AxisY.Minimum = dataMinYValue; //chart.ChartAreas[0].AxisY.Maximum = System.Math.Ceiling(dataCenterY + (dataSpanY / 2)); // dataMaxYValue; //chart.ChartAreas[0].AxisY.Minimum = System.Math.Floor(dataCenterY - (dataSpanY / 2)); // dataMinYValue; // https://jira.maxim-ic.com/browse/OS24EVK-42 Autoscale tuning (Larry 2014-11-24) Round to nearest multiple of 100. // - Y axis: No decimals. Round to nearest multiple of 100. // private double _AutoScaleYmultiples = 100; int iMaxY_target = (int)((dataCenterY + (dataSpanY / 2)) / _AutoScaleYmultiples); int iMinY_target = (int)((dataCenterY - (dataSpanY / 2)) / _AutoScaleYmultiples); double dampingConstantK = 0.2; int iMaxY_damped = iMaxY_target; int iMinY_damped = iMinY_target; if (double.IsNaN(_chart.ChartAreas[chartAreaName].AxisY.Maximum) == false) { int iMaxY_chart = (int)(_chart.ChartAreas[chartAreaName].AxisY.Maximum / _AutoScaleYmultiples); int iMinY_chart = (int)(_chart.ChartAreas[chartAreaName].AxisY.Minimum / _AutoScaleYmultiples); iMaxY_damped = (int)((dampingConstantK * (double)iMaxY_target) + ((1 - dampingConstantK) * (double)iMaxY_chart) + 0.5); iMinY_damped = (int)((dampingConstantK * (double)iMinY_target) + ((1 - dampingConstantK) * (double)iMinY_chart)); } double maxY = iMaxY_target * _AutoScaleYmultiples; double minY = iMinY_target * _AutoScaleYmultiples; _chart.ChartAreas[chartAreaName].AxisY.Maximum = maxY; _chart.ChartAreas[chartAreaName].AxisY.Minimum = minY; // if chart.ChartAreas[chartAreaName].AxisY.Maximum is NaN then just assign maxY //if (double.IsNaN(chart.ChartAreas[chartAreaName].AxisY.Maximum)) //{ // chart.ChartAreas[chartAreaName].AxisY.Maximum = maxY; // chart.ChartAreas[chartAreaName].AxisY.Minimum = minY; //} //else //{ // chart.ChartAreas[chartAreaName].AxisY.Maximum = (dampingConstantK * maxY) + ((1 - dampingConstantK) * chart.ChartAreas[chartAreaName].AxisY.Maximum); // chart.ChartAreas[chartAreaName].AxisY.Minimum = (dampingConstantK * minY) + ((1 - dampingConstantK) * chart.ChartAreas[chartAreaName].AxisY.Minimum); //} #if PROFILER // TODO1: OS24EVK-54 profiling: capture max interval, number of intervals, cumulative interval _profilingStopwatchAutoScaleEvaluate.Stop(); #endif // PROFILER } /// <summary> /// Raw data format /// </summary> public DataFormats DataFormat = DataFormats.FormatUnsigned; public enum DataFormats { /// <summary> /// Interpret raw data as unsigned values /// </summary> FormatUnsigned, /// <summary> /// Interpret raw data as 16-bit, signed 2's complement values /// </summary> Format16bit2sComplement, } } // public class MedicalChartHelper } // namespace Maxim.MAX30101