1987 lines
59 KiB
C#
1987 lines
59 KiB
C#
|
//-------------------------------------------------------------
|
|||
|
// <copyright company=<3D>Microsoft Corporation<6F>>
|
|||
|
// Copyright <20> 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<6F>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 <20>integer type<70>
|
|||
|
/// 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 <20>date-time type<70>
|
|||
|
/// 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
|
|||
|
}
|
|||
|
}
|