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

1987 lines
59 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: ChartAreaAxes.cs
//
// Namespace: System.Web.UI.WebControls[Windows.Forms].Charting
//
// Classes: ChartAreaAxes
//
// Purpose: ChartAreaAxes is base class of Chart Area class.
// This class searches for all series, which belongs
// to this chart area and sets axes minimum and
// maximum values using data. This class also checks
// for chart types, which belong to this chart area
// and prepare axis scale according to them (Stacked
// chart types have different max and min values).
// This class recognizes indexed values and prepares
// axes for them.
//
// Reviewed: GS - Jul 31, 2002
// AG - August 7, 2002
//
//===================================================================
#region Used namespaces
using System;
using System.Collections;
using System.Collections.Generic;
#if Microsoft_CONTROL
using System.Windows.Forms.DataVisualization.Charting;
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;
#else
using System.Web.UI.DataVisualization.Charting.Data;
using System.Web.UI.DataVisualization.Charting.ChartTypes;
#endif
#endregion
#if Microsoft_CONTROL
namespace System.Windows.Forms.DataVisualization.Charting
#else
namespace System.Web.UI.DataVisualization.Charting
#endif
{
/// <summary>
/// ChartAreaAxes class represents axes (X, Y, X2 and Y2) in the chart area.
/// It contains methods that collect statistical information on the series data and
/// other axes related methods.
/// </summary>
public partial class ChartArea
{
#region Fields
// Axes which belong to this Chart Area
internal Axis axisY = null;
internal Axis axisX = null;
internal Axis axisX2 = null;
internal Axis axisY2 = null;
// Array of series which belong to this chart area
private List<string> _series = new List<string>();
// Array of chart types which belong to this chart area
internal ArrayList chartTypes = new ArrayList();
/// <summary>
/// List of series names that last interval numbers where cashed for
/// </summary>
private string _intervalSeriesList = "";
// Minimum interval between two data points for all
// series which belong to this chart area.
internal double intervalData = double.NaN;
// Minimum interval between two data points for all
// series which belong to this chart area.
// IsLogarithmic version of the interval.
internal double intervalLogData = double.NaN;
// Series with minimum interval between two data points for all
// series which belong to this chart area.
private Series _intervalSeries = null;
// Indicates that points are located through equal X intervals
internal bool intervalSameSize = false;
// Indicates that points alignment checked
internal bool diffIntervalAlignmentChecked = false;
// Chart Area contains stacked chart types
internal bool stacked = false;
// Chart type with two y values used for scale ( bubble chart type )
internal bool secondYScale = false;
// The X and Y axes are switched
internal bool switchValueAxes = false;
// True for all chart types, which have axes. False for doughnut and pie chart.
internal bool requireAxes = true;
// Indicates that chart area has circular shape (like in radar or polar chart)
internal bool chartAreaIsCurcular = false;
// Chart Area contains 100 % stacked chart types
internal bool hundredPercent = false;
// Chart Area contains 100 % stacked chart types
internal bool hundredPercentNegative = false;
#endregion
#region Internal properties
/// <summary>
/// True if sub axis supported on this chart area
/// </summary>
internal bool IsSubAxesSupported
{
get
{
if(((ChartArea)this).Area3DStyle.Enable3D ||
((ChartArea)this).chartAreaIsCurcular)
{
return false;
}
return true;
}
}
/// <summary>
/// Data series which belongs to this chart area.
/// </summary>
internal List<string> Series
{
get
{
return _series;
}
}
/// <summary>
/// Chart types which belongs to this chart area.
/// </summary>
internal ArrayList ChartTypes
{
get
{
return chartTypes;
}
}
#endregion
#region Methods
/// <summary>
/// Gets main or sub axis from the chart area.
/// </summary>
/// <param name="axisName">Axis name. NOTE: This parameter only defines X or Y axis.
/// Second axisType parameter is used to select primary or secondary axis. </param>
/// <param name="axisType">Axis type.</param>
/// <param name="subAxisName">Sub-axis name or empty string.</param>
/// <returns>Main or sub axis of the chart area.</returns>
internal Axis GetAxis(AxisName axisName, AxisType axisType, string subAxisName)
{
// Ignore sub axis in 3D
if( ((ChartArea)this).Area3DStyle.Enable3D)
{
subAxisName = string.Empty;
}
if(axisName == AxisName.X || axisName == AxisName.X2)
{
if(axisType == AxisType.Primary)
{
return ((ChartArea)this).AxisX.GetSubAxis(subAxisName);
}
return ((ChartArea)this).AxisX2.GetSubAxis(subAxisName);
}
else
{
if(axisType == AxisType.Primary)
{
return ((ChartArea)this).AxisY.GetSubAxis(subAxisName);
}
return ((ChartArea)this).AxisY2.GetSubAxis(subAxisName);
}
}
/// <summary>
/// Sets default axis values for all different chart type
/// groups. Chart type groups are sets of chart types.
/// </summary>
internal void SetDefaultAxesValues( )
{
// The X and Y axes are switched ( Bar chart, stacked bar ... )
if( switchValueAxes )
{
// Set axis positions
axisY.AxisPosition = AxisPosition.Bottom;
axisX.AxisPosition = AxisPosition.Left;
axisX2.AxisPosition = AxisPosition.Right;
axisY2.AxisPosition = AxisPosition.Top;
}
else
{
// Set axis positions
axisY.AxisPosition = AxisPosition.Left;
axisX.AxisPosition = AxisPosition.Bottom;
axisX2.AxisPosition = AxisPosition.Top;
axisY2.AxisPosition = AxisPosition.Right;
}
// Reset opposite Axes field. This cashing
// value is used for optimization.
foreach( Axis axisItem in ((ChartArea)this).Axes )
{
axisItem.oppositeAxis = null;
#if SUBAXES
foreach( SubAxis subAxisItem in axisItem.SubAxes )
{
subAxisItem.m_oppositeAxis = null;
}
#endif // SUBAXES
}
// ***********************
// Primary X Axes
// ***********************
// Find the number of series which belong to this axis
if (this.chartAreaIsCurcular)
{
// Set axis Maximum/Minimum and Interval for circular chart
axisX.SetAutoMaximum(360.0);
axisX.SetAutoMinimum(0.0);
axisX.SetInterval = Math.Abs(axisX.maximum - axisX.minimum) / 12.0;
}
else
{
SetDefaultFromIndexesOrData(axisX, AxisType.Primary);
}
#if SUBAXES
// ***********************
// Primary X Sub-Axes
// ***********************
foreach(SubAxis subAxis in axisX.SubAxes)
{
SetDefaultFromIndexesOrData(subAxis, AxisType.Primary);
}
#endif // SUBAXES
// ***********************
// Secondary X Axes
// ***********************
SetDefaultFromIndexesOrData(axisX2, AxisType.Secondary);
#if SUBAXES
// ***********************
// Secondary X Sub-Axes
// ***********************
foreach(SubAxis subAxis in axisX2.SubAxes)
{
SetDefaultFromIndexesOrData(subAxis, AxisType.Secondary);
}
#endif // SUBAXES
// ***********************
// Primary Y axis
// ***********************
if( GetYAxesSeries( AxisType.Primary, string.Empty ).Count != 0 )
{
// Find minimum and maximum from Y values.
SetDefaultFromData( axisY );
axisY.EstimateAxis();
}
#if SUBAXES
// ***********************
// Primary Y Sub-Axes
// ***********************
foreach(SubAxis subAxis in axisY.SubAxes)
{
// Find the number of series which belong to this axis
if( GetYAxesSeries( AxisType.Primary, subAxis.SubAxisName ).Count != 0 )
{
// Find minimum and maximum from Y values.
SetDefaultFromData( subAxis );
subAxis.EstimateAxis();
}
}
#endif // SUBAXES
// ***********************
// Secondary Y axis
// ***********************
if( GetYAxesSeries( AxisType.Secondary, string.Empty ).Count != 0 )
{
// Find minimum and maximum from Y values.
SetDefaultFromData( axisY2 );
axisY2.EstimateAxis();
}
#if SUBAXES
// ***********************
// Secondary Y Sub-Axes
// ***********************
foreach(SubAxis subAxis in axisY2.SubAxes)
{
// Find the number of series which belong to this axis
if( GetYAxesSeries( AxisType.Secondary, subAxis.SubAxisName ).Count != 0 )
{
// Find minimum and maximum from Y values.
SetDefaultFromData( subAxis );
subAxis.EstimateAxis();
}
}
#endif // SUBAXES
// Sets axis position. Axis position depends
// on crossing and reversed value.
axisX.SetAxisPosition();
axisX2.SetAxisPosition();
axisY.SetAxisPosition();
axisY2.SetAxisPosition();
// Enable axes, which are
// used in data series.
this.EnableAxes();
// Get scale break segments
Axis[] axesYArray = new Axis[] { axisY, axisY2 };
foreach(Axis currentAxis in axesYArray)
{
// Get automatic scale break segments
currentAxis.ScaleBreakStyle.GetAxisSegmentForScaleBreaks(currentAxis.ScaleSegments);
// Make sure axis scale do not exceed segments scale
if(currentAxis.ScaleSegments.Count > 0)
{
// Save flag that scale segments are used
currentAxis.scaleSegmentsUsed = true;
if(currentAxis.minimum < currentAxis.ScaleSegments[0].ScaleMinimum)
{
currentAxis.minimum = currentAxis.ScaleSegments[0].ScaleMinimum;
}
if(currentAxis.minimum > currentAxis.ScaleSegments[currentAxis.ScaleSegments.Count - 1].ScaleMaximum)
{
currentAxis.minimum = currentAxis.ScaleSegments[currentAxis.ScaleSegments.Count - 1].ScaleMaximum;
}
}
}
bool useScaleSegments = false;
// Fill Labels
Axis[] axesArray = new Axis[] { axisX, axisX2, axisY, axisY2 };
foreach(Axis currentAxis in axesArray)
{
useScaleSegments = (currentAxis.ScaleSegments.Count > 0);
if(!useScaleSegments)
{
currentAxis.FillLabels(true);
}
else
{
bool removeLabels = true;
int segmentIndex = 0;
foreach(AxisScaleSegment scaleSegment in currentAxis.ScaleSegments)
{
scaleSegment.SetTempAxisScaleAndInterval();
currentAxis.FillLabels(removeLabels);
removeLabels = false;
scaleSegment.RestoreAxisScaleAndInterval();
// Remove last label for all segments except of the last
if(segmentIndex < (currentAxis.ScaleSegments.Count - 1) &&
currentAxis.CustomLabels.Count > 0)
{
currentAxis.CustomLabels.RemoveAt(currentAxis.CustomLabels.Count - 1);
}
++segmentIndex;
}
}
}
foreach (Axis currentAxis in axesArray)
{
currentAxis.PostFillLabels();
}
}
/// <summary>
/// Sets the axis defaults.
/// If the at least one of the series bound to this axis is Indexed then the defaults are set using the SetDefaultsFromIndexes().
/// Otherwise the SetDefaultFromData() is used.
/// </summary>
/// <param name="axis">Axis to process</param>
/// <param name="axisType">Axis type</param>
private void SetDefaultFromIndexesOrData(Axis axis, AxisType axisType)
{
//Get array of the series that are linked to this axis
List<string> axisSeriesNames = GetXAxesSeries(axisType, axis.SubAxisName);
// VSTS: 196381
// before this change: If we find one indexed series we will treat all series as indexed.
// after this change : We will assume that all series are indexed.
// If we find one non indexed series we will treat all series as non indexed.
bool indexedSeries = true;
// DT comments 1:
// If we have mix of indexed with non-indexed series
// enforce all indexed series as non-indexed;
// The result of mixed type of series will be more natural
// and easy to detect the problem - all datapoints of indexed
// series will be displayed on zero position.
//=====================================
// bool nonIndexedSeries = false;
//=======================================
//Loop through the series looking for a indexed one
foreach(string seriesName in axisSeriesNames)
{
// Get series
Series series = Common.DataManager.Series[seriesName];
// Check if series is indexed
if (!ChartHelper.IndexedSeries(series))
{
// found one nonindexed series - we will treat all series as non indexed.
indexedSeries = false;
break;
}
// DT comments 2
//else
//{
// nonIndexedSeries = true;
//}
}
//DT comments 3
//if (!indexedSeries && nonIndexedSeries)
//{
// foreach (string seriesName in axisSeriesNames)
// {
// // Get series
// Series series = Common.DataManager.Series[seriesName];
// series.xValuesZeros = false;
// }
//}
if (indexedSeries)
{
if (axis.IsLogarithmic)
{
throw (new InvalidOperationException(SR.ExceptionChartAreaAxisScaleLogarithmicUnsuitable));
}
//Set axis defaults from the indexed series
SetDefaultFromIndexes(axis);
//We are done...
return;
}
// If haven't found any indexed series -> Set axis defaults from the series data
SetDefaultFromData(axis);
axis.EstimateAxis();
}
/// <summary>
/// Enable axes, which are
/// used in chart area data series.
/// </summary>
private void EnableAxes()
{
if( _series == null )
{
return;
}
bool activeX = false;
bool activeY = false;
bool activeX2 = false;
bool activeY2 = false;
// Data series from this chart area
foreach( string ser in _series )
{
Series dataSeries = Common.DataManager.Series[ ser ];
// X axes
if( dataSeries.XAxisType == AxisType.Primary )
{
activeX = true;
#if SUBAXES
this.Activate( axisX, true, dataSeries.XSubAxisName );
#else
this.Activate( axisX, true );
#endif // SUBAXES
}
else
{
activeX2 = true;
#if SUBAXES
this.Activate( axisX2, true, dataSeries.XSubAxisName );
#else
this.Activate( axisX2, true );
#endif // SUBAXES
}
// Y axes
if( dataSeries.YAxisType == AxisType.Primary )
{
activeY = true;
#if SUBAXES
this.Activate( axisY, true, dataSeries.YSubAxisName );
#else
this.Activate( axisY, true );
#endif // SUBAXES
}
else
{
activeY2 = true;
#if SUBAXES
this.Activate( axisY2, true, dataSeries.YSubAxisName );
#else
this.Activate( axisY2, true );
#endif // SUBAXES
}
}
#if SUBAXES
// Enable Axes
if(!activeX)
this.Activate( axisX, false, string.Empty );
if(!activeY)
this.Activate( axisY, false, string.Empty );
if(!activeX2)
this.Activate( axisX2, false, string.Empty );
if(!activeY2)
this.Activate( axisY2, false, string.Empty );
#else // SUBAXES
// Enable Axes
if(!activeX)
this.Activate( axisX, false);
if(!activeY)
this.Activate( axisY, false);
if(!activeX2)
this.Activate( axisX2, false);
if(!activeY2)
this.Activate( axisY2, false);
#endif // SUBAXES
}
#if SUBAXES
/// <summary>
/// Enable axis.
/// </summary>
/// <param name="axis">Axis.</param>
/// <param name="active">True if axis is active.</param>
/// <param name="subAxisName">Sub axis name to activate.</param>
private void Activate( Axis axis, bool active, string subAxisName )
{
// Auto-Enable axis
if( axis.autoEnabled == true )
{
axis.enabled = active;
}
// Auto-Enable sub axes
if(subAxisName.Length > 0)
{
SubAxis subAxis = axis.SubAxes.FindByName(subAxisName);
if(subAxis != null)
{
if( subAxis.autoEnabled == true )
{
subAxis.enabled = active;
}
}
}
}
#else
/// <summary>
/// Enable axis.
/// </summary>
/// <param name="axis">Axis.</param>
/// <param name="active">True if axis is active.</param>
private void Activate( Axis axis, bool active )
{
if( axis.autoEnabled == true )
{
axis.enabled = active;
}
}
#endif // SUBAXES
/// <summary>
/// Check if all data points from series in
/// this chart area are empty.
/// </summary>
/// <returns>True if all points are empty</returns>
bool AllEmptyPoints()
{
// Data series from this chart area
foreach( string seriesName in this._series )
{
Series dataSeries = Common.DataManager.Series[ seriesName ];
// Data point loop
foreach( DataPoint point in dataSeries.Points )
{
if( !point.IsEmpty )
{
return false;
}
}
}
return true;
}
/// <summary>
/// This method sets default minimum and maximum
/// values from values in the data manager. This
/// case is used if X values are not equal to 0 or IsXValueIndexed flag is set.
/// </summary>
/// <param name="axis">Axis</param>
private void SetDefaultFromData( Axis axis )
{
#if SUBAXES
// Process all sub-axes
if(!axis.IsSubAxis)
{
foreach(SubAxis subAxis in axis.SubAxes)
{
this.SetDefaultFromData( subAxis );
}
}
#endif // SUBAXES
// Used for scrolling with logarithmic axes.
if( !Double.IsNaN(axis.ScaleView.Position) &&
!Double.IsNaN(axis.ScaleView.Size) &&
!axis.refreshMinMaxFromData &&
axis.IsLogarithmic )
{
return;
}
// Get minimum and maximum from data source
double autoMaximum;
double autoMinimum;
this.GetValuesFromData( axis, out autoMinimum, out autoMaximum );
// ***************************************************
// This part of code is used to add a margin to the
// axis and to set minimum value to zero if
// IsStartedFromZero property is used. There is special
// code for logarithmic scale, which will set minimum
// to one instead of zero.
// ***************************************************
// The minimum and maximum values from data manager dont exist.
if( axis.enabled &&
( (axis.AutoMaximum || double.IsNaN( axis.Maximum )) && (autoMaximum == Double.MaxValue || autoMaximum == Double.MinValue)) ||
( (axis.AutoMinimum || double.IsNaN( axis.Minimum )) && (autoMinimum == Double.MaxValue || autoMinimum == Double.MinValue )) )
{
if( this.AllEmptyPoints() )
{
// Supress exception and use predefined min & max
autoMaximum = 8.0;
autoMinimum = 1.0;
}
else
{
if(!this.Common.ChartPicture.SuppressExceptions)
{
throw (new InvalidOperationException(SR.ExceptionAxisMinimumMaximumInvalid));
}
}
}
// Axis margin used for zooming
axis.marginView = 0.0;
if( axis.margin == 100 && (axis.axisType == AxisName.X || axis.axisType == AxisName.X2) )
{
axis.marginView = this.GetPointsInterval( false, 10 );
}
// If minimum and maximum are same margin always exist.
if( autoMaximum == autoMinimum &&
axis.Maximum == axis.Minimum )
{
axis.marginView = 1;
}
// Do not make axis margine for logarithmic axes
if( axis.IsLogarithmic )
{
axis.marginView = 0.0;
}
// Adjust Maximum - Add a gap
if( axis.AutoMaximum )
{
// Add a Gap for X axis
if( !axis.roundedXValues && ( axis.axisType == AxisName.X || axis.axisType == AxisName.X2 ) )
{
axis.SetAutoMaximum( autoMaximum + axis.marginView );
}
else
{
if( axis.isStartedFromZero && autoMaximum < 0 )
{
axis.SetAutoMaximum( 0.0 );
}
else
{
axis.SetAutoMaximum( autoMaximum );
}
}
}
// Adjust Minimum - make rounded values and add a gap
if( axis.AutoMinimum )
{
// IsLogarithmic axis
if( axis.IsLogarithmic )
{
if( autoMinimum < 1.0 )
{
axis.SetAutoMinimum( autoMinimum );
}
else if( axis.isStartedFromZero )
{
axis.SetAutoMinimum( 1.0 );
}
else
{
axis.SetAutoMinimum( autoMinimum );
}
}
else
{
if( autoMinimum > 0.0 ) // If Auto calculated Minimum value is positive
{
// Adjust Minimum
if( !axis.roundedXValues && ( axis.axisType == AxisName.X || axis.axisType == AxisName.X2 ) )
{
axis.SetAutoMinimum( autoMinimum - axis.marginView );
}
// If start From Zero property is true 0 is always on the axis.
// NOTE: Not applicable if date-time values are drawn. Fixes issue #5644
else if( axis.isStartedFromZero &&
!this.SeriesDateTimeType( axis.axisType, axis.SubAxisName ) )
{
axis.SetAutoMinimum( 0.0 );
}
else
{
axis.SetAutoMinimum( autoMinimum );
}
}
else // If Auto calculated Minimum value is non positive
{
if( axis.axisType == AxisName.X || axis.axisType == AxisName.X2 )
{
axis.SetAutoMinimum( autoMinimum - axis.marginView );
}
else
{
// If start From Zero property is true 0 is always on the axis.
axis.SetAutoMinimum( autoMinimum );
}
}
}
}
// If maximum or minimum are not auto set value to non logarithmic
if( axis.IsLogarithmic && axis.logarithmicConvertedToLinear )
{
if( !axis.AutoMinimum )
{
axis.minimum = axis.logarithmicMinimum;
}
if( !axis.AutoMaximum )
{
axis.maximum = axis.logarithmicMaximum;
}
// Min and max will take real values again if scale is logarithmic.
axis.logarithmicConvertedToLinear = false;
}
// Check if Minimum == Maximum
if(this.Common.ChartPicture.SuppressExceptions &&
axis.maximum == axis.minimum)
{
axis.minimum = axis.maximum;
axis.maximum = axis.minimum + 1.0;
}
}
/// <summary>
/// This method checks if all series in the chart area have “integer type”
/// for specified axes, which means int, uint, long and ulong.
/// </summary>
/// <param name="axisName">Name of the axis</param>
/// <param name="subAxisName">Sub axis name.</param>
/// <returns>True if all series are integer</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "subAxisName")]
internal bool SeriesIntegerType( AxisName axisName, string subAxisName )
{
// Series which belong to this chart area
foreach( string seriesName in this._series )
{
Series ser = Common.DataManager.Series[ seriesName ];
// X axes type
if( axisName == AxisName.X )
{
#if SUBAXES
if( ser.XAxisType == AxisType.Primary && ser.XSubAxisName == subAxisName)
#else //SUBAXES
if ( ser.XAxisType == AxisType.Primary)
#endif //SUBAXES
{
if(ser.XValueType != ChartValueType.Int32 &&
ser.XValueType != ChartValueType.UInt32 &&
ser.XValueType != ChartValueType.UInt64 &&
ser.XValueType != ChartValueType.Int64 )
{
return false;
}
else
{
return true;
}
}
}
// X axes type
else if( axisName == AxisName.X2 )
{
#if SUBAXES
if( ser.XAxisType == AxisType.Secondary && ser.XSubAxisName == subAxisName)
#else //SUBAXES
if ( ser.XAxisType == AxisType.Secondary)
#endif //SUBAXES
{
if(ser.XValueType != ChartValueType.Int32 &&
ser.XValueType != ChartValueType.UInt32 &&
ser.XValueType != ChartValueType.UInt64 &&
ser.XValueType != ChartValueType.Int64 )
{
return false;
}
else
{
return true;
}
}
}
// Y axes type
else if( axisName == AxisName.Y )
{
#if SUBAXES
if( ser.YAxisType == AxisType.Primary && ser.YSubAxisName == subAxisName)
#else //SUBAXES
if ( ser.YAxisType == AxisType.Primary)
#endif //SUBAXES
{
if(ser.YValueType != ChartValueType.Int32 &&
ser.YValueType != ChartValueType.UInt32 &&
ser.YValueType != ChartValueType.UInt64 &&
ser.YValueType != ChartValueType.Int64 )
{
return false;
}
else
{
return true;
}
}
}
else if( axisName == AxisName.Y2 )
{
#if SUBAXES
if( ser.YAxisType == AxisType.Secondary && ser.YSubAxisName == subAxisName)
#else //SUBAXES
if ( ser.YAxisType == AxisType.Secondary)
#endif //SUBAXES
{
if(ser.YValueType != ChartValueType.Int32 &&
ser.YValueType != ChartValueType.UInt32 &&
ser.YValueType != ChartValueType.UInt64 &&
ser.YValueType != ChartValueType.Int64 )
{
return false;
}
else
{
return true;
}
}
}
}
return false;
}
/// <summary>
/// This method checks if all series in the chart area have “date-time type”
/// for specified axes.
/// </summary>
/// <param name="axisName">Name of the axis</param>
/// <param name="subAxisName">Sub axis name.</param>
/// <returns>True if all series are date-time.</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "subAxisName")]
internal bool SeriesDateTimeType( AxisName axisName, string subAxisName )
{
// Series which belong to this chart area
foreach( string seriesName in this._series )
{
Series ser = Common.DataManager.Series[ seriesName ];
// X axes type
if( axisName == AxisName.X )
{
#if SUBAXES
if( ser.XAxisType == AxisType.Primary && ser.XSubAxisName == subAxisName)
#else //SUBAXES
if ( ser.XAxisType == AxisType.Primary)
#endif //SUBAXES
{
if(ser.XValueType != ChartValueType.Date &&
ser.XValueType != ChartValueType.DateTime &&
ser.XValueType != ChartValueType.Time &&
ser.XValueType != ChartValueType.DateTimeOffset)
{
return false;
}
else
{
return true;
}
}
}
// X axes type
else if( axisName == AxisName.X2 )
{
#if SUBAXES
if( ser.XAxisType == AxisType.Secondary && ser.XSubAxisName == subAxisName)
#else //SUBAXES
if ( ser.XAxisType == AxisType.Secondary)
#endif //SUBAXES
{
if(ser.XValueType != ChartValueType.Date &&
ser.XValueType != ChartValueType.DateTime &&
ser.XValueType != ChartValueType.Time &&
ser.XValueType != ChartValueType.DateTimeOffset)
{
return false;
}
else
{
return true;
}
}
}
// Y axes type
else if( axisName == AxisName.Y )
{
#if SUBAXES
if( ser.YAxisType == AxisType.Primary && ser.YSubAxisName == subAxisName)
#else //SUBAXES
if ( ser.YAxisType == AxisType.Primary)
#endif //SUBAXES
{
if(ser.YValueType != ChartValueType.Date &&
ser.YValueType != ChartValueType.DateTime &&
ser.YValueType != ChartValueType.Time &&
ser.YValueType != ChartValueType.DateTimeOffset)
{
return false;
}
else
{
return true;
}
}
}
else if( axisName == AxisName.Y2 )
{
#if SUBAXES
if( ser.YAxisType == AxisType.Secondary && ser.YSubAxisName == subAxisName)
#else //SUBAXES
if ( ser.YAxisType == AxisType.Secondary)
#endif //SUBAXES
{
if(ser.YValueType != ChartValueType.Date &&
ser.YValueType != ChartValueType.DateTime &&
ser.YValueType != ChartValueType.Time &&
ser.YValueType != ChartValueType.DateTimeOffset)
{
return false;
}
else
{
return true;
}
}
}
}
return false;
}
/// <summary>
/// This method calculates minimum and maximum from data series.
/// </summary>
/// <param name="axis">Axis which is used to find minimum and maximum</param>
/// <param name="autoMinimum">Minimum value from data.</param>
/// <param name="autoMaximum">Maximum value from data.</param>
private void GetValuesFromData( Axis axis, out double autoMinimum, out double autoMaximum )
{
// Get number of points in series
int currentPointsNumber = this.GetNumberOfAllPoints();
if( !axis.refreshMinMaxFromData &&
!double.IsNaN(axis.minimumFromData) &&
!double.IsNaN(axis.maximumFromData) &&
axis.numberOfPointsInAllSeries == currentPointsNumber )
{
autoMinimum = axis.minimumFromData;
autoMaximum = axis.maximumFromData;
return;
}
// Set Axis type
AxisType type = AxisType.Primary;
if( axis.axisType == AxisName.X2 || axis.axisType == AxisName.Y2 )
{
type = AxisType.Secondary;
}
// Creates a list of series, which have same X axis type.
string [] xAxesSeries = GetXAxesSeries(type, axis.SubAxisName).ToArray();
// Creates a list of series, which have same Y axis type.
string [] yAxesSeries = GetYAxesSeries( type, axis.SubAxisName ).ToArray();
// Get auto maximum and auto minimum value
if( axis.axisType == AxisName.X2 || axis.axisType == AxisName.X ) // X axis type is used (X or X2)
{
if( stacked ) // Chart area has a stacked chart types
{
try
{
Common.DataManager.GetMinMaxXValue(out autoMinimum, out autoMaximum, xAxesSeries );
}
catch(System.Exception)
{
throw (new InvalidOperationException(SR.ExceptionAxisStackedChartsDataPointsNumberMismatch));
}
}
// Chart type with two y values used for scale ( bubble chart type )
else if( secondYScale )
{
autoMaximum = Common.DataManager.GetMaxXWithRadiusValue( (ChartArea)this, xAxesSeries );
autoMinimum = Common.DataManager.GetMinXWithRadiusValue( (ChartArea)this, xAxesSeries );
ChartValueType valueTypes = Common.DataManager.Series[xAxesSeries[0]].XValueType;
if( valueTypes != ChartValueType.Date &&
valueTypes != ChartValueType.DateTime &&
valueTypes != ChartValueType.Time &&
valueTypes != ChartValueType.DateTimeOffset )
{
axis.roundedXValues = true;
}
}
else
{
Common.DataManager.GetMinMaxXValue(out autoMinimum, out autoMaximum, xAxesSeries );
}
}
else // Y axis type is used (Y or Y2)
{
// *****************************
// Stacked Chart AxisName
// *****************************
if( stacked ) // Chart area has a stacked chart types
{
try
{
if(hundredPercent) // It's a hundred percent stacked chart
{
autoMaximum = Common.DataManager.GetMaxHundredPercentStackedYValue(hundredPercentNegative, yAxesSeries );
autoMinimum = Common.DataManager.GetMinHundredPercentStackedYValue(hundredPercentNegative, yAxesSeries );
}
else
{
// If stacked groupes are used Min/Max range must calculated
// for each group seperatly.
double stackMaxBarColumn = double.MinValue;
double stackMinBarColumn = double.MaxValue;
double stackMaxArea = double.MinValue;
double stackMinArea = double.MaxValue;
// Split series by group names
ArrayList stackedGroups = this.SplitSeriesInStackedGroups(yAxesSeries);
foreach(string[] groupSeriesNames in stackedGroups)
{
// For stacked bar and column
double stackMaxBarColumnForGroup = Common.DataManager.GetMaxStackedYValue(0, groupSeriesNames );
double stackMinBarColumnForGroup = Common.DataManager.GetMinStackedYValue(0, groupSeriesNames );
// For stacked area
double stackMaxAreaForGroup = Common.DataManager.GetMaxUnsignedStackedYValue(0, groupSeriesNames );
double stackMinAreaForGroup = Common.DataManager.GetMinUnsignedStackedYValue(0, groupSeriesNames );
// Select minimum/maximum
stackMaxBarColumn = Math.Max(stackMaxBarColumn, stackMaxBarColumnForGroup);
stackMinBarColumn = Math.Min(stackMinBarColumn, stackMinBarColumnForGroup);
stackMaxArea = Math.Max(stackMaxArea, stackMaxAreaForGroup);
stackMinArea = Math.Min(stackMinArea, stackMinAreaForGroup);
}
autoMaximum = Math.Max(stackMaxBarColumn,stackMaxArea);
autoMinimum = Math.Min(stackMinBarColumn,stackMinArea);
}
// IsLogarithmic axis
if( axis.IsLogarithmic && autoMinimum < 1.0 )
autoMinimum = 1.0;
}
catch(System.Exception)
{
throw (new InvalidOperationException(SR.ExceptionAxisStackedChartsDataPointsNumberMismatch));
}
}
// Chart type with two y values used for scale ( bubble chart type )
else if( secondYScale )
{
autoMaximum = Common.DataManager.GetMaxYWithRadiusValue( (ChartArea)this, yAxesSeries );
autoMinimum = Common.DataManager.GetMinYWithRadiusValue( (ChartArea)this, yAxesSeries );
}
// *****************************
// Non Stacked Chart Types
// *****************************
else
{
// Check if any series in the area has ExtraYValuesConnectedToYAxis flag set
bool extraYValuesConnectedToYAxis = false;
if(this.Common != null && this.Common.Chart != null)
{
foreach(Series series in this.Common.Chart.Series)
{
if(series.ChartArea == ((ChartArea)this).Name)
{
IChartType charType = Common.ChartTypeRegistry.GetChartType( series.ChartTypeName );
if(charType != null && charType.ExtraYValuesConnectedToYAxis)
{
extraYValuesConnectedToYAxis = true;
break;
}
}
}
}
// The first Chart type can have many Y values (Stock Chart, Range Chart)
if( extraYValuesConnectedToYAxis )
{
Common.DataManager.GetMinMaxYValue(out autoMinimum, out autoMaximum, yAxesSeries );
}
else
{ // The first Chart type can have only one Y value
Common.DataManager.GetMinMaxYValue(0, out autoMinimum, out autoMaximum, yAxesSeries );
}
}
}
// Store Minimum and maximum from data. There is no
// reason to calculate this values every time.
axis.maximumFromData = autoMaximum;
axis.minimumFromData = autoMinimum;
axis.refreshMinMaxFromData = false;
// Make extra test for stored minimum and maximum values
// from data. If Number of points is different then data
// source is changed. That means that we should read
// data again.
axis.numberOfPointsInAllSeries = currentPointsNumber;
}
/// <summary>
/// Splits a single array of series names into multiple arrays
/// based on the stacked group name.
/// </summary>
/// <param name="seriesNames">Array of series name to split.</param>
/// <returns>An array list that contains sub-arrays of series names split by group name.</returns>
private ArrayList SplitSeriesInStackedGroups(string[] seriesNames)
{
Hashtable groupsHashTable = new Hashtable();
foreach(string seriesName in seriesNames)
{
// Get series object
Series series = this.Common.Chart.Series[seriesName];
// NOTE: Fix for issue #6716
// Double check that series supports stacked group feature
string groupName = string.Empty;
if(StackedColumnChart.IsSeriesStackGroupNameSupported(series))
{
// Get stacked group name (empty string by default)
groupName = StackedColumnChart.GetSeriesStackGroupName(series);
}
// Check if this group was alreday added in to the hashtable
if (groupsHashTable.ContainsKey(groupName))
{
ArrayList list = (ArrayList)groupsHashTable[groupName];
list.Add(seriesName);
}
else
{
ArrayList list = new ArrayList();
list.Add(seriesName);
groupsHashTable.Add(groupName, list);
}
}
// Convert results to a list that contains array of strings
ArrayList result = new ArrayList();
foreach(DictionaryEntry entry in groupsHashTable)
{
ArrayList list = (ArrayList)entry.Value;
if(list.Count > 0)
{
int index = 0;
string[] stringArray = new String[list.Count];
foreach(string str in list)
{
stringArray[index++] = str;
}
result.Add(stringArray);
}
}
return result;
}
/// <summary>
/// Find number of points for all series
/// </summary>
/// <returns>Number of points</returns>
private int GetNumberOfAllPoints()
{
int numOfPoints = 0;
foreach( Series series in Common.DataManager.Series )
{
numOfPoints += series.Points.Count;
}
return numOfPoints;
}
/// <summary>
/// This method sets default minimum and maximum values from
/// indexes. This case is used if all X values in a series
/// have 0 value or IsXValueIndexed flag is set.
/// </summary>
/// <param name="axis">Axis</param>
private void SetDefaultFromIndexes( Axis axis )
{
// Adjust margin for side-by-side charts like column
axis.SetTempAxisOffset( );
// Set Axis type
AxisType type = AxisType.Primary;
if( axis.axisType == AxisName.X2 || axis.axisType == AxisName.Y2 )
{
type = AxisType.Secondary;
}
// The maximum is equal to the number of data points.
double autoMaximum = Common.DataManager.GetNumberOfPoints( GetXAxesSeries( type, axis.SubAxisName ).ToArray() );
double autoMinimum = 0.0;
// Axis margin used only for zooming
axis.marginView = 0.0;
if( axis.margin == 100 )
axis.marginView = 1.0;
// If minimum and maximum are same margin always exist.
if( autoMaximum + axis.margin/100 == autoMinimum - axis.margin/100 + 1 )
{
// Set Maximum Number.
axis.SetAutoMaximum( autoMaximum + 1 );
axis.SetAutoMinimum( autoMinimum );
}
else // Nomal case
{
// Set Maximum Number.
axis.SetAutoMaximum( autoMaximum + axis.margin/100 );
axis.SetAutoMinimum( autoMinimum - axis.margin/100 + 1 );
}
// Find the interval. If the nuber of points
// is less then 10 interval is 1.
double axisInterval;
if( axis.ViewMaximum - axis.ViewMinimum <= 10 )
{
axisInterval = 1.0;
}
else
{
axisInterval = axis.CalcInterval( ( axis.ViewMaximum - axis.ViewMinimum ) / 5 );
}
ChartArea area = (ChartArea)this;
if( area.Area3DStyle.Enable3D && !double.IsNaN(axis.interval3DCorrection) )
{
axisInterval = Math.Ceiling( axisInterval / axis.interval3DCorrection );
axis.interval3DCorrection = double.NaN;
// Use interval
if( axisInterval > 1.0 &&
axisInterval < 4.0 &&
axis.ViewMaximum - axis.ViewMinimum <= 4 )
{
axisInterval = 1.0;
}
}
axis.SetInterval = axisInterval;
// If temporary offsets were defined for the margin,
// adjust offset for minor ticks and grids.
if(axis.offsetTempSet)
{
axis.minorGrid.intervalOffset -= axis.MajorGrid.GetInterval();
axis.minorTickMark.intervalOffset -= axis.MajorTickMark.GetInterval();
}
}
/// <summary>
/// Sets the names of all data series which belong to
/// this chart area to collection and sets a list of all
/// different chart types.
/// </summary>
internal void SetData()
{
this.SetData(true, true);
}
/// <summary>
/// Sets the names of all data series which belong to
/// this chart area to collection and sets a list of all
/// different chart types.
/// </summary>
/// <param name="initializeAxes">If set to <c>true</c> the method will initialize axes default values.</param>
/// <param name="checkIndexedAligned">If set to <c>true</c> the method will check that all primary X axis series are aligned if use the IsXValueIndexed flag.</param>
internal void SetData( bool initializeAxes, bool checkIndexedAligned)
{
// Initialize chart type properties
stacked = false;
switchValueAxes = false;
requireAxes = true;
hundredPercent = false;
hundredPercentNegative = false;
chartAreaIsCurcular = false;
secondYScale = false;
// AxisName of the chart area already set.
bool typeSet = false;
// Remove all elements from the collection
this._series.Clear();
// Add series to the collection
foreach( Series series in Common.DataManager.Series )
{
if (series.ChartArea == this.Name && series.IsVisible() && series.Points.Count > 0)
{
this._series.Add(series.Name);
}
}
// Remove all elements from the collection
this.chartTypes.Clear();
// Add series to the collection
foreach( Series series in Common.DataManager.Series )
{
// A item already exist.
bool foundItem = false;
if (series.IsVisible() && series.ChartArea==this.Name)
{
foreach( string type in chartTypes )
{
// AxisName already exist in the chart area
if( type == series.ChartTypeName )
{
foundItem = true;
}
}
// Add chart type to the collection of
// Chart area's chart types
if( !foundItem )
{
// Set stacked type
if( Common.ChartTypeRegistry.GetChartType(series.ChartTypeName).Stacked )
{
stacked = true;
}
if( !typeSet )
{
if( Common.ChartTypeRegistry.GetChartType(series.ChartTypeName).SwitchValueAxes )
switchValueAxes = true;
if( !Common.ChartTypeRegistry.GetChartType(series.ChartTypeName).RequireAxes )
requireAxes = false;
if( Common.ChartTypeRegistry.GetChartType(series.ChartTypeName).CircularChartArea )
chartAreaIsCurcular = true;
if( Common.ChartTypeRegistry.GetChartType(series.ChartTypeName).HundredPercent )
hundredPercent = true;
if( Common.ChartTypeRegistry.GetChartType(series.ChartTypeName).HundredPercentSupportNegative )
hundredPercentNegative = true;
if( Common.ChartTypeRegistry.GetChartType(series.ChartTypeName).SecondYScale )
secondYScale = true;
typeSet = true;
}
else
{
if( Common.ChartTypeRegistry.GetChartType(series.ChartTypeName).SwitchValueAxes != switchValueAxes )
{
throw (new InvalidOperationException(SR.ExceptionChartAreaChartTypesCanNotCombine));
}
}
// Series is not empty
if( Common.DataManager.GetNumberOfPoints( series.Name ) != 0 )
{
this.chartTypes.Add( series.ChartTypeName );
}
}
}
}
// Check that all primary X axis series are aligned if use the IsXValueIndexed flag
if (checkIndexedAligned)
{
for (int axisIndex = 0; axisIndex <= 1; axisIndex++)
{
List<string> seriesArray = this.GetXAxesSeries((axisIndex == 0) ? AxisType.Primary : AxisType.Secondary, string.Empty);
if (seriesArray.Count > 0)
{
bool indexed = false;
string seriesNamesStr = "";
foreach (string seriesName in seriesArray)
{
seriesNamesStr = seriesNamesStr + seriesName.Replace(",", "\\,") + ",";
if (Common.DataManager.Series[seriesName].IsXValueIndexed)
{
indexed = true;
}
}
if (indexed)
{
try
{
Common.DataManipulator.CheckXValuesAlignment(
Common.DataManipulator.ConvertToSeriesArray(seriesNamesStr.TrimEnd(','), false));
}
catch (Exception e)
{
throw (new ArgumentException(SR.ExceptionAxisSeriesNotAligned + e.Message));
}
}
}
}
}
if (initializeAxes)
{
// Set default min, max etc.
SetDefaultAxesValues();
}
}
/// <summary>
/// Returns names of all series, which belong to this chart area
/// and have same chart type.
/// </summary>
/// <param name="chartType">Chart type</param>
/// <returns>Collection with series names</returns>
internal List<string> GetSeriesFromChartType( string chartType )
{
// New collection
List<string> list = new List<string>();
foreach( string seriesName in _series )
{
if( String.Compare( chartType, Common.DataManager.Series[seriesName].ChartTypeName, StringComparison.OrdinalIgnoreCase ) == 0 )
{
// Add a series name to the collection
list.Add( seriesName );
}
}
return list;
}
/// <summary>
/// Returns all series which belong to this chart area.
/// </summary>
/// <returns>Collection with series</returns>
internal List<Series> GetSeries( )
{
// New collection
List<Series> list = new List<Series>();
foreach( string seriesName in _series )
{
list.Add(Common.DataManager.Series[seriesName]);
}
return list;
}
/// <summary>
/// Creates a list of series, which have same X axis type.
/// </summary>
/// <param name="type">Axis type</param>
/// <param name="subAxisName">Sub Axis name</param>
/// <returns>A list of series</returns>
internal List<string> GetXAxesSeries( AxisType type, string subAxisName )
{
// Create a new collection of series
List<string> list = new List<string>();
if (_series.Count == 0)
{
return list;
}
// Ignore sub axis in 3D
if( !this.IsSubAxesSupported )
{
if(subAxisName.Length > 0)
{
return list;
}
}
// Find series which have same axis type
foreach( string ser in _series )
{
#if SUBAXES
if( Common.DataManager.Series[ser].XAxisType == type &&
(Common.DataManager.Series[ser].XSubAxisName == subAxisName || !this.IsSubAxesSupported) )
#else // SUBAXES
if ( Common.DataManager.Series[ser].XAxisType == type)
#endif // SUBAXES
{
// Add a series to the collection
list.Add( ser );
}
}
#if SUBAXES
// If series list is empty for the sub-axis then
// try using the main axis.
if ( list.Count == 0 && subAxisName.Length > 0 )
{
return GetXAxesSeries( type, string.Empty );
}
#endif // SUBAXES
// If primary series do not exist return secondary series
// Axis should always be connected with any series.
if ( list.Count == 0 )
{
if (type == AxisType.Secondary)
{
return GetXAxesSeries(AxisType.Primary, string.Empty);
}
return GetXAxesSeries(AxisType.Secondary, string.Empty);
}
return list;
}
/// <summary>
/// Creates a list of series, which have same Y axis type.
/// </summary>
/// <param name="type">Axis type</param>
/// <param name="subAxisName">Sub Axis name</param>
/// <returns>A list of series</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "subAxisName")]
internal List<string> GetYAxesSeries( AxisType type, string subAxisName )
{
// Create a new collection of series
List<string> list = new List<string>();
// Find series which have same axis type
foreach( string ser in _series )
{
// Get series Y axis type
AxisType seriesYAxisType = Common.DataManager.Series[ser].YAxisType;
#if SUBAXES
string seriesYSubAxisName = subAxisName;
#endif // SUBAXES
// NOTE: Fixes issue #6969
// Ignore series settings if only Primary Y axis supported by the chart type
if (Common.DataManager.Series[ser].ChartType == SeriesChartType.Radar ||
Common.DataManager.Series[ser].ChartType == SeriesChartType.Polar)
{
seriesYAxisType = AxisType.Primary;
#if SUBAXES
seriesYSubAxisName = string.Empty;
#endif // SUBAXES
}
#if SUBAXES
if( seriesYAxisType == type &&
(Common.DataManager.Series[ser].YSubAxisName == seriesYSubAxisName || !this.IsSubAxesSupported) )
#else // SUBAXES
if (seriesYAxisType == type)
#endif // SUBAXES
{
// Add a series to the collection
list.Add( ser );
}
}
#if SUBAXES
// If series list is empty for the sub-axis then
// try using the main axis.
if ( list.Count == 0 && subAxisName.Length > 0 )
{
return GetYAxesSeries( type, string.Empty );
}
#endif // SUBAXES
// If primary series do not exist return secondary series
// Axis should always be connected with any series.
if ( list.Count == 0 && type == AxisType.Secondary )
{
return GetYAxesSeries( AxisType.Primary, string.Empty );
}
return list;
}
/// <summary>
/// Get first series from the chart area
/// </summary>
/// <returns>Data series</returns>
internal Series GetFirstSeries()
{
if( _series.Count == 0 )
{
throw (new InvalidOperationException(SR.ExceptionChartAreaSeriesNotFound));
}
return Common.DataManager.Series[_series[0]];
}
/// <summary>
/// This method returns minimum interval between
/// any two data points from series which belong
/// to this chart area.
/// </summary>
/// <param name="isLogarithmic">Indicates logarithmic scale.</param>
/// <param name="logarithmBase">Logarithm Base</param>
/// <returns>Minimum Interval</returns>
internal double GetPointsInterval(bool isLogarithmic, double logarithmBase)
{
bool sameInterval;
return GetPointsInterval( _series, isLogarithmic, logarithmBase, false, out sameInterval );
}
/// <summary>
/// This method returns minimum interval between
/// any two data points from specified series.
/// </summary>
/// <param name="seriesList">List of series.</param>
/// <param name="isLogarithmic">Indicates logarithmic scale.</param>
/// <param name="logarithmBase">Base for logarithmic base</param>
/// <param name="checkSameInterval">True if check for the same interval should be performed.</param>
/// <param name="sameInterval">Return true if interval is the same.</param>
/// <returns>Minimum Interval</returns>
internal double GetPointsInterval( List<string> seriesList, bool isLogarithmic, double logarithmBase, bool checkSameInterval, out bool sameInterval )
{
Series nullSeries = null;
return GetPointsInterval(seriesList, isLogarithmic, logarithmBase, checkSameInterval, out sameInterval, out nullSeries);
}
/// <summary>
/// This method returns minimum interval between
/// any two data points from specified series.
/// </summary>
/// <param name="seriesList">List of series.</param>
/// <param name="isLogarithmic">Indicates logarithmic scale.</param>
/// <param name="logarithmicBase">Logarithm Base</param>
/// <param name="checkSameInterval">True if check for the same interval should be performed.</param>
/// <param name="sameInterval">Return true if interval is the same.</param>
/// <param name="series">Series with the smallest interval between points.</param>
/// <returns>Minimum Interval</returns>
internal double GetPointsInterval( List<string> seriesList, bool isLogarithmic, double logarithmicBase, bool checkSameInterval, out bool sameInterval, out Series series )
{
long ticksInterval = long.MaxValue;
int monthsInteval = 0;
double previousInterval = double.MinValue;
double oldInterval = Double.MaxValue;
// Initialize return value
sameInterval = true;
series = null;
// Create comma separate string of series names
string seriesNames = "";
if(seriesList != null)
{
foreach( string serName in seriesList )
{
seriesNames += serName + ",";
}
}
// Do not calculate interval every time;
if( checkSameInterval == false || diffIntervalAlignmentChecked == true)
{
if (!isLogarithmic)
{
if( !double.IsNaN(intervalData) && _intervalSeriesList == seriesNames)
{
sameInterval = intervalSameSize;
series = _intervalSeries;
return intervalData;
}
}
else
{
if( !double.IsNaN(intervalLogData) && _intervalSeriesList == seriesNames)
{
sameInterval = intervalSameSize;
series = _intervalSeries;
return intervalLogData;
}
}
}
// Data series loop
int seriesIndex = 0;
Series currentSmallestSeries = null;
ArrayList[] seriesXValues = new ArrayList[seriesList.Count];
foreach( string ser in seriesList )
{
Series dataSeries = Common.DataManager.Series[ ser ];
bool isXValueDateTime = dataSeries.IsXValueDateTime();
// Copy X values to array and prepare for sorting Sort X values.
seriesXValues[seriesIndex] = new ArrayList();
bool sortPoints = false;
double prevXValue = double.MinValue;
double curentXValue = 0.0;
if(dataSeries.Points.Count > 0)
{
if (isLogarithmic)
{
prevXValue = Math.Log(dataSeries.Points[0].XValue, logarithmicBase);
}
else
{
prevXValue = dataSeries.Points[0].XValue;
}
}
foreach( DataPoint point in dataSeries.Points )
{
if (isLogarithmic)
{
curentXValue = Math.Log(point.XValue, logarithmicBase);
}
else
{
curentXValue = point.XValue;
}
if(prevXValue > curentXValue)
{
sortPoints = true;
}
seriesXValues[seriesIndex].Add(curentXValue);
prevXValue = curentXValue;
}
// Sort X values
if(sortPoints)
{
seriesXValues[seriesIndex].Sort();
}
// Data point loop
for( int point = 1; point < seriesXValues[seriesIndex].Count; point++ )
{
// Interval between two sorted data points.
double interval = Math.Abs( (double)seriesXValues[seriesIndex][ point - 1 ] - (double)seriesXValues[seriesIndex][ point ] );
// Check if all intervals are same
if(sameInterval)
{
if(isXValueDateTime)
{
if(ticksInterval == long.MaxValue)
{
// Calculate first interval
GetDateInterval(
(double)seriesXValues[seriesIndex][ point - 1 ],
(double)seriesXValues[seriesIndex][ point ],
out monthsInteval,
out ticksInterval);
}
else
{
// Calculate current interval
long curentTicksInterval = long.MaxValue;
int curentMonthsInteval = 0;
GetDateInterval(
(double)seriesXValues[seriesIndex][ point - 1 ],
(double)seriesXValues[seriesIndex][ point ],
out curentMonthsInteval,
out curentTicksInterval);
// Compare current interval with previous
if(curentMonthsInteval != monthsInteval || curentTicksInterval != ticksInterval)
{
sameInterval = false;
}
}
}
else
{
if( previousInterval != interval && previousInterval != double.MinValue )
{
sameInterval = false;
}
}
}
previousInterval = interval;
// If not minimum interval keep the old one
if( oldInterval > interval && interval != 0)
{
oldInterval = interval;
currentSmallestSeries = dataSeries;
}
}
++seriesIndex;
}
// If interval is not the same check if points from all series are aligned
this.diffIntervalAlignmentChecked = false;
if( checkSameInterval && !sameInterval && seriesXValues.Length > 1)
{
bool sameXValue = false;
this.diffIntervalAlignmentChecked = true;
// All X values must be same
int listIndex = 0;
foreach(ArrayList xList in seriesXValues)
{
for(int pointIndex = 0; pointIndex < xList.Count && !sameXValue; pointIndex++)
{
double xValue = (double)xList[pointIndex];
// Loop through all other lists and see if point is there
for(int index = listIndex + 1; index < seriesXValues.Length && !sameXValue; index++)
{
if( (pointIndex < seriesXValues[index].Count && (double)seriesXValues[index][pointIndex] == xValue) ||
seriesXValues[index].Contains(xValue))
{
sameXValue = true;
break;
}
}
}
++listIndex;
}
// Use side-by-side if at least one xommon X value between eries found
if(sameXValue)
{
sameInterval = true;
}
}
// Interval not found. Interval is 1.
if( oldInterval == Double.MaxValue)
{
oldInterval = 1;
}
intervalSameSize = sameInterval;
if (!isLogarithmic)
{
intervalData = oldInterval;
_intervalSeries = currentSmallestSeries;
series = _intervalSeries;
_intervalSeriesList = seriesNames;
return intervalData;
}
else
{
intervalLogData = oldInterval;
_intervalSeries = currentSmallestSeries;
series = _intervalSeries;
_intervalSeriesList = seriesNames;
return intervalLogData;
}
}
/// <summary>
/// Calculates the difference between two values in years, months, days, ...
/// </summary>
/// <param name="value1">First value.</param>
/// <param name="value2">Second value.</param>
/// <param name="monthsInteval">Interval in months.</param>
/// <param name="ticksInterval">Interval in ticks.</param>
private void GetDateInterval(double value1, double value2, out int monthsInteval, out long ticksInterval)
{
// Convert values to dates
DateTime date1 = DateTime.FromOADate(value1);
DateTime date2 = DateTime.FromOADate(value2);
// Calculate months difference
monthsInteval = date2.Month - date1.Month;
monthsInteval += (date2.Year - date1.Year) * 12;
// Calculate interval in ticks for days, hours, ...
ticksInterval = 0;
ticksInterval += (date2.Day - date1.Day) * TimeSpan.TicksPerDay;
ticksInterval += (date2.Hour - date1.Hour) * TimeSpan.TicksPerHour;
ticksInterval += (date2.Minute - date1.Minute) * TimeSpan.TicksPerMinute;
ticksInterval += (date2.Second - date1.Second) * TimeSpan.TicksPerSecond;
ticksInterval += (date2.Millisecond - date1.Millisecond) * TimeSpan.TicksPerMillisecond;
}
#endregion
}
}