File: Common\General\ChartArea3D.cs
Project: ndp\fx\src\DataVisualization\System.Windows.Forms.DataVisualization.csproj (System.Windows.Forms.DataVisualization)
//-------------------------------------------------------------
// <copyright company=�Microsoft Corporation�>
//   Copyright � Microsoft Corporation. All Rights Reserved.
// </copyright>
//-------------------------------------------------------------
// @owner=alexgor, deliant
//=================================================================
//  File:		ChartArea3D.cs
//
//  Namespace:	System.Web.UI.WebControls[Windows.Forms].Charting
//
//	Classes:	ChartArea3DStyle, ChartArea3D
//
//  Purpose:	ChartArea3D class represents 3D chart area. It contains
//              methods for coordinates transformation, drawing the 3D
//              scene and many 3D related helper methods.
//
//	Reviewed:	AG - Microsoft 16, 2007
//
//===================================================================
 
#region Used namespaces
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Collections;
using System.Globalization;
using System.Collections.Generic;
 
#if WINFORMS_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;
	using System.Web.UI.DataVisualization.Charting.ChartTypes;
    using System.Web.UI.DataVisualization.Charting.Utilities;
	using System.Web.UI;
#endif
 
 
#endregion
 
#if WINFORMS_CONTROL
	namespace System.Windows.Forms.DataVisualization.Charting
#else
namespace System.Web.UI.DataVisualization.Charting
 
#endif
{
	#region 3D lightStyle style enumerations
 
		/// <summary>
		/// A lighting style for a 3D chart area.
		/// </summary>
		public enum LightStyle
		{
			/// <summary>
			/// No lighting.
			/// </summary>
			None,
            /// <summary>
            /// Simplistic lighting.
            /// </summary>
			Simplistic,
            /// <summary>
            /// Realistic lighting.
            /// </summary>
			Realistic
		}
 
	#endregion
 
	#region 3D Center of Projetion coordinates enumeration
 
		/// <summary>
		/// Coordinates of the Center Of Projection
		/// </summary>
		[Flags]
		internal enum COPCoordinates
		{
			/// <summary>
			/// Check X coordinate.
			/// </summary>
			X = 1,
			/// <summary>
			/// Check Y coordinate.
			/// </summary>
			Y = 2,
			/// <summary>
			/// Check Z coordinate.
			/// </summary>
			Z = 4
		}
 
	#endregion
 
        /// <summary>
        /// The ChartArea3DStyleClass class provides the functionality for 3D attributes of chart areas,
        /// such as rotation angles and perspective.
        /// </summary>
#if ASPPERM_35
	[AspNetHostingPermission(System.Security.Permissions.SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
    [AspNetHostingPermission(System.Security.Permissions.SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
#endif
    public class ChartArea3DStyle
	{
		#region Constructor and Initialization
 
		/// <summary>
        /// ChartArea3DStyle constructor.
		/// </summary>
		public ChartArea3DStyle()
		{
		}
 
		/// <summary>
        /// ChartArea3DStyle constructor.
		/// </summary>
		public ChartArea3DStyle(ChartArea chartArea)
		{
            this._chartArea = chartArea;
		}
 
        /// <summary>
        /// Initialize Chart area and axes
        /// </summary>
        /// <param name="chartArea">Chart area object.</param>
        internal void Initialize(ChartArea chartArea)
        {
            this._chartArea = chartArea;
        }
 
        #endregion
		
		#region Fields
 
		// Reference to the chart area object
		private	ChartArea	_chartArea = null;
 
		// Enables/disables 3D chart types in the area.
		private	bool		_enable3D	= false;
 
		// Indicates that axes are set at the right angle independent of the rotation.
		private	bool		_isRightAngleAxes	= true;
 
		// Indicates that series should be drawn as isClustered.
		private	bool		_isClustered	= false;
 
		// 3D area lightStyle style.
		private LightStyle	_lightStyle = LightStyle.Simplistic;
 
		// 3D area perspective which controls the scaleView of the chart depth.
		private int			_perspective = 0;
 
		// Chart area rotation angle around the X axis.
		private int			_inclination = 30;
 
		// Chart area rotation angle around the Y axis.
		private int			_rotation = 30;
 
		// Chart area walls width.
		private int			_wallWidth = 7;
 
		// Series points depth in percentages
		private int			_pointDepth = 100;
 
		// Series points gap depth in percentages
		private int			_pointGapDepth = 100;
 
		#endregion
 
		#region Properties
 
        /// <summary>
        /// Gets or sets a Boolean value that toggles 3D for a chart area on and off.
        /// </summary>
		[
		SRCategory("CategoryAttribute3D"),
		Bindable(true),
		DefaultValue(false),
		SRDescription("DescriptionAttributeChartArea3DStyle_Enable3D"),
		ParenthesizePropertyNameAttribute(true)
		]
		public bool Enable3D
		{
			get
			{
                return this._enable3D;
			}
			set
			{
                if (this._enable3D != value)
				{
                    this._enable3D = value;
 
                    if (this._chartArea != null)
					{
#if SUBAXES
						// If one of the axes has sub axis the scales has to be recalculated
						foreach(Axis axis in this._chartArea.Axes)
						{
							if(axis.SubAxes.Count > 0)
							{
								this._chartArea.ResetAutoValues();
								break;
							}
						}
#endif // SUBAXES
 
                        this._chartArea.Invalidate();
					}
				}
			}
		}
 
 
        /// <summary>
        /// Gets or sets a Boolean that determines if a chart area  is displayed using an isometric projection.  
        /// </summary>
		[
        SRCategory("CategoryAttribute3D"),
		Bindable(true),
		DefaultValue(true),
		SRDescription("DescriptionAttributeChartArea3DStyle_RightAngleAxes"),
		RefreshPropertiesAttribute(RefreshProperties.All)
		]
		public bool IsRightAngleAxes
		{
			get
			{
                return _isRightAngleAxes;
			}
			set
			{
                _isRightAngleAxes = value;
 
				// Adjust 3D properties values
                if (_isRightAngleAxes)
				{
					// Disable perspective if right angle axis are used
                    this._perspective = 0;
				}
 
                if (this._chartArea != null)
				{
                    this._chartArea.Invalidate();
				}
			}
		}
 
 
        /// <summary>
        /// Gets or sets a Boolean value that determines if bar chart or column 
        /// chart data series are clustered (displayed along distinct rows).  
        /// </summary>
		[
        SRCategory("CategoryAttribute3D"),
		Bindable(true),
		DefaultValue(false),
		SRDescription("DescriptionAttributeChartArea3DStyle_Clustered"),
		]
		public bool IsClustered
		{
			get
			{
                return _isClustered;
			}
			set
			{
                _isClustered = value;
                if (this._chartArea != null)
				{
                    this._chartArea.Invalidate();
				}
			}
		}
 
		/// <summary>
        /// Gets or sets the style of lighting for a 3D chart area.  
		/// </summary>
		[
        SRCategory("CategoryAttribute3D"),
		Bindable(true),
		DefaultValue(typeof(LightStyle), "Simplistic"),
		SRDescription("DescriptionAttributeChartArea3DStyle_Light"),
		]
		public LightStyle LightStyle
		{
			get
			{
                return _lightStyle;
			}
			set
			{
                _lightStyle = value;
                if (this._chartArea != null)
				{
                    this._chartArea.Invalidate();
				}
			}
		}
 
		/// <summary>
        /// Gets or sets the percent of perspective for a 3D chart area.
		/// </summary>
		[
		SRCategory("CategoryAttribute3D"),
		Bindable(true),
		DefaultValue(0),
		SRDescription("DescriptionAttributeChartArea3DStyle_Perspective"),
		RefreshPropertiesAttribute(RefreshProperties.All)
		]
		public int Perspective
		{
			get
			{
                return _perspective;
			}
			set
			{
				if(value < 0 || value > 100)
				{
                    throw (new ArgumentOutOfRangeException("value", SR.ExceptionChartArea3DPerspectiveInvalid));
				}
 
                _perspective = value;
 
				// Adjust 3D properties values
                if (_perspective != 0)
				{
					// Disable right angle axes
                    this._isRightAngleAxes = false;
				}
 
                if (this._chartArea != null)
				{
                    this._chartArea.Invalidate();
				}
			}
		}
 
		/// <summary>
        /// Gets or sets the inclination for a 3D chart area.
		/// </summary>
		[
		SRCategory("CategoryAttribute3D"),
		Bindable(true),
		DefaultValue(30),
		SRDescription("DescriptionAttributeChartArea3DStyle_Inclination"),
		RefreshPropertiesAttribute(RefreshProperties.All)
		]
		public int Inclination
		{
			get
			{
                return _inclination;
			}
			set
			{
				if(value < -90 || value > 90)
				{
                    throw (new ArgumentOutOfRangeException("value", SR.ExceptionChartArea3DInclinationInvalid));
				}
                _inclination = value;
 
                if (this._chartArea != null)
				{
                    this._chartArea.Invalidate();
				}
			}
		}
 
		/// <summary>
        /// Gets or sets the rotation angle for a 3D chart area.
		/// </summary>
		[
		SRCategory("CategoryAttribute3D"),
		Bindable(true),
		DefaultValue(30),
		SRDescription("DescriptionAttributeChartArea3DStyle_Rotation"),
		RefreshPropertiesAttribute(RefreshProperties.All)
		]
		public int Rotation
		{
			get
			{
                return _rotation;
			}
			set
			{
				if(value < -180 || value > 180)
				{
                    throw (new ArgumentOutOfRangeException("value", SR.ExceptionChartArea3DRotationInvalid));
				}
                _rotation = value;
 
                if (this._chartArea != null)
				{
                    this._chartArea.Invalidate();
				}
			}
		}
 
		/// <summary>
        /// Gets or sets the width of the walls displayed in 3D chart areas.  
		/// </summary>
		[
		SRCategory("CategoryAttribute3D"),
		Bindable(true),
		DefaultValue(7),
		SRDescription("DescriptionAttributeChartArea3DStyle_WallWidth"),
		RefreshPropertiesAttribute(RefreshProperties.All)
		]
		public int WallWidth
		{
			get
			{
                return _wallWidth;
			}
			set
			{
				if(value < 0 || value > 30)
				{
                    throw (new ArgumentOutOfRangeException("value", SR.ExceptionChartArea3DWallWidthInvalid));
				}
 
                _wallWidth = value;
                if (this._chartArea != null)
				{
                    this._chartArea.Invalidate();
				}
			}
		}
 
		/// <summary>
        /// Gets or sets the depth of data points displayed in 3D chart areas (0-1000%).  
		/// </summary>
		[
		SRCategory("CategoryAttribute3D"),
		Bindable(true),
		DefaultValue(100),
		SRDescription("DescriptionAttributeChartArea3DStyle_PointDepth"),
		RefreshPropertiesAttribute(RefreshProperties.All)
		]
		public int PointDepth
		{
			get
			{
                return _pointDepth;
			}
			set
			{
				if(value < 0 || value > 1000)
				{
                    throw (new ArgumentOutOfRangeException("value", SR.ExceptionChartArea3DPointsDepthInvalid));
				}
 
                _pointDepth = value;
                if (this._chartArea != null)
				{
                    this._chartArea.Invalidate();
				}
			}
		}
 
		/// <summary>
        /// Gets or sets the distance between series rows in 3D chart areas (0-1000%).
		/// </summary>
		[
		SRCategory("CategoryAttribute3D"),
		Bindable(true),
		DefaultValue(100),
		SRDescription("DescriptionAttributeChartArea3DStyle_PointGapDepth"),
		RefreshPropertiesAttribute(RefreshProperties.All)
		]
		public int PointGapDepth
		{
			get
			{
                return _pointGapDepth;
			}
			set
			{
				if(value < 0 || value > 1000)
				{
                    throw (new ArgumentOutOfRangeException("value", SR.ExceptionChartArea3DPointsGapInvalid));
				}
 
                _pointGapDepth = value;
                if (this._chartArea != null)
				{
                    this._chartArea.Invalidate();
				}
			}
		}
 
		#endregion
	}
 
	/// <summary>
    /// ChartArea3D class represents 3D chart area. It contains all the 3D 
    /// scene settings and methods for drawing the 3D plotting area, and calculating 
    /// the depth of chart elements.
	/// </summary>
	public partial class ChartArea
	{
		#region Fields
 
		// Chart area 3D style attribuytes
		private		ChartArea3DStyle	_area3DStyle = new ChartArea3DStyle();
 
		// Coordinate convertion matrix
		internal	Matrix3D			matrix3D = new Matrix3D();
 
		// Chart area scene wall width in relative coordinates
		internal	SizeF				areaSceneWallWidth = SizeF.Empty;
 
		// Chart area scene depth
		internal	float				areaSceneDepth = 0;
 
		// Visible surfaces in plotting area
		private		SurfaceNames			_visibleSurfaces;
 
		// Z axis depth of series points
		private		double				_pointsDepth = 0;
 
		// Z axis depth of the gap between isClustered series
		private		double				_pointsGapDepth = 0;
 
		/// <summary>
		/// Indicates that series order should be reversed to simulate Y axis rotation.
		/// </summary>
		private	bool				_reverseSeriesOrder = false;
 
		/// <summary>
		/// Old X axis reversed flag
		/// </summary>
		internal	bool				oldReverseX = false;
 
		/// <summary>
		/// Old Y axis reversed flag
		/// </summary>
		internal	bool				oldReverseY = false;
 
		/// <summary>
		/// Old Y axis rotation angle
		/// </summary>
		internal	int					oldYAngle = 30;
 
		/// <summary>
		/// List of all stack group names
		/// </summary>
		private	ArrayList			_stackGroupNames = null;
 
        /// <summary>
        /// This list contains an array of series names for each 3D cluster
        /// </summary>
		internal	List<List<string>>	seriesClusters = null;
 
        #endregion
 
		#region 3D Style properties
 
		/// <summary>
        /// Gets or sets a ChartArea3DStyle object, used to draw all series in a chart area in 3D.
		/// </summary>
		[
		SRCategory("CategoryAttribute3D"),
		Bindable(true),
		DefaultValue(null),
		SRDescription("DescriptionAttributeArea3DStyle"),
		DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
		TypeConverter(typeof(NoNameExpandableObjectConverter)),
#if !WINFORMS_CONTROL
		PersistenceMode(PersistenceMode.InnerProperty),
#endif
		]
		public ChartArea3DStyle Area3DStyle
		{
			get
			{
                return _area3DStyle;
			}
			set
			{
                _area3DStyle = value;
 
				// Initialize style object
                _area3DStyle.Initialize((ChartArea)this);
			}
		}
 
        /// <summary>
        /// Indicates that series order should be reversed to simulate Y axis rotation.
        /// </summary>
        internal bool ReverseSeriesOrder
        {
            get { return _reverseSeriesOrder; }
        }
 
        /// <summary>
        /// Gets the list of all stack group names
        /// </summary>
        internal ArrayList StackGroupNames
        {
            get { return _stackGroupNames; }
        }
 
		#endregion
 
		#region 3D Coordinates transfotmation methods
 
		/// <summary>
        /// Call this method to apply 3D transformations on an array of 3D points (must be done before calling GDI+ drawing methods).
		/// </summary>
		/// <param name="points">3D Points array.</param>
		public void TransformPoints( Point3D[] points )
		{
			// Convert Z coordinates from 0-100% to axis values
			foreach(Point3D pt in points)
			{
				pt.Z = (pt.Z / 100f) * this.areaSceneDepth;
			}
 
			// Transform points
			this.matrix3D.TransformPoints( points );
		}
 
		#endregion
 
		#region 3D Scene drawing methods
		
		/// <summary>
		/// Draws chart area 3D scene, which consists of 3 or 2 walls.
		/// </summary>
		/// <param name="graph">Chart graphics object.</param>
		/// <param name="position">Chart area 2D position.</param>
		internal void DrawArea3DScene(ChartGraphics graph, RectangleF position)
		{
			// Reference to the chart area class
			ChartArea chartArea = (ChartArea)this;
 
			// Calculate relative size of the wall
			areaSceneWallWidth = graph.GetRelativeSize( new SizeF(this.Area3DStyle.WallWidth, this.Area3DStyle.WallWidth));
 
			//***********************************************************
			//** Calculate the depth of the chart area scene
			//***********************************************************
			areaSceneDepth = GetArea3DSceneDepth();
 
			//***********************************************************
			//** Initialize coordinate transformation matrix
			//***********************************************************
			this.matrix3D.Initialize(
				position, 
				areaSceneDepth, 
				this.Area3DStyle.Inclination,
				this.Area3DStyle.Rotation,
				this.Area3DStyle.Perspective,
				this.Area3DStyle.IsRightAngleAxes);
 
			//***********************************************************
			//** Initialize Lighting
			//***********************************************************
			this.matrix3D.InitLight( 
				this.Area3DStyle.LightStyle
			);
 
			//***********************************************************
			//** Find chart area visible surfaces
			//***********************************************************
            _visibleSurfaces = graph.GetVisibleSurfaces(
				position, 
				0, 
				areaSceneDepth, 
				this.matrix3D);
 
			//***********************************************************
			//** Chech if area scene should be drawn
			//***********************************************************
			Color	sceneBackColor = chartArea.BackColor;
 
			// Do not draw the transparent walls
			if(sceneBackColor == Color.Transparent)
			{
				// Area wall is not visible
				areaSceneWallWidth = SizeF.Empty;
				return;
			}
 
			// If color is not set (default) - use LightGray
			if(sceneBackColor == Color.Empty)
			{
				sceneBackColor = Color.LightGray;
			}
 
			//***********************************************************
			//** Adjust scene 2D rectangle so that wall are drawn
			//** outside plotting area.
			//***********************************************************
			// If bottom wall is visible
			if(IsBottomSceneWallVisible())
			{
				position.Height += areaSceneWallWidth.Height;
			}
 
			// Adjust for the left/right wall
			position.Width += areaSceneWallWidth.Width;
			if(this.Area3DStyle.Rotation > 0)
			{
				position.X -= areaSceneWallWidth.Width;
			}
 
			//***********************************************************
			//** Draw scene walls
			//***********************************************************
 
			// Draw back wall
			RectangleF	wallRect2D = new RectangleF(position.Location, position.Size);
			float		wallDepth = areaSceneWallWidth.Width;
			float		wallZPosition = -wallDepth;
 
			// For isometric projection Front wall should be visible sometimes
			if( IsMainSceneWallOnFront())
			{
				wallZPosition = areaSceneDepth;
			}
 
			graph.Fill3DRectangle( 
				wallRect2D,
				wallZPosition,
				wallDepth,
				this.matrix3D,
				chartArea.Area3DStyle.LightStyle,
				sceneBackColor, 
				chartArea.BorderColor, 
				chartArea.BorderWidth, 
				chartArea.BorderDashStyle, 
				DrawingOperationTypes.DrawElement );
 
			// Draw side wall on the left or right side
			wallRect2D = new RectangleF(position.Location, position.Size);
			wallRect2D.Width = areaSceneWallWidth.Width;
			if(!IsSideSceneWallOnLeft())
			{
				// Wall is on the right side
				wallRect2D.X = position.Right - areaSceneWallWidth.Width;
			}
			graph.Fill3DRectangle( 
				wallRect2D,
				0f,
				areaSceneDepth,
				this.matrix3D,
				chartArea.Area3DStyle.LightStyle,
				sceneBackColor, 
				chartArea.BorderColor, 
				chartArea.BorderWidth, 
				chartArea.BorderDashStyle, 
				DrawingOperationTypes.DrawElement);
 
			// Draw bottom wall
			if(IsBottomSceneWallVisible())
			{
				wallRect2D = new RectangleF(position.Location, position.Size);
				wallRect2D.Height = areaSceneWallWidth.Height;
				wallRect2D.Y = position.Bottom - areaSceneWallWidth.Height;
				wallRect2D.Width -= areaSceneWallWidth.Width;
				if(IsSideSceneWallOnLeft())
				{
					wallRect2D.X += areaSceneWallWidth.Width;
				}
 
				wallZPosition = 0;
				graph.Fill3DRectangle( 
					wallRect2D,
					0f,
					areaSceneDepth,
					this.matrix3D,
					chartArea.Area3DStyle.LightStyle,
					sceneBackColor, 
					chartArea.BorderColor, 
					chartArea.BorderWidth, 
					chartArea.BorderDashStyle, 
					DrawingOperationTypes.DrawElement );
			}
 
		}
 
		/// <summary>
		/// Helper method which return True if bottom wall of the 
		/// chart area scene is visible.
		/// </summary>
		/// <returns>True if bottom wall is visible.</returns>
		internal bool IsBottomSceneWallVisible()
		{
			return (this.Area3DStyle.Inclination >= 0);
		}
 
        /// <summary>
        /// Helper method which return True if main wall of the 
        /// chart area scene is displayed on the front side.
        /// </summary>
        /// <returns>True if front wall is visible.</returns>
		internal bool IsMainSceneWallOnFront()
		{
			// Note: Not used in this version!
			return false;
		}
 
        /// <summary>
        /// Helper method which return True if side wall of the 
        /// chart area scene is displayed on the left side.
        /// </summary>
        /// <returns>True if bottom wall is visible.</returns>
		internal bool IsSideSceneWallOnLeft()
		{
			return (this.Area3DStyle.Rotation > 0);
		}
 
		#endregion
 
		#region 3D Scene depth claculation methods
 
		/// <summary>
        /// Call this method to get the Z position of a series (useful for custom drawing).
		/// </summary>
        /// <param name="series">The series to retrieve the Z position for.</param>
		/// <returns>The Z position of the specified series. Measured as a percentage of the chart area's depth.</returns>
		public float GetSeriesZPosition(Series series)
		{
			float	positionZ, depth;
			GetSeriesZPositionAndDepth(series, out depth, out positionZ);
			return ((positionZ + depth/2f) / this.areaSceneDepth) * 100f;
		}
 
		/// <summary>
		/// Call this method to get the depth of a series in a chart area.
		/// </summary>
        /// <param name="series">The series to retrieve the depth for.</param>
		/// <returns>The depth of the specified series. Measured as a percentage of the chart area's depth.</returns>
		public float GetSeriesDepth(Series series)
		{
			float	positionZ, depth;
			GetSeriesZPositionAndDepth(series, out depth, out positionZ);
			return (depth / this.areaSceneDepth) * 100f;
		}
 
		/// <summary>
		/// Calculates area 3D scene depth depending on the number of isClustered 
		/// series and interval between points.
		/// </summary>
		/// <returns>Returns the depth of the chart area scene.</returns>
		private float GetArea3DSceneDepth()
		{
			//***********************************************************
			//** Calculate the smallest interval between points
			//***********************************************************
 
			// Check if any series attached to the area is indexed
            bool indexedSeries = ChartHelper.IndexedSeries(this.Common, this._series.ToArray());
 
			// Smallest interval series
			Series	smallestIntervalSeries = null;
			if(this._series.Count > 0)
			{
				smallestIntervalSeries = this.Common.DataManager.Series[(string)this._series[0]];
			}
 
			// Get X axis
			Axis	xAxis = ((ChartArea)this).AxisX;
			if(this._series.Count > 0)
			{
				Series	firstSeries = this.Common.DataManager.Series[this._series[0]];
				if(firstSeries != null && firstSeries.XAxisType == AxisType.Secondary)
				{
					xAxis = ((ChartArea)this).AxisX2;
				}
			}
 
			// Get smallest interval between points (use interval 1 for indexed series)
			double clusteredInterval = 1;
			if(!indexedSeries)
			{
				bool sameInterval;
				clusteredInterval = this.GetPointsInterval(this._series, xAxis.IsLogarithmic, xAxis.logarithmBase, false, out sameInterval, out smallestIntervalSeries);
			}
 
			//***********************************************************
			//** Check if "DrawSideBySide" attribute is set.
			//***********************************************************
			bool	drawSideBySide = false;
			if(smallestIntervalSeries != null)
			{
				drawSideBySide = Common.ChartTypeRegistry.GetChartType(smallestIntervalSeries.ChartTypeName).SideBySideSeries;
				foreach(string seriesName in this._series)
				{
					if(this.Common.DataManager.Series[seriesName].IsCustomPropertySet(CustomPropertyName.DrawSideBySide))
					{
						string attribValue = this.Common.DataManager.Series[seriesName][CustomPropertyName.DrawSideBySide];
						if(String.Compare(attribValue, "False", StringComparison.OrdinalIgnoreCase) == 0)
						{
							drawSideBySide = false;
						}
						else if(String.Compare(attribValue, "True", StringComparison.OrdinalIgnoreCase) == 0)
						{
							drawSideBySide = true;
						}
                        else if (String.Compare(attribValue, "Auto", StringComparison.OrdinalIgnoreCase) == 0)
						{
							// Do nothing
						}
						else
						{
                            throw (new InvalidOperationException(SR.ExceptionAttributeDrawSideBySideInvalid));
						}
					}
				}
			}
 
			// Get smallest interval cate----cal axis
			Axis	categoricalAxis = ((ChartArea)this).AxisX;
			if(smallestIntervalSeries != null && smallestIntervalSeries.XAxisType == AxisType.Secondary)
			{
				categoricalAxis = ((ChartArea)this).AxisX2;
			}
 
			//***********************************************************
			//** If series with the smallest interval is displayed
			//** side-by-side - devide the interval by number of series
			//** of the same chart type.
			//***********************************************************
			double pointWidthSize = 0.8;
			int	seriesNumber = 1;
			if(smallestIntervalSeries != null)
			{
				// Check if series is side-by-side
				if(this.Area3DStyle.IsClustered && drawSideBySide)
				{
					// Count number of side-by-side series
					seriesNumber = 0;
					foreach(string seriesName in this._series)
					{
						// Get series object from name
						Series	curSeries = this.Common.DataManager.Series[seriesName];
						if(String.Compare(curSeries.ChartTypeName, smallestIntervalSeries.ChartTypeName, StringComparison.OrdinalIgnoreCase) == 0 )
						{
							++seriesNumber;
						}
					}
				}
			}
 
 
 
			//***********************************************************
			//** Stacked column and bar charts can be drawn side-by-side
			//** using the StackGroupName custom properties. The code 
			//** checks if multiple groups are used how many of these
			//** groups exsist.
			//**
			//** If isClustered mode enabled each stack group is drawn 
			//** using it's own cluster.
			//***********************************************************
			if(smallestIntervalSeries != null && this.Area3DStyle.IsClustered)
			{
				// Check series support stack groups
				if(Common.ChartTypeRegistry.GetChartType(smallestIntervalSeries.ChartTypeName).SupportStackedGroups)
				{
					// Calculate how many stack groups exsist
					seriesNumber = 0;
					ArrayList stackGroupNames = new ArrayList();
					foreach(string seriesName in this._series)
					{
						// Get series object from name
						Series	curSeries = this.Common.DataManager.Series[seriesName];
						if(String.Compare(curSeries.ChartTypeName, smallestIntervalSeries.ChartTypeName, StringComparison.OrdinalIgnoreCase) == 0 )
						{
							string seriesStackGroupName = string.Empty;
							if(curSeries.IsCustomPropertySet(CustomPropertyName.StackedGroupName))
							{
								seriesStackGroupName = curSeries[CustomPropertyName.StackedGroupName];
							}
 
							// Add group name if it do not already exsist
							if(!stackGroupNames.Contains(seriesStackGroupName))
							{
								stackGroupNames.Add(seriesStackGroupName);
							}
						}
					}
					seriesNumber = stackGroupNames.Count;
				}
			}
 
 
 
			//***********************************************************
			//** Check if series provide custom value for point\gap depth
			//***********************************************************
            _pointsDepth = clusteredInterval * pointWidthSize * this.Area3DStyle.PointDepth / 100.0;
            _pointsDepth = categoricalAxis.GetPixelInterval(_pointsDepth);
			if(smallestIntervalSeries != null)
			{
                _pointsDepth = smallestIntervalSeries.GetPointWidth(
					this.Common.graph, 
					categoricalAxis, 
					clusteredInterval, 
					0.8) / seriesNumber;
                _pointsDepth *= this.Area3DStyle.PointDepth / 100.0;
			}
            _pointsGapDepth = (_pointsDepth * 0.8) * this.Area3DStyle.PointGapDepth / 100.0;
 
			// Get point depth and gap from series
			if(smallestIntervalSeries != null)
			{
                smallestIntervalSeries.GetPointDepthAndGap(
					this.Common.graph, 
					categoricalAxis,
                    ref _pointsDepth,
                    ref _pointsGapDepth);
			}
 
 
			//***********************************************************
			//** Calculate scene depth
			//***********************************************************
            return (float)((_pointsGapDepth + _pointsDepth) * GetNumberOfClusters());
		}
 
		/// <summary>
		/// Calculates the depth and Z position for specified series.
		/// </summary>
		/// <param name="series">Series object.</param>
		/// <param name="depth">Returns series depth.</param>
		/// <param name="positionZ">Returns series Z position.</param>
		internal void GetSeriesZPositionAndDepth(Series series, out float depth, out float positionZ)
		{
            // Check arguments
            if (series == null)
                throw new ArgumentNullException("series");
 
			// Get series cluster index
			int seriesIndex = GetSeriesClusterIndex(series);
 
			// Initialize the output parameters
            depth = (float)_pointsDepth;
            positionZ = (float)(_pointsGapDepth / 2.0 + (_pointsDepth + _pointsGapDepth) * seriesIndex);
		}
 
 
 
		/// <summary>
		/// Returns number of clusters on the Z axis.
		/// </summary>
		/// <returns>Number of clusters on the Z axis.</returns>
		internal int GetNumberOfClusters()
		{
			if(this.seriesClusters == null)
			{
				// Lists that hold processed chart types and stacked groups
				ArrayList	processedChartTypes = new ArrayList();
				ArrayList	processedStackedGroups = new ArrayList();
 
				// Reset series cluster list
				this.seriesClusters = new List<List<string>>();
			
				// Iterate through all series that belong to this chart area
				int clusterIndex = -1;
				foreach(string seriesName in this._series)
				{
					// Get series object by name
					Series	curSeries = this.Common.DataManager.Series[seriesName];
 
					// Check if stacked chart type is using multiple groups that 
					// can be displayed in individual clusters
					if(!this.Area3DStyle.IsClustered &&
						Common.ChartTypeRegistry.GetChartType(curSeries.ChartTypeName).SupportStackedGroups)
					{
						// Get group name
						string stackGroupName = StackedColumnChart.GetSeriesStackGroupName(curSeries);
 
						// Check if group was already counted
						if(processedStackedGroups.Contains(stackGroupName))
						{
							// Find in which cluster this stacked group is located
							bool found = false;
							for(int index = 0; !found && index < this.seriesClusters.Count; index++)
							{
								foreach(string name in this.seriesClusters[index])
								{
									// Get series object by name
									Series	ser = this.Common.DataManager.Series[name];
									if(stackGroupName == StackedColumnChart.GetSeriesStackGroupName(ser))
									{
										clusterIndex = index;
										found = true;
									}
								}
							}
						}
						else
						{
							// Increase cluster index
							clusterIndex = this.seriesClusters.Count;
 
							// Add processed group name
							processedStackedGroups.Add(stackGroupName);
						}
					}
 
 
						// Chech if series is displayed in the same cluster than other series
					else if( Common.ChartTypeRegistry.GetChartType(curSeries.ChartTypeName).Stacked ||
						(this.Area3DStyle.IsClustered && Common.ChartTypeRegistry.GetChartType(curSeries.ChartTypeName).SideBySideSeries) )
					{
						// Check if this chart type is already in the list
						if(processedChartTypes.Contains(curSeries.ChartTypeName.ToUpper(System.Globalization.CultureInfo.InvariantCulture)))
						{
							// Find in which cluster this chart type is located
							bool found = false;
							for(int index = 0; !found && index < this.seriesClusters.Count; index++)
							{
								foreach(string name in this.seriesClusters[index])
								{
									// Get series object by name
									Series	ser = this.Common.DataManager.Series[name];
									if(ser.ChartTypeName.ToUpper(System.Globalization.CultureInfo.InvariantCulture) == 
										curSeries.ChartTypeName.ToUpper(System.Globalization.CultureInfo.InvariantCulture))
									{
										clusterIndex = index;
										found = true;
									}
								}
							}
						}
						else
						{
							// Increase cluster index
							clusterIndex = this.seriesClusters.Count;
 
							// Add new chart type into the collection
							processedChartTypes.Add(curSeries.ChartTypeName.ToUpper(System.Globalization.CultureInfo.InvariantCulture));
						}
					}
					else
					{
						// Create New cluster
						clusterIndex = this.seriesClusters.Count;
					}
 
					// Create an item in the cluster list that will hold all series names
					if(this.seriesClusters.Count <= clusterIndex)
					{
						this.seriesClusters.Add(new List<string>());
					}
 
					// Add series name into the current cluster
					this.seriesClusters[clusterIndex].Add(seriesName);
				}
			}
				
			return this.seriesClusters.Count;
		}
 
		/// <summary>
		/// Get series cluster index.
		/// </summary>
		/// <param name="series">Series object.</param>
		/// <returns>Series cluster index.</returns>
		internal int GetSeriesClusterIndex(Series series)
		{
			// Fill list of clusters
			if(this.seriesClusters == null)
			{
				this.GetNumberOfClusters();
			}
 
			// Iterate through all clusters
			for(int clusterIndex = 0; clusterIndex < this.seriesClusters.Count; clusterIndex++)
			{
				List<string> seriesNames = this.seriesClusters[clusterIndex];
 
				// Iterate through all series names
				foreach(string seriesName in seriesNames)
				{
					if(seriesName == series.Name)
					{
						// Check if series are drawn in reversed order
						if(this._reverseSeriesOrder)
						{
							clusterIndex = (this.seriesClusters.Count - 1) - clusterIndex;
						}
						return clusterIndex;
					}
				}
			}
			return 0;
		}
 
 
 
		#endregion
 
		#region 3D Scene helper methods
 
		/// <summary>
		/// This method is used to calculate estimated scene 
		/// depth. Regular scene depth method can not be used 
		/// because Plot area position is zero. Instead, Chart 
		/// area position is used to find depth of the scene. 
		/// Algorithm which draws axis labels will decide what 
		/// should be size and position of plotting area.
		/// </summary>
		/// <returns>Returns estimated scene depth</returns>
		private float GetEstimatedSceneDepth()
		{
			float sceneDepth;
 
			ChartArea area = (ChartArea) this;
 
 
			// Reset current list of clusters
			this.seriesClusters = null;
 
 
			ElementPosition plottingAreaRect = area.InnerPlotPosition;
 
			area.AxisX.PlotAreaPosition = area.Position;
			area.AxisY.PlotAreaPosition = area.Position;
			area.AxisX2.PlotAreaPosition = area.Position;
			area.AxisY2.PlotAreaPosition = area.Position;
 
			sceneDepth = GetArea3DSceneDepth();
 
			area.AxisX.PlotAreaPosition = plottingAreaRect;
			area.AxisY.PlotAreaPosition = plottingAreaRect;
			area.AxisX2.PlotAreaPosition = plottingAreaRect;
			area.AxisY2.PlotAreaPosition = plottingAreaRect;
 
			return sceneDepth;
		}
 
		/// <summary>
		/// Estimate Interval for 3D Charts. When scene is rotated the 
		/// number of labels should be changed.
		/// </summary>
		/// <param name="graph">Chart graphics object.</param>
		internal void Estimate3DInterval(ChartGraphics graph )
		{
			// Reference to the chart area class
            ChartArea area = (ChartArea)this;
 
			// Calculate relative size of the wall
			areaSceneWallWidth = graph.GetRelativeSize( new SizeF(this.Area3DStyle.WallWidth, this.Area3DStyle.WallWidth));
			
			//***********************************************************
			//** Calculate the depth of the chart area scene
			//***********************************************************
			areaSceneDepth = GetEstimatedSceneDepth();
 
			RectangleF plottingRect = area.Position.ToRectangleF();
 
			// Check if plot area position was recalculated.
			// If not and non-auto InnerPlotPosition & Position were
			// specified - do all needed calculations
			if(PlotAreaPosition.Width == 0 && 
				PlotAreaPosition.Height == 0 &&
				!area.InnerPlotPosition.Auto 
				&& !area.Position.Auto)
			{
				// Initialize plotting area position
				
				if(!area.InnerPlotPosition.Auto)
				{
					plottingRect.X += (area.Position.Width / 100F) * area.InnerPlotPosition.X;
					plottingRect.Y += (area.Position.Height / 100F) * area.InnerPlotPosition.Y;
					plottingRect.Width = (area.Position.Width / 100F) * area.InnerPlotPosition.Width;
					plottingRect.Height = (area.Position.Height / 100F) * area.InnerPlotPosition.Height;
				}
 
			}
 
			int yAngle = GetRealYAngle( );
			
			//***********************************************************
			//** Initialize coordinate transformation matrix
			//***********************************************************
			Matrix3D intervalMatrix3D = new Matrix3D();
			intervalMatrix3D.Initialize(
				plottingRect, 
				areaSceneDepth, 
				this.Area3DStyle.Inclination,
				yAngle,
				this.Area3DStyle.Perspective,
				this.Area3DStyle.IsRightAngleAxes);
			bool notUsed;
			float zPosition;
			double size;
			
			Point3D [] points = new Point3D[8];
			
			if( area.switchValueAxes )
			{
				
				// X Axis
				zPosition = axisX.GetMarksZPosition( out notUsed );
			
				points[0] = new Point3D( plottingRect.X, plottingRect.Y, zPosition );
				points[1] = new Point3D( plottingRect.X, plottingRect.Bottom, zPosition );
 
				// Y Axis
				zPosition = axisY.GetMarksZPosition( out notUsed );
 
				points[2] = new Point3D( plottingRect.X, plottingRect.Bottom, zPosition );
				points[3] = new Point3D( plottingRect.Right, plottingRect.Bottom, zPosition );
 
				// X2 Axis
				zPosition = axisX2.GetMarksZPosition( out notUsed );
			
				points[4] = new Point3D( plottingRect.X, plottingRect.Y, zPosition );
				points[5] = new Point3D( plottingRect.X, plottingRect.Bottom, zPosition );
 
				// Y2 Axis
				zPosition = axisY2.GetMarksZPosition( out notUsed );
 
				points[6] = new Point3D( plottingRect.X, plottingRect.Y, zPosition );
				points[7] = new Point3D( plottingRect.Right, plottingRect.Y, zPosition );
			}
			else
			{
				// X Axis
				zPosition = axisX.GetMarksZPosition( out notUsed );
			
				points[0] = new Point3D( plottingRect.X, plottingRect.Bottom, zPosition );
				points[1] = new Point3D( plottingRect.Right, plottingRect.Bottom, zPosition );
 
				// Y Axis
				zPosition = axisY.GetMarksZPosition( out notUsed );
 
				points[2] = new Point3D( plottingRect.X, plottingRect.Y, zPosition );
				points[3] = new Point3D( plottingRect.X, plottingRect.Bottom, zPosition );
 
				// X2 Axis
				zPosition = axisX2.GetMarksZPosition( out notUsed );
			
				points[4] = new Point3D( plottingRect.X, plottingRect.Y, zPosition );
				points[5] = new Point3D( plottingRect.Right, plottingRect.Y, zPosition );
 
				// Y2 Axis
				zPosition = axisY2.GetMarksZPosition( out notUsed );
 
				points[6] = new Point3D( plottingRect.X, plottingRect.Y, zPosition );
				points[7] = new Point3D( plottingRect.X, plottingRect.Bottom, zPosition );
			}
 
			// Crossing has to be reset because interval and 
			// sometimes minimum and maximum are changed.
			foreach( Axis axis in area.Axes )
			{
				axis.crossing = axis.tempCrossing;
			}
 
			// Transform all points
			intervalMatrix3D.TransformPoints( points );
 
			int axisIndx = 0;
			foreach( Axis axis in area.Axes )
			{
				// Find size of projected axis
				size = Math.Sqrt( 
					( points[axisIndx].X - points[axisIndx+1].X ) * ( points[axisIndx].X - points[axisIndx+1].X ) + 
					( points[axisIndx].Y - points[axisIndx+1].Y ) * ( points[axisIndx].Y - points[axisIndx+1].Y ) );
 
				// At the beginning plotting area chart is not calculated because 
				// algorithm for labels calculates plotting area position. To 
				// calculate labels position we need interval and interval 
				// need this correction. Because of that Chart area is used 
				// instead of plotting area position. If secondary label is 
				// enabled error for using chart area position instead of 
				// plotting area position is much bigger. This value 
				// corrects this error.
				float plottingChartAreaCorrection = 1;
				if( !area.switchValueAxes )
				{
					plottingChartAreaCorrection = 0.5F;
				}
 
				// Set correction for axis size
				if( axis.AxisName == AxisName.X || axis.AxisName == AxisName.X2 )
				{
					if( area.switchValueAxes )
						axis.interval3DCorrection =  size / plottingRect.Height;
					else
						axis.interval3DCorrection =  size / plottingRect.Width;
				}
				else
				{
					if( area.switchValueAxes )
						axis.interval3DCorrection =  size / plottingRect.Width;
					else
						axis.interval3DCorrection =  size / plottingRect.Height * plottingChartAreaCorrection;
				}
 
				// There is a limit for correction
				if( axis.interval3DCorrection < 0.15 )
					axis.interval3DCorrection = 0.15;
 
				// There is a limit for correction
				if( axis.interval3DCorrection > 0.8 )
					axis.interval3DCorrection = 1.0;
 
				axisIndx += 2;
 
			}
		}
 
 
		/// <summary>
		/// Calculates real Y angle from Y angle and reverseSeriesOrder field
		/// </summary>
		/// <returns>Real Y angle</returns>
		internal int GetRealYAngle( )
		{
			int yAngle;
 
			// Case from -90 to 90
			yAngle = this.Area3DStyle.Rotation;
 
			// Case from 90 to 180
			if( this._reverseSeriesOrder && this.Area3DStyle.Rotation >= 0 )
				yAngle = this.Area3DStyle.Rotation - 180;
 
			// Case from -90 to -180
			if( this._reverseSeriesOrder && this.Area3DStyle.Rotation <= 0 )
				yAngle = this.Area3DStyle.Rotation + 180;
 
			return yAngle;
		}
 
        /// <summary>
        /// Check if surface element should be drawn on the Back or Front layer.
        /// </summary>
        /// <param name="surfaceName">Surface name.</param>
        /// <param name="backLayer">Back/front layer.</param>
        /// <param name="onEdge">Indicates that element is on the edge (drawn on the back layer).</param>
        /// <returns>True if element should be drawn.</returns>
		internal bool ShouldDrawOnSurface(SurfaceNames surfaceName, bool backLayer, bool onEdge)
		{
			// Check if surface element should be drawn on the Back or Front layer.
            bool isVisible = ((this._visibleSurfaces & surfaceName) == surfaceName);
 
			// Elements on the edge are drawn on the back layer
			if(onEdge)
			{
				return backLayer;
			}
 
			return (backLayer == (!isVisible) );
		}
 
		/// <summary>
		/// Indicates that data points in all series of this 
		/// chart area should be drawn in reversed order.
		/// </summary>
		/// <returns>True if series points should be drawn in reversed order.</returns>
		internal bool DrawPointsInReverseOrder()
		{
			return (this.Area3DStyle.Rotation <= 0);
		}
 
        /// <summary>
        /// Checks if points should be drawn from sides to center.
        /// </summary>
        /// <param name="coord">Which coordinate of COP (X, Y or Z) to test for surface overlapping</param>
        /// <returns>True if points should be drawn from sides to center.</returns>
		internal bool DrawPointsToCenter(ref COPCoordinates coord)
		{
			bool			result = false;
			COPCoordinates	resultCoordinates = 0;
 
			// Check only if perspective is set
			if(this.Area3DStyle.Perspective != 0)
			{
				if( (coord & COPCoordinates.X) == COPCoordinates.X )
				{
					// Only when Left & Right sides of plotting area are invisible
                    if ((this._visibleSurfaces & SurfaceNames.Left) == 0 &&
                        (this._visibleSurfaces & SurfaceNames.Right) == 0)
					{
						result = true;
					}
					resultCoordinates = resultCoordinates | COPCoordinates.X;
				}
				if( (coord & COPCoordinates.Y) == COPCoordinates.Y )
				{
					// Only when Top & Bottom sides of plotting area are invisible
                    if ((this._visibleSurfaces & SurfaceNames.Top) == 0 &&
                        (this._visibleSurfaces & SurfaceNames.Bottom) == 0)
					{
						result = true;
					}
					resultCoordinates = resultCoordinates | COPCoordinates.Y;
				}
				if( (coord & COPCoordinates.Z) == COPCoordinates.Z )
				{
					// Only when Front & Back sides of plotting area are invisible
                    if ((this._visibleSurfaces & SurfaceNames.Front) == 0 &&
                        (this._visibleSurfaces & SurfaceNames.Back) == 0)
					{
						result = true;
					}
					resultCoordinates = resultCoordinates | COPCoordinates.Z;
				}
			}
 
			return result;
		}
 
		/// <summary>
		/// Checks if series should be drawn from sides to center.
		/// </summary>
		/// <returns>True if series should be drawn from sides to center.</returns>
		internal bool DrawSeriesToCenter()
		{
			// Check only if perspective is set
			if(this.Area3DStyle.Perspective != 0)
			{
				// Only when Left & Right sides of plotting area are invisible
                if ((this._visibleSurfaces & SurfaceNames.Front) == 0 &&
                    (this._visibleSurfaces & SurfaceNames.Back) == 0)
				{
					return true;
				}
			}
 
			return false;
		}
 
		#endregion
 
		#region 3D Series drawing and selection methods
 
		/// <summary>
		/// Draws 3D series in the chart area.
		/// </summary>
		/// <param name="graph">Chart graphics object.</param>
		internal void PaintChartSeries3D( ChartGraphics graph )
		{
			// Reference to the chart area object
			ChartArea	area = (ChartArea)this;
 
			// Get order of series drawing
			List<Series>	seriesDrawingOrder = GetSeriesDrawingOrder(_reverseSeriesOrder);
 
			// Loop through all series in the order of drawing
			IChartType type;
			foreach( object seriesObj in seriesDrawingOrder)
			{
				Series series = (Series)seriesObj;
				type = Common.ChartTypeRegistry.GetChartType(series.ChartTypeName);
				type.Paint( graph, Common, area, series );
			}
		}
 
		#endregion
 
		#region 3D Series & Points drawing order methods
                
		/// <summary>
		/// Gets a list of series names that belong to the same 3D cluster.
		/// </summary>
		/// <param name="seriesName">One of the series names that belongs to the cluster.</param>
		/// <returns>List of all series names that belong to the same cluster as specified series.</returns>
		internal List<string> GetClusterSeriesNames(string seriesName)
		{
			// Iterate through all clusters
			foreach(List<string> seriesNames in this.seriesClusters)
			{
				if(seriesNames.Contains(seriesName))
				{
					return seriesNames;
				}
			}
			return new List<string>();
		}
 
        /// <summary>
        /// Gets the series list in drawing order.
        /// </summary>
        /// <param name="reverseSeriesOrder">Series order should be reversed because of the Y axis angle.</param>
        /// <returns>Gets the series list in drawing order.</returns>
		private List<Series> GetSeriesDrawingOrder(bool reverseSeriesOrder)
		{
			// Create list of series
            List<Series> seriesList = new List<Series>();
 
			// Iterate through all clusters
			foreach(List<string> seriesNames in this.seriesClusters)
			{
				// Make sure there is at least one series
				if(seriesNames.Count > 0)
				{
					// Get first series object in the current cluster
					Series series = Common.DataManager.Series[seriesNames[0]];
 
					// Add series into the drawing list
					seriesList.Add(series);
				}
			}
 
			// Reversed series list
			if(reverseSeriesOrder)
			{
				seriesList.Reverse();
			}
 
			// Check if series should be drawn from sides into the center
			if(DrawSeriesToCenter() &&
				this.matrix3D.IsInitialized())
			{
				// Get Z coordinate of Center Of Projection
				Point3D		areaProjectionCenter = new Point3D(float.NaN, float.NaN, float.NaN);
				areaProjectionCenter = this.GetCenterOfProjection(COPCoordinates.Z);
				if(!float.IsNaN(areaProjectionCenter.Z))
				{
					// Loop through all series
					for(int seriesIndex = 0; seriesIndex < seriesList.Count; seriesIndex++)
					{
						// Check if series is not empty
						if(((Series)seriesList[seriesIndex]).Points.Count == 0)
						{
							continue;
						}
 
						// Get series Z position
						float seriesDepth, seriesZPosition;
						this.GetSeriesZPositionAndDepth((Series)seriesList[seriesIndex], out seriesDepth, out seriesZPosition);
 
						// Check if series passes the Z coordinate of Center of Projection
						if(seriesZPosition >= areaProjectionCenter.Z)
						{
							// Reversed all series order staring from previous series
							--seriesIndex;
							if(seriesIndex < 0)
								seriesIndex = 0;
							seriesList.Reverse(seriesIndex, seriesList.Count - seriesIndex);
							break;
						}
					}
				}
			}
 
			return seriesList;
		}
 
 
		/// <summary>
		/// Gets number of stack groups in specified array of series names.
		/// </summary>
		/// <param name="seriesNamesList">Array of series names.</param>
		/// <returns>Number of stack groups. One by default.</returns>
		private int GetNumberOfStackGroups(IList<string> seriesNamesList)
		{
			this._stackGroupNames = new ArrayList();
			foreach( object seriesName in seriesNamesList )
			{
				// Get series object
				Series	ser = this.Common.DataManager.Series[(string)seriesName];
 
				// Get stack group name from the series
				string stackGroupName = string.Empty;
				if(ser.IsCustomPropertySet(CustomPropertyName.StackedGroupName))
				{
					stackGroupName = ser[CustomPropertyName.StackedGroupName];
				}
 
				// Add group name if it do not already exsist
				if(!this._stackGroupNames.Contains(stackGroupName))
				{
					this._stackGroupNames.Add(stackGroupName);
				}
			}
 
			return this._stackGroupNames.Count;
		}
 
		/// <summary>
		/// Gets index of the series stack group.
		/// </summary>
		/// <param name="series">Series to get the index for.</param>
		/// <param name="stackGroupName">Group name this series belongs to.</param>
		/// <returns>Index of series stack group.</returns>
		internal int GetSeriesStackGroupIndex(Series series, ref string stackGroupName)
		{
			stackGroupName = string.Empty;
			if(this._stackGroupNames != null)
			{
				// Get stack group name from the series
				if(series.IsCustomPropertySet(CustomPropertyName.StackedGroupName))
				{
					stackGroupName = series[CustomPropertyName.StackedGroupName];
				}
				return this._stackGroupNames.IndexOf(stackGroupName);
			}
			return 0;
		}
 
 
 
        /// <summary>
        /// Determine the order of points drawing from one or several series of the same type.
        /// </summary>
        /// <param name="seriesNamesList">List of series names.</param>
        /// <param name="chartType">Chart type.</param>
        /// <param name="selection">If True selection mode is active (points order should be reversed).</param>
        /// <param name="coord">Which coordinate of COP (X, Y or Z) to test for surface overlapping</param>
        /// <param name="comparer">Points comparer class. Can be Null.</param>
        /// <param name="mainYValueIndex">Index of the main Y value.</param>
        /// <param name="sideBySide">Series should be drawn side by side.</param>
        /// <returns>Array list of points in drawing order.</returns>
		internal ArrayList GetDataPointDrawingOrder(
			List<string> seriesNamesList, 
			IChartType chartType, 
			bool selection, 
			COPCoordinates coord, 
			IComparer comparer,
			int mainYValueIndex,
			bool sideBySide)
		{
			ChartArea area = (ChartArea)this;
 
			// Array of points in all series
			ArrayList pointsList = new ArrayList();
 
			//************************************************************
			//** Analyse input series
			//************************************************************
 
			// Find the number of data series for side-by-side drawing
			double	numOfSeries = 1;
			if(area.Area3DStyle.IsClustered && !chartType.Stacked && sideBySide)
			{
				numOfSeries = seriesNamesList.Count;
			}
 
 
			// Check stacked series group names
			if(chartType.SupportStackedGroups)
			{
				// Fill the list of group names and get the number of unique groups
				int numberOfGroups = this.GetNumberOfStackGroups(seriesNamesList);
 
				// If series are not isClustered set series number to the stacked group number
				if(this.Area3DStyle.IsClustered &&
					seriesNamesList.Count > 0)
				{
					numOfSeries = numberOfGroups;
				}
			}
 
 
			// Check if chart series are indexed
            bool indexedSeries = ChartHelper.IndexedSeries(this.Common, seriesNamesList.ToArray());
 
			//************************************************************
			//** Loop through all series and fill array of points
			//************************************************************
			int	seriesIndx = 0;
			foreach( object seriesName in seriesNamesList )
			{
				// Get series object
				Series	ser = this.Common.DataManager.Series[(string)seriesName];
 
 
				// Check if stacked groups present
				if(chartType.SupportStackedGroups && 
					this._stackGroupNames != null)
				{
					// Get index of the series using stacked group
					string groupName = string.Empty;
					seriesIndx = this.GetSeriesStackGroupIndex(ser, ref groupName);
 
					// Set current group name
                    StackedColumnChart stackedColumnChart = chartType as StackedColumnChart;
                    if (stackedColumnChart != null)
					{
                        stackedColumnChart.currentStackGroup = groupName;
					}
					else
					{
                        StackedBarChart stackedBarChart = chartType as StackedBarChart;
                        if (stackedBarChart != null)
                        {
                            stackedBarChart.currentStackGroup = groupName;
                        }
					}
				}
 
 
				// Set active vertical/horizontal axis and their max/min values
				Axis	vAxis = (ser.YAxisType == AxisType.Primary) ? area.AxisY : area.AxisY2;
				Axis	hAxis = (ser.XAxisType == AxisType.Primary) ? area.AxisX : area.AxisX2;
 
				// Get points interval:
				//  - set interval to 1 for indexed series
				//  - if points are not equaly spaced, the minimum interval between points is selected.
				//  - if points have same interval bars do not overlap each other.
				bool	sameInterval = true;
				double	interval = 1;
				if(!indexedSeries)
				{
					interval = area.GetPointsInterval( seriesNamesList, hAxis.IsLogarithmic, hAxis.logarithmBase, true, out sameInterval );
				}
 
				// Get column width
				double	width = ser.GetPointWidth(area.Common.graph, hAxis, interval, 0.8) / numOfSeries;
					
				// Get series depth and Z position
				float seriesDepth, seriesZPosition;
				this.GetSeriesZPositionAndDepth(ser, out seriesDepth, out seriesZPosition);
 
				//************************************************************
				//** Loop through all points in series
				//************************************************************
				int	index = 0;
				foreach( DataPoint point in ser.Points )
				{
					// Increase point index
					index++;
						
					// Set x position
					double	xCenterVal;
					double	xPosition;
					if( indexedSeries )
					{
						// The formula for position is based on a distance 
						//from the grid line or nPoints position.
						xPosition = hAxis.GetPosition( (double)index ) - width * ((double) numOfSeries) / 2.0 + width/2 + seriesIndx * width;
						xCenterVal = hAxis.GetPosition( (double)index );
 
					}
					else if( sameInterval )
					{
						xPosition = hAxis.GetPosition( point.XValue ) - width * ((double) numOfSeries) / 2.0 + width/2 + seriesIndx * width;
						xCenterVal = hAxis.GetPosition( point.XValue );
					}
					else
					{
						xPosition = hAxis.GetPosition( point.XValue );
						xCenterVal = hAxis.GetPosition( point.XValue );
					}
 
	
					//************************************************************
					//** Create and add new DataPoint3D object
					//************************************************************
					DataPoint3D pointEx = new DataPoint3D();
					pointEx.indexedSeries = indexedSeries;
					pointEx.dataPoint = point;
					pointEx.index = index;
					pointEx.xPosition = xPosition;
					pointEx.xCenterVal = xCenterVal;
					pointEx.width = ser.GetPointWidth(area.Common.graph, hAxis, interval, 0.8) / numOfSeries;
					pointEx.depth = seriesDepth;
					pointEx.zPosition = seriesZPosition;
 
					// Set Y value and height
                    double yValue = chartType.GetYValue(Common, area, ser, point, index - 1, mainYValueIndex);
                    if (point.IsEmpty && Double.IsNaN(yValue))
                    {
                        yValue = 0.0;
                    }
					pointEx.yPosition = vAxis.GetPosition(yValue);
					pointEx.height = vAxis.GetPosition(yValue - chartType.GetYValue(Common, area, ser, point, index - 1, -1));
 
 
					pointsList.Add(pointEx);
				}
 
				// Data series index
				if(numOfSeries > 1 && sideBySide)
				{
					seriesIndx++;
				}
			}
		
			//************************************************************
			//** Sort points in drawing order
			//************************************************************
			if(comparer == null)
			{
				comparer = new PointsDrawingOrderComparer((ChartArea)this, selection, coord);
			}
			pointsList.Sort(comparer);
 
			return pointsList;
		}
		
		#endregion	
 
		#region Points drawing order comparer class
 
		/// <summary>
		/// Used to compare points in array and sort them by drawing order.
		/// </summary>
		internal class PointsDrawingOrderComparer : IComparer
		{
			/// <summary>
			/// Chart area object reference.
			/// </summary>
			private	ChartArea	_area = null;
 
			/// <summary>
			/// Area X position where visible sides are switched.
			/// </summary>
			private	Point3D		_areaProjectionCenter = new Point3D(float.NaN, float.NaN, float.NaN);
 
			/// <summary>
			/// Selection mode. Points order should be reversed.
			/// </summary>
			private bool		_selection = false;
 
            /// <summary>
            /// Public constructor.
            /// </summary>
            /// <param name="area">Chart area.</param>
            /// <param name="selection">Selection indicator.</param>
            /// <param name="coord">Which coordinate of COP (X, Y or Z) to test for surface overlapping</param>
			public PointsDrawingOrderComparer(ChartArea	area, bool selection, COPCoordinates coord)
			{
				this._area = area;
				this._selection = selection;
 
				// Get center of projection
				if(area.DrawPointsToCenter(ref coord))
				{
					_areaProjectionCenter = area.GetCenterOfProjection(coord);
				}
			}
 
            /// <summary>
            /// Comparer method.
            /// </summary>
            /// <param name="o1">First object.</param>
            /// <param name="o2">Second object.</param>
            /// <returns>Comparison result.</returns>
			public int Compare(object o1, object o2)
			{
				DataPoint3D point1 = (DataPoint3D) o1;
				DataPoint3D point2 = (DataPoint3D) o2;
 
				int	result = 0;
				if(point1.xPosition < point2.xPosition)
				{
					result = -1;
				}
				else if(point1.xPosition > point2.xPosition)
				{
					result = 1;
				}
				else
				{
					
					// If X coordinate is the same - filter by Y coordinate
					if(point1.yPosition < point2.yPosition)
					{
						result = 1;
					}
					else if(point1.yPosition > point2.yPosition)
					{
						result = -1;
					}
 
					// Order points from sides to center
					if(!float.IsNaN(_areaProjectionCenter.Y))
					{
						double yMin1 = Math.Min(point1.yPosition, point1.height);
						double yMax1 = Math.Max(point1.yPosition, point1.height);
						double yMin2 = Math.Min(point2.yPosition, point2.height);
						double yMax2 = Math.Max(point2.yPosition, point2.height);
 
						if(_area.IsBottomSceneWallVisible())
						{
							if( yMin1 <= _areaProjectionCenter.Y && yMin2 <= _areaProjectionCenter.Y )
							{
								result *= -1;
							}
							else if( yMin1 <= _areaProjectionCenter.Y)
							{
								result = 1;
							}
 
						}
						else
						{
							
							if( yMax1 >= _areaProjectionCenter.Y && yMax2 >= _areaProjectionCenter.Y )
							{
								result *= 1;
							}
							else if( yMax1 >= _areaProjectionCenter.Y)
							{
								result = 1;
							}
							else
							{
								result *= -1;
							}
						}
					}
 
						// Reversed order if looking from the bottom
					else if(!_area.IsBottomSceneWallVisible())
					{
						result *= -1;
					}
			
				}
 
				if(point1.xPosition != point2.xPosition)
				{
					// Order points from sides to center
					if (!float.IsNaN(_areaProjectionCenter.X))
					{
                        if ((point1.xPosition + point1.width / 2f) >= _areaProjectionCenter.X &&
                            (point2.xPosition + point2.width / 2f) >= _areaProjectionCenter.X)
						{
							result *= -1;
						}
					}
 
					// Reversed order of points by X value
                    else if (_area.DrawPointsInReverseOrder())
					{
						result *= -1;
					}
				}
 
                return (_selection) ? -result : result;
			}
		}
 
#endregion
 
		#region Center of Projection calculation methods
 
		/// <summary>
		/// Returns one or many (X, Y, Z) coordinates of the center of projection.
		/// </summary>
		/// <param name="coord">Defines coordinates to return.</param>
		/// <returns>Center of projection. Value can be set to float.NaN if not defined or outside plotting area.</returns>
		internal Point3D GetCenterOfProjection(COPCoordinates coord)
		{
			// Define 2 points in the opposite corners of the plotting area
			Point3D[]	points = new Point3D[2];
			points[0] = new Point3D(this.PlotAreaPosition.X, this.PlotAreaPosition.Bottom, 0f);
			points[1] = new Point3D(this.PlotAreaPosition.Right, this.PlotAreaPosition.Y, this.areaSceneDepth);
 
			// Check if surfaces (points 1 & 2) has same orientation
			bool	xSameOrientation, ySameOrientation, zSameOrientation;
			CheckSurfaceOrientation(
				coord,
				points[0],
				points[1],
				out xSameOrientation, 
				out ySameOrientation, 
				out zSameOrientation);
 
			// If orientation of all surfaces is the same - no futher processing is required (COP is outside of plotting area)
			Point3D		resultPoint = new Point3D(
				(xSameOrientation) ? float.NaN : 0f,
				(ySameOrientation) ? float.NaN : 0f,
				(zSameOrientation) ? float.NaN : 0f);
			if( ( ((coord & COPCoordinates.X) != COPCoordinates.X) || xSameOrientation ) &&
				( ((coord & COPCoordinates.Y) != COPCoordinates.Y) || ySameOrientation ) &&
				( ((coord & COPCoordinates.Z) != COPCoordinates.Z) || zSameOrientation ) )
			{
				return resultPoint;
			}
 
			// Calculate the smallest interval (0.5 pixels) in relative coordinates
			SizeF	interval = new SizeF(0.5f, 0.5f);
#if WINFORMS_CONTROL
			interval.Width = interval.Width * 100F / ((float)(this.Common.Chart.Width - 1)); 
			interval.Height = interval.Height * 100F / ((float)(this.Common.Chart.Height - 1)); 
#else
			interval.Width = interval.Width * 100F / ((float)(this.Common.Chart.Width.Value - 1)); 
			interval.Height = interval.Height * 100F / ((float)(this.Common.Chart.Height.Value - 1)); 
#endif	//#if WINFORMS_CONTROL
 
			// Find middle point and check it's surface orientation
			bool	doneFlag = false;
			while(!doneFlag)
			{
				// Find middle point
				Point3D	middlePoint = new Point3D( 
					(points[0].X + points[1].X) / 2f,
					(points[0].Y + points[1].Y) / 2f,
					(points[0].Z + points[1].Z) / 2f);
 
				// Check if surfaces (points 1 & middle) has same orientation
				CheckSurfaceOrientation(
					coord,
					points[0],
					middlePoint,
					out xSameOrientation, 
					out ySameOrientation, 
					out zSameOrientation);
 
				// Calculate points 1 & 2 depending on surface orientation of the middle point
				points[(xSameOrientation) ? 0 : 1].X = middlePoint.X;
				points[(ySameOrientation) ? 0 : 1].Y = middlePoint.Y;
				points[(zSameOrientation) ? 0 : 1].Z = middlePoint.Z;
 
				// Check if no more calculations required
				doneFlag = true;
				if( (coord & COPCoordinates.X) == COPCoordinates.X && 
					Math.Abs(points[1].X - points[0].X) >= interval.Width)
				{
					doneFlag = false;
				}
				if( (coord & COPCoordinates.Y) == COPCoordinates.Y && 
					Math.Abs(points[1].Y - points[0].Y) >= interval.Height)
				{
					doneFlag = false;
				}
				if( (coord & COPCoordinates.Z) == COPCoordinates.Z && 
					Math.Abs(points[1].Z - points[0].Z) >= interval.Width)
				{
					doneFlag = false;
				}
			}
 
			// Calculate result point
			if(!float.IsNaN(resultPoint.X))
				resultPoint.X = (points[0].X + points[1].X) / 2f;
			if(!float.IsNaN(resultPoint.Y))
				resultPoint.Y = (points[0].Y + points[1].Y) / 2f;
			if(!float.IsNaN(resultPoint.Z))
				resultPoint.Z = (points[0].Z + points[1].Z) / 2f;
			return resultPoint;
		}
 
		/// <summary>
		/// Checks orientations of two normal surfaces for each coordinate X, Y and Z.
		/// </summary>
		/// <param name="coord">Defines coordinates to return.</param>
		/// <param name="point1">First point.</param>
		/// <param name="point2">Second point.</param>
		/// <param name="xSameOrientation">X surfaces orientation is the same.</param>
		/// <param name="ySameOrientation">Y surfaces orientation is the same.</param>
		/// <param name="zSameOrientation">Z surfaces orientation is the same.</param>
		private void CheckSurfaceOrientation(
			COPCoordinates coord,
			Point3D point1, 
			Point3D point2, 
			out bool xSameOrientation, 
			out bool ySameOrientation, 
			out bool zSameOrientation)
		{
			Point3D[]	pointsSurface = new Point3D[3];
			bool		surf1, surf2;
 
			// Initialize returned values
			xSameOrientation = true;
			ySameOrientation = true;
			zSameOrientation = true;
 
			// Check X axis coordinates (ledt & right surfaces)
			if( (coord & COPCoordinates.X) == COPCoordinates.X )
			{
				// Define Left surface coordinates, transform them and check visibility
				pointsSurface[0] = new Point3D(point1.X, this.PlotAreaPosition.Y, 0f);
				pointsSurface[1] = new Point3D(point1.X, this.PlotAreaPosition.Bottom, 0f);
				pointsSurface[2] = new Point3D(point1.X, this.PlotAreaPosition.Bottom, this.areaSceneDepth);
				this.matrix3D.TransformPoints( pointsSurface );
				surf1 = ChartGraphics.IsSurfaceVisible(pointsSurface[0], pointsSurface[1], pointsSurface[2]);
 
				// Define Right surface coordinates, transform them and check visibility
				pointsSurface[0] = new Point3D(point2.X, this.PlotAreaPosition.Y, 0f);
				pointsSurface[1] = new Point3D(point2.X, this.PlotAreaPosition.Bottom, 0f);
				pointsSurface[2] = new Point3D(point2.X, this.PlotAreaPosition.Bottom, this.areaSceneDepth);
				this.matrix3D.TransformPoints( pointsSurface );
				surf2 = ChartGraphics.IsSurfaceVisible(pointsSurface[0], pointsSurface[1], pointsSurface[2]);
 
				// Check if surfaces have same visibility
				xSameOrientation = (surf1 == surf2);
			}
 
			// Check Y axis coordinates (top & bottom surfaces)
			if( (coord & COPCoordinates.Y) == COPCoordinates.Y )
			{
				// Define Bottom surface coordinates, transform them and check visibility
				pointsSurface[0] = new Point3D(this.PlotAreaPosition.X, point1.Y, this.areaSceneDepth);
				pointsSurface[1] = new Point3D(this.PlotAreaPosition.X, point1.Y, 0f);
				pointsSurface[2] = new Point3D(this.PlotAreaPosition.Right, point1.Y, 0f);
				this.matrix3D.TransformPoints( pointsSurface );
				surf1 = ChartGraphics.IsSurfaceVisible(pointsSurface[0], pointsSurface[1], pointsSurface[2]);
 
				// Define Top surface coordinates, transform them and check visibility
				pointsSurface[0] = new Point3D(this.PlotAreaPosition.X, point2.Y, this.areaSceneDepth);
				pointsSurface[1] = new Point3D(this.PlotAreaPosition.X, point2.Y, 0f);
				pointsSurface[2] = new Point3D(this.PlotAreaPosition.Right, point2.Y, 0f);
				this.matrix3D.TransformPoints( pointsSurface );
				surf2 = ChartGraphics.IsSurfaceVisible(pointsSurface[0], pointsSurface[1], pointsSurface[2]);
 
				// Check if surfaces have same visibility
				ySameOrientation = (surf1 == surf2);
			}
 
			// Check Y axis coordinates (front & back surfaces)
			if( (coord & COPCoordinates.Z) == COPCoordinates.Z )
			{
				// Define Front surface coordinates, transform them and check visibility
				pointsSurface[0] = new Point3D(this.PlotAreaPosition.X, this.PlotAreaPosition.Y, point1.Z);
				pointsSurface[1] = new Point3D(this.PlotAreaPosition.X, this.PlotAreaPosition.Bottom, point1.Z);
				pointsSurface[2] = new Point3D(this.PlotAreaPosition.Right, this.PlotAreaPosition.Bottom, point1.Z);
				this.matrix3D.TransformPoints( pointsSurface );
				surf1 = ChartGraphics.IsSurfaceVisible(pointsSurface[0], pointsSurface[1], pointsSurface[2]);
 
				// Define Back surface coordinates, transform them and check visibility
				pointsSurface[0] = new Point3D(this.PlotAreaPosition.X, this.PlotAreaPosition.Y, point2.Z);
				pointsSurface[1] = new Point3D(this.PlotAreaPosition.X, this.PlotAreaPosition.Bottom, point2.Z);
				pointsSurface[2] = new Point3D(this.PlotAreaPosition.Right, this.PlotAreaPosition.Bottom, point2.Z);
				this.matrix3D.TransformPoints( pointsSurface );
				surf2 = ChartGraphics.IsSurfaceVisible(pointsSurface[0], pointsSurface[1], pointsSurface[2]);
 
				// Check if surfaces have same visibility
				zSameOrientation = (surf1 == surf2);
			}
		}
#endregion
	}
}