|
//-------------------------------------------------------------
// <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 don’t 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
}
}
|