|
//-------------------------------------------------------------
// <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
}
}
|