Click here to Skip to main content
15,896,606 members
Articles / Multimedia / GDI+

A flexible charting library for .NET

Rate me:
Please Sign up or sign in to vote.
4.70/5 (1,112 votes)
6 Jun 200730 min read 9.1M   180.2K   2.1K  
Looking for a way to draw 2D line graphs with C#? Here's yet another charting class library with a high degree of configurability, that is also easy to use.
//============================================================================
//ZedGraph Class Library - A Flexible Line Graph/Bar Graph Library in C#
//Copyright (C) 2004  John Champion
//
//This library is free software; you can redistribute it and/or
//modify it under the terms of the GNU Lesser General Public
//License as published by the Free Software Foundation; either
//version 2.1 of the License, or (at your option) any later version.
//
//This library is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//Lesser General Public License for more details.
//
//You should have received a copy of the GNU Lesser General Public
//License along with this library; if not, write to the Free Software
//Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//=============================================================================

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Runtime.Serialization;
using System.Security.Permissions;

namespace ZedGraph
{
	/// <summary>
	/// A class representing all the characteristics of the Line
	/// segments that make up a curve on the graph.
	/// </summary>
	/// 
	/// <author> John Champion </author>
	/// <version> $Revision: 3.22 $ $Date: 2006/03/09 06:13:13 $ </version>
	[Serializable]
	public class Line : ICloneable, ISerializable
	{
	#region Fields
		/// <summary>
		/// Private field that stores the pen width for this
		/// <see cref="Line"/>.  Use the public
		/// property <see cref="Width"/> to access this value.
		/// </summary>
		private float width;
		/// <summary>
		/// Private field that stores the <see cref="DashStyle"/> for this
		/// <see cref="Line"/>.  Use the public
		/// property <see cref="Style"/> to access this value.
		/// </summary>
		private DashStyle style;
		/// <summary>
		/// Private field that stores the visibility of this
		/// <see cref="Line"/>.  Use the public
		/// property <see cref="IsVisible"/> to access this value.
		/// </summary>
		private bool isVisible;
		/// <summary>
		/// Private field that stores the smoothing flag for this
		/// <see cref="Line"/>.  Use the public
		/// property <see cref="IsSmooth"/> to access this value.
		/// </summary>
		private bool isSmooth;
		/// <summary>
		/// Private field that stores the smoothing tension
		/// for this <see cref="Line"/>.  Use the public property
		/// <see cref="SmoothTension"/> to access this value.
		/// </summary>
		/// <value>A floating point value indicating the level of smoothing.
		/// 0.0F for no smoothing, 1.0F for lots of smoothing, >1.0 for odd
		/// smoothing.</value>
		/// <seealso cref="IsSmooth"/>
		/// <seealso cref="Default.IsSmooth"/>
		/// <seealso cref="Default.SmoothTension"/>
		private float smoothTension;
		/// <summary>
		/// Private field that stores the color of this
		/// <see cref="Line"/>.  Use the public
		/// property <see cref="Color"/> to access this value.  If this value is
		/// false, the line will not be shown (but the <see cref="Symbol"/> may
		/// still be shown).
		/// </summary>
		private Color color;
		/// <summary>
		/// Private field that stores the <see cref="ZedGraph.StepType"/> for this
		/// <see cref="CurveItem"/>.  Use the public
		/// property <see cref="StepType"/> to access this value.
		/// </summary>
		private StepType	stepType;
		/// <summary>
		/// Private field that stores the <see cref="ZedGraph.Fill"/> data for this
		/// <see cref="Line"/>.  Use the public property <see cref="Fill"/> to
		/// access this value.
		/// </summary>
		private Fill		fill;

	#endregion
	
	#region Defaults
		/// <summary>
		/// A simple struct that defines the
		/// default property values for the <see cref="Line"/> class.
		/// </summary>
		public struct Default
		{
			// Default Line properties
			/// <summary>
			/// The default color for curves (line segments connecting the points).
			/// This is the default value for the <see cref="Line.Color"/> property.
			/// </summary>
			public static Color Color = Color.Red;
			/// <summary>
			/// The default color for filling in the area under the curve
			/// (<see cref="ZedGraph.Fill.Color"/> property).
			/// </summary>
			public static Color FillColor = Color.Red;
			/// <summary>
			/// The default custom brush for filling in the area under the curve
			/// (<see cref="ZedGraph.Fill.Brush"/> property).
			/// </summary>
			public static Brush FillBrush = null;
			/// <summary>
			/// The default fill mode for the curve (<see cref="ZedGraph.Fill.Type"/> property).
			/// </summary>
			public static FillType FillType = FillType.None;
			/// <summary>
			/// The default mode for displaying line segments (<see cref="Line.IsVisible"/>
			/// property).  True to show the line segments, false to hide them.
			/// </summary>
			public static bool IsVisible = true;
			/// <summary>
			/// The default width for line segments (<see cref="Line.Width"/> property).
            /// Units are points (1/72 inch).
            /// </summary>
			public static float Width = 1;
			/// <summary>
			/// The default value for the <see cref="Line.IsSmooth"/>
			/// property.
			/// </summary>
			public static bool IsSmooth = false;
			/// <summary>
			/// The default value for the <see cref="Line.SmoothTension"/> property.
			/// </summary>
			public static float SmoothTension = 0.5F;
			/// <summary>
			/// The default drawing style for line segments (<see cref="Line.Style"/> property).
			/// This is defined with the <see cref="DashStyle"/> enumeration.
			/// </summary>
			public static DashStyle Style = DashStyle.Solid;
			/// <summary>
			/// Default value for the curve type property
			/// (<see cref="Line.StepType"/>).  This determines if the curve
			/// will be drawn by directly connecting the points from the
			/// <see cref="CurveItem.Points"/> data collection,
			/// or if the curve will be a "stair-step" in which the points are
			/// connected by a series of horizontal and vertical lines that
			/// represent discrete, staticant values.  Note that the values can
			/// be forward oriented <code>ForwardStep</code> (<see cref="StepType"/>) or
			/// rearward oriented <code>RearwardStep</code>.
			/// That is, the points are defined at the beginning or end
			/// of the staticant value for which they apply, respectively.
			/// </summary>
			/// <value><see cref="StepType"/> enum value</value>
			public static StepType StepType = StepType.NonStep;
		}
	#endregion

	#region Properties
		/// <summary>
		/// The color of the <see cref="Line"/>
		/// </summary>
		/// <seealso cref="Default.Color"/>
		public Color Color
		{
			get { return color; }
			set { color = value; }
		}
		/// <summary>
		/// The style of the <see cref="Line"/>, defined as a <see cref="DashStyle"/> enum.
		/// This allows the line to be solid, dashed, or dotted.
		/// </summary>
		/// <seealso cref="Default.Style"/>
		public DashStyle Style
		{
			get { return style; }
			set { style = value;}
		}
		/// <summary>
        /// The pen width used to draw the <see cref="Line"/>, in points (1/72 inch)
        /// </summary>
		/// <seealso cref="Default.Width"/>
		public float Width
		{
			get { return width; }
			set { width = value; }
		}
		/// <summary>
		/// Gets or sets a property that shows or hides the <see cref="Line"/>.
		/// </summary>
		/// <value>true to show the line, false to hide it</value>
		/// <seealso cref="Default.IsVisible"/>
		public bool IsVisible
		{
			get { return isVisible; }
			set { isVisible = value; }
		}
		/// <summary>
		/// Gets or sets a property that determines if this <see cref="Line"/>
		/// will be drawn smooth.  The "smoothness" is controlled by
		/// the <see cref="SmoothTension"/> property.
		/// </summary>
		/// <value>true to smooth the line, false to just connect the dots
		/// with linear segments</value>
		/// <seealso cref="SmoothTension"/>
		/// <seealso cref="Default.IsSmooth"/>
		/// <seealso cref="Default.SmoothTension"/>
		public bool IsSmooth
		{
			get { return isSmooth; }
			set { isSmooth = value; }
		}
		/// <summary>
		/// Gets or sets a property that determines the smoothing tension
		/// for this <see cref="Line"/>.  This property is only used if
		/// <see cref="IsSmooth"/> is true.  A tension value 0.0 will just
		/// draw ordinary line segments like an unsmoothed line.  A tension
		/// value of 1.0 will be smooth.  Values greater than 1.0 will generally
		/// give odd results.
		/// </summary>
		/// <value>A floating point value indicating the level of smoothing.
		/// 0.0F for no smoothing, 1.0F for lots of smoothing, >1.0 for odd
		/// smoothing.</value>
		/// <seealso cref="IsSmooth"/>
		/// <seealso cref="Default.IsSmooth"/>
		/// <seealso cref="Default.SmoothTension"/>
		public float SmoothTension
		{
			get { return smoothTension; }
			set { smoothTension = value; }
		}
		/// <summary>
		/// Determines if the <see cref="CurveItem"/> will be drawn by directly connecting the
		/// points from the <see cref="CurveItem.Points"/> data collection,
		/// or if the curve will be a "stair-step" in which the points are
		/// connected by a series of horizontal and vertical lines that
		/// represent discrete, constant values.  Note that the values can
		/// be forward oriented <c>ForwardStep</c> (<see cref="ZedGraph.StepType"/>) or
		/// rearward oriented <c>RearwardStep</c>.
		/// That is, the points are defined at the beginning or end
		/// of the constant value for which they apply, respectively.
		/// The <see cref="StepType"/> property is ignored for lines
		/// that have <see cref="IsSmooth"/> set to true.
		/// </summary>
		/// <value><see cref="ZedGraph.StepType"/> enum value</value>
		/// <seealso cref="Default.StepType"/>
		public StepType StepType
		{
			get { return stepType; }
			set { stepType = value;}
		}

		/// <summary>
		/// Gets or sets the <see cref="ZedGraph.Fill"/> data for this
		/// <see cref="Line"/>.
		/// </summary>
		public Fill	Fill
		{
			get { return fill; }
			set { fill = value; }
		}

	#endregion
	
	#region Constructors
		/// <summary>
		/// Default constructor that sets all <see cref="Line"/> properties to default
		/// values as defined in the <see cref="Default"/> class.
		/// </summary>
		public Line() : this( Color.Empty )
		{
		}

		/// <summary>
		/// Constructor that sets the color property to the specified value, and sets
		/// the remaining <see cref="Line"/> properties to default
		/// values as defined in the <see cref="Default"/> class.
		/// </summary>
		/// <param name="color">The color to assign to this new Line object</param>
		public Line( Color color )
		{
			this.width = Default.Width;
			this.style = Default.Style;
			this.isVisible = Default.IsVisible;
			this.color = color.IsEmpty ? Default.Color : color;
			this.stepType = Default.StepType;
			this.isSmooth = Default.IsSmooth;
			this.smoothTension = Default.SmoothTension;
			this.fill = new Fill( Default.FillColor, Default.FillBrush, Default.FillType );
		}

		/// <summary>
		/// The Copy Constructor
		/// </summary>
		/// <param name="rhs">The Line object from which to copy</param>
		public Line( Line rhs )
		{
			width = rhs.Width;
			style = rhs.Style;
			isVisible = rhs.IsVisible;
			color = rhs.Color;
			stepType = rhs.StepType;
			isSmooth = rhs.IsSmooth;
			smoothTension = rhs.SmoothTension;
			fill = rhs.Fill.Clone();
		}

		/// <summary>
		/// Implement the <see cref="ICloneable" /> interface in a typesafe manner by just
		/// calling the typed version of <see cref="Clone" />
		/// </summary>
		/// <returns>A deep copy of this object</returns>
		object ICloneable.Clone()
		{
			return this.Clone();
		}

		/// <summary>
		/// Typesafe, deep-copy clone method.
		/// </summary>
		/// <returns>A new, independent copy of this class</returns>
		public Line Clone()
		{
			return new Line( this );
		}

	#endregion

	#region Serialization
		/// <summary>
		/// Current schema value that defines the version of the serialized file
		/// </summary>
		public const int schema = 1;

		/// <summary>
		/// Constructor for deserializing objects
		/// </summary>
		/// <param name="info">A <see cref="SerializationInfo"/> instance that defines the serialized data
		/// </param>
		/// <param name="context">A <see cref="StreamingContext"/> instance that contains the serialized data
		/// </param>
		protected Line( SerializationInfo info, StreamingContext context )
		{
			// The schema value is just a file version parameter.  You can use it to make future versions
			// backwards compatible as new member variables are added to classes
			int sch = info.GetInt32( "schema" );

			width = info.GetSingle( "width" );
			style = (DashStyle) info.GetValue( "style", typeof(DashStyle) );
			isVisible = info.GetBoolean( "isVisible" );
			isSmooth = info.GetBoolean( "isSmooth" );
			smoothTension = info.GetSingle( "smoothTension" );
			color = (Color) info.GetValue( "color", typeof(Color) );
			stepType = (StepType) info.GetValue( "stepType", typeof(StepType) );
			fill = (Fill) info.GetValue( "fill", typeof(Fill) );
		}
		/// <summary>
		/// Populates a <see cref="SerializationInfo"/> instance with the data needed to serialize the target object
		/// </summary>
		/// <param name="info">A <see cref="SerializationInfo"/> instance that defines the serialized data</param>
		/// <param name="context">A <see cref="StreamingContext"/> instance that contains the serialized data</param>
		[SecurityPermissionAttribute(SecurityAction.Demand,SerializationFormatter=true)]
		public virtual void GetObjectData( SerializationInfo info, StreamingContext context )
		{
			info.AddValue( "schema", schema );
			info.AddValue( "width", width );
			info.AddValue( "style", style );
			info.AddValue( "isVisible", isVisible );
			info.AddValue( "isSmooth", isSmooth );
			info.AddValue( "smoothTension", smoothTension );
			info.AddValue( "color", color );
			info.AddValue( "stepType", stepType );
			info.AddValue( "fill", fill );
		}
	#endregion

	#region Rendering Methods
		/// <summary>
		/// Do all rendering associated with this <see cref="Line"/> to the specified
		/// <see cref="Graphics"/> device.  This method is normally only
		/// called by the Draw method of the parent <see cref="LineItem"/> object.
		/// </summary>
		/// <param name="g">
		/// A graphic device object to be drawn into.  This is normally e.Graphics from the
		/// PaintEventArgs argument to the Paint() method.
		/// </param>
        /// <param name="scaleFactor">
        /// The scaling factor to be used for rendering objects.  This is calculated and
        /// passed down by the parent <see cref="GraphPane"/> object using the
        /// <see cref="PaneBase.CalcScaleFactor"/> method, and is used to proportionally adjust
        /// font sizes, etc. according to the actual size of the graph.
        /// </param>
        /// <param name="pane">
        /// A reference to the <see cref="ZedGraph.GraphPane"/> object that is the parent or
		/// owner of this object.
		/// </param>
		/// <param name="curve">A <see cref="LineItem"/> representing this
		/// curve.</param>
		public void Draw( Graphics g, GraphPane pane, CurveItem curve, float scaleFactor )
        {
			// If the line is being shown, draw it
			if ( this.IsVisible )
			{
				if ( curve is StickItem )
					DrawSticks( g, pane, curve, scaleFactor );
				else if ( this.IsSmooth || this.Fill.IsVisible )
                    DrawSmoothFilledCurve( g, pane, curve, scaleFactor );
                else
                    DrawCurve( g, pane, curve, scaleFactor );
            }
		}		

		/// <summary>
		/// Render a single <see cref="Line"/> segment to the specified
		/// <see cref="Graphics"/> device.
		/// </summary>
		/// <param name="g">
		/// A graphic device object to be drawn into.  This is normally e.Graphics from the
		/// PaintEventArgs argument to the Paint() method.
		/// </param>
        /// <param name="pane">
        /// A reference to the <see cref="ZedGraph.GraphPane"/> object that is the parent or
        /// owner of this object.
        /// </param>
        /// <param name="scaleFactor">
        /// The scaling factor to be used for rendering objects.  This is calculated and
        /// passed down by the parent <see cref="GraphPane"/> object using the
        /// <see cref="PaneBase.CalcScaleFactor"/> method, and is used to proportionally adjust
        /// font sizes, etc. according to the actual size of the graph.
        /// </param>
        /// <param name="x1">The x position of the starting point that defines the
        /// line segment in screen pixel units</param>
		/// <param name="y1">The y position of the starting point that defines the
		/// line segment in screen pixel units</param>
		/// <param name="x2">The x position of the ending point that defines the
		/// line segment in screen pixel units</param>
		/// <param name="y2">The y position of the ending point that defines the
		/// line segment in screen pixel units</param>
        public void DrawSegment( Graphics g, GraphPane pane, float x1, float y1,
                            float x2, float y2, float scaleFactor )
        {
			if ( this.isVisible && !this.Color.IsEmpty )
			{
				Pen pen = new Pen( this.color, pane.ScaledPenWidth(width, scaleFactor) );
				pen.DashStyle = this.Style;
				g.DrawLine( pen, x1, y1, x2, y2 );
			}
		}

		/// <summary>
		/// Render the <see cref="Line"/>'s as vertical sticks (from a <see cref="StickItem" />) to
		/// the specified <see cref="Graphics"/> device.
		/// </summary>
		/// <param name="g">
		/// A graphic device object to be drawn into.  This is normally e.Graphics from the
		/// PaintEventArgs argument to the Paint() method.
		/// </param>
        /// <param name="pane">
        /// A reference to the <see cref="ZedGraph.GraphPane"/> object that is the parent or
        /// owner of this object.
        /// </param>
		/// <param name="curve">A <see cref="CurveItem"/> representing this
		/// curve.</param>
        /// <param name="scaleFactor">
        /// The scaling factor to be used for rendering objects.  This is calculated and
        /// passed down by the parent <see cref="GraphPane"/> object using the
        /// <see cref="PaneBase.CalcScaleFactor"/> method, and is used to proportionally adjust
        /// font sizes, etc. according to the actual size of the graph.
        /// </param>
		public void DrawSticks( Graphics g, GraphPane pane, CurveItem curve, float scaleFactor )
		{
			Axis yAxis = curve.GetYAxis( pane );
			float basePix = yAxis.Scale.Transform( 0.0 );
			Pen pen = new Pen( this.color, pane.ScaledPenWidth(width, scaleFactor) );
			pen.DashStyle = this.Style;

			for ( int i=0; i<curve.Points.Count; i++ )
			{
				PointPair pt = curve.Points[i];

				if ( 	pt.X != PointPair.Missing &&
						pt.Y != PointPair.Missing &&
						!System.Double.IsNaN( pt.X ) &&
						!System.Double.IsNaN( pt.Y ) &&
						!System.Double.IsInfinity( pt.X ) &&
						!System.Double.IsInfinity( pt.Y ) &&
						( !pane.XAxis.IsLog || pt.X > 0.0 ) &&
						( !yAxis.IsLog || pt.Y > 0.0 ) )
				{
					float pixY = yAxis.Scale.Transform( curve.IsOverrideOrdinal, i, pt.Y );
					float pixX = pane.XAxis.Scale.Transform( curve.IsOverrideOrdinal, i, pt.X );
					g.DrawLine( pen, pixX, pixY, pixX, basePix );
				}
			}
		}

		/// <summary>
		/// Draw the this <see cref="CurveItem"/> to the specified <see cref="Graphics"/>
		/// device using the specified smoothing property (<see cref="ZedGraph.Line.SmoothTension"/>).
		/// The routine draws the line segments and the area fill (if any, see <see cref="FillType"/>;
		/// the symbols are drawn by the <see cref="Symbol.Draw"/> method.  This method
		/// is normally only called by the Draw method of the
		/// <see cref="CurveItem"/> object.  Note that the <see cref="StepType"/> property
		/// is ignored for smooth lines (e.g., when <see cref="ZedGraph.Line.IsSmooth"/> is true).
		/// </summary>
		/// <param name="g">
		/// A graphic device object to be drawn into.  This is normally e.Graphics from the
		/// PaintEventArgs argument to the Paint() method.
		/// </param>
        /// <param name="scaleFactor">
        /// The scaling factor to be used for rendering objects.  This is calculated and
        /// passed down by the parent <see cref="GraphPane"/> object using the
        /// <see cref="PaneBase.CalcScaleFactor"/> method, and is used to proportionally adjust
        /// font sizes, etc. according to the actual size of the graph.
        /// </param>
        /// <param name="pane">
        /// A reference to the <see cref="GraphPane"/> object that is the parent or
		/// owner of this object.
		/// </param>
		/// <param name="curve">A <see cref="LineItem"/> representing this
		/// curve.</param>
		public void DrawSmoothFilledCurve( Graphics g, GraphPane pane,
                                CurveItem curve, float scaleFactor )
        {
			PointF[]	arrPoints;
			int			count;
			IPointList points = curve.Points;

			if ( this.IsVisible && !this.Color.IsEmpty && points != null &&
				BuildPointsArray( pane, curve, out arrPoints, out count ) &&
				count > 2 )
			{
                Pen pen = new Pen(this.Color, pane.ScaledPenWidth( width, scaleFactor ) );
                pen.DashStyle = this.Style;
				float tension = this.isSmooth ? this.smoothTension : 0f;
				
				// Fill the curve if needed
				if ( this.Fill.IsVisible )
				{
					Axis yAxis = curve.GetYAxis( pane );

					GraphicsPath path = new GraphicsPath( FillMode.Winding );
					path.AddCurve( arrPoints, 0, count-2, tension );

					double yMin = yAxis.Min < 0 ? 0.0 : yAxis.Min;
					CloseCurve( pane, curve, arrPoints, count, yMin, path );
				
					RectangleF rect = path.GetBounds();
					Brush brush = this.fill.MakeBrush( rect );
					g.FillPath( brush, path );
					brush.Dispose();

					// restore the zero line if needed (since the fill tends to cover it up)
					yAxis.FixZeroLine( g, pane, scaleFactor, rect.Left, rect.Right );
				}

				// Stroke the curve
				g.DrawCurve( pen, arrPoints, 0, count-2, tension );

				pen.Dispose();
			}
		}

		/// <summary>
		/// Draw the this <see cref="CurveItem"/> to the specified <see cref="Graphics"/>
		/// device.  The format (stair-step or line) of the curve is
		/// defined by the <see cref="StepType"/> property.  The routine
		/// only draws the line segments; the symbols are drawn by the
		/// <see cref="Symbol.Draw"/> method.  This method
		/// is normally only called by the Draw method of the
		/// <see cref="CurveItem"/> object
		/// </summary>
		/// <param name="g">
		/// A graphic device object to be drawn into.  This is normally e.Graphics from the
		/// PaintEventArgs argument to the Paint() method.
		/// </param>
        /// <param name="scaleFactor">
        /// The scaling factor to be used for rendering objects.  This is calculated and
        /// passed down by the parent <see cref="GraphPane"/> object using the
        /// <see cref="PaneBase.CalcScaleFactor"/> method, and is used to proportionally adjust
        /// font sizes, etc. according to the actual size of the graph.
        /// </param>
        /// <param name="pane">
        /// A reference to the <see cref="GraphPane"/> object that is the parent or
		/// owner of this object.
		/// </param>
		/// <param name="curve">A <see cref="LineItem"/> representing this
		/// curve.</param>
		public void DrawCurve( Graphics g, GraphPane pane,
                                CurveItem curve, float scaleFactor)
        {
			float	tmpX, tmpY,
					lastX = float.MaxValue,
					lastY = float.MaxValue;
			double	curX, curY, lowVal;
			bool	lastBad = true;
			IPointList points = curve.Points;
			ValueHandler valueHandler = new ValueHandler( pane, false );
			Axis yAxis = curve.GetYAxis( pane );

			Pen pen = new Pen( this.color, pane.ScaledPenWidth( width, scaleFactor ) );
			pen.DashStyle = this.Style;

			if ( points != null && !this.color.IsEmpty && this.IsVisible )
			{
				// Loop over each point in the curve
				for ( int i=0; i<points.Count; i++ )
				{
					if ( pane.LineType == LineType.Stack )
					{
						if ( !valueHandler.GetValues( curve, i, out curX, out lowVal, out curY ) )
						{
							curX = PointPair.Missing;
							curY = PointPair.Missing;
						}
					}
					else
					{
						curX = points[i].X;
						curY = points[i].Y;
					}
					
					// Any value set to double max is invalid and should be skipped
					// This is used for calculated values that are out of range, divide
					//   by zero, etc.
					// Also, any value <= zero on a log scale is invalid
					if ( 	curX == PointPair.Missing ||
							curY == PointPair.Missing ||
							System.Double.IsNaN( curX ) ||
							System.Double.IsNaN( curY ) ||
							System.Double.IsInfinity( curX ) ||
							System.Double.IsInfinity( curY ) ||
							( pane.XAxis.IsLog && curX <= 0.0 ) ||
							( yAxis.IsLog && curY <= 0.0 ) )
					{
						// If the point is invalid, then make a linebreak only if IsIgnoreMissing is false
						// LastX and LastY are always the last valid point, so this works out
						lastBad = lastBad || !pane.IsIgnoreMissing;
					}
					else
					{
						// Transform the current point from user scale units to
						// screen coordinates
						tmpX = pane.XAxis.Scale.Transform( curve.IsOverrideOrdinal, i, curX );
						tmpY = yAxis.Scale.Transform( curve.IsOverrideOrdinal, i, curY );

						if ( !lastBad )
						{
							try
							{
								// GDI+ plots the data wrong and/or throws an exception for
								// outrageous coordinates, so we do a sanity check here
								if ( lastX > 5000000 || lastX < -5000000 ||
										lastY > 5000000 || lastY < -5000000 ||
										tmpX > 5000000 || tmpX < -5000000 ||
										tmpY > 5000000 || tmpY < -5000000 )
									InterpolatePoint( g, pane, pen, lastX, lastY, tmpX, tmpY );

								if ( this.StepType == StepType.ForwardStep )
								{
									g.DrawLine( pen, lastX, lastY, tmpX, lastY );
									g.DrawLine( pen, tmpX, lastY, tmpX, tmpY );
								}
								else if ( this.StepType == StepType.RearwardStep )
								{
									g.DrawLine( pen, lastX, lastY, lastX, tmpY );
									g.DrawLine( pen, lastX, tmpY, tmpX, tmpY );
								}
								else 		// non-step
									g.DrawLine( pen, lastX, lastY, tmpX, tmpY );

							}
							catch
							{
								InterpolatePoint( g, pane, pen, lastX, lastY, tmpX, tmpY );
							}
						}

						lastX = tmpX;
						lastY = tmpY;
						lastBad = false;
					}
				}
			}
		}

		/// <summary>
		/// This method just handles the case where one or more of the coordinates are outrageous,
		/// or GDI+ threw an exception.  This method attempts to correct the outrageous coordinates by
		/// interpolating them to a point (along the original line) that lies at the edge of the AxisRect
		/// so that GDI+ will handle it properly.  GDI+ will throw an exception, or just plot the data
		/// incorrectly if the coordinates are too large (empirically, this appears to be when the
		/// coordinate value is greater than 5,000,000 or less than -5,000,000).  Although you typically
		/// would not see coordinates like this, if you repeatedly zoom in on a ZedGraphControl, eventually
		/// all your points will be way outside the bounds of the plot.
		/// </summary>
		private void InterpolatePoint( Graphics g, GraphPane pane, Pen pen, float lastX, float lastY,
						float tmpX, float tmpY )
		{
			try
			{
				// try to interpolate values
				bool lastIn = pane.AxisRect.Contains( lastX, lastY );
				bool curIn = pane.AxisRect.Contains( tmpX, tmpY );

				// If both points are outside the AxisRect, make a new point that is on the LastX/Y
				// side of the AxisRect, and fall through to the code that handles lastIn == true
				if ( !lastIn )
				{
					float newX, newY;

					if ( Math.Abs( lastX ) > Math.Abs( lastY ) )
					{
						newX = lastX < 0 ? pane.AxisRect.Left : pane.AxisRect.Right;
						newY = lastY + (tmpY - lastY) * (newX - lastX) / (tmpX - lastX);
					}
					else
					{
						newY = lastY < 0 ? pane.AxisRect.Top : pane.AxisRect.Bottom;
						newX = lastX + (tmpX - lastX) * (newY - lastY) / (tmpY - lastY);
					}

					lastX = newX;
					lastY = newY;
				}

				if ( !curIn )
				{
					float newX, newY;

					if ( Math.Abs( tmpX ) > Math.Abs( tmpY ) )
					{
						newX = tmpX < 0 ? pane.AxisRect.Left : pane.AxisRect.Right;
						newY = tmpY + ( lastY - tmpY ) * ( newX - tmpX ) / ( lastX - tmpX );
					}
					else
					{
						newY = tmpY < 0 ? pane.AxisRect.Top : pane.AxisRect.Bottom;
						newX = tmpX + ( lastX - tmpX ) * ( newY - tmpY ) / ( lastY - tmpY );
					}

					tmpX = newX;
					tmpY = newY;
				}

				if ( this.StepType == StepType.ForwardStep )
				{
					g.DrawLine( pen, lastX, lastY, tmpX, lastY );
					g.DrawLine( pen, tmpX, lastY, tmpX, tmpY );
				}
				else if ( this.StepType == StepType.RearwardStep )
				{
					g.DrawLine( pen, lastX, lastY, lastX, tmpY );
					g.DrawLine( pen, lastX, tmpY, tmpX, tmpY );
				}
				else 		// non-step
					g.DrawLine( pen, lastX, lastY, tmpX, tmpY );

			}

			catch { }
		}

		/// <summary>
		/// Build an array of <see cref="PointF"/> values (pixel coordinates) that represents
		/// the current curve.  Note that this drawing routine ignores <see cref="PointPair.Missing"/>
		/// values, but it does not "break" the line to indicate values are missing.
		/// </summary>
		/// <param name="pane">A reference to the <see cref="GraphPane"/> object that is the parent or
		/// owner of this object.</param>
		/// <param name="curve">A <see cref="LineItem"/> representing this
		/// curve.</param>
		/// <param name="arrPoints">An array of <see cref="PointF"/> values in pixel
		/// coordinates representing the current curve.</param>
		/// <param name="count">The number of points contained in the "arrPoints"
		/// parameter.</param>
		/// <returns>true for a successful points array build, false for data problems</returns>
		public bool BuildPointsArray( GraphPane pane, CurveItem curve,
			out PointF[] arrPoints, out int count )
		{
			arrPoints = null;
			count = 0;
			IPointList points = curve.Points;

			if ( this.IsVisible && !this.Color.IsEmpty && points != null )
			{
				int		index = 0;
				float	curX, curY,
						lastX = 0,
						lastY = 0;
				double	x, y, lowVal;
				ValueHandler valueHandler = new ValueHandler( pane, false );

				// Step type plots get twice as many points.  Always add three points so there is
				// room to close out the curve for area fills.
				arrPoints = new PointF[ ( this.stepType == ZedGraph.StepType.NonStep ? 1 : 2 ) *
											points.Count + 1 ];

				// Loop over all points in the curve
				for ( int i=0; i<points.Count; i++ )
				{
					// make sure that the current point is valid
					if ( !points[i].IsInvalid )
					{
						// Get the user scale values for the current point
						// use the valueHandler only for stacked types
						if ( pane.LineType == LineType.Stack )
						{
							valueHandler.GetValues( curve, i, out x, out lowVal, out y );
						}
						// otherwise, just access the values directly.  Avoiding the valueHandler for
						// non-stacked types is an optimization to minimize overhead in case there are
						// a large number of points.
						else
						{
							x = points[i].X;
							y = points[i].Y;
						}
						
						// Transform the user scale values to pixel locations
						curX = pane.XAxis.Scale.Transform( curve.IsOverrideOrdinal, i, x );
						Axis yAxis = curve.GetYAxis( pane );
						curY = yAxis.Scale.Transform( curve.IsOverrideOrdinal, i, y );

						if ( curX < -1000000 || curY < -1000000 || curX > 1000000 || curY > 1000000 )
							continue;
						
						// Add the pixel value pair into the points array
						// Two points are added for step type curves
						// ignore step-type setting for smooth curves
						if ( this.isSmooth || index == 0 || this.StepType == StepType.NonStep )
						{
							arrPoints[index].X = curX;
							arrPoints[index].Y = curY;
						}
						else if ( this.StepType == StepType.ForwardStep )
						{
							arrPoints[index].X = curX;
							arrPoints[index].Y = lastY;
							index++;
							arrPoints[index].X = curX;
							arrPoints[index].Y = curY;
						}
						else if ( this.StepType == StepType.RearwardStep )
						{
							arrPoints[index].X = lastX;
							arrPoints[index].Y = curY;
							index++;
							arrPoints[index].X = curX;
							arrPoints[index].Y = curY;
						}

						lastX = curX;
						lastY = curY;
						index++;
						
					}

				}

				// Make sure there is at least one valid point
				if ( index == 0 )
					return false;
					
				// Add an extra point at the end, since the smoothing algorithm requires it
				arrPoints[index] = arrPoints[index-1];
				index++;
				
				count = index;
				return true;
			}
			else
			{
				return false;
			}
		}

		/// <summary>
		/// Build an array of <see cref="PointF"/> values (pixel coordinates) that represents
		/// the low values for the current curve.
		/// </summary>
		/// <remarks>Note that this drawing routine ignores <see cref="PointPair.Missing"/>
		/// values, but it does not "break" the line to indicate values are missing.
		/// </remarks>
		/// <param name="pane">A reference to the <see cref="GraphPane"/> object that is the parent or
		/// owner of this object.</param>
		/// <param name="curve">A <see cref="LineItem"/> representing this
		/// curve.</param>
		/// <param name="arrPoints">An array of <see cref="PointF"/> values in pixel
		/// coordinates representing the current curve.</param>
		/// <param name="count">The number of points contained in the "arrPoints"
		/// parameter.</param>
		/// <returns>true for a successful points array build, false for data problems</returns>
		public bool BuildLowPointsArray( GraphPane pane, CurveItem curve,
						out PointF[] arrPoints, out int count )
		{
			arrPoints = null;
			count = 0;
			IPointList points = curve.Points;

			if ( this.IsVisible && !this.Color.IsEmpty && points != null )
			{
				int		index = 0;
				float	curX, curY,
						lastX = 0,
						lastY = 0;
				double	x, y, hiVal;
				ValueHandler valueHandler = new ValueHandler( pane, false );

				// Step type plots get twice as many points.  Always add three points so there is
				// room to close out the curve for area fills.
				arrPoints = new PointF[ ( this.stepType == ZedGraph.StepType.NonStep ? 1 : 2 ) *
					( pane.LineType == LineType.Stack ? 2 : 1 ) *
					points.Count + 1 ];

				// Loop backwards over all points in the curve
				// In this case an array of points was already built forward by BuildPointsArray().
				// This time we build backwards to complete a loop around the area between two curves.
				for ( int i=points.Count-1; i>=0; i-- )
				{
					// Make sure the current point is valid
					if ( !points[i].IsInvalid )
					{
						// Get the user scale values for the current point
						valueHandler.GetValues( curve, i, out x, out y, out hiVal );
						
						// Transform the user scale values to pixel locations
						curX = pane.XAxis.Scale.Transform( curve.IsOverrideOrdinal, i, x );
						Axis yAxis = curve.GetYAxis( pane );
						curY = yAxis.Scale.Transform( curve.IsOverrideOrdinal, i, y );

						// Add the pixel value pair into the points array
						// Two points are added for step type curves
						// ignore step-type setting for smooth curves
						if ( this.isSmooth || index == 0 || this.StepType == StepType.NonStep )
						{
							arrPoints[index].X = curX;
							arrPoints[index].Y = curY;
						}
						else if ( this.StepType == StepType.ForwardStep )
						{
							arrPoints[index].X = curX;
							arrPoints[index].Y = lastY;
							index++;
							arrPoints[index].X = curX;
							arrPoints[index].Y = curY;
						}
						else if ( this.StepType == StepType.RearwardStep )
						{
							arrPoints[index].X = lastX;
							arrPoints[index].Y = curY;
							index++;
							arrPoints[index].X = curX;
							arrPoints[index].Y = curY;
						}

						lastX = curX;
						lastY = curY;
						index++;
						
					}

				}

				// Make sure there is at least one valid point
				if ( index == 0 )
					return false;
					
				// Add an extra point at the end, since the smoothing algorithm requires it
				arrPoints[index] = arrPoints[index-1];
				index++;
				
				count = index;
				return true;
			}
			else
			{
				return false;
			}
		}

		/// <summary>
		/// Close off a <see cref="GraphicsPath"/> that defines a curve
		/// </summary>
		/// <param name="pane">A reference to the <see cref="GraphPane"/> object that is the parent or
		/// owner of this object.</param>
		/// <param name="curve">A <see cref="LineItem"/> representing this
		/// curve.</param>
		/// <param name="arrPoints">An array of <see cref="PointF"/> values in screen pixel
		/// coordinates representing the current curve.</param>
		/// <param name="count">The number of points contained in the "arrPoints"
		/// parameter.</param>
		/// <param name="yMin">The Y axis value location where the X axis crosses.</param>
		/// <param name="path">The <see cref="GraphicsPath"/> class that represents the curve.</param>
		public void CloseCurve( GraphPane pane, CurveItem curve, PointF[] arrPoints,
									int count, double yMin, GraphicsPath path )
		{
			// For non-stacked lines, the fill area is just the area between the curve and the X axis
			if ( pane.LineType != LineType.Stack )
			{
				// Determine the current value for the bottom of the curve (usually the Y value where
				// the X axis crosses)
				float yBase;
				Axis yAxis = curve.GetYAxis( pane );
				yBase = yAxis.Scale.Transform( yMin );

				// Add three points to the path to move from the end of the curve (as defined by
				// arrPoints) to the X axis, from there to the start of the curve at the X axis,
				// and from there back up to the beginning of the curve.
				path.AddLine( arrPoints[count-1].X, arrPoints[count-1].Y, arrPoints[count-1].X, yBase );
				path.AddLine( arrPoints[count-1].X, yBase, arrPoints[0].X, yBase );
				path.AddLine( arrPoints[0].X, yBase, arrPoints[0].X, arrPoints[0].Y );
			}
			// For stacked line types, the fill area is the area between this curve and the curve below it
			else
			{
				PointF[]	arrPoints2;
				int			count2;
				
				float		tension = this.isSmooth ? this.smoothTension : 0f;
				
				// Find the next lower curve in the curveList that is also a LineItem type, and use
				// its smoothing properties for the lower side of the filled area.
				int index = pane.CurveList.IndexOf( curve );
				if ( index > 0 )
				{
					CurveItem tmpCurve;
					for ( int i=index-1; i>=0; i-- )
					{
						tmpCurve = pane.CurveList[i];
						if ( tmpCurve is LineItem )
						{
							tension = ((LineItem)tmpCurve).Line.IsSmooth ? ((LineItem)tmpCurve).Line.SmoothTension : 0f;
							break;
						}
					}
				}
				
				// Build another points array consisting of the low points (which are actually the points for
				// the curve below the current curve)
				BuildLowPointsArray( pane, curve, out arrPoints2, out count2 );
				
				// Add the new points to the GraphicsPath
				path.AddCurve( arrPoints2, 0, count2-2, tension );
			}
			
		}

	#endregion
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Engineer
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions