File: Common\ChartTypes\AreaChart.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:		AreaChart.cs
//
//  Namespace:	DataVisualization.Charting.ChartTypes
//
//	Classes:	AreaChart, SplineAreaChart
//
//  Purpose:	Provide 2D/3D drawing and hit testing functionality 
//              for the Area and SplineArea charts. Spline chart 
//              type is used as a base for the Area and SplineArea
//              charts.
//
//	Reviewed:	AG - August 6, 2002; 
//              GS - August 8, 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.Data;
	using System.Windows.Forms.DataVisualization.Charting.ChartTypes;
	using System.Windows.Forms.DataVisualization.Charting.Utilities;
	using System.Windows.Forms.DataVisualization.Charting.Borders3D;
	using System.Windows.Forms.DataVisualization.Charting;
#else
	using System.Web.UI.DataVisualization.Charting;
	using System.Web.UI.DataVisualization.Charting.Data;
	using System.Web.UI.DataVisualization.Charting.ChartTypes;
	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>
    /// SplineAreaChart class extends the AreaChart class by 
    /// providing a different initial tension for the line.
	/// </summary>
	internal class SplineAreaChart : AreaChart
	{
		#region Constructor
 
		/// <summary>
		/// Default constructor.
		/// </summary>
		public SplineAreaChart()
		{
			// 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.SplineArea;}}
 
		/// <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. For spline charts it's always 0.5.
		/// </summary>
		/// <returns>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>
    /// AreaChart class provides 2D/3D drawing and hit testing 
    /// functionality for the Area and SplineArea charts. The 
    /// only difference of the SplineArea 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 AreaChart : SplineChart
	{
		#region Fields
 
		/// <summary>
		/// Fields used to fill area with gradient
		/// </summary>
 
		protected	bool			gradientFill = false;
 
		/// <summary>
		/// Coordinates of the area path
		/// </summary>
		protected	GraphicsPath	areaPath = null;
 
		/// <summary>
		/// Reference to the current series object
		/// </summary>
        protected Series Series { get; set; }
 
		/// <summary>
		/// Horizontal axis position
		/// </summary>
		protected	PointF			axisPos	= PointF.Empty;
 
		#endregion
 
		#region Constructor
 
		/// <summary>
		/// Area chart constructor
		/// </summary>
		public AreaChart()
		{
			this.drawOutsideLines = true;
 
			// Set default line tension
			base.lineTension = 0f;
 
			// Reset axis position
			axisPos	= PointF.Empty;
		}
 
		#endregion 
 
		#region Default tension method
 
		/// <summary>
		/// Gets default line tension.
		/// </summary>
		/// <returns>Line tension.</returns>
		override protected float GetDefaultTension()
		{
			return 0f;
		}
 
		#endregion 
 
		#region IChartType interface implementation
 
		/// <summary>
		/// Chart type name
		/// </summary>
        public override string Name { get { return ChartTypeNames.Area; } }
 
		/// <summary>
		/// If the crossing value is auto Crossing value should be 
		/// automatically set to zero for some chart 
		/// types (Bar, column, area etc.)
		/// </summary>
		public override bool ZeroCrossing { 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 Painting and Selection methods
 
		/// <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 )
		{
			// Reset background gradient fill flag
			gradientFill = false;
 
			// Reset axis position
			axisPos	= PointF.Empty;
 
			// Call base SplineChart class
			base.ProcessChartType(selection, graph, common, area, seriesToDraw);
 
			// Fill background gradient for the 2D chart. Feature is not supported in 3D.
			if(!area.Area3DStyle.Enable3D)
			{
				FillLastSeriesGradient(graph);
			}
 
		}
 
		/// <summary>
		/// This method is overriden to fill the area and draw border line.
		/// </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)
        {
            // Start drawing from the second point
            if (pointIndex <= 0)
            {
                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;
                }
            }
            else
            {
                this.Series = series;
            }
 
            // Calculate points position
            PointF point1 = points[pointIndex - 1];
            PointF point2 = points[pointIndex];
            point1.X = (float)Math.Round(point1.X);
            point1.Y = (float)Math.Round(point1.Y);
            point2.X = (float)Math.Round(point2.X);
            point2.Y = (float)Math.Round(point2.Y);
            if (axisPos == PointF.Empty)
            {
                axisPos.X = (float)VAxis.GetPosition(this.VAxis.Crossing);
                axisPos.Y = (float)VAxis.GetPosition(this.VAxis.Crossing);
                axisPos = graph.GetAbsolutePoint(axisPos);
                axisPos.X = (float)Math.Round(axisPos.X);
                axisPos.Y = (float)Math.Round(axisPos.Y);
            }
 
            // Point properties
            Color pointColor = point.Color;
            Color pointBorderColor = point.BorderColor;
            int pointBorderWidth = point.BorderWidth;
            ChartDashStyle pointBorderDashStyle = point.BorderDashStyle;
 
            // Create area brush
            Brush areaBrush = null;
            if (point.BackHatchStyle != ChartHatchStyle.None)
            {
                areaBrush = graph.GetHatchBrush(point.BackHatchStyle, pointColor, 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(pointColor);
            }
 
            // Calculate data point area segment path
            GraphicsPath path = new GraphicsPath();
 
            path.AddLine(point1.X, axisPos.Y, point1.X, point1.Y);
            if (this.lineTension == 0)
            {
                path.AddLine(points[pointIndex - 1], points[pointIndex]);
            }
            else
            {
                path.AddCurve(points, pointIndex - 1, 1, this.lineTension);
            }
            path.AddLine(point2.X, point2.Y, point2.X, axisPos.Y);
 
            // Draw shadow
            if (series.ShadowColor != Color.Empty && series.ShadowOffset != 0)
            {
                if (pointColor != Color.Empty && pointColor != Color.Transparent)
                {
                    Region shadowRegion = new Region(path);
                    using (Brush shadowBrush = new SolidBrush((series.ShadowColor.A != 255) ? series.ShadowColor : Color.FromArgb(pointColor.A / 2, series.ShadowColor)))
                    {
                        // Set offset transformation
                        GraphicsState graphicsState = graph.Save();
                        Region clipRegion = null;
                        Region clipRegionOld = null;
                        if (!graph.IsClipEmpty && !graph.Clip.IsInfinite(graph.Graphics))
                        {
                            clipRegionOld = graph.Clip.Clone();
                            clipRegion = graph.Clip;
                            clipRegion.Translate(series.ShadowOffset, series.ShadowOffset);
                            graph.Clip = clipRegion;
                        }
                        graph.TranslateTransform(series.ShadowOffset, series.ShadowOffset);
 
                        // Draw top and bottom lines
                        if (graph.SmoothingMode != SmoothingMode.None)
                        {
                            using (Pen areaLinePen = new Pen(shadowBrush, 1))
                            {
                                if (this.lineTension == 0)
                                {
                                    graph.DrawLine(areaLinePen, points[pointIndex - 1], points[pointIndex]);
                                }
                                else
                                {
                                    graph.DrawCurve(areaLinePen, points, pointIndex - 1, 1, this.lineTension);
                                }
                            }
                        }
 
                        // Fill shadow region
                        graph.FillRegion(shadowBrush, shadowRegion);
 
                        // Restore transformation matrix
                        graph.Restore(graphicsState);
                        if (clipRegion != null && clipRegionOld != null)
                        {
                            graph.Clip = clipRegionOld;
                        }
                    }
                }
            }
 
            // Draw area
            if (!gradientFill)
            {
                // Turn off anti aliasing and fill area
                SmoothingMode oldMode = graph.SmoothingMode;
                graph.SmoothingMode = SmoothingMode.None;
 
                // Fill area
                graph.FillPath(areaBrush, path);
 
                // Draw right side of the area (not filled by FillPath)
 
                // Restore smoothing mode
                graph.SmoothingMode = oldMode;
 
                // Draw top and bottom lines
                if (graph.SmoothingMode != SmoothingMode.None)
                {
                    // Start Svg Selection mode
                    graph.StartHotRegion(point);
 
                    using (Pen areaLinePen = new Pen(areaBrush, 1))
                    {
                        if (this.lineTension == 0)
                        {
                            // Draw horizontal and vertical lines without anti-aliasing
                            if (!(points[pointIndex - 1].X == points[pointIndex].X ||
                                points[pointIndex - 1].Y == points[pointIndex].Y))
                            {
                                graph.DrawLine(areaLinePen, points[pointIndex - 1], points[pointIndex]);
                            }
                        }
                        else
                        {
                            graph.DrawCurve(areaLinePen, points, pointIndex - 1, 1, this.lineTension);
                        }
                    }
 
                    // End Svg Selection mode
                    graph.EndHotRegion();
                }
 
            }
 
            if (areaBrush != null)
                areaBrush.Dispose();
 
            // Add first line
            if (areaPath == null)
            {
                areaPath = new GraphicsPath();
                areaPath.AddLine(point1.X, axisPos.Y, point1.X, point1.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);
            }
 
            // Draw area border line
            if (pointBorderWidth > 0 && pointBorderColor != Color.Empty)
            {
                Pen pen = new Pen((pointBorderColor != Color.Empty) ? pointBorderColor : pointColor, pointBorderWidth);
                pen.DashStyle = graph.GetPenStyle(pointBorderDashStyle);
 
                // Set Rounded Cap
                pen.StartCap = LineCap.Round;
                pen.EndCap = LineCap.Round;
 
                if (this.lineTension == 0)
                {
                    graph.DrawLine(pen, points[pointIndex - 1], points[pointIndex]);
                }
                else
                {
                    graph.DrawCurve(pen, points, pointIndex - 1, 1, this.lineTension);
                }
            }
 
            //************************************************************
            // Hot Regions mode used for image maps, tool tips and 
            // hit test function
            //************************************************************
            if (common.ProcessModeRegions)
            {
                //**************************************************************
                //** Add area for the inside of the area
                //**************************************************************
 
                // Create grapics path object dor the curve
                GraphicsPath mapAreaPath = new GraphicsPath();
                mapAreaPath.AddLine(point1.X, axisPos.Y, point1.X, point1.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(point2.X, point2.Y, point2.X, axisPos.Y);
                mapAreaPath.AddLine(point2.X, axisPos.Y, point1.X, axisPos.Y);
 
                // Allocate array of floats
                PointF pointNew = PointF.Empty;
                float[] coord = new float[mapAreaPath.PointCount * 2];
                PointF[] areaPoints = mapAreaPath.PathPoints;
                for (int i = 0; i < mapAreaPath.PointCount; i++)
                {
                    pointNew = graph.GetRelativePoint(areaPoints[i]);
                    coord[2 * i] = pointNew.X;
                    coord[2 * i + 1] = pointNew.Y;
                }
 
                //************************************************************
                // Hot Regions mode used for image maps, tool tips and 
                // hit test function
                //************************************************************
                common.HotRegionsList.AddHotRegion(
                    mapAreaPath,
                    false,
                    coord,
                    point,
                    series.Name,
                    pointIndex);
 
                //**************************************************************
                //** Add area for the top line (with thickness)
                //**************************************************************
                if (pointBorderWidth > 1 && pointBorderDashStyle != ChartDashStyle.NotSet && pointBorderColor != Color.Empty)
                {
                    try
                    {
 
                        mapAreaPath.Dispose();
                        // Reset path
                        mapAreaPath = new GraphicsPath();
                        if (this.lineTension == 0)
                        {
                            mapAreaPath.AddLine(points[pointIndex - 1], points[pointIndex]);
                        }
                        else
                        {
                            mapAreaPath.AddCurve(points, pointIndex - 1, 1, this.lineTension);
                            mapAreaPath.Flatten();
                        }
 
                        // Widen the lines to the size of pen plus 2
                        mapAreaPath.Widen(new Pen(pointColor, pointBorderWidth + 2));
                    }
                    catch (OutOfMemoryException)
                    {
                        // GraphicsPath.Widen incorrectly throws OutOfMemoryException
                        // catching here and reacting by not widening
                    }
                    catch (ArgumentException)
                    {
                    }
 
                    // Allocate array of floats
                    pointNew = PointF.Empty;
                    coord = new float[mapAreaPath.PointCount * 2];
                    PointF[] mapAreaPathPoints = mapAreaPath.PathPoints;
                    for (int i = 0; i < mapAreaPathPoints.Length; i++)
                    {
                        pointNew = graph.GetRelativePoint(mapAreaPathPoints[i]);
                        coord[2 * i] = pointNew.X;
                        coord[2 * i + 1] = pointNew.Y;
                    }
 
                    //************************************************************
                    // Hot Regions mode used for image maps, tool tips and 
                    // hit test function
                    //************************************************************
                    common.HotRegionsList.AddHotRegion(
                        mapAreaPath,
                        false,
                        coord,
                        point,
                        series.Name,
                        pointIndex);
 
                }
                mapAreaPath.Dispose();
            }
        }
 
		/// <summary>
		/// Fills last series area with gradient.
		/// </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, axisPos.Y);
			}
			
			// Fill whole area with gradient
			if(gradientFill && areaPath != null)
			{
				// Set clip region
				graph.SetClip( Area.PlotAreaPosition.ToRectangleF() );
 
				// Create brush
                using (Brush areaGradientBrush = graph.GetGradientBrush(areaPath.GetBounds(), this.Series.Color, this.Series.BackSecondaryColor, this.Series.BackGradientStyle))
                {
                    // Fill area with gradient
                    graph.FillPath(areaGradientBrush, areaPath);
                    gradientFill = false;
                }
 
				// Reset clip region
				graph.ResetClip();
			}
			if(areaPath != null)
			{
				areaPath.Dispose();
				areaPath = null;
			}
		}
 
		/// <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 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;
			}
 
 
			//****************************************************************
			//** Check if reversed drawing order required
			//****************************************************************
			reversed = false;
			int	indexOffset = 1;
			while((pointIndex + indexOffset) < points.Count)
			{
				DataPoint3D p = (DataPoint3D)points[pointIndex + indexOffset];
				if(p.dataPoint.series.Name == firstPoint.dataPoint.series.Name)
				{
					if(p.index == firstPoint.index)
					{
						reversed = true;
					}
					break;
				}
 
				++indexOffset;
			}
 
			//****************************************************************
			//** Check line tension and draw spline area if non zero.
			//****************************************************************
			// Check tension
			if(tension != 0f)
			{
				// Get spline flatten path
				GraphicsPath splineSurfacePath = graph.GetSplineFlattenPath(
					area, positionZ, 
					firstPoint, secondPoint, points, tension, true, false, 0);
 
				// Reversed array of points if needed
				PointF[]	splinePoints = null;
				reversed = (pointIndex < pointArrayIndex);
				if(reversed)
				{
					splineSurfacePath.Reverse();
				}
				splinePoints = splineSurfacePath.PathPoints;
 
				// Loop through all segment lines the spline consist off
				DataPoint3D	dp1 = new DataPoint3D();
				DataPoint3D	dp2 = new DataPoint3D();
				//LineSegmentType	surfaceSegmentType = (!reversed) ? LineSegmentType.First : LineSegmentType.Last;
				LineSegmentType	surfaceSegmentType = LineSegmentType.Middle;
				for(int pIndex = 1; pIndex < splinePoints.Length; pIndex++)
				{
					// Calculate surface coordinates
					if(!reversed)
					{
						dp1.dataPoint = firstPoint.dataPoint;
						dp1.index = firstPoint.index;
						dp1.xPosition = splinePoints[pIndex - 1].X;
						dp1.yPosition = splinePoints[pIndex - 1].Y;
 
						dp2.dataPoint = secondPoint.dataPoint;
						dp2.index = secondPoint.index;
						dp2.xPosition = splinePoints[pIndex].X;
						dp2.yPosition = splinePoints[pIndex].Y;
					}
					else
					{
						dp2.dataPoint = firstPoint.dataPoint;
						dp2.index = firstPoint.index;
						dp2.xPosition = splinePoints[pIndex - 1].X;
						dp2.yPosition = splinePoints[pIndex - 1].Y;
 
						dp1.dataPoint = secondPoint.dataPoint;
						dp1.index = secondPoint.index;
						dp1.xPosition = splinePoints[pIndex].X;
						dp1.yPosition = splinePoints[pIndex].Y;
					}
 
					// Get sefment type
					surfaceSegmentType = LineSegmentType.Middle;
					if(pIndex == 1)
					{
						if(!reversed)
							surfaceSegmentType = LineSegmentType.First;
						else
							surfaceSegmentType = LineSegmentType.Last;
					}
					else if(pIndex == splinePoints.Length - 1)
					{
						if(!reversed)
							surfaceSegmentType = LineSegmentType.Last;
						else
							surfaceSegmentType = LineSegmentType.First;
					}
 
					// Draw each segment of the spline area
					area.IterationCounter = 0;
					GraphicsPath segmentResultPath =  Draw3DSurface( dp1, dp2, reversed,
						area, graph, matrix, lightStyle, prevDataPointEx,
						positionZ, depth, points, pointIndex, pointLoopIndex,
						0f, operationType, surfaceSegmentType,
						topDarkening, bottomDarkening,
						new PointF(float.NaN, float.NaN),
						new PointF(float.NaN, float.NaN),
						clippedSegment,
						true, true);
 
					// Add selection path
					if(resultPath != null && segmentResultPath != null && segmentResultPath.PointCount > 0)
					{
						resultPath.AddPath(segmentResultPath, true);
					}
				}
 
				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;
 
			//**********************************************************************
			//** Check surface coordinates
			//**********************************************************************
			if(Math.Round(firstPoint.xPosition, 3) == Math.Round(secondPoint.xPosition, 3) &&
				Math.Round(firstPoint.yPosition, 3) == Math.Round(secondPoint.yPosition, 3))
			{
				return resultPath;
			}
 
			//****************************************************************
			//** 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)Math.Round(VAxis.GetPosition(this.VAxis.Crossing), 3);
 
 
			//****************************************************************
			//** 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( ((decimal)firstPoint.yPosition) >= ((decimal)axisPosition) && 
				((decimal)secondPoint.yPosition) >= ((decimal)axisPosition) )
			{
				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;
				}
			}
 
			// Get visibility of the top surface (different from bounding rectangle)
			GetTopSurfaceVisibility(
				area, 
				firstPoint, 
				secondPoint, 
				upSideDown, 
				positionZ, 
				depth, 
				matrix, 
				ref visibleSurfaces);
			
			//****************************************************************
			//** Calculate position of top/bootom points.
			//****************************************************************
			PointF	thirdPoint, fourthPoint; 
			GetBottomPointsPosition(
				Common, 
				area, 
				axisPosition, 
				ref firstPoint, 
				ref secondPoint, 
				thirdPointPosition,
				fourthPointPosition,
				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;
			}
 
			if(float.IsNaN(thirdPoint.X) ||
				float.IsNaN(thirdPoint.Y) ||
				float.IsNaN(fourthPoint.X) ||
				float.IsNaN(fourthPoint.Y) )
			{
				return resultPath;
			}
 
			//****************************************************************
			//** Clip area first and second data points inside 
			//** the plotting area.
			//****************************************************************
			if(clipOnTop && 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(clipOnBottom && 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;
			}
 
 
			//****************************************************************
			//** Check if area points are on the different sides of the axis.
			//** In this case split area point into two points where the line
			//** intersects the axis line
			//****************************************************************
			if( (Math.Round((decimal)firstPoint.yPosition, 3) > (decimal)axisPosition + 0.001M && Math.Round((decimal)secondPoint.yPosition, 3) < (decimal)axisPosition - 0.001M) ||
				(Math.Round((decimal)firstPoint.yPosition, 3) < (decimal)axisPosition - 0.001M && Math.Round((decimal)secondPoint.yPosition, 3) > (decimal)axisPosition + 0.001M) )
			{
				// Find intersection point
				DataPoint3D	intersectionPoint = GetAxisIntersection(firstPoint, secondPoint, axisPosition);
 
				for(int segmentIndex = 0; segmentIndex <= 1; segmentIndex++)
				{
					GraphicsPath segmentPath = null;
					if(segmentIndex == 0 && !reversed ||
						segmentIndex == 1 && reversed)
					{
						// 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,
							clipOnTop, clipOnBottom);
					}
 
					if(segmentIndex == 1 && !reversed ||
						segmentIndex == 0 && reversed)
					{
						// 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,
							clipOnTop, clipOnBottom);
					}
 
					// Add segment path
					if(resultPath != null && segmentPath != null && segmentPath.PointCount > 0)
					{
						resultPath.AddPath(segmentPath, true);
					}
				}
 
				return resultPath;
			}
 
			//**********************************************************************
			//** Check surface coordinates
			//**********************************************************************
			if(Math.Round(firstPoint.xPosition, 3) == Math.Round(secondPoint.xPosition, 3) &&
				Math.Round(firstPoint.yPosition, 3) == Math.Round(secondPoint.yPosition, 3))
			{
				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 = new SurfaceNames[] {SurfaceNames.Back, SurfaceNames.Bottom, SurfaceNames.Top, SurfaceNames.Left, SurfaceNames.Right, SurfaceNames.Front};
				LineSegmentType lineSegmentType = LineSegmentType.Middle;
				foreach(SurfaceNames	currentSurface in surfacesOrder)
				{
					// Check if surface should be drawn
                    if (ChartGraphics.ShouldDrawLineChartSurface(area, area.ReverseSeriesOrder, currentSurface, visibleSurfaces, color, 
						points, firstPoint, secondPoint, this.multiSeries, ref lineSegmentType) != elemLayer)
					{
						continue;
					}
 
					// To solvce segments overlapping issues with semi-transparent colors ->
					// Draw invisible surfaces in the first loop, visible in second
					if(this.allPointsLoopsNumber == 2 && (operationType & DrawingOperationTypes.DrawElement) == DrawingOperationTypes.DrawElement)
					{
						if(pointLoopIndex == 0)
						{
							if(currentSurface == SurfaceNames.Front ||
								elemLayer == 2 && (currentSurface == SurfaceNames.Left || currentSurface == SurfaceNames.Right))
							{
								continue;
							}
						}
						if(pointLoopIndex == 1)
						{
							if(currentSurface == SurfaceNames.Back || 
								currentSurface != SurfaceNames.Front)
							{
								if(elemLayer == 1)
								{
									continue;
								}
								else if(currentSurface != SurfaceNames.Left && currentSurface != SurfaceNames.Right)
								{
									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 );
						}
					}
 
					// Check if marker lines should be drawn
					bool forceThinLines = this.showPointLines;
					if(surfaceSegmentType == LineSegmentType.Middle)
					{
						forceThinLines = false;
					}
 
					// 3D clipped segment is drawn - draw only top & bottom
					if(clippedSegment && 
						currentSurface != SurfaceNames.Top && 
						currentSurface != SurfaceNames.Bottom)
					{
						continue;
					}
 
					// Draw surfaces
					GraphicsPath surfacePath = null;
					switch(currentSurface)
					{
						case(SurfaceNames.Top):
						{
							// Darken colors
							Color topColor = (topDarkening == 0f) ? surfaceColor : ChartGraphics.GetGradientColor(surfaceColor, Color.Black, topDarkening); 
							Color topBorderColor = (topDarkening == 0f) ? surfaceBorderColor : ChartGraphics.GetGradientColor(surfaceBorderColor, Color.Black, topDarkening); 
 
							surfacePath = graph.Draw3DSurface( area, matrix, lightStyle, currentSurface, positionZ,  depth, 
								topColor, topBorderColor, pointAttr.dataPoint.BorderWidth, dashStyle, 
								firstPoint, secondPoint,  points, pointIndex,
								0f, operationType, surfaceSegmentType,
                                forceThinLines, false, area.ReverseSeriesOrder, this.multiSeries, 0, true);
 
							break;
						}
						case(SurfaceNames.Bottom):
						{
							// Calculate coordinates
							DataPoint3D	dp1 = new DataPoint3D();
							dp1.index = firstPoint.index;
							dp1.dataPoint = firstPoint.dataPoint;
							dp1.xPosition = firstPoint.xPosition;
							dp1.yPosition = thirdPoint.Y;
							DataPoint3D	dp2 = new DataPoint3D();
							dp2.index = secondPoint.index;
							dp2.dataPoint = secondPoint.dataPoint;
							dp2.xPosition = secondPoint.xPosition;
							dp2.yPosition = fourthPoint.Y;
 
							// Darken colors
							Color bottomColor = (bottomDarkening == 0f) ? surfaceColor : ChartGraphics.GetGradientColor(surfaceColor, Color.Black, topDarkening); 
							Color bottomBorderColor = (bottomDarkening == 0f) ? surfaceBorderColor : ChartGraphics.GetGradientColor(surfaceBorderColor, Color.Black, topDarkening); 
 
							// Draw surface
							surfacePath = graph.Draw3DSurface( area, matrix, lightStyle, currentSurface, positionZ, depth, 
								bottomColor, bottomBorderColor, pointAttr.dataPoint.BorderWidth, dashStyle, 
								dp1, dp2, points, pointIndex,
								0f, operationType, surfaceSegmentType,
                                forceThinLines, false, area.ReverseSeriesOrder, this.multiSeries, 0, 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.index = leftMostPoint.index;
								dp1.dataPoint = leftMostPoint.dataPoint;
								dp1.xPosition = leftMostPoint.xPosition;
								dp1.yPosition = (firstPoint.xPosition <= secondPoint.xPosition) ? thirdPoint.Y : fourthPoint.Y;
								DataPoint3D	dp2 = new DataPoint3D();
								dp2.index = leftMostPoint.index;
								dp2.dataPoint = leftMostPoint.dataPoint;
								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, true, 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.index = rightMostPoint.index;
								dp1.dataPoint = rightMostPoint.dataPoint;
								dp1.xPosition = rightMostPoint.xPosition;
								dp1.yPosition = (secondPoint.xPosition >= firstPoint.xPosition) ? fourthPoint.Y : thirdPoint.Y;
								DataPoint3D	dp2 = new DataPoint3D();
								dp2.index = rightMostPoint.index;
								dp2.dataPoint = rightMostPoint.dataPoint;
								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, true, true, area.ReverseSeriesOrder, this.multiSeries, 0, true);
									
							}
 
							break;
						}
						case(SurfaceNames.Back):
						{
							// Calculate coordinates
							DataPoint3D	dp1 = new DataPoint3D();
							dp1.index = firstPoint.index;
							dp1.dataPoint = firstPoint.dataPoint;
							dp1.xPosition = firstPoint.xPosition;
							dp1.yPosition = thirdPoint.Y;
							DataPoint3D	dp2 = new DataPoint3D();
							dp2.index = secondPoint.index;
							dp2.dataPoint = secondPoint.dataPoint;
							dp2.xPosition = secondPoint.xPosition;
							dp2.yPosition = fourthPoint.Y;
 
							// Border line is required on the data point boundary
							SurfaceNames	thinBorderSides = 0;
							if(forceThinLines)
							{
								if(surfaceSegmentType == LineSegmentType.Single)
									thinBorderSides = SurfaceNames.Left | SurfaceNames.Right;
								else if(surfaceSegmentType == LineSegmentType.First)
									thinBorderSides = SurfaceNames.Left;
								else if(surfaceSegmentType == LineSegmentType.Last)
									thinBorderSides = SurfaceNames.Right;
							}
 
 
							// Draw surface
							surfacePath = graph.Draw3DPolygon( area, matrix, currentSurface, positionZ, 
								surfaceColor, surfaceBorderColor, pointAttr.dataPoint.BorderWidth, 
								firstPoint, secondPoint, dp2, dp1, operationType, lineSegmentType, 
								thinBorderSides);
							break;
						}
						case(SurfaceNames.Front):
						{
							// Calculate coordinates
							DataPoint3D	dp1 = new DataPoint3D();
							dp1.index = firstPoint.index;
							dp1.dataPoint = firstPoint.dataPoint;
							dp1.xPosition = firstPoint.xPosition;
							dp1.yPosition = thirdPoint.Y;
 
							DataPoint3D	dp2 = new DataPoint3D();
							dp2.index = secondPoint.index;
							dp2.dataPoint = secondPoint.dataPoint;
							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;
								}
							}
 
							// Border line is required on the data point boundary
							SurfaceNames	thinBorderSides = 0;
							if(forceThinLines)
							{
								if(surfaceSegmentType == LineSegmentType.Single)
									thinBorderSides = SurfaceNames.Left | SurfaceNames.Right;
								else if(surfaceSegmentType == LineSegmentType.First)
									thinBorderSides = SurfaceNames.Left;
								else if(surfaceSegmentType == LineSegmentType.Last)
									thinBorderSides = SurfaceNames.Right;
							}
 
							// Draw surface
							surfacePath = graph.Draw3DPolygon( area, matrix, currentSurface, positionZ + depth, 
								surfaceColor, surfaceBorderColor, pointAttr.dataPoint.BorderWidth, 
								firstPoint, secondPoint, dp2, dp1, operationType, lineSegmentType, 
								thinBorderSides);
 
							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.SetMarkers();
						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)
		{
			// 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;
			}
		}
 
		/// <summary>
		/// Gets intersection point coordinates between point line and axis line.
		/// </summary>
		/// <param name="firstPoint">First data point.</param>
		/// <param name="secondPoint">Second data point.</param>
		/// <param name="axisPosition">Axis line position.</param>
		/// <returns>Intersection point coordinates.</returns>
		internal DataPoint3D GetAxisIntersection(DataPoint3D firstPoint, DataPoint3D secondPoint, float axisPosition)
		{
			DataPoint3D	intersectionPoint = new DataPoint3D();
			intersectionPoint.yPosition = axisPosition;
			intersectionPoint.xPosition = (axisPosition - firstPoint.yPosition) *
				(secondPoint.xPosition - firstPoint.xPosition) / 
				(secondPoint.yPosition - firstPoint.yPosition) + 
				firstPoint.xPosition ;
			return intersectionPoint;
		}
 
		/// <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="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="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, 
			PointF thirdPointPosition,
			PointF fourthPointPosition,
			out PointF thirdPoint, 
			out PointF fourthPoint)
		{
			thirdPoint = new PointF((float)firstPoint.xPosition, axisPosition);
			fourthPoint = new PointF((float)secondPoint.xPosition, axisPosition);
		}
 
		/// <summary>
		/// Returns how many loops through all data points is required (1 or 2)
		/// </summary>
		/// <param name="selection">Selection indicator.</param>
		/// <param name="pointsArray">Points array list.</param>
		/// <returns>Number of loops (1 or 2).</returns>
		override protected int GetPointLoopNumber(bool selection, ArrayList pointsArray)
		{
			// Always one loop for selection
			if(selection)
			{
				return 1;
			}
 
			// Second loop will be required for semi-transparent colors
			int loopNumber = 1;
			foreach(object obj in pointsArray)
			{
				// Get point & series
				DataPoint3D	pointEx = (DataPoint3D) obj;
 
				// Check properties
				if(pointEx.dataPoint.Color.A != 255)
				{
					loopNumber = 2;
				}
			}
 
			return loopNumber;
		}
 
		#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)
            {   // Managed resources
                if (this.areaPath != null)
                {
                    this.areaPath.Dispose();
                    this.areaPath = null;
                }
            }
            base.Dispose(disposing);
        }
        #endregion
 
    }
}