//------------------------------------------------------------- // // Copyright © Microsoft Corporation. All Rights Reserved. // //------------------------------------------------------------- // @owner=alexgor, deliant //================================================================= // File: AxisScaleBreaks.cs // // Namespace: System.Web.UI.WebControls[Windows.Forms].Charting // // Classes: AxisScaleBreakStyle // // Purpose: Automatic scale breaks feature related classes. // // Reviewed: // //=================================================================== #region Used namespaces using System; using System.Collections; using System.Collections.Specialized; using System.ComponentModel; using System.ComponentModel.Design; using System.Data; using System.Drawing; using System.Drawing.Design; using System.Drawing.Drawing2D; using System.Globalization; #if Microsoft_CONTROL using System.Windows.Forms.DataVisualization.Charting.Data; using System.Windows.Forms.DataVisualization.Charting.ChartTypes; using System.Windows.Forms.DataVisualization.Charting.Utilities; using System.Windows.Forms.DataVisualization.Charting.Borders3D; using System.Windows.Forms.DataVisualization.Charting; #else using System.Web; using System.Web.UI; using System.Web.UI.DataVisualization.Charting; using System.Web.UI.DataVisualization.Charting.Data; using System.Web.UI.DataVisualization.Charting.ChartTypes; using System.Web.UI.DataVisualization.Charting.Utilities; #endif #endregion #if Microsoft_CONTROL namespace System.Windows.Forms.DataVisualization.Charting #else namespace System.Web.UI.DataVisualization.Charting #endif { #region Enumerations /// /// An enumeration of line styles for axis scale breaks. /// public enum BreakLineStyle { /// /// No scale break line visible. /// None, /// /// Straight scale break. /// Straight, /// /// Wave scale break. /// Wave, /// /// Ragged scale break. /// Ragged, } /// /// An enumeration which indicates whether an axis segment should start /// from zero when scale break is used. /// public enum StartFromZero { /// /// Auto mode /// Auto, /// /// Start the axis segment scale from zero. /// Yes, /// /// Do not start the axis segment scale from zero. /// No }; #endregion // Enumerations /// /// AxisScaleBreakStyle class represents the settings that control the scale break. /// [ SRDescription("DescriptionAttributeAxisScaleBreakStyle_AxisScaleBreakStyle"), DefaultProperty("Enabled"), ] #if ASPPERM_35 [AspNetHostingPermission(System.Security.Permissions.SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)] [AspNetHostingPermission(System.Security.Permissions.SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)] #endif public class AxisScaleBreakStyle { #region Fields // Associated axis internal Axis axis = null; // True if scale breaks are enabled private bool _enabled = false; // AxisName of the break line private BreakLineStyle _breakLineStyle = BreakLineStyle.Ragged; // Spacing between scale segments created by scale breaks private double _segmentSpacing = 1.5; // Break line color private Color _breakLineColor = Color.Black; // Break line width private int _breakLineWidth = 1; // Break line style private ChartDashStyle _breakLineDashStyle = ChartDashStyle.Solid; // Minimum segment size in axis length percentage private double _minSegmentSize = 10.0; // Number of segments the axis is devided into to perform statistical analysis private int _totalNumberOfSegments = 100; // Minimum "empty" size to be replace by the scale break private int _minimumNumberOfEmptySegments = 25; // Maximum number of breaks private int _maximumNumberOfBreaks = 2; // Indicates if scale segment should start from zero. private StartFromZero _startFromZero = StartFromZero.Auto; #endregion // Fields #region Constructor /// /// AxisScaleBreakStyle constructor. /// public AxisScaleBreakStyle() { } /// /// AxisScaleBreakStyle constructor. /// /// Chart axis this class belongs to. internal AxisScaleBreakStyle(Axis axis) { this.axis = axis; } #endregion // Constructor #region Properties /// /// Gets or sets a flag which indicates whether one of the axis segments should start its scale from zero /// when scale break is used. /// /// /// When property is set to StartFromZero.Auto, the range of the scale determines /// if zero value should be included in the scale. /// [ SRCategory("CategoryAttributeMisc"), DefaultValue(StartFromZero.Auto), SRDescription("DescriptionAttributeAxisScaleBreakStyle_StartFromZero"), ] public StartFromZero StartFromZero { get { return this._startFromZero; } set { this._startFromZero = value; this.Invalidate(); } } /// /// Maximum number of scale breaks that can be used. /// [ SRCategory("CategoryAttributeMisc"), DefaultValue(2), SRDescription("DescriptionAttributeAxisScaleBreakStyle_MaxNumberOfBreaks"), ] public int MaxNumberOfBreaks { get { return this._maximumNumberOfBreaks; } set { if(value < 1 || value > 5) { throw (new ArgumentOutOfRangeException("value", SR.ExceptionAxisScaleBreaksNumberInvalid)); } this._maximumNumberOfBreaks = value; this.Invalidate(); } } /// /// Minimum axis scale region size, in percentage of the total axis length, /// that can be collapsed with the scale break. /// [ SRCategory("CategoryAttributeMisc"), DefaultValue(25), SRDescription("DescriptionAttributeAxisScaleBreakStyle_CollapsibleSpaceThreshold"), ] public int CollapsibleSpaceThreshold { get { return this._minimumNumberOfEmptySegments; } set { if(value < 10 || value > 90) { throw (new ArgumentOutOfRangeException("value", SR.ExceptionAxisScaleBreaksCollapsibleSpaceInvalid)); } this._minimumNumberOfEmptySegments = value; this.Invalidate(); } } /// /// Gets or sets a flag which determines if axis automatic scale breaks are enabled. /// [ SRCategory("CategoryAttributeMisc"), DefaultValue(false), SRDescription("DescriptionAttributeAxisScaleBreakStyle_Enabled"), ParenthesizePropertyNameAttribute(true), ] public bool Enabled { get { return this._enabled; } set { this._enabled = value; this.Invalidate(); } } /// /// Gets or sets the style of the scale break line. /// [ SRCategory("CategoryAttributeAppearance"), DefaultValue(BreakLineStyle.Ragged), SRDescription("DescriptionAttributeAxisScaleBreakStyle_BreakLineType"), ] public BreakLineStyle BreakLineStyle { get { return this._breakLineStyle; } set { this._breakLineStyle = value; this.Invalidate(); } } /// /// Gets or sets the spacing of the scale break. /// [ SRCategory("CategoryAttributeMisc"), DefaultValue(1.5), SRDescription("DescriptionAttributeAxisScaleBreakStyle_Spacing"), ] public double Spacing { get { return this._segmentSpacing; } set { if(value < 0.0 || value > 10) { throw (new ArgumentOutOfRangeException("value", SR.ExceptionAxisScaleBreaksSpacingInvalid)); } this._segmentSpacing = value; this.Invalidate(); } } /// /// Gets or sets the color of the scale break line. /// [ SRCategory("CategoryAttributeAppearance"), DefaultValue(typeof(Color), "Black"), SRDescription("DescriptionAttributeLineColor"), TypeConverter(typeof(ColorConverter)), Editor(Editors.ChartColorEditor.Editor, Editors.ChartColorEditor.Base) ] public Color LineColor { get { return this._breakLineColor; } set { this._breakLineColor = value; this.Invalidate(); } } /// /// Gets or sets the width of the scale break line. /// [ SRCategory("CategoryAttributeAppearance"), DefaultValue(1), SRDescription("DescriptionAttributeLineWidth"), ] public int LineWidth { get { return this._breakLineWidth; } set { if(value < 1.0 || value > 10) { throw (new ArgumentOutOfRangeException("value", SR.ExceptionAxisScaleBreaksLineWidthInvalid)); } this._breakLineWidth = value; this.Invalidate(); } } /// /// Gets or sets the line style of the scale break line. /// [ SRCategory("CategoryAttributeAppearance"), DefaultValue(ChartDashStyle.Solid), SRDescription("DescriptionAttributeLineDashStyle"), ] public ChartDashStyle LineDashStyle { get { return this._breakLineDashStyle; } set { this._breakLineDashStyle = value; this.Invalidate(); } } #endregion // Properties #region Helper Methods /// /// Checks if automatic scale breaks are currently enabled. /// /// True if scale breaks are currently enabled. internal bool IsEnabled() { // Axis scale breaks must be enabled AND supported by the axis. if(this.Enabled && this.CanUseAxisScaleBreaks()) { return true; } return false; } /// /// Checks if scale breaks can be used on specified axis. /// /// True if scale breaks can be used on this axis internal bool CanUseAxisScaleBreaks() { // Check input parameters if(this.axis == null || this.axis.ChartArea == null || this.axis.ChartArea.Common.Chart == null) { return false; } // No scale breaks in 3D charts if(this.axis.ChartArea.Area3DStyle.Enable3D) { return false; } // Axis scale break can only be applied to the Y and Y 2 axis if(this.axis.axisType == AxisName.X || this.axis.axisType == AxisName.X2) { return false; } // No scale breaks for logarithmic axis if(this.axis.IsLogarithmic) { return false; } // No scale breaks if axis zooming is enabled if(this.axis.ScaleView.IsZoomed) { return false; } // Check series associated with this axis ArrayList axisSeries = AxisScaleBreakStyle.GetAxisSeries(this.axis); foreach(Series series in axisSeries) { // Some special chart type are not supported if(series.ChartType == SeriesChartType.Renko || series.ChartType == SeriesChartType.PointAndFigure) { return false; } // Get chart type interface IChartType chartType = this.axis.ChartArea.Common.ChartTypeRegistry.GetChartType(series.ChartTypeName); if(chartType == null) { return false; } // Circular and stacked chart types can not use scale breaks if(chartType.CircularChartArea || chartType.Stacked || !chartType.RequireAxes) { return false; } } return true; } /// /// Gets a list of series objects attached to the specified axis. /// /// Axis to get the series for. /// A list of series that are attached to the specified axis. static internal ArrayList GetAxisSeries(Axis axis) { ArrayList seriesList = new ArrayList(); if(axis != null && axis.ChartArea != null && axis.ChartArea.Common.Chart != null) { // Iterate through series in the chart foreach(Series series in axis.ChartArea.Common.Chart.Series) { // Series should be on the same chart area and visible if(series.ChartArea == axis.ChartArea.Name && series.Enabled) { // Check primary/secondary axis if( (axis.axisType == AxisName.Y && series.YAxisType == AxisType.Secondary) || (axis.axisType == AxisName.Y2 && series.YAxisType == AxisType.Primary)) { continue; } // Add series into the list seriesList.Add(series); } } } return seriesList; } /// /// Invalidate chart control. /// private void Invalidate() { if(this.axis != null) { this.axis.Invalidate(); } } #endregion // Helper Methods #region Series StatisticFormula Methods /// /// Get collection of axis segments to present scale breaks. /// /// Collection of axis scale segments. internal void GetAxisSegmentForScaleBreaks(AxisScaleSegmentCollection axisSegments) { // Clear segment collection axisSegments.Clear(); // Check if scale breaks are enabled if(this.IsEnabled()) { // Fill collection of segments this.FillAxisSegmentCollection(axisSegments); // Check if more than 1 segments were defined if(axisSegments.Count >= 1) { // Get index of segment which scale should start from zero int startFromZeroSegmentIndex = this.GetStartScaleFromZeroSegmentIndex(axisSegments); // Calculate segment interaval and round the scale int index = 0; foreach(AxisScaleSegment axisScaleSegment in axisSegments) { // Check if segment scale should start from zero bool startFromZero = (index == startFromZeroSegmentIndex) ? true : false; // Calculate interval and round scale double minimum = axisScaleSegment.ScaleMinimum; double maximum = axisScaleSegment.ScaleMaximum; axisScaleSegment.Interval = this.axis.EstimateNumberAxis( ref minimum, ref maximum, startFromZero, this.axis.prefferedNumberofIntervals, true, true); axisScaleSegment.ScaleMinimum = minimum; axisScaleSegment.ScaleMaximum = maximum; // Make sure new scale break value range do not exceed axis current scale if (axisScaleSegment.ScaleMinimum < this.axis.Minimum) { axisScaleSegment.ScaleMinimum = this.axis.Minimum; } if (axisScaleSegment.ScaleMaximum > this.axis.Maximum) { axisScaleSegment.ScaleMaximum = this.axis.Maximum; } // Increase segment index ++index; } // Defined axis scale segments cannot overlap. // Check for overlapping and join segments or readjust min/max. bool adjustPosition = false; AxisScaleSegment prevSegment = axisSegments[0]; for (int segmentIndex = 1; segmentIndex < axisSegments.Count; segmentIndex++) { AxisScaleSegment currentSegment = axisSegments[segmentIndex]; if (currentSegment.ScaleMinimum <= prevSegment.ScaleMaximum) { if (currentSegment.ScaleMaximum > prevSegment.ScaleMaximum) { // If segments are partially overlapping make sure the previous // segment scale is extended prevSegment.ScaleMaximum = currentSegment.ScaleMaximum; } // Remove the overlapped segment adjustPosition = true; axisSegments.RemoveAt(segmentIndex); --segmentIndex; } else { prevSegment = currentSegment; } } // Calculate the position of each segment if (adjustPosition) { this.SetAxisSegmentPosition(axisSegments); } } } } /// /// Gets index of segment that should be started from zero. /// /// Axis scale segment collection. /// Index axis segment or -1. private int GetStartScaleFromZeroSegmentIndex(AxisScaleSegmentCollection axisSegments) { if (this.StartFromZero == StartFromZero.Auto || this.StartFromZero == StartFromZero.Yes) { int index = 0; foreach(AxisScaleSegment axisScaleSegment in axisSegments) { // Check if zero value is already part of the scale if(axisScaleSegment.ScaleMinimum < 0.0 && axisScaleSegment.ScaleMaximum > 0.0) { return -1; } // As soon as we get first segment with positive minimum value or // we reached last segment adjust scale to start from zero. if(axisScaleSegment.ScaleMinimum > 0.0 || index == (axisSegments.Count - 1) ) { // Check if setting minimum scale to zero will make the // data points in the segment hard to read. This may hapen // when the distance from zero to current minimum is // significantly larger than current scale size. if (this.StartFromZero == StartFromZero.Auto && axisScaleSegment.ScaleMinimum > 2.0 * (axisScaleSegment.ScaleMaximum - axisScaleSegment.ScaleMinimum) ) { return -1; } return index; } // Increase segment index ++index; } } return -1; } /// /// Sets position of all scale segments in the axis. /// /// Collection of axis scale segments. private void SetAxisSegmentPosition(AxisScaleSegmentCollection axisSegments) { // Calculate total number of points int totalPointNumber = 0; foreach(AxisScaleSegment axisScaleSegment in axisSegments) { if(axisScaleSegment.Tag is int) { totalPointNumber += (int)axisScaleSegment.Tag; } } // Calculate segment minimum size double minSize = Math.Min(this._minSegmentSize, Math.Floor(100.0 / axisSegments.Count)); // Set segment position double currentPosition = 0.0; for(int index = 0; index < axisSegments.Count; index++) { axisSegments[index].Position = (currentPosition > 100.0) ? 100.0 : currentPosition; axisSegments[index].Size = Math.Round(((int)axisSegments[index].Tag) / (totalPointNumber / 100.0),5); if(axisSegments[index].Size < minSize) { axisSegments[index].Size = minSize; } // Set spacing for all segments except the last one if(index < (axisSegments.Count - 1) ) { axisSegments[index].Spacing = this._segmentSpacing; } // Advance current position currentPosition += axisSegments[index].Size; } // Make sure we do not exceed the 100% axis length double totalHeight = 0.0; do { // Calculate total height totalHeight = 0.0; double maxSize = double.MinValue; int maxSizeIndex = -1; for(int index = 0; index < axisSegments.Count; index++) { totalHeight += axisSegments[index].Size; if(axisSegments[index].Size > maxSize) { maxSize = axisSegments[index].Size; maxSizeIndex = index; } } // If height is too large find largest segment if(totalHeight > 100.0) { // Adjust segment size axisSegments[maxSizeIndex].Size -= totalHeight - 100.0; if(axisSegments[maxSizeIndex].Size < minSize) { axisSegments[maxSizeIndex].Size = minSize; } // Adjust position of the next segment double curentPosition = axisSegments[maxSizeIndex].Position + axisSegments[maxSizeIndex].Size; for(int index = maxSizeIndex + 1; index < axisSegments.Count; index++) { axisSegments[index].Position = curentPosition; curentPosition += axisSegments[index].Size; } } } while(totalHeight > 100.0); } /// /// Fill collection of axis scale segments. /// /// Collection of axis segments. private void FillAxisSegmentCollection(AxisScaleSegmentCollection axisSegments) { // Clear axis segments collection axisSegments.Clear(); // Get statistics for the series attached to the axis double minYValue = 0.0; double maxYValue = 0.0; double segmentSize = 0.0; double[] segmentMaxValue = null; double[] segmentMinValue = null; int[] segmentPointNumber = GetSeriesDataStatistics( this._totalNumberOfSegments, out minYValue, out maxYValue, out segmentSize, out segmentMaxValue, out segmentMinValue); if (segmentPointNumber == null) { return; } // Calculate scale maximum and minimum double minimum = minYValue; double maximum = maxYValue; this.axis.EstimateNumberAxis( ref minimum, ref maximum, this.axis.IsStartedFromZero, this.axis.prefferedNumberofIntervals, true, true); // Make sure max/min Y values are not the same if (maxYValue == minYValue) { return; } // Calculate the percentage of the scale range covered by the data range. double dataRangePercent = (maxYValue - minYValue) / ((maximum - minimum) / 100.0); // Get sequences of empty segments ArrayList emptySequences = new ArrayList(); bool doneFlag = false; while(!doneFlag) { doneFlag = true; // Get longest sequence of segments with no points int startSegment = 0; int numberOfSegments = 0; this.GetLargestSequenseOfSegmentsWithNoPoints( segmentPointNumber, out startSegment, out numberOfSegments); // Adjust minimum empty segments number depending on current segments int minEmptySegments = (int)(this._minimumNumberOfEmptySegments * (100.0 / dataRangePercent)); if(axisSegments.Count > 0 && numberOfSegments > 0) { // Find the segment which contain newly found empty segments sequence foreach(AxisScaleSegment axisScaleSegment in axisSegments) { if(startSegment > 0 && (startSegment + numberOfSegments) <= segmentMaxValue.Length - 1) { if(segmentMaxValue[startSegment - 1] >= axisScaleSegment.ScaleMinimum && segmentMinValue[startSegment + numberOfSegments] <= axisScaleSegment.ScaleMaximum) { // Get percentage of segment scale that is empty and suggested for collapsing double segmentScaleRange = axisScaleSegment.ScaleMaximum - axisScaleSegment.ScaleMinimum; double emptySpaceRange = segmentMinValue[startSegment + numberOfSegments] - segmentMaxValue[startSegment - 1]; double emptySpacePercent = emptySpaceRange / (segmentScaleRange / 100.0); emptySpacePercent = emptySpacePercent / 100 * axisScaleSegment.Size; if(emptySpacePercent > minEmptySegments && numberOfSegments > this._minSegmentSize) { minEmptySegments = numberOfSegments; } } } } } // Check if found sequence is long enough if(numberOfSegments >= minEmptySegments) { doneFlag = false; // Store start segment and number of segments in the list emptySequences.Add(startSegment); emptySequences.Add(numberOfSegments); // Check if there are any emty segments sequence found axisSegments.Clear(); if(emptySequences.Count > 0) { double segmentFrom = double.NaN; double segmentTo = double.NaN; // Based on the segments that need to be excluded create axis segments that // will present on the axis scale. int numberOfPoints = 0; for(int index = 0; index < segmentPointNumber.Length; index++) { // Check if current segment is excluded bool excludedSegment = this.IsExcludedSegment(emptySequences, index); // If not excluded segment - update from/to range if they were set if(!excludedSegment && !double.IsNaN(segmentMinValue[index]) && !double.IsNaN(segmentMaxValue[index])) { // Calculate total number of points numberOfPoints += segmentPointNumber[index]; // Set From/To of the visible segment if(double.IsNaN(segmentFrom)) { segmentFrom = segmentMinValue[index]; segmentTo = segmentMaxValue[index]; } else { segmentTo = segmentMaxValue[index]; } } // If excluded or last segment - add current visible segment range if(!double.IsNaN(segmentFrom) && (excludedSegment || index == (segmentPointNumber.Length - 1) )) { // Make sure To and From do not match if(segmentTo == segmentFrom) { segmentFrom -= segmentSize; segmentTo += segmentSize; } // Add axis scale segment AxisScaleSegment axisScaleSegment = new AxisScaleSegment(); axisScaleSegment.ScaleMaximum = segmentTo; axisScaleSegment.ScaleMinimum = segmentFrom; axisScaleSegment.Tag = numberOfPoints; axisSegments.Add(axisScaleSegment); // Reset segment range segmentFrom = double.NaN; segmentTo = double.NaN; numberOfPoints = 0; } } } // Calculate the position of each segment this.SetAxisSegmentPosition(axisSegments); } // Make sure we do not exceed specified number of breaks if( (axisSegments.Count - 1) >= this._maximumNumberOfBreaks) { doneFlag = true; } } } /// /// Check if segment was excluded. /// /// Array of segment indexes. /// Index of the segment to check. /// True if segment with specified index is marked as excluded. private bool IsExcludedSegment(ArrayList excludedSegments, int segmentIndex) { for(int index = 0; index < excludedSegments.Count; index += 2) { if(segmentIndex >= (int)excludedSegments[index] && segmentIndex < (int)excludedSegments[index] + (int)excludedSegments[index + 1]) { return true; } } return false; } /// /// Collect statistical information about the series. /// /// Segment count. /// Minimum Y value. /// Maximum Y value. /// Segment size. /// Array of segment scale maximum values. /// Array of segment scale minimum values. /// internal int[] GetSeriesDataStatistics( int segmentCount, out double minYValue, out double maxYValue, out double segmentSize, out double[] segmentMaxValue, out double[] segmentMinValue) { // Get all series associated with the axis ArrayList axisSeries = AxisScaleBreakStyle.GetAxisSeries(this.axis); // Get range of Y values from axis series minYValue = 0.0; maxYValue = 0.0; axis.Common.DataManager.GetMinMaxYValue(axisSeries, out minYValue, out maxYValue); int numberOfPoints = 0; foreach (Series series in axisSeries) { numberOfPoints = Math.Max(numberOfPoints, series.Points.Count); } if (axisSeries.Count == 0 || numberOfPoints == 0) { segmentSize = 0.0; segmentMaxValue = null; segmentMinValue = null; return null; } // Split range of values into predefined number of segments and calculate // how many points will be in each segment. segmentSize = (maxYValue - minYValue) / segmentCount; int[] segmentPointNumber = new int[segmentCount]; segmentMaxValue = new double[segmentCount]; segmentMinValue = new double[segmentCount]; for(int index = 0; index < segmentCount; index++) { segmentMaxValue[index] = double.NaN; segmentMinValue[index] = double.NaN; } foreach(Series series in axisSeries) { // Get number of Y values to process int maxYValueCount = 1; IChartType chartType = this.axis.ChartArea.Common.ChartTypeRegistry.GetChartType(series.ChartTypeName); if(chartType != null) { if(chartType.ExtraYValuesConnectedToYAxis && chartType.YValuesPerPoint > 1) { maxYValueCount = chartType.YValuesPerPoint; } } // Iterate throug all data points foreach(DataPoint dataPoint in series.Points) { if(!dataPoint.IsEmpty) { // Iterate through all yValues for(int yValueIndex = 0; yValueIndex < maxYValueCount; yValueIndex++) { // Calculate index of the scale segment int segmentIndex = (int)Math.Floor((dataPoint.YValues[yValueIndex] - minYValue) / segmentSize); if(segmentIndex < 0) { segmentIndex = 0; } if(segmentIndex > segmentCount - 1) { segmentIndex = segmentCount - 1; } // Increase number points in that segment ++segmentPointNumber[segmentIndex]; // Store Min/Max values for the segment if(segmentPointNumber[segmentIndex] == 1) { segmentMaxValue[segmentIndex] = dataPoint.YValues[yValueIndex]; segmentMinValue[segmentIndex] = dataPoint.YValues[yValueIndex]; } else { segmentMaxValue[segmentIndex] = Math.Max(segmentMaxValue[segmentIndex], dataPoint.YValues[yValueIndex]); segmentMinValue[segmentIndex] = Math.Min(segmentMinValue[segmentIndex], dataPoint.YValues[yValueIndex]); } } } } } return segmentPointNumber; } /// /// Gets largest segment with no points. /// /// Array that stores number of points for each segment. /// Returns largest empty segment sequence starting index. /// Returns largest empty segment sequence length. /// True if long empty segment sequence was found. internal bool GetLargestSequenseOfSegmentsWithNoPoints( int[] segmentPointNumber, out int startSegment, out int numberOfSegments) { // Find the longest sequence of empty segments startSegment = -1; numberOfSegments = 0; int currentSegmentStart = -1; int currentNumberOfSegments = -1; for(int index = 0; index < segmentPointNumber.Length; index++) { // Check for the segment with no points if(segmentPointNumber[index] == 0) { if(currentSegmentStart == -1) { currentSegmentStart = index; currentNumberOfSegments = 1; } else { ++currentNumberOfSegments; } } // Check if longest sequence found if(currentNumberOfSegments > 0 && (segmentPointNumber[index] != 0 || index == segmentPointNumber.Length - 1)) { if(currentNumberOfSegments > numberOfSegments) { startSegment = currentSegmentStart; numberOfSegments = currentNumberOfSegments; } currentSegmentStart = -1; currentNumberOfSegments = 0; } } // Store value of "-1" in found sequence if(numberOfSegments != 0) { for(int index = startSegment; index < (startSegment + numberOfSegments); index++) { segmentPointNumber[index] = -1; } return true; } return false; } #endregion // Series StatisticFormula Methods } }