File: Common\ChartTypes\StepLineChart.cs
Project: ndp\fx\src\DataVisualization\System.Web.DataVisualization.csproj (System.Web.DataVisualization)
//-------------------------------------------------------------
// <copyright company=’Microsoft Corporation’>
//   Copyright © Microsoft Corporation. All Rights Reserved.
// </copyright>
//-------------------------------------------------------------
// @owner=alexgor, deliant
//=================================================================
//  File:		StepLineChart.cs
//
//  Namespace:	DataVisualization.Charting.ChartTypes
//
//	Classes:	StepLineChart
//
//  Purpose:	Step Line chart uses two line segments (horizontal 
//              and vertical) to connect data points. Markers and 
//              labels drawing code is inherited from the Line chart.
//
//	Reviewed:	AG - Aug 6, 2002
//              AG - Microsoft 7, 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.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>
    /// StepLine class extends its base class LineChart by changing how two 
    /// neighbouring data points are connected with a line. Step Line chart 
    /// uses two line segments (horizontal and vertical) to connect data 
    /// points. Markers and labels drawing code is inherited from the Line chart.
	/// </summary>
	internal class StepLineChart : LineChart
	{
		#region Constructor
 
		/// <summary>
		/// StepLineChart class constructor.
		/// </summary>
		public StepLineChart()
		{
		}
 
		#endregion
 
		#region IChartType interface implementation
 
		/// <summary>
		/// Chart type name
		/// </summary>
		public override string Name			{ get{ return ChartTypeNames.StepLine;}}
 
		/// <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 Line drawing and selecting methods
 
		/// <summary>
		/// Draw chart line using horisontal and vertical 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 points 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;
			}
 
			// Darw two lines
			PointF	point1 = points[pointIndex - 1];
			PointF	point2 = new PointF(points[pointIndex].X, points[pointIndex - 1].Y);
			PointF	point3 = points[pointIndex];
			graph.DrawLineRel( point.Color, point.BorderWidth, point.BorderDashStyle, graph.GetRelativePoint(point1), graph.GetRelativePoint(point2), series.ShadowColor, series.ShadowOffset );
			graph.DrawLineRel( point.Color, point.BorderWidth, point.BorderDashStyle, graph.GetRelativePoint(point2), graph.GetRelativePoint(point3), series.ShadowColor, series.ShadowOffset );
 
			if( common.ProcessModeRegions )
			{
				// Create grapics path object for the line
				// Split line into 2 segments.
				GraphicsPath	path = new GraphicsPath();
				try
				{
					path.AddLine(point2, point3);
                    if (!point2.Equals(point3))
                    {
                        path.Widen(new Pen(point.Color, point.BorderWidth + 2));
                    }
				}
                catch (OutOfMemoryException)
                {
                    // GraphicsPath.Widen incorrectly throws OutOfMemoryException
                    // catching here and reacting by not widening
                }
                catch (ArgumentException)
                {
                }
 
				// Allocate array of floats
				PointF	pointNew = PointF.Empty;
				float[]	coord = new float[path.PointCount * 2];
				PointF[] pathPoints = path.PathPoints;
				for( int i = 0; i < path.PointCount; i++ )
				{
					pointNew = graph.GetRelativePoint( pathPoints[i] );
					coord[2*i] = pointNew.X;
					coord[2*i + 1] = pointNew.Y;
				}
 
				common.HotRegionsList.AddHotRegion( 
					path, 
					false, 
					coord, 
					point, 
					series.Name, 
					pointIndex );
                path.Dispose();
				// Create grapics path object for the line
				path = new GraphicsPath();
				try
				{
					path.AddLine(point1, point2);
					path.Widen(new Pen(point.Color, point.BorderWidth + 2));
				}
                catch (OutOfMemoryException)
                {
                    // GraphicsPath.Widen incorrectly throws OutOfMemoryException
                    // catching here and reacting by not widening
                }
                catch (ArgumentException)
                {
                }
 
				// Allocate array of floats
				coord = new float[path.PointCount * 2];
				pathPoints = path.PathPoints;
				for( int i = 0; i < path.PointCount; i++ )
				{
					pointNew = graph.GetRelativePoint( pathPoints[i] );
					coord[2*i] = pointNew.X;
					coord[2*i + 1] = pointNew.Y;
				}
 
				common.HotRegionsList.AddHotRegion( 
					path, 
					false, 
					coord, 
					series.Points[pointIndex - 1],
					series.Name, 
					pointIndex - 1);
                path.Dispose();
			}
		}
		
		#endregion
 
		#region 3D Line drawing and selection
 
		/// <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;
 
			// 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);
 
			// 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;
			}
 
			//************************************************************
			//** Create "middle" point
			//************************************************************
			DataPoint3D	middlePoint = new DataPoint3D();
			middlePoint.xPosition = secondPoint.xPosition;
			middlePoint.yPosition = firstPoint.yPosition;
 
			// Check if reversed drawing order required
			bool originalDrawOrder = true;
			if((pointIndex + 1) < points.Count)
			{
				DataPoint3D p = (DataPoint3D)points[pointIndex + 1];
				if(p.index == firstPoint.index)
				{
					originalDrawOrder = false;
				}
			}
 
			// Check in which order vertical & horizontal lines segments should be drawn
			if(centerPointIndex != int.MaxValue)
			{
				if(pointIndex >= centerPointIndex)
				{
					originalDrawOrder = false;
				}
			}
 
			// Draw two segments of the step line
			GraphicsPath resultPathLine1, resultPathLine2;
			if(originalDrawOrder)
			{
				// Draw first line
				middlePoint.dataPoint = secondPoint.dataPoint;
				resultPathLine1 = graph.Draw3DSurface( 
					area, matrix, lightStyle, SurfaceNames.Top, positionZ, depth, color, 
					pointAttr.dataPoint.BorderColor, pointAttr.dataPoint.BorderWidth, dashStyle, 
					firstPoint, middlePoint, 
					points, pointIndex, 0f, operationType, LineSegmentType.First, 
					(this.showPointLines) ? true : false, false,
                    area.ReverseSeriesOrder,
					this.multiSeries, 0, true);
 
				// No second draw of the prev. front line required
				graph.frontLinePen = null;
 
				// Draw second line
				middlePoint.dataPoint = firstPoint.dataPoint;
				resultPathLine2 = graph.Draw3DSurface( 
					area, matrix, lightStyle, SurfaceNames.Top, positionZ, depth, color, 
					pointAttr.dataPoint.BorderColor, pointAttr.dataPoint.BorderWidth, dashStyle, 
					middlePoint, secondPoint, 
					points, pointIndex, 0f, operationType, LineSegmentType.Last, 
					(this.showPointLines) ? true : false, false,
                    area.ReverseSeriesOrder,
					this.multiSeries, 0, true);
 
				// No second draw of the prev. front line required
				graph.frontLinePen = null;
			}
			else
			{
				// Draw second line
				middlePoint.dataPoint = firstPoint.dataPoint;
				resultPathLine2 = graph.Draw3DSurface( 
					area, matrix, lightStyle, SurfaceNames.Top, positionZ, depth, color, 
					pointAttr.dataPoint.BorderColor, pointAttr.dataPoint.BorderWidth, dashStyle, 
					middlePoint, secondPoint, 
					points, pointIndex, 0f, operationType, LineSegmentType.Last, 
					(this.showPointLines) ? true : false, false,
                    area.ReverseSeriesOrder,
					this.multiSeries, 0, true);
				
				// No second draw of the prev. front line required
				graph.frontLinePen = null;
				
				// Draw first line
				middlePoint.dataPoint = secondPoint.dataPoint;
				resultPathLine1 = graph.Draw3DSurface( 
					area, matrix, lightStyle, SurfaceNames.Top, positionZ, depth, color, 
					pointAttr.dataPoint.BorderColor, pointAttr.dataPoint.BorderWidth, dashStyle, 
					firstPoint, middlePoint, 
					points, pointIndex, 0f, operationType, LineSegmentType.First, 
					(this.showPointLines) ? true : false, false,
                    area.ReverseSeriesOrder,
					this.multiSeries, 0, true);
 
				// No second draw of the prev. front line required
				graph.frontLinePen = null;
			}
 
			if(resultPath != null)
			{
				if( area.Common.ProcessModeRegions)
				{
					if(resultPathLine1 != null && resultPathLine1.PointCount > 0)
					{
						area.Common.HotRegionsList.AddHotRegion( 
							resultPathLine1, 
							false, 
							graph, 
							prevDataPointEx.dataPoint, 
							prevDataPointEx.dataPoint.series.Name, 
							prevDataPointEx.index - 1 );
					}
				}
 
				if(resultPathLine2 != null && resultPathLine2.PointCount > 0)
				{
					resultPath.AddPath(resultPathLine2, true);
				}
			}
			return resultPath;
		}
 
		#endregion
	}
}