File: Common\ChartTypes\RangeChart.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:		RangeChart.cs
//
//  Namespace:	DataVisualization.Charting.ChartTypes
//
//	Classes:	RangeChart, SplineRangeChart
//
//  Purpose:	Provides 2D/3D drawing and hit testing functionality 
//              for the Range and SplineRange charts. The Range chart 
//              displays a range of data by plotting two Y values per 
//              data point, with each Y value being drawn as a line 
//              chart. The range between the Y values can then be 
//              filled with color. Spline Range chart changes the 
//              default tension of the lines between data points.
//              
//	Reviewed:	AG - Aug 6, 2002
//              AG - Microsoft 6, 2007
//
//===================================================================
 
#region Used namespaces
 
using System;
using System.Resources;
using System.Reflection;
using System.Collections;
using System.Drawing;
using System.Drawing.Drawing2D;
 
#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;
 
	using System.Web.UI.DataVisualization.Charting.ChartTypes;
	using System.Web.UI.DataVisualization.Charting.Data;
	using System.Web.UI.DataVisualization.Charting.Utilities;
#endif
 
#endregion
 
#if Microsoft_CONTROL
	namespace System.Windows.Forms.DataVisualization.Charting.ChartTypes
#else
	namespace System.Web.UI.DataVisualization.Charting.ChartTypes
#endif
{
	/// <summary>
    /// SplineRangeChart class extends the RangeChart class by 
    /// providing a different initial tension for the lines.
    /// </summary>
	internal class SplineRangeChart : RangeChart
	{
		#region Constructor
 
		/// <summary>
		/// Default constructor.
		/// </summary>
		public SplineRangeChart()
		{
			// Set default line tension
			base.lineTension = 0.5f;
		}
 
		#endregion
 
		#region IChartType interface implementation
 
		/// <summary>
		/// Chart type name
		/// </summary>
		public override string Name			{ get{ return ChartTypeNames.SplineRange;}}
 
		/// <summary>
		/// Gets chart type image.
		/// </summary>
		/// <param name="registry">Chart types registry object.</param>
		/// <returns>Chart type image.</returns>
		override public System.Drawing.Image GetImage(ChartTypeRegistry registry)
		{
			return (System.Drawing.Image)registry.ResourceManager.GetObject(this.Name + "ChartType");
		}
 
		#endregion
 
		#region Default tension method
 
		/// <summary>
		/// Gets default line tension.
		/// </summary>
		/// <returns>Default line tension.</returns>
		override protected float GetDefaultTension()
		{
			return 0.5f;
		}
 
		/// <summary>
		/// Checks if line tension is supported by the chart type.
		/// </summary>
		/// <returns>True if line tension is supported.</returns>
		protected override bool IsLineTensionSupported()
		{
			return true;
		}
 
		#endregion
	}
 
	/// <summary>
    /// RangeChart class provides 2D/3D drawing and hit testing 
    /// functionality for the Range and SplineRange charts. The 
    /// only difference of the SplineRange chart is the default 
    /// tension of the line.
    /// 
    /// SplineChart base class provides most of the functionality 
    /// like drawing lines, labels and markers.
    /// </summary>
	internal class RangeChart : SplineChart
	{
		#region Fields
 
		/// <summary>
		/// Fields used to fill area with gradient 
		/// </summary>
        internal bool gradientFill = false;
 
		/// <summary>
		/// Shape of the low values
		/// </summary>
        internal GraphicsPath areaBottomPath = new GraphicsPath();
 
		/// <summary>
		/// Coordinates of the area path
		/// </summary>
		protected	GraphicsPath	areaPath = null;
 
		/// <summary>
		/// Reference to the current series object
		/// </summary>
        private Series _series = null;
 
		/// <summary>
		/// Array of low line values
		/// </summary>
        internal PointF[] lowPoints = null;
 
		/// <summary>
		/// Check if series are indexed based
		/// </summary>
        internal bool indexedBasedX = false;
 
		/// <summary>
		/// Secondary Y coordinate that should be used for bottom line of the range (left point)
		/// </summary>
		private		float			_thirdPointY2Value = float.NaN;
 
		/// <summary>
		/// Secondary Y coordinate that should be used for bottom line of the range (right point)
		/// </summary>
		private		float			_fourthPointY2Value = float.NaN;
 
		#endregion
 
		#region Constructor
 
		/// <summary>
		/// Default constructor.
		/// </summary>
		public RangeChart()
		{
			this.drawOutsideLines = true;
		}
 
		#endregion 
 
		#region IChartType interface implementation
 
		/// <summary>
		/// Chart type name
		/// </summary>
		public override string Name			{ get{ return ChartTypeNames.Range;}}
 
		/// <summary>
		/// Number of supported Y value(s) per point 
		/// </summary>
		override public int YValuesPerPoint	{ get { return 2; } }
 
		/// <summary>
		/// Indicates that extra Y values are connected to the scale of the Y axis
		/// </summary>
		override public bool ExtraYValuesConnectedToYAxis{ get { return true; } }
 
		/// <summary>
		/// How to draw series/points in legend:
		/// Filled rectangle, Line or Marker
		/// </summary>
		/// <param name="series">Legend item series.</param>
		/// <returns>Legend item style.</returns>
		override public LegendImageStyle GetLegendImageStyle(Series series)
		{
			return LegendImageStyle.Rectangle;
		}
 
		/// <summary>
		/// Gets chart type image.
		/// </summary>
		/// <param name="registry">Chart types registry object.</param>
		/// <returns>Chart type image.</returns>
		override public System.Drawing.Image GetImage(ChartTypeRegistry registry)
		{
			return (System.Drawing.Image)registry.ResourceManager.GetObject(this.Name + "ChartType");
		}
 
		#endregion
 
		#region Default tension method
 
		/// <summary>
		/// Gets default line tension.
		/// </summary>
		/// <returns>Line tension.</returns>
		override protected float GetDefaultTension()
		{
			return 0.0f;
		}
 
		/// <summary>
		/// Checks if line tension is supported by the chart type.
		/// </summary>
		/// <returns>True if line tension is supported.</returns>
		protected override bool IsLineTensionSupported()
		{
			return false;
		}
 
		#endregion
 
		#region Painting and Selection related methods
 
		/// <summary>
		/// Fills last series area with gradient.
		/// If gradient is used as a back color of the series it must be drawn 
		/// at the same time.
		/// </summary>
		/// <param name="graph">The Chart Graphics object.</param>
		private void FillLastSeriesGradient(ChartGraphics graph)
		{
			// Add last line in the path
			if(areaPath != null)
			{
				areaPath.AddLine(
					areaPath.GetLastPoint().X, 
					areaPath.GetLastPoint().Y, 
					areaPath.GetLastPoint().X, 
					areaBottomPath.GetLastPoint().Y);
			}
			
			// Fill whole area with gradient
			if(gradientFill && areaPath != null)
			{
				// Set clip region
				graph.SetClip( Area.PlotAreaPosition.ToRectangleF() );
 
				// Create new path from high/low lines 
                using (GraphicsPath gradientPath = new GraphicsPath())
                {
                    gradientPath.AddPath(areaPath, true);
                    areaBottomPath.Reverse();
                    gradientPath.AddPath(areaBottomPath, true);
 
                    // Create brush
                    using (Brush areaGradientBrush = graph.GetGradientBrush(gradientPath.GetBounds(), this._series.Color, this._series.BackSecondaryColor, this._series.BackGradientStyle))
                    {
                        // Fill area with gradient
                        graph.FillPath(areaGradientBrush, gradientPath);
                        gradientFill = false;
                    }
                }
 
				// Reset clip region
				graph.ResetClip();
			}
			if(areaPath != null)
			{
				areaPath.Dispose();
				areaPath = null;
			}
 
			// Reset bottom area path
			areaBottomPath.Reset();
		}
 
		/// <summary>
		/// This method recalculates position of the end points of lines. This method 
		/// is used from Paint or Select method.
		/// </summary>
		/// <param name="selection">If True selection mode is active, otherwise paint mode is active.</param>
		/// <param name="graph">The Chart Graphics object.</param>
		/// <param name="common">The Common elements object.</param>
		/// <param name="area">Chart area for this chart.</param>
		/// <param name="seriesToDraw">Chart series to draw.</param>
		protected override void ProcessChartType( 
			bool selection, 
			ChartGraphics graph, 
			CommonElements common, 
			ChartArea area, 
			Series seriesToDraw )
		{
			gradientFill = false;
			lowPoints = null;
 
			// Check if series is indexed based
            indexedBasedX = ChartHelper.IndexedSeries(common, area.GetSeriesFromChartType(this.Name).ToArray());
 
			// Call base class
			base.ProcessChartType(selection, graph, common, area, seriesToDraw);
 
			// Fill gradient fro the previous series
			FillLastSeriesGradient(graph);
		}
 
		/// <summary>
		/// Override line drawing method to fill the range and draw lines.
		/// </summary>
		/// <param name="graph">Graphics object.</param>
		/// <param name="common">The Common elements object.</param>
		/// <param name="point">Point to draw the line for.</param>
		/// <param name="series">Point series.</param>
		/// <param name="points">Array of oints coordinates.</param>
		/// <param name="pointIndex">Index of point to draw.</param>
		/// <param name="tension">Line tension.</param>
		override protected void DrawLine(
			ChartGraphics graph,  
			CommonElements common, 
			DataPoint point, 
			Series series, 
			PointF[] points, 
			int pointIndex, 
			float tension)
		{
			// Two Y values required
			if(point.YValues.Length < 2)
			{
				throw(new InvalidOperationException( SR.ExceptionChartTypeRequiresYValues( this.Name, "2" )));
			}
 
			// Start drawing from the second point
			if(pointIndex <= 0)
			{
				return;
			}
 
			// Do nothing for the low values line
			if(this.YValueIndex == 1)
			{
				return;
			}
 
			// Check if its a beginning of a new series
			if(this._series != null)
			{
				if(this._series.Name != series.Name)
				{
					// Fill gradient from the previous series
					FillLastSeriesGradient(graph);
					this._series = series;
					lowPoints = null;
					areaBottomPath.Reset();
				}
			}
			else
			{
				this._series = series;
			}
 
			// Fill array of lower points of the range
			if(lowPoints == null)
			{
				this.YValueIndex = 1;
				lowPoints = GetPointsPosition(graph, series, indexedBasedX);
				this.YValueIndex = 0;
			}
 
			// Calculate points position
			PointF	highPoint1 = points[pointIndex-1];
			PointF	highPoint2 = points[pointIndex];
			PointF	lowPoint1 = lowPoints[pointIndex-1];
			PointF	lowPoint2 = lowPoints[pointIndex];
			
			// Create area brush
			Brush	areaBrush = null;
			if( point.BackHatchStyle != ChartHatchStyle.None )
			{
				areaBrush = graph.GetHatchBrush( point.BackHatchStyle, point.Color, point.BackSecondaryColor);
			}
			else if( point.BackGradientStyle != GradientStyle.None )
			{
				this.gradientFill = true;
				this._series = point.series;
			}
			else if( point.BackImage.Length > 0 && point.BackImageWrapMode != ChartImageWrapMode.Unscaled && point.BackImageWrapMode != ChartImageWrapMode.Scaled)
			{
				areaBrush = graph.GetTextureBrush(point.BackImage, point.BackImageTransparentColor, point.BackImageWrapMode, point.Color );
			}
			else
			{
				areaBrush = new SolidBrush(point.Color);
			}
 
			// Calculate data point area segment path
			GraphicsPath path = new GraphicsPath();
			path.AddLine(highPoint1.X, lowPoint1.Y, highPoint1.X, highPoint1.Y);
			if(this.lineTension == 0)
			{
				path.AddLine(points[pointIndex-1], points[pointIndex]);
			}
			else
			{
				path.AddCurve(points, pointIndex-1, 1, this.lineTension);
			}
 
			path.AddLine(highPoint2.X, highPoint2.Y, highPoint2.X, lowPoint2.Y);
 
			// Because of SVG Rendering order of points in the close shape 
			// has to be respected.
			if( graph.ActiveRenderingType == RenderingType.Svg )
			{
                using (GraphicsPath pathReverse = new GraphicsPath())
                {
                    // Add curve to the new graphics path
                    if (this.lineTension == 0)
                    {
                        path.AddLine(lowPoints[pointIndex - 1], lowPoints[pointIndex]);
                    }
                    else
                    {
                        pathReverse.AddCurve(lowPoints, pointIndex - 1, 1, this.lineTension);
 
                        // Convert to polygon
                        pathReverse.Flatten();
 
                        // Reversed points order in the aray
                        PointF[] pointsReversed = pathReverse.PathPoints;
                        PointF[] pointF = new PointF[pointsReversed.Length];
                        int pntIndex = pointsReversed.Length - 1;
                        foreach (PointF pp in pointsReversed)
                        {
                            pointF[pntIndex] = pp;
                            pntIndex--;
                        }
 
                        // Path can not have polygon width two points
                        if (pointF.Length == 2)
                        {
                            PointF[] newPointF = new PointF[3];
                            newPointF[0] = pointF[0];
                            newPointF[1] = pointF[1];
                            newPointF[2] = pointF[1];
                            pointF = newPointF;
                        }
 
                        // Add Polygon to the path
                        path.AddPolygon(pointF);
                    }
                }
			}
			else
			{
				if(this.lineTension == 0)
				{
					path.AddLine(lowPoints[pointIndex-1], lowPoints[pointIndex]);
				}
				else
				{
					path.AddCurve(lowPoints, pointIndex-1, 1, this.lineTension);
				}
 
			}
 
			// Check if bottom line is partialy in the data scaleView
			if(!clipRegionSet)
			{
				double	xValue = (indexedSeries) ? pointIndex + 1 : series.Points[pointIndex].XValue;
				double	xPrevValue = (indexedSeries) ? pointIndex : series.Points[pointIndex - 1].XValue;
				if(xPrevValue < hAxisMin || xPrevValue > hAxisMax || 
					xValue > hAxisMax || xValue < hAxisMin ||
					series.Points[pointIndex-1].YValues[1] < vAxisMin || series.Points[pointIndex-1].YValues[1] > vAxisMax ||
					series.Points[pointIndex].YValues[1] < vAxisMin || series.Points[pointIndex].YValues[1] > vAxisMax )
				{
					// Set clipping region for bottom line drawing 
					graph.SetClip( Area.PlotAreaPosition.ToRectangleF() );
					clipRegionSet = true;
				}
			}
 
			// Draw shadow
			if(series.ShadowColor != Color.Empty && series.ShadowOffset != 0)
			{
				if(point.Color != Color.Empty && point.Color != Color.Transparent)
				{
					// Translate drawing matrix
					Matrix translateMatrix = graph.Transform.Clone();
					translateMatrix.Translate(series.ShadowOffset, series.ShadowOffset);
					Matrix oldMatrix = graph.Transform;
					graph.Transform = translateMatrix;
 
					Region	shadowRegion = new Region(path);
                    using (Brush shadowBrush = new SolidBrush((series.ShadowColor.A != 255) ? series.ShadowColor : Color.FromArgb(point.Color.A / 2, series.ShadowColor)))
                    {
                        Region clipRegion = null;
                        if (!graph.IsClipEmpty && !graph.Clip.IsInfinite(graph.Graphics))
                        {
                            clipRegion = graph.Clip;
                            clipRegion.Translate(series.ShadowOffset + 1, series.ShadowOffset + 1);
                            graph.Clip = clipRegion;
 
                        }
 
                        // Fill region
                        graph.FillRegion(shadowBrush, shadowRegion);
 
                        // Draw leftmost and rightmost vertical lines
                        using (Pen areaLinePen = new Pen(shadowBrush, 1))
                        {
                            if (pointIndex == 0)
                            {
                                graph.DrawLine(areaLinePen, highPoint1.X, lowPoint1.Y, highPoint1.X, highPoint1.Y);
                            }
                            if (pointIndex == series.Points.Count - 1)
                            {
                                graph.DrawLine(areaLinePen, highPoint2.X, highPoint2.Y, highPoint2.X, lowPoint2.Y);
                            }
                        }
 
                        // Restore graphics parameters
                        graph.Transform = oldMatrix;
 
                        // Draw high and low line shadows
                        this.drawShadowOnly = true;
                        base.DrawLine(graph, common, point, series, points, pointIndex, tension);
                        this.YValueIndex = 1;
                        base.DrawLine(graph, common, point, series, lowPoints, pointIndex, tension);
                        this.YValueIndex = 0;
                        this.drawShadowOnly = false;
 
                        // Restore clip region
                        if (clipRegion != null)
                        {
                            clipRegion = graph.Clip;
                            clipRegion.Translate(-(series.ShadowOffset + 1), -(series.ShadowOffset + 1));
                            graph.Clip = clipRegion;
                        }
                    }
                }
            }
 
			// Draw area
			if(!gradientFill)
			{
				// Turn off anti aliasing and fill area
				SmoothingMode oldMode = graph.SmoothingMode;
				graph.SmoothingMode = SmoothingMode.None;
				path.CloseAllFigures();
				graph.FillPath(areaBrush, path);
				graph.SmoothingMode = oldMode;
 
				// Draw top and bottom lines, because anti aliasing is not working for the FillPath method
				if(graph.SmoothingMode != SmoothingMode.None)
				{
					Pen areaLinePen = new Pen(areaBrush, 1);
 
					// This code is introduce because of problem 
					// with Svg and Hatch Color
                    HatchBrush hatchBrush = areaBrush as HatchBrush;
					if( hatchBrush != null )
					{
						areaLinePen.Color = hatchBrush.ForegroundColor;
					}
 
					if(pointIndex == 0)
					{
						graph.DrawLine(areaLinePen, highPoint1.X, lowPoint1.Y, highPoint1.X, highPoint1.Y);
					}
					if(pointIndex == series.Points.Count - 1)
					{
						graph.DrawLine(areaLinePen, highPoint2.X, highPoint2.Y, highPoint2.X, lowPoint2.Y);
					}
					
					if(this.lineTension == 0)
					{
						graph.DrawLine(areaLinePen, points[pointIndex - 1], points[pointIndex]);
					}
					else
					{
						graph.DrawCurve(areaLinePen, points, pointIndex-1, 1, this.lineTension);
					}
 
					if(this.lineTension == 0)
					{
						graph.DrawLine(areaLinePen, lowPoints[pointIndex - 1], lowPoints[pointIndex]);
					}
					else
					{
						graph.DrawCurve(areaLinePen, lowPoints, pointIndex-1, 1, this.lineTension);
					}
				}
			}
			
			// Add first line
			if(areaPath == null)
			{
				areaPath = new GraphicsPath();
				areaPath.AddLine(highPoint1.X, lowPoint1.Y, highPoint1.X, highPoint1.Y);
			}
 
			// Add line to the gradient path
			if(this.lineTension == 0)
			{
				areaPath.AddLine(points[pointIndex-1], points[pointIndex]);
			}
			else
			{
				areaPath.AddCurve(points, pointIndex-1, 1, this.lineTension);
			}
 
			if(this.lineTension == 0)
			{
				areaBottomPath.AddLine(lowPoints[pointIndex-1], lowPoints[pointIndex]);
			}
			else
			{
				areaBottomPath.AddCurve(lowPoints, pointIndex-1, 1, this.lineTension);
			}
 
			// Draw range High and Low border lines
			if((point.BorderWidth > 0 && 
				point.BorderDashStyle != ChartDashStyle.NotSet && 
				point.BorderColor != Color.Empty) || 
				areaBrush is SolidBrush)
			{
				this.useBorderColor = true;
				this.disableShadow = true;
				base.DrawLine(graph, common, point, series, points, pointIndex, tension);
				this.YValueIndex = 1;
				base.DrawLine(graph, common, point, series, lowPoints, pointIndex, tension);
				this.YValueIndex = 0;
				this.useBorderColor = false;
				this.disableShadow = false;
			}
 
			if( common.ProcessModeRegions )
			{
				//**************************************************************
				//** Add area for the inside of the area
				//**************************************************************
 
				path.AddLine(highPoint1.X, lowPoint1.Y, highPoint1.X, highPoint1.Y);
				if(this.lineTension == 0)
				{
					path.AddLine(points[pointIndex-1], points[pointIndex]);
				}
				else
				{
					path.AddCurve(points, pointIndex-1, 1, this.lineTension);
				}
				path.AddLine(highPoint2.X, highPoint2.Y, highPoint2.X, lowPoint2.Y);
				if(this.lineTension == 0)
				{
					path.AddLine(lowPoints[pointIndex-1], lowPoints[pointIndex]);
				}
				else
				{
					path.AddCurve(lowPoints, pointIndex-1, 1, this.lineTension);
				}
 
				// Create grapics path object dor the curved area
				GraphicsPath mapAreaPath = new GraphicsPath();
				mapAreaPath.AddLine(highPoint1.X, lowPoint1.Y, highPoint1.X, highPoint1.Y);
				if(this.lineTension == 0)
				{
					mapAreaPath.AddLine(points[pointIndex - 1], points[pointIndex]);
				}
				else
				{
					mapAreaPath.AddCurve(points, pointIndex-1, 1, this.lineTension);
					mapAreaPath.Flatten();
				}
				mapAreaPath.AddLine(highPoint2.X, highPoint2.Y, highPoint2.X, lowPoint2.Y);
				if(this.lineTension == 0)
				{
					mapAreaPath.AddLine(lowPoints[pointIndex - 1], lowPoints[pointIndex]);
				}
				else
				{
					mapAreaPath.AddCurve(lowPoints, pointIndex-1, 1, this.lineTension);
					mapAreaPath.Flatten();
				}
 
				// Allocate array of floats
				PointF	pointNew = PointF.Empty;
				float[]	coord = new float[mapAreaPath.PointCount * 2];
				PointF[] pathPoints = mapAreaPath.PathPoints;
				for( int i = 0; i < mapAreaPath.PointCount; i++ )
				{
					pointNew = graph.GetRelativePoint( pathPoints[i] );
					coord[2*i] = pointNew.X;
					coord[2*i + 1] = pointNew.Y;
				}
 
				common.HotRegionsList.AddHotRegion(
					mapAreaPath, 
					false, 
					coord, 
					point, 
					series.Name,
					pointIndex );
			
			}
            //Clean up
            if (areaBrush != null)
                areaBrush.Dispose();
            if (path != null)
            {
                path.Dispose();
                path = null;
            }
 
		}
 
		
		#endregion
 
		#region 3D painting and selection methods
 
		/// <summary>
		/// Draws a 3D surface connecting the two specified points in 2D space.
		/// Used to draw Line based charts.
		/// </summary>
		/// <param name="area">Chart area reference.</param>
		/// <param name="graph">Chart graphics.</param>
		/// <param name="matrix">Coordinates transformation matrix.</param>
		/// <param name="lightStyle">LightStyle style (None, Simplistic, Realistic).</param>
		/// <param name="prevDataPointEx">Previous data point object.</param>
		/// <param name="positionZ">Z position of the back side of the 3D surface.</param>
		/// <param name="depth">Depth of the 3D surface.</param>
		/// <param name="points">Array of points.</param>
		/// <param name="pointIndex">Index of point to draw.</param>
		/// <param name="pointLoopIndex">Index of points loop.</param>
		/// <param name="tension">Line tension.</param>
		/// <param name="operationType">AxisName of operation Drawing, Calculating Path or Both</param>
		/// <param name="topDarkening">Darkenning scale for top surface. 0 - None.</param>
		/// <param name="bottomDarkening">Darkenning scale for bottom surface. 0 - None.</param>
		/// <param name="thirdPointPosition">Position where the third point is actually located or float.NaN if same as in "firstPoint".</param>
		/// <param name="fourthPointPosition">Position where the fourth point is actually located or float.NaN if same as in "secondPoint".</param>
		/// <param name="clippedSegment">Indicates that drawn segment is 3D clipped. Only top/bottom should be drawn.</param>
		/// <returns>Returns elemnt shape path if operationType parameter is set to CalcElementPath, otherwise Null.</returns>
		protected override GraphicsPath Draw3DSurface( 
			ChartArea area,
			ChartGraphics graph, 
			Matrix3D matrix,
			LightStyle lightStyle,
			DataPoint3D prevDataPointEx,
			float positionZ, 
			float depth, 
			ArrayList points,
			int pointIndex, 
			int pointLoopIndex,
			float tension,
			DrawingOperationTypes operationType,
			float topDarkening,
			float bottomDarkening,
			PointF thirdPointPosition,
			PointF fourthPointPosition,
			bool clippedSegment)
		{
			// Create graphics path for selection
			GraphicsPath	resultPath = ((operationType & DrawingOperationTypes.CalcElementPath) == DrawingOperationTypes.CalcElementPath)
				? new GraphicsPath() : null;
 
 
			//****************************************************************
			//** Find line first and second points.
			//****************************************************************
 
			// Check if points are drawn from sides to center (do only once)
			if(centerPointIndex == int.MaxValue)
			{
				centerPointIndex = GetCenterPointIndex(points);
			}
 
			//************************************************************
			//** Find line first & second points
			//************************************************************
			DataPoint3D	secondPoint = (DataPoint3D)points[pointIndex];
			int pointArrayIndex = pointIndex;
			DataPoint3D firstPoint = ChartGraphics.FindPointByIndex(
				points, 
				secondPoint.index - 1, 
				(this.multiSeries) ? secondPoint : null, 
				ref pointArrayIndex);
 
			//****************************************************************
			//** Switch first and second points.
			//****************************************************************
			bool reversed = false;
			if(firstPoint.index > secondPoint.index)
			{
				DataPoint3D	tempPoint = firstPoint;
				firstPoint = secondPoint;
				secondPoint = tempPoint;
				reversed = true;
			}
 
 
			// Points can be drawn from sides to the center.
			// In this case can't use index in the list to find first point.
			// Use point series and real point index to find the first point.
			// Get required point index
			if(matrix.Perspective != 0 && centerPointIndex != int.MaxValue)
			{
				pointArrayIndex = pointIndex;
				if( pointIndex != (centerPointIndex + 1))
				{
					firstPoint = ChartGraphics.FindPointByIndex(points, secondPoint.index - 1, (this.multiSeries) ? secondPoint : null, ref pointArrayIndex);
				}
				else
				{
                    if (!area.ReverseSeriesOrder)
					{
						secondPoint = ChartGraphics.FindPointByIndex(points, firstPoint.index + 1, (this.multiSeries) ? secondPoint : null, ref pointArrayIndex);
					}
					else
					{
						firstPoint = secondPoint;
						secondPoint = ChartGraphics.FindPointByIndex(points, secondPoint.index - 1, (this.multiSeries) ? secondPoint : null, ref pointArrayIndex);
					}
				}
			}
 
			// Check if points are not null
			if(firstPoint == null || secondPoint == null)
			{
				return resultPath;
			}
 
 
            // Area point is drawn as one segment
			return Draw3DSurface( firstPoint, secondPoint, reversed,
				area, graph, matrix, lightStyle, prevDataPointEx,
				positionZ, depth, points, pointIndex, pointLoopIndex,
				tension, operationType, LineSegmentType.Single,
				topDarkening, bottomDarkening,
				thirdPointPosition, fourthPointPosition,
				clippedSegment,
				true, true);
		}
 
		/// <summary>
		/// Draws a 3D surface connecting the two specified points in 2D space.
		/// Used to draw Line based charts.
		/// </summary>
		/// <param name="firstPoint">First data point.</param>
		/// <param name="secondPoint">Second data point.</param>
		/// <param name="reversed">Points are in reversed order.</param>
		/// <param name="area">Chart area reference.</param>
		/// <param name="graph">Chart graphics.</param>
		/// <param name="matrix">Coordinates transformation matrix.</param>
		/// <param name="lightStyle">LightStyle style (None, Simplistic, Realistic).</param>
		/// <param name="prevDataPointEx">Previous data point object.</param>
		/// <param name="positionZ">Z position of the back side of the 3D surface.</param>
		/// <param name="depth">Depth of the 3D surface.</param>
		/// <param name="points">Array of points.</param>
		/// <param name="pointIndex">Index of point to draw.</param>
		/// <param name="pointLoopIndex">Index of points loop.</param>
		/// <param name="tension">Line tension.</param>
		/// <param name="operationType">AxisName of operation Drawing, Calculating Path or Both</param>
		/// <param name="surfaceSegmentType">Define surface segment type if it consists of several segments.</param>
		/// <param name="topDarkening">Darkenning scale for top surface. 0 - None.</param>
		/// <param name="bottomDarkening">Darkenning scale for bottom surface. 0 - None.</param>
		/// <param name="thirdPointPosition">Position where the third point is actually located or float.NaN if same as in "firstPoint".</param>
		/// <param name="fourthPointPosition">Position where the fourth point is actually located or float.NaN if same as in "secondPoint".</param>
		/// <param name="clippedSegment">Indicates that drawn segment is 3D clipped. Only top/bottom should be drawn.</param>
		/// <param name="clipOnTop">Indicates that top segment line should be clipped to the pkot area.</param>
		/// <param name="clipOnBottom">Indicates that bottom segment line should be clipped to the pkot area.</param>
		/// <returns>Returns elemnt shape path if operationType parameter is set to CalcElementPath, otherwise Null.</returns>
		protected override GraphicsPath Draw3DSurface( 
			DataPoint3D firstPoint, 
			DataPoint3D secondPoint, 
			bool reversed,
			ChartArea area,
			ChartGraphics graph, 
			Matrix3D matrix,
			LightStyle lightStyle,
			DataPoint3D prevDataPointEx,
			float positionZ, 
			float depth, 
			ArrayList points,
			int pointIndex, 
			int pointLoopIndex,
			float tension,
			DrawingOperationTypes operationType,
			LineSegmentType surfaceSegmentType,
			float topDarkening,
			float bottomDarkening,
			PointF thirdPointPosition,
			PointF fourthPointPosition,
			bool clippedSegment,
			bool clipOnTop,
			bool clipOnBottom)
		{
			// Create graphics path for selection
			GraphicsPath	resultPath = ((operationType & DrawingOperationTypes.CalcElementPath) == DrawingOperationTypes.CalcElementPath)
				? new GraphicsPath() : null;
 
			// Fint point with line properties
			DataPoint3D		pointAttr = secondPoint;
			if(prevDataPointEx.dataPoint.IsEmpty)
			{
				pointAttr = prevDataPointEx;
			}
			else if(firstPoint.index > secondPoint.index)
			{
				pointAttr = firstPoint;
			}
 
			//****************************************************************
			//** Adjust point visual properties.
			//****************************************************************
			Color			color = (useBorderColor) ? pointAttr.dataPoint.BorderColor : pointAttr.dataPoint.Color;
			ChartDashStyle	dashStyle = pointAttr.dataPoint.BorderDashStyle;
			if( pointAttr.dataPoint.IsEmpty && pointAttr.dataPoint.Color == Color.Empty)
			{
				color = Color.Gray;
			}
			if( pointAttr.dataPoint.IsEmpty && pointAttr.dataPoint.BorderDashStyle == ChartDashStyle.NotSet )
			{
				dashStyle = ChartDashStyle.Solid;
			}
 
			//****************************************************************
			//** Get axis position
			//****************************************************************
			float	axisPosition = (float)VAxis.GetPosition(this.VAxis.Crossing);
 
 
			//****************************************************************
			//** Calculate position of top/bootom points.
			//****************************************************************
			PointF	thirdPoint, fourthPoint; 
			GetBottomPointsPosition(
				Common, 
				area, 
				axisPosition, 
				ref firstPoint, 
				ref secondPoint, 
				out thirdPoint, 
				out fourthPoint);
 
			// Check if point's position provided as parameter
			if(!float.IsNaN(thirdPointPosition.Y))
			{
				thirdPoint.Y = thirdPointPosition.Y;
			}
			if(!float.IsNaN(fourthPointPosition.Y))
			{
				fourthPoint.Y = fourthPointPosition.Y;
			}
 
 
			//****************************************************************
			//** Check if top/bottom lines of range segment intersect.
			//****************************************************************
            double smallDouble = 0.0001;
            if (Math.Abs(firstPoint.yPosition - (double)thirdPoint.Y) > smallDouble && 
                Math.Abs(secondPoint.yPosition - (double)fourthPoint.Y) > smallDouble &&
                ((firstPoint.yPosition > thirdPoint.Y && secondPoint.yPosition < fourthPoint.Y) ||
                (firstPoint.yPosition < thirdPoint.Y && secondPoint.yPosition > fourthPoint.Y)))
			{
				// This feature is not supported in 3D SplineRange chart type
				if(tension != 0f)
				{
                    throw (new InvalidOperationException(SR.Exception3DSplineY1ValueIsLessThenY2));
				}
 
				// Find intersection point
				PointF	intersectionCoordinates = ChartGraphics.GetLinesIntersection(
					(float)firstPoint.xPosition, (float)firstPoint.yPosition,
					(float)secondPoint.xPosition, (float)secondPoint.yPosition,
					thirdPoint.X, thirdPoint.Y,
					fourthPoint.X, fourthPoint.Y);
				DataPoint3D	intersectionPoint = new DataPoint3D();
				intersectionPoint.xPosition = intersectionCoordinates.X;
				intersectionPoint.yPosition = intersectionCoordinates.Y;
 
				// Check if intersection point is valid
				bool splitDraw = true;
				if( double.IsNaN(intersectionCoordinates.X) ||
					double.IsNaN(intersectionCoordinates.Y) )
				{
					splitDraw = false;
				}
				else
				{
					if( (decimal)intersectionCoordinates.X == (decimal)firstPoint.xPosition && 
						(decimal)intersectionCoordinates.Y == (decimal)firstPoint.yPosition )
					{
						splitDraw = false;
					}
					if( (decimal)intersectionCoordinates.X == (decimal)secondPoint.xPosition && 
						(decimal)intersectionCoordinates.Y == (decimal)secondPoint.yPosition )
					{
						splitDraw = false;
					}
				}
 
				if(splitDraw)
				{
					// Check if reversed drawing order required
					reversed = false;
					if((pointIndex + 1) < points.Count)
					{
						DataPoint3D p = (DataPoint3D)points[pointIndex + 1];
						if(p.index == firstPoint.index)
						{
							reversed = true;
						}
					}
 
					// Draw two segments
					for(int segmentIndex = 0; segmentIndex <= 1; segmentIndex++)
					{
						GraphicsPath segmentPath = null;
						if(segmentIndex == 0 && !reversed ||
							segmentIndex == 1 && reversed)
						{
							// Set coordinates of bottom points
							_fourthPointY2Value = (float)intersectionPoint.yPosition;
 
							// Draw first segment
							intersectionPoint.dataPoint = secondPoint.dataPoint;
							intersectionPoint.index = secondPoint.index;
							segmentPath =  Draw3DSurface( firstPoint, intersectionPoint, reversed,
								area, graph, matrix, lightStyle, prevDataPointEx,
								positionZ, depth, points, pointIndex, pointLoopIndex,
								tension, operationType, surfaceSegmentType,
								topDarkening, bottomDarkening,
								new PointF(float.NaN, float.NaN),
								new PointF(float.NaN, float.NaN),
								clippedSegment,
								true, true);
						}
 
						if(segmentIndex == 1 && !reversed ||
							segmentIndex == 0 && reversed)
						{
							// Set coordinates of bottom points
							_thirdPointY2Value = (float)intersectionPoint.yPosition;
 
							// Draw second segment
							intersectionPoint.dataPoint = firstPoint.dataPoint;
							intersectionPoint.index = firstPoint.index;
							segmentPath =  Draw3DSurface( intersectionPoint, secondPoint, reversed,
								area, graph, matrix, lightStyle, prevDataPointEx,
								positionZ, depth, points, pointIndex, pointLoopIndex,
								tension, operationType, surfaceSegmentType,
								topDarkening, bottomDarkening,
								new PointF(float.NaN, float.NaN),
								new PointF(float.NaN, float.NaN),
								clippedSegment,
								true, true);
						}
 
						// Add segment path
						if(resultPath != null && segmentPath != null && segmentPath.PointCount > 0)
						{
							resultPath.AddPath(segmentPath, true);
						}
 
						// Reset bottom line "forced" Y coordinates
						_thirdPointY2Value = float.NaN;
						_fourthPointY2Value = float.NaN;
 
					}
 
					return resultPath;
				}
			}
 
            
			//****************************************************************
			//** Detect visibility of the bounding rectangle.
			//****************************************************************
			float minX = (float)Math.Min(firstPoint.xPosition, secondPoint.xPosition);
			float minY = (float)Math.Min(firstPoint.yPosition, secondPoint.yPosition);
			minY = (float)Math.Min(minY, axisPosition);
			float maxX = (float)Math.Max(firstPoint.xPosition, secondPoint.xPosition);
			float maxY = (float)Math.Max(firstPoint.yPosition, secondPoint.yPosition);
			maxY = (float)Math.Max(maxY, axisPosition);
			RectangleF position = new RectangleF(minX, minY, maxX - minX, maxY - minY);
			SurfaceNames visibleSurfaces = graph.GetVisibleSurfaces(position,positionZ,depth,matrix);
 
			// Check if area point is drawn upside down.
			bool upSideDown = false;
			if(firstPoint.yPosition >= thirdPoint.Y && secondPoint.yPosition >= fourthPoint.Y)
			{
				upSideDown = true;
 
				// Switch visibility between Top & Bottom surfaces
				bool topVisible = ( (visibleSurfaces & SurfaceNames.Top) == SurfaceNames.Top );
				bool bottomVisible = ( (visibleSurfaces & SurfaceNames.Bottom) == SurfaceNames.Bottom );
				visibleSurfaces ^= SurfaceNames.Bottom;
				visibleSurfaces ^= SurfaceNames.Top;
				if(topVisible)
				{
					visibleSurfaces |= SurfaceNames.Bottom;
				}
				if(bottomVisible)
				{
					visibleSurfaces |= SurfaceNames.Top;
				}
			}
 
			// Check Top/Bottom surface visibility
			GetTopSurfaceVisibility(area, firstPoint, secondPoint, upSideDown, positionZ, depth, matrix, ref visibleSurfaces);
 
			// Top and bottom surfaces are always visible for spline range
			bool bottomFirst = true;
			if(tension != 0f)
			{
				if( (visibleSurfaces & SurfaceNames.Bottom) == SurfaceNames.Bottom )
				{
					bottomFirst = false;
				}
				if( (visibleSurfaces & SurfaceNames.Bottom) == 0 &&
					(visibleSurfaces & SurfaceNames.Top) == 0 )
				{
					bottomFirst = false;
				}
 
 
				visibleSurfaces |= SurfaceNames.Bottom;
				visibleSurfaces |= SurfaceNames.Top;
			}
 
			// Round double values to 5 decimals
			firstPoint.xPosition = Math.Round(firstPoint.xPosition, 5);
			firstPoint.yPosition = Math.Round(firstPoint.yPosition, 5);
			secondPoint.xPosition = Math.Round(secondPoint.xPosition, 5);
			secondPoint.yPosition = Math.Round(secondPoint.yPosition, 5);
 
			//****************************************************************
			//** Clip area first and second data points inside 
			//** the plotting area.
			//****************************************************************
			if(ClipTopPoints(
				resultPath,
				ref firstPoint, 
				ref secondPoint, 
				reversed,
				area,
				graph, 
				matrix,
				lightStyle,
				prevDataPointEx,
				positionZ, 
				depth, 
				points,
				pointIndex, 
				pointLoopIndex,
				tension,
				operationType,
				surfaceSegmentType,
				topDarkening,
				bottomDarkening
				) == true)
			{
				return resultPath;
			}
 
			//****************************************************************
			//** Clip area third and fourth data points inside 
			//** the plotting area.
			//****************************************************************
			if(ClipBottomPoints(
				resultPath,
				ref firstPoint, 
				ref secondPoint, 
				ref thirdPoint,
				ref fourthPoint,
				reversed,
				area,
				graph, 
				matrix,
				lightStyle,
				prevDataPointEx,
				positionZ, 
				depth, 
				points,
				pointIndex, 
				pointLoopIndex,
				tension,
				operationType,
				surfaceSegmentType,
				topDarkening,
				bottomDarkening
				) == true)
			{
				return resultPath;
			}
 
			//****************************************************************
			//** Draw elements of area chart in 2 layers (back & front)
			//****************************************************************
			for(int elemLayer = 1; elemLayer <= 2; elemLayer++)
			{
				// Loop through all surfaces
				SurfaceNames[]	surfacesOrder = null;
				if(bottomFirst)
					surfacesOrder = new SurfaceNames[] {SurfaceNames.Back, SurfaceNames.Bottom, SurfaceNames.Top, SurfaceNames.Left, SurfaceNames.Right, SurfaceNames.Front};
				else
					surfacesOrder = new SurfaceNames[] {SurfaceNames.Back, SurfaceNames.Top, SurfaceNames.Bottom, SurfaceNames.Left, SurfaceNames.Right, SurfaceNames.Front};
 
				LineSegmentType lineSegmentType = LineSegmentType.Middle;
				foreach(SurfaceNames	currentSurface in surfacesOrder)
				{
					// Check id surface should be drawn
                    if (ChartGraphics.ShouldDrawLineChartSurface(area, area.ReverseSeriesOrder, currentSurface, visibleSurfaces, color, 
						points, firstPoint, secondPoint, this.multiSeries, ref lineSegmentType) != elemLayer)
					{
						continue;
					}
 
					// Draw only borders of the invisible elements on the back layer
					Color	surfaceColor = color;
					Color	surfaceBorderColor = pointAttr.dataPoint.BorderColor;
					if(elemLayer == 1)
					{
						// Draw only if point color is semi-transparent
						if(surfaceColor.A == 255)
						{
							continue;
						}
 
						// Define drawing colors
						surfaceColor = Color.Transparent;
						if(surfaceBorderColor == Color.Empty)
						{
							// If border color is emty use color slightly darker than main back color
							surfaceBorderColor = ChartGraphics.GetGradientColor( color, Color.Black, 0.2 );
						}
					}
 
					// Draw surfaces
					GraphicsPath surfacePath = null;
					switch(currentSurface)
					{
						case(SurfaceNames.Top):
							surfacePath = graph.Draw3DSurface( area, matrix, lightStyle, currentSurface, positionZ,  depth, 
								surfaceColor, surfaceBorderColor, pointAttr.dataPoint.BorderWidth, dashStyle, 
								firstPoint, secondPoint,  points, pointIndex,
								tension, operationType, LineSegmentType.Middle,
                                (this.showPointLines) ? true : false, false, area.ReverseSeriesOrder, this.multiSeries, 0, true);
							break;
						case(SurfaceNames.Bottom):
						{
							// Calculate coordinates
							DataPoint3D	dp1 = new DataPoint3D();
							dp1.dataPoint = firstPoint.dataPoint;
							dp1.index = firstPoint.index;
							dp1.xPosition = firstPoint.xPosition;
							dp1.yPosition = thirdPoint.Y;
							DataPoint3D	dp2 = new DataPoint3D();
							dp2.dataPoint = secondPoint.dataPoint;
							dp2.index = secondPoint.index;
							dp2.xPosition = secondPoint.xPosition;
							dp2.yPosition = fourthPoint.Y;
 
							// Draw surface
							surfacePath = graph.Draw3DSurface( area, matrix, lightStyle, currentSurface, positionZ, depth, 
								surfaceColor, surfaceBorderColor, pointAttr.dataPoint.BorderWidth, dashStyle, 
								dp1, dp2, points, pointIndex,
								tension, operationType, LineSegmentType.Middle,
                                (this.showPointLines) ? true : false, false, area.ReverseSeriesOrder, this.multiSeries, 1, true);
							break;
						}
 
						case(SurfaceNames.Left):
						{
							if(surfaceSegmentType == LineSegmentType.Single ||
                                (!area.ReverseSeriesOrder && surfaceSegmentType == LineSegmentType.First) ||
                                (area.ReverseSeriesOrder && surfaceSegmentType == LineSegmentType.Last))
							{
								
								// Calculate coordinates
								DataPoint3D	leftMostPoint = (firstPoint.xPosition <= secondPoint.xPosition) ? firstPoint : secondPoint;
								DataPoint3D	dp1 = new DataPoint3D();
								dp1.xPosition = leftMostPoint.xPosition;
								dp1.yPosition = (firstPoint.xPosition <= secondPoint.xPosition) ? thirdPoint.Y : fourthPoint.Y;
								DataPoint3D	dp2 = new DataPoint3D();
								dp2.xPosition = leftMostPoint.xPosition;;
								dp2.yPosition = leftMostPoint.yPosition;
 
								// Draw surface
								surfacePath = graph.Draw3DSurface( area, matrix, lightStyle, currentSurface, positionZ, depth, 
									surfaceColor, surfaceBorderColor, pointAttr.dataPoint.BorderWidth, dashStyle, 
									dp1, dp2, points, pointIndex,
                                    0f, operationType, LineSegmentType.Single, false, true, area.ReverseSeriesOrder, this.multiSeries, 0, true);
									
							}
							break;
						}
						case(SurfaceNames.Right):
						{
							if(surfaceSegmentType == LineSegmentType.Single ||
                                (!area.ReverseSeriesOrder && surfaceSegmentType == LineSegmentType.Last) ||
                                (area.ReverseSeriesOrder && surfaceSegmentType == LineSegmentType.First))
 
							{
								// Calculate coordinates
								DataPoint3D	rightMostPoint = (secondPoint.xPosition >= firstPoint.xPosition) ? secondPoint : firstPoint;
								DataPoint3D	dp1 = new DataPoint3D();
								dp1.xPosition = rightMostPoint.xPosition;
								dp1.yPosition = (secondPoint.xPosition >= firstPoint.xPosition) ? fourthPoint.Y : thirdPoint.Y;
								DataPoint3D	dp2 = new DataPoint3D();
								dp2.xPosition = rightMostPoint.xPosition;
								dp2.yPosition = rightMostPoint.yPosition;
 
								// Draw surface
								surfacePath = graph.Draw3DSurface( area, matrix, lightStyle, currentSurface, positionZ, depth, 
									surfaceColor, surfaceBorderColor, pointAttr.dataPoint.BorderWidth, dashStyle, 
									dp1, dp2, points, pointIndex,
                                    0f, operationType, LineSegmentType.Single, false, true, area.ReverseSeriesOrder, this.multiSeries, 0, true);
							}
 
							break;
						}
						case(SurfaceNames.Back):
						{
							// Calculate coordinates
							DataPoint3D	dp1 = new DataPoint3D();
							dp1.dataPoint = firstPoint.dataPoint;
							dp1.index = firstPoint.index;
							dp1.xPosition = firstPoint.xPosition;
							dp1.yPosition = thirdPoint.Y;
							DataPoint3D	dp2 = new DataPoint3D();
							dp2.dataPoint = secondPoint.dataPoint;
							dp2.index = secondPoint.index;
							dp2.xPosition = secondPoint.xPosition;
							dp2.yPosition = fourthPoint.Y;
 
							// Draw surface
							surfacePath = Draw3DSplinePolygon( graph, area, positionZ,
								surfaceColor, surfaceBorderColor, pointAttr.dataPoint.BorderWidth, 
								firstPoint, secondPoint, dp2, dp1, points, 
								tension, operationType, lineSegmentType, 
								(this.showPointLines) ? true : false);
 
							break;
						}
						case(SurfaceNames.Front):
						{
							
							// Calculate coordinates
							DataPoint3D	dp1 = new DataPoint3D();
							dp1.dataPoint = firstPoint.dataPoint;
							dp1.index = firstPoint.index;
							dp1.xPosition = firstPoint.xPosition;
							dp1.yPosition = thirdPoint.Y;
							DataPoint3D	dp2 = new DataPoint3D();
							dp2.dataPoint = secondPoint.dataPoint;
							dp2.index = secondPoint.index;
							dp2.xPosition = secondPoint.xPosition;
							dp2.yPosition = fourthPoint.Y;
 
							// Change segment type for the reversed series order
                            if (area.ReverseSeriesOrder)
							{
								if(lineSegmentType == LineSegmentType.First)
								{
									lineSegmentType = LineSegmentType.Last;
								}
								else if(lineSegmentType == LineSegmentType.Last)
								{
									lineSegmentType = LineSegmentType.First;
								}
							}
 
							if(surfaceSegmentType != LineSegmentType.Single)
							{
								if( surfaceSegmentType == LineSegmentType.Middle ||
									( surfaceSegmentType == LineSegmentType.First && lineSegmentType != LineSegmentType.First) ||
									( surfaceSegmentType == LineSegmentType.Last && lineSegmentType != LineSegmentType.Last) )
								{
									lineSegmentType = LineSegmentType.Middle;
								}
								if(reversed) 
								{
									if(lineSegmentType == LineSegmentType.First)
									{
										lineSegmentType = LineSegmentType.Last;
									}
									else if(lineSegmentType == LineSegmentType.Last)
									{
										lineSegmentType = LineSegmentType.First;
									}
								}
							}
 
							// Draw surface
							surfacePath = Draw3DSplinePolygon( graph, area, positionZ + depth,
								surfaceColor, surfaceBorderColor, pointAttr.dataPoint.BorderWidth, 
								firstPoint, secondPoint, dp2, dp1, points, 
								tension, operationType, lineSegmentType, 
								(this.showPointLines) ? true : false);
								
							break;
						}
					}
 
					// Add path of the fully visible surfaces to the result surface
					if(elemLayer == 2 && resultPath != null && surfacePath != null && surfacePath.PointCount > 0)
					{
						resultPath.CloseFigure();
						resultPath.AddPath(surfacePath, true);
					}
 
				}
			}
 
			return resultPath;
		}
 
		/// <summary>
		/// Gets visibility of the top surface.
		/// </summary>
		/// <param name="area">Chart area object.</param>
		/// <param name="firstPoint">First data point of the line.</param>
		/// <param name="secondPoint">Second data point of the line.</param>
		/// <param name="upSideDown">Indicates that Y values of the data points are below axis line.</param>
		/// <param name="positionZ">Z coordinate of the back side of the cube.</param>
		/// <param name="depth">Cube depth.</param>
		/// <param name="matrix">Coordinate transformation matrix.</param>
		/// <param name="visibleSurfaces">Surface visibility reference. Initialized with bounary cube visibility.</param>
		protected virtual void GetTopSurfaceVisibility(
			ChartArea area,
			DataPoint3D firstPoint, 
			DataPoint3D secondPoint, 
			bool upSideDown,
			float positionZ, 
			float depth, 
			Matrix3D matrix,
			ref SurfaceNames visibleSurfaces)
		{
			//***********************************************************************
			//** Check Top surface visibility
			//***********************************************************************
			// If Top surface visibility in bounding rectangle - do not gurantee angled linde visibility
			if( (visibleSurfaces & SurfaceNames.Top) == SurfaceNames.Top)
			{
				visibleSurfaces ^= SurfaceNames.Top;
			}
 
			// Create top surface coordinates in 3D space
			Point3D[] cubePoints = new Point3D[3];
            if (!area.ReverseSeriesOrder)
			{
				if(!upSideDown && firstPoint.xPosition < secondPoint.xPosition ||
					upSideDown && firstPoint.xPosition > secondPoint.xPosition)
				{
					cubePoints[0] = new Point3D( (float)firstPoint.xPosition, (float)firstPoint.yPosition, positionZ + depth );
					cubePoints[1] = new Point3D( (float)firstPoint.xPosition, (float)firstPoint.yPosition, positionZ );
					cubePoints[2] = new Point3D( (float)secondPoint.xPosition, (float)secondPoint.yPosition, positionZ );
				}
				else
				{
					cubePoints[0] = new Point3D( (float)secondPoint.xPosition, (float)secondPoint.yPosition, positionZ + depth );
					cubePoints[1] = new Point3D( (float)secondPoint.xPosition, (float)secondPoint.yPosition, positionZ );
					cubePoints[2] = new Point3D( (float)firstPoint.xPosition, (float)firstPoint.yPosition, positionZ );
				}
			}
			else
			{
				if(!upSideDown && secondPoint.xPosition < firstPoint.xPosition ||
					upSideDown && secondPoint.xPosition > firstPoint.xPosition)
				{
					cubePoints[0] = new Point3D( (float)secondPoint.xPosition, (float)secondPoint.yPosition, positionZ + depth );
					cubePoints[1] = new Point3D( (float)secondPoint.xPosition, (float)secondPoint.yPosition, positionZ );
					cubePoints[2] = new Point3D( (float)firstPoint.xPosition, (float)firstPoint.yPosition, positionZ );
				}
				else
				{
					cubePoints[0] = new Point3D( (float)firstPoint.xPosition, (float)firstPoint.yPosition, positionZ + depth );
					cubePoints[1] = new Point3D( (float)firstPoint.xPosition, (float)firstPoint.yPosition, positionZ );
					cubePoints[2] = new Point3D( (float)secondPoint.xPosition, (float)secondPoint.yPosition, positionZ );
				}
			}
 
			// Tranform coordinates 
			matrix.TransformPoints( cubePoints );
 
			// Check the top side visibility
			if(ChartGraphics.IsSurfaceVisible(cubePoints[0],cubePoints[1],cubePoints[2]))
			{
				visibleSurfaces |= SurfaceNames.Top;
			}
 
 
			//***********************************************************************
			//** Check Bottom surface visibility
			//***********************************************************************
 
			// Get bottom surface points
			PointF	thirdPoint, fourthPoint; 
			GetBottomPointsPosition(area.Common, area, 0, ref firstPoint, ref secondPoint, out thirdPoint, out fourthPoint);
 
 
			// If Bottom surface visibility in bounding rectangle - do not gurantee angled linde visibility
			if( (visibleSurfaces & SurfaceNames.Bottom) == SurfaceNames.Bottom)
			{
				visibleSurfaces ^= SurfaceNames.Bottom;
			}
 
			// Create top surface coordinates in 3D space
			cubePoints = new Point3D[3];
            if (!area.ReverseSeriesOrder)
			{
				if(!upSideDown && firstPoint.xPosition < secondPoint.xPosition ||
					upSideDown && firstPoint.xPosition > secondPoint.xPosition)
				{
					cubePoints[0] = new Point3D( (float)firstPoint.xPosition, (float)thirdPoint.Y, positionZ + depth );
					cubePoints[1] = new Point3D( (float)firstPoint.xPosition, (float)thirdPoint.Y, positionZ );
					cubePoints[2] = new Point3D( (float)secondPoint.xPosition, (float)fourthPoint.Y, positionZ );
				}
				else
				{
					cubePoints[0] = new Point3D( (float)secondPoint.xPosition, (float)fourthPoint.Y, positionZ + depth );
					cubePoints[1] = new Point3D( (float)secondPoint.xPosition, (float)fourthPoint.Y, positionZ );
					cubePoints[2] = new Point3D( (float)firstPoint.xPosition, (float)thirdPoint.Y, positionZ );
				}
			}
			else
			{
				if(!upSideDown && secondPoint.xPosition < firstPoint.xPosition ||
					upSideDown && secondPoint.xPosition > firstPoint.xPosition)
				{
					cubePoints[0] = new Point3D( (float)secondPoint.xPosition, (float)fourthPoint.Y, positionZ + depth );
					cubePoints[1] = new Point3D( (float)secondPoint.xPosition, (float)fourthPoint.Y, positionZ );
					cubePoints[2] = new Point3D( (float)firstPoint.xPosition, (float)thirdPoint.Y, positionZ );
				}
				else
				{
					cubePoints[0] = new Point3D( (float)firstPoint.xPosition, (float)thirdPoint.Y, positionZ + depth );
					cubePoints[1] = new Point3D( (float)firstPoint.xPosition, (float)thirdPoint.Y, positionZ );
					cubePoints[2] = new Point3D( (float)secondPoint.xPosition, (float)fourthPoint.Y, positionZ );
				}
			}
 
			// Tranform coordinates 
			matrix.TransformPoints( cubePoints );
 
			// Check the top side visibility
			if(ChartGraphics.IsSurfaceVisible(cubePoints[2],cubePoints[1],cubePoints[0]))
			{
				visibleSurfaces |= SurfaceNames.Bottom;
			}
 
		}
 
		/// <summary>
		/// Gets position ob the bottom points in area chart.
		/// </summary>
		/// <param name="common">Chart common elements.</param>
		/// <param name="area">Chart area the series belongs to.</param>
		/// <param name="axisPosition">Axis position.</param>
		/// <param name="firstPoint">First top point coordinates.</param>
		/// <param name="secondPoint">Second top point coordinates.</param>
		/// <param name="thirdPoint">Returns third bottom point coordinates.</param>
		/// <param name="fourthPoint">Returns fourth bottom point coordinates.</param>
		protected virtual void GetBottomPointsPosition(
			CommonElements common, 
			ChartArea area, 
			float axisPosition, 
			ref DataPoint3D firstPoint, 
			ref DataPoint3D secondPoint, 
			out PointF thirdPoint, 
			out PointF fourthPoint)
		{
			// Set active vertical axis
			Axis	vAxis = area.GetAxis(AxisName.Y, firstPoint.dataPoint.series.YAxisType, firstPoint.dataPoint.series.YSubAxisName);
 
			// Initialize points using second Y value
			float secondYValue = (float)vAxis.GetPosition(firstPoint.dataPoint.YValues[1]);
			thirdPoint = new PointF((float)firstPoint.xPosition, secondYValue);
			secondYValue = (float)vAxis.GetPosition(secondPoint.dataPoint.YValues[1]);
			fourthPoint = new PointF((float)secondPoint.xPosition, secondYValue);
 
			// Check if "forced" Y values where set
			if(!float.IsNaN(_thirdPointY2Value))
			{
				thirdPoint.Y = _thirdPointY2Value;
				
			}
			if(!float.IsNaN(_fourthPointY2Value))
			{
				fourthPoint.Y = _fourthPointY2Value;
			}
		}
 
		/// <summary>
		/// Draws a 3D polygon defined by 4 points in 2D space.
		/// Top and Bottom lines are drawn as splines of specified tension.
		/// </summary>
		/// <param name="area">Chart area reference.</param>
		/// <param name="graph">Chart graphics.</param>
		/// <param name="positionZ">Z position of the back side of the 3D surface.</param>
		/// <param name="backColor">Color of rectangle</param>
		/// <param name="borderColor">Border Color</param>
		/// <param name="borderWidth">Border Width</param>
		/// <param name="firstPoint">First point.</param>
		/// <param name="secondPoint">Second point.</param>
		/// <param name="thirdPoint">Third point.</param>
		/// <param name="fourthPoint">Fourth point.</param>
		/// <param name="points">Array of points.</param>
		/// <param name="tension">Line tension.</param>
		/// <param name="operationType">AxisName of operation Drawing, Calculating Path or Both</param>
		/// <param name="lineSegmentType">AxisName of line segment. Used for step lines and splines.</param>
		/// <param name="forceThinBorder">Thin border will be drawn on all segments.</param>
		/// <returns>Returns elemnt shape path if operationType parameter is set to CalcElementPath, otherwise Null.</returns>
		internal GraphicsPath Draw3DSplinePolygon( 
			ChartGraphics graph, 
			ChartArea area,
			float positionZ, 
			Color backColor, 
			Color borderColor, 
			int borderWidth, 
			DataPoint3D	firstPoint,
			DataPoint3D	secondPoint, 
			DataPoint3D	thirdPoint,
			DataPoint3D	fourthPoint, 
			ArrayList points,
			float tension,
			DrawingOperationTypes operationType,
			LineSegmentType lineSegmentType,
			bool forceThinBorder)
		{
			// Check tension parameter
			if(tension == 0f)
			{
				SurfaceNames	thinBorderSides = 0;
				if(forceThinBorder)
				{
					thinBorderSides = SurfaceNames.Left | SurfaceNames.Right;
				}
 
				return graph.Draw3DPolygon( area, area.matrix3D, SurfaceNames.Front, positionZ, 
					backColor, borderColor, borderWidth, 
					firstPoint, secondPoint, thirdPoint, fourthPoint, 
					operationType, lineSegmentType, thinBorderSides);
			}
 
			// Create graphics path for selection
			bool	drawElements = ((operationType & DrawingOperationTypes.DrawElement) == DrawingOperationTypes.DrawElement);
			GraphicsPath	resultPath = new GraphicsPath();
 
			//**********************************************************************
			//** Prepare, transform polygon coordinates
			//**********************************************************************
 
			// Get top line path
			GraphicsPath	topLine = graph.GetSplineFlattenPath(
				area, positionZ, 
				firstPoint, secondPoint, points, tension, false, true, 0);
 
			// Get bottom line path
			GraphicsPath	bottomLine = graph.GetSplineFlattenPath(
				area, positionZ, 
				thirdPoint, fourthPoint, points, tension, false, true, 1);
 
			// Add paths to the result path
			resultPath.AddPath(topLine, true);
			resultPath.AddPath(bottomLine, true);
			resultPath.CloseAllFigures();
 
 
			//**********************************************************************
			//** Define drawing colors
			//**********************************************************************
 
			// Define 3 points polygon
			Point3D [] points3D = new Point3D[3];
			points3D[0] = new Point3D((float)firstPoint.xPosition, (float)firstPoint.yPosition, positionZ);
			points3D[1] = new Point3D((float)secondPoint.xPosition, (float)secondPoint.yPosition, positionZ);
			points3D[2] = new Point3D((float)thirdPoint.xPosition, (float)thirdPoint.yPosition, positionZ);
 
			// Transform coordinates
			area.matrix3D.TransformPoints( points3D );
 
			// Get colors
			bool topIsVisible = ChartGraphics.IsSurfaceVisible( points3D[0], points3D[1], points3D[2]);
            Color polygonColor = area.matrix3D.GetPolygonLight(points3D, backColor, topIsVisible, area.Area3DStyle.Rotation, SurfaceNames.Front, area.ReverseSeriesOrder);
			Color	surfaceBorderColor = borderColor;
			if(surfaceBorderColor == Color.Empty)
			{
				// If border color is emty use color slightly darker than main back color
				surfaceBorderColor = ChartGraphics.GetGradientColor( backColor, Color.Black, 0.2 );
			}
 
			//**********************************************************************
			//** Draw elements if required.
			//**********************************************************************
			Pen thickBorderPen = null;
			if(drawElements)
			{
				// Remember SmoothingMode and turn off anti aliasing
				SmoothingMode oldSmoothingMode = graph.SmoothingMode;
				graph.SmoothingMode = SmoothingMode.Default;
 
				// Draw the polygon
                using (Brush brush = new SolidBrush(polygonColor))
                {
                    graph.FillPath(brush, resultPath);
                }
 
				// Return old smoothing mode
				graph.SmoothingMode = oldSmoothingMode;
			
				// Draw thin polygon border of darker color around the whole polygon
				if(forceThinBorder)
				{
					graph.DrawPath(new Pen(surfaceBorderColor, 1), resultPath);
				}
				else if(polygonColor.A == 255)
				{
					graph.DrawPath(new Pen(polygonColor, 1), resultPath);
				}
 
				// Create thick border line pen
				thickBorderPen = new Pen(surfaceBorderColor, borderWidth);
				thickBorderPen.StartCap = LineCap.Round;
				thickBorderPen.EndCap = LineCap.Round;
 
				// Draw thick Top & Bottom lines
				graph.DrawPath(thickBorderPen, topLine);
				graph.DrawPath(thickBorderPen, bottomLine);
 
				// Draw thick Right & Left lines on first & last segments of the line
				if(lineSegmentType == LineSegmentType.First)
				{
					graph.DrawLine(thickBorderPen, topLine.PathPoints[0], bottomLine.GetLastPoint());
				
				}
				else if(lineSegmentType == LineSegmentType.Last)
				{
					graph.DrawLine(thickBorderPen, topLine.GetLastPoint(), bottomLine.PathPoints[0]);					
				}
			}
 
 
			// Calculate path for selection
			if(resultPath != null && thickBorderPen != null)
			{
				// Widen result path
				try
				{
					resultPath.Widen(thickBorderPen);
				}
                catch (OutOfMemoryException)
                {
                    // GraphicsPath.Widen incorrectly throws OutOfMemoryException
                    // catching here and reacting by not widening
                }
                catch (ArgumentException)
                {
                }
			}
 
			return resultPath;
		}
 
	#endregion
 
        #region IDisposable overrides
        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                // Dispose managed resources
                if (this.areaBottomPath != null)
                {
                    this.areaBottomPath.Dispose();
                    this.areaBottomPath = null;
                }
                if (this.areaPath != null)
                {
                    this.areaPath.Dispose();
                    this.areaPath = null;
                }
            }
            base.Dispose(disposing);
        }
        #endregion
 
	}
}