Xamarin Public Jenkins (auto-signing) 536cd135cc Imported Upstream version 5.4.0.167
Former-commit-id: 5624ac747d633e885131e8349322922b6a59baaa
2017-08-21 15:34:15 +00:00

1109 lines
31 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//-------------------------------------------------------------
// <copyright company=Microsoft Corporation>
// Copyright © Microsoft Corporation. All Rights Reserved.
// </copyright>
//-------------------------------------------------------------
// @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
/// <summary>
/// An enumeration of line styles for axis scale breaks.
/// </summary>
public enum BreakLineStyle
{
/// <summary>
/// No scale break line visible.
/// </summary>
None,
/// <summary>
/// Straight scale break.
/// </summary>
Straight,
/// <summary>
/// Wave scale break.
/// </summary>
Wave,
/// <summary>
/// Ragged scale break.
/// </summary>
Ragged,
}
/// <summary>
/// An enumeration which indicates whether an axis segment should start
/// from zero when scale break is used.
/// </summary>
public enum StartFromZero
{
/// <summary>
/// Auto mode
/// </summary>
Auto,
/// <summary>
/// Start the axis segment scale from zero.
/// </summary>
Yes,
/// <summary>
/// Do not start the axis segment scale from zero.
/// </summary>
No
};
#endregion // Enumerations
/// <summary>
/// <b>AxisScaleBreakStyle</b> class represents the settings that control the scale break.
/// </summary>
[
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
/// <summary>
/// AxisScaleBreakStyle constructor.
/// </summary>
public AxisScaleBreakStyle()
{
}
/// <summary>
/// AxisScaleBreakStyle constructor.
/// </summary>
/// <param name="axis">Chart axis this class belongs to.</param>
internal AxisScaleBreakStyle(Axis axis)
{
this.axis = axis;
}
#endregion // Constructor
#region Properties
/// <summary>
/// Gets or sets a flag which indicates whether one of the axis segments should start its scale from zero
/// when scale break is used.
/// </summary>
/// <remarks>
/// When property is set to <b>StartFromZero.Auto</b>, the range of the scale determines
/// if zero value should be included in the scale.
/// </remarks>
[
SRCategory("CategoryAttributeMisc"),
DefaultValue(StartFromZero.Auto),
SRDescription("DescriptionAttributeAxisScaleBreakStyle_StartFromZero"),
]
public StartFromZero StartFromZero
{
get
{
return this._startFromZero;
}
set
{
this._startFromZero = value;
this.Invalidate();
}
}
/// <summary>
/// Maximum number of scale breaks that can be used.
/// </summary>
[
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();
}
}
/// <summary>
/// Minimum axis scale region size, in percentage of the total axis length,
/// that can be collapsed with the scale break.
/// </summary>
[
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();
}
}
/// <summary>
/// Gets or sets a flag which determines if axis automatic scale breaks are enabled.
/// </summary>
[
SRCategory("CategoryAttributeMisc"),
DefaultValue(false),
SRDescription("DescriptionAttributeAxisScaleBreakStyle_Enabled"),
ParenthesizePropertyNameAttribute(true),
]
public bool Enabled
{
get
{
return this._enabled;
}
set
{
this._enabled = value;
this.Invalidate();
}
}
/// <summary>
/// Gets or sets the style of the scale break line.
/// </summary>
[
SRCategory("CategoryAttributeAppearance"),
DefaultValue(BreakLineStyle.Ragged),
SRDescription("DescriptionAttributeAxisScaleBreakStyle_BreakLineType"),
]
public BreakLineStyle BreakLineStyle
{
get
{
return this._breakLineStyle;
}
set
{
this._breakLineStyle = value;
this.Invalidate();
}
}
/// <summary>
/// Gets or sets the spacing of the scale break.
/// </summary>
[
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();
}
}
/// <summary>
/// Gets or sets the color of the scale break line.
/// </summary>
[
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();
}
}
/// <summary>
/// Gets or sets the width of the scale break line.
/// </summary>
[
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();
}
}
/// <summary>
/// Gets or sets the line style of the scale break line.
/// </summary>
[
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
/// <summary>
/// Checks if automatic scale breaks are currently enabled.
/// </summary>
/// <returns>True if scale breaks are currently enabled.</returns>
internal bool IsEnabled()
{
// Axis scale breaks must be enabled AND supported by the axis.
if(this.Enabled &&
this.CanUseAxisScaleBreaks())
{
return true;
}
return false;
}
/// <summary>
/// Checks if scale breaks can be used on specified axis.
/// </summary>
/// <returns>True if scale breaks can be used on this axis</returns>
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;
}
/// <summary>
/// Gets a list of series objects attached to the specified axis.
/// </summary>
/// <param name="axis">Axis to get the series for.</param>
/// <returns>A list of series that are attached to the specified axis.</returns>
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;
}
/// <summary>
/// Invalidate chart control.
/// </summary>
private void Invalidate()
{
if(this.axis != null)
{
this.axis.Invalidate();
}
}
#endregion // Helper Methods
#region Series StatisticFormula Methods
/// <summary>
/// Get collection of axis segments to present scale breaks.
/// </summary>
/// <param name="axisSegments">Collection of axis scale segments.</param>
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);
}
}
}
}
/// <summary>
/// Gets index of segment that should be started from zero.
/// </summary>
/// <param name="axisSegments">Axis scale segment collection.</param>
/// <returns>Index axis segment or -1.</returns>
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;
}
/// <summary>
/// Sets position of all scale segments in the axis.
/// </summary>
/// <param name="axisSegments">Collection of axis scale segments.</param>
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);
}
/// <summary>
/// Fill collection of axis scale segments.
/// </summary>
/// <param name="axisSegments">Collection of axis segments.</param>
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;
}
}
}
/// <summary>
/// Check if segment was excluded.
/// </summary>
/// <param name="excludedSegments">Array of segment indexes.</param>
/// <param name="segmentIndex">Index of the segment to check.</param>
/// <returns>True if segment with specified index is marked as excluded.</returns>
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;
}
/// <summary>
/// Collect statistical information about the series.
/// </summary>
/// <param name="segmentCount">Segment count.</param>
/// <param name="minYValue">Minimum Y value.</param>
/// <param name="maxYValue">Maximum Y value.</param>
/// <param name="segmentSize">Segment size.</param>
/// <param name="segmentMaxValue">Array of segment scale maximum values.</param>
/// <param name="segmentMinValue">Array of segment scale minimum values.</param>
/// <returns></returns>
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;
}
/// <summary>
/// Gets largest segment with no points.
/// </summary>
/// <param name="segmentPointNumber">Array that stores number of points for each segment.</param>
/// <param name="startSegment">Returns largest empty segment sequence starting index.</param>
/// <param name="numberOfSegments">Returns largest empty segment sequence length.</param>
/// <returns>True if long empty segment sequence was found.</returns>
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
}
}