//============================================================================ //ZedGraph Class Library - A Flexible Line Graph/Bar Graph Library in C# //Copyright ?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 DrawGraph { /// /// A class representing all the characteristics of the Line /// segments that make up a curve on the graph. /// /// /// John Champion /// $Revision: 3.41 $ $Date: 2007/04/16 00:03:02 $ [Serializable] public class Line : LineBase, ICloneable, ISerializable { #region Fields /// /// Private field that stores the smoothing flag for this /// . Use the public /// property to access this value. /// private bool _isSmooth; /// /// Private field that stores the smoothing tension /// for this . Use the public property /// to access this 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. /// /// /// private float _smoothTension; /// /// Private field that stores the for this /// . Use the public /// property to access this value. /// private StepType _stepType; /// /// Private field that stores the data for this /// . Use the public property to /// access this value. /// private Fill _fill; #endregion #region Defaults /// /// A simple struct that defines the /// default property values for the class. /// new public struct Default { // Default Line properties /// /// The default color for curves (line segments connecting the points). /// This is the default value for the property. /// public static Color Color = Color.Red; /// /// The default color for filling in the area under the curve /// ( property). /// public static Color FillColor = Color.Red; /// /// The default custom brush for filling in the area under the curve /// ( property). /// public static Brush FillBrush = null; /// /// The default fill mode for the curve ( property). /// public static FillType FillType = FillType.None; /// /// The default value for the /// property. /// public static bool IsSmooth = false; /// /// The default value for the property. /// public static float SmoothTension = 0.5F; /// /// Default value for the curve type property /// (). This determines if the curve /// will be drawn by directly connecting the points from the /// 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 ForwardStep () or /// rearward oriented RearwardStep. /// That is, the points are defined at the beginning or end /// of the staticant value for which they apply, respectively. /// /// enum value public static StepType StepType = StepType.NonStep; } #endregion #region Properties /// /// Gets or sets a property that determines if this /// will be drawn smooth. The "smoothness" is controlled by /// the property. /// /// true to smooth the line, false to just connect the dots /// with linear segments /// /// /// public bool IsSmooth { get { return _isSmooth; } set { _isSmooth = value; } } /// /// Gets or sets a property that determines the smoothing tension /// for this . This property is only used if /// 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. /// /// 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. /// /// /// public float SmoothTension { get { return _smoothTension; } set { _smoothTension = value; } } /// /// Determines if the will be drawn by directly connecting the /// points from the 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 ForwardStep () or /// rearward oriented RearwardStep. /// That is, the points are defined at the beginning or end /// of the constant value for which they apply, respectively. /// The property is ignored for lines /// that have set to true. /// /// enum value /// public StepType StepType { get { return _stepType; } set { _stepType = value; } } /// /// Gets or sets the data for this /// . /// public Fill Fill { get { return _fill; } set { _fill = value; } } #endregion #region Constructors /// /// Default constructor that sets all properties to default /// values as defined in the class. /// public Line() : this( Color.Empty ) { } /// /// Constructor that sets the color property to the specified value, and sets /// the remaining properties to default /// values as defined in the class. /// /// The color to assign to this new Line object public Line( Color color ) { _color = color.IsEmpty ? Default.Color : color; _stepType = Default.StepType; _isSmooth = Default.IsSmooth; _smoothTension = Default.SmoothTension; _fill = new Fill( Default.FillColor, Default.FillBrush, Default.FillType ); } /// /// The Copy Constructor /// /// The Line object from which to copy public Line( Line rhs ) { _stepType = rhs._stepType; _isSmooth = rhs._isSmooth; _smoothTension = rhs._smoothTension; _fill = rhs._fill.Clone(); } /// /// Implement the interface in a typesafe manner by just /// calling the typed version of /// /// A deep copy of this object object ICloneable.Clone() { return this.Clone(); } /// /// Typesafe, deep-copy clone method. /// /// A new, independent copy of this class public Line Clone() { return new Line( this ); } #endregion #region Serialization /// /// Current schema value that defines the version of the serialized file /// public const int schema = 12; /// /// Constructor for deserializing objects /// /// A instance that defines the serialized data /// /// A instance that contains the serialized data /// protected Line( SerializationInfo info, StreamingContext context ) : base( info, 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" ); _isSmooth = info.GetBoolean( "isSmooth" ); _smoothTension = info.GetSingle( "smoothTension" ); _stepType = (StepType)info.GetValue( "stepType", typeof( StepType ) ); _fill = (Fill)info.GetValue( "fill", typeof( Fill ) ); } /// /// Populates a instance with the data needed to serialize the target object /// /// A instance that defines the serialized data /// A instance that contains the serialized data [SecurityPermissionAttribute( SecurityAction.Demand, SerializationFormatter = true )] public override void GetObjectData( SerializationInfo info, StreamingContext context ) { base.GetObjectData( info, context ); info.AddValue( "schema", schema ); info.AddValue( "isSmooth", _isSmooth ); info.AddValue( "smoothTension", _smoothTension ); info.AddValue( "stepType", _stepType ); info.AddValue( "fill", _fill ); } #endregion #region Rendering Methods /// /// Do all rendering associated with this to the specified /// device. This method is normally only /// called by the Draw method of the parent object. /// /// /// A graphic device object to be drawn into. This is normally e.Graphics from the /// PaintEventArgs argument to the Paint() method. /// /// /// The scaling factor to be used for rendering objects. This is calculated and /// passed down by the parent object using the /// method, and is used to proportionally adjust /// font sizes, etc. according to the actual size of the graph. /// /// /// A reference to the object that is the parent or /// owner of this object. /// /// A representing this /// curve. public void Draw( Graphics g, GraphPane pane, CurveItem curve, float scaleFactor ) { // If the line is being shown, draw it if ( this.IsVisible ) { //How to handle fill vs nofill? //if ( isSelected ) // GraphPane.Default.SelectedLine. SmoothingMode sModeSave = g.SmoothingMode; if ( _isAntiAlias ) g.SmoothingMode = SmoothingMode.HighQuality; 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 ); g.SmoothingMode = sModeSave; } } /// /// Render a single segment to the specified /// device. /// /// /// A graphic device object to be drawn into. This is normally e.Graphics from the /// PaintEventArgs argument to the Paint() method. /// /// /// A reference to the object that is the parent or /// owner of this object. /// /// /// The scaling factor to be used for rendering objects. This is calculated and /// passed down by the parent object using the /// method, and is used to proportionally adjust /// font sizes, etc. according to the actual size of the graph. /// /// The x position of the starting point that defines the /// line segment in screen pixel units /// The y position of the starting point that defines the /// line segment in screen pixel units /// The x position of the ending point that defines the /// line segment in screen pixel units /// The y position of the ending point that defines the /// line segment in screen pixel units public void DrawSegment( Graphics g, GraphPane pane, float x1, float y1, float x2, float y2, float scaleFactor ) { if ( _isVisible && !this.Color.IsEmpty ) { using ( Pen pen = GetPen( pane, scaleFactor ) ) { g.DrawLine( pen, x1, y1, x2, y2 ); } } } /// /// Render the 's as vertical sticks (from a ) to /// the specified device. /// /// /// A graphic device object to be drawn into. This is normally e.Graphics from the /// PaintEventArgs argument to the Paint() method. /// /// /// A reference to the object that is the parent or /// owner of this object. /// /// A representing this /// curve. /// /// The scaling factor to be used for rendering objects. This is calculated and /// passed down by the parent object using the /// method, and is used to proportionally adjust /// font sizes, etc. according to the actual size of the graph. /// public void DrawSticks( Graphics g, GraphPane pane, CurveItem curve, float scaleFactor ) { Line source = this; if ( curve.IsSelected ) source = Selection.Line; Axis yAxis = curve.GetYAxis( pane ); Axis xAxis = curve.GetXAxis( pane ); float basePix = yAxis.Scale.Transform( 0.0 ); using ( Pen pen = source.GetPen( pane, scaleFactor ) ) { 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 ) && ( !xAxis._scale.IsLog || pt.X > 0.0 ) && ( !yAxis._scale.IsLog || pt.Y > 0.0 ) ) { float pixY = yAxis.Scale.Transform( curve.IsOverrideOrdinal, i, pt.Y ); float pixX = xAxis.Scale.Transform( curve.IsOverrideOrdinal, i, pt.X ); if ( pixX >= pane.Chart._rect.Left && pixX <= pane.Chart._rect.Right ) { if ( pixY > pane.Chart._rect.Bottom ) pixY = pane.Chart._rect.Bottom; if ( pixY < pane.Chart._rect.Top ) pixY = pane.Chart._rect.Top; if ( !curve.IsSelected && this._gradientFill.IsGradientValueType ) { using ( Pen tPen = GetPen( pane, scaleFactor, pt ) ) g.DrawLine( tPen, pixX, pixY, pixX, basePix ); } else g.DrawLine( pen, pixX, pixY, pixX, basePix ); } } } } } /// /// Draw the this to the specified /// device using the specified smoothing property (). /// The routine draws the line segments and the area fill (if any, see ; /// the symbols are drawn by the method. This method /// is normally only called by the Draw method of the /// object. Note that the property /// is ignored for smooth lines (e.g., when is true). /// /// /// A graphic device object to be drawn into. This is normally e.Graphics from the /// PaintEventArgs argument to the Paint() method. /// /// /// The scaling factor to be used for rendering objects. This is calculated and /// passed down by the parent object using the /// method, and is used to proportionally adjust /// font sizes, etc. according to the actual size of the graph. /// /// /// A reference to the object that is the parent or /// owner of this object. /// /// A representing this /// curve. public void DrawSmoothFilledCurve( Graphics g, GraphPane pane, CurveItem curve, float scaleFactor ) { Line source = this; if ( curve.IsSelected ) source = Selection.Line; 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 ) { float tension = _isSmooth ? _smoothTension : 0f; // Fill the curve if needed if ( this.Fill.IsVisible ) { Axis yAxis = curve.GetYAxis( pane ); using ( GraphicsPath path = new GraphicsPath( FillMode.Winding ) ) { path.AddCurve( arrPoints, 0, count - 2, tension ); double yMin = yAxis._scale._min < 0 ? 0.0 : yAxis._scale._min; CloseCurve( pane, curve, arrPoints, count, yMin, path ); RectangleF rect = path.GetBounds(); using ( Brush brush = source._fill.MakeBrush( rect ) ) { if ( pane.LineType == LineType.Stack && yAxis.Scale._min < 0 && this.IsFirstLine( pane, curve ) ) { float zeroPix = yAxis.Scale.Transform( 0 ); RectangleF tRect = pane.Chart._rect; tRect.Height = zeroPix - tRect.Top; if ( tRect.Height > 0 ) { Region reg = g.Clip; g.SetClip( tRect ); g.FillPath( brush, path ); g.SetClip( pane.Chart._rect ); } } else 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 ); } } // If it's a smooth curve, go ahead and render the path. Otherwise, use the // standard drawcurve method just in case there are missing values. if ( _isSmooth ) { using ( Pen pen = GetPen( pane, scaleFactor ) ) { // Stroke the curve g.DrawCurve( pen, arrPoints, 0, count - 2, tension ); //pen.Dispose(); } } else DrawCurve( g, pane, curve, scaleFactor ); } } private bool IsFirstLine( GraphPane pane, CurveItem curve ) { CurveList curveList = pane.CurveList; for ( int j = 0; j < curveList.Count; j++ ) { CurveItem tCurve = curveList[j]; if ( tCurve is LineItem && tCurve.IsY2Axis == curve.IsY2Axis && tCurve.YAxisIndex == curve.YAxisIndex ) { return tCurve == curve; } } return false; } /// /// Draw the this to the specified /// device. The format (stair-step or line) of the curve is /// defined by the property. The routine /// only draws the line segments; the symbols are drawn by the /// method. This method /// is normally only called by the Draw method of the /// object /// /// /// A graphic device object to be drawn into. This is normally e.Graphics from the /// PaintEventArgs argument to the Paint() method. /// /// /// The scaling factor to be used for rendering objects. This is calculated and /// passed down by the parent object using the /// method, and is used to proportionally adjust /// font sizes, etc. according to the actual size of the graph. /// /// /// A reference to the object that is the parent or /// owner of this object. /// /// A representing this /// curve. public void DrawCurve( Graphics g, GraphPane pane, CurveItem curve, float scaleFactor ) { Line source = this; if ( curve.IsSelected ) source = Selection.Line; float tmpX, tmpY, lastX = float.MaxValue, lastY = float.MaxValue; double curX, curY, lowVal; PointPair curPt, lastPt = new PointPair(); bool lastBad = true; IPointList points = curve.Points; ValueHandler valueHandler = new ValueHandler( pane, false ); Axis yAxis = curve.GetYAxis( pane ); Axis xAxis = curve.GetXAxis( pane ); bool xIsLog = xAxis._scale.IsLog; bool yIsLog = yAxis._scale.IsLog; float minX = pane.Chart.Rect.Left; float maxX = pane.Chart.Rect.Right; using ( Pen pen = source.GetPen( pane, scaleFactor ) ) { if ( points != null && !_color.IsEmpty && this.IsVisible ) { bool lastOut = false; bool isOut; // Loop over each point in the curve for ( int i = 0; i < points.Count; i++ ) { curPt = points[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 = curPt.X; curY = curPt.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 ) || ( xIsLog && curX <= 0.0 ) || ( yIsLog && 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; isOut = true; } else { // Transform the current point from user scale units to // screen coordinates tmpX = xAxis.Scale.Transform( curve.IsOverrideOrdinal, i, curX ); tmpY = yAxis.Scale.Transform( curve.IsOverrideOrdinal, i, curY ); isOut = (tmpX < minX || tmpX > maxX); 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, curve, lastPt, scaleFactor, pen, lastX, lastY, tmpX, tmpY ); else if ( !lastOut || !isOut ) { if ( !curve.IsSelected && this._gradientFill.IsGradientValueType ) { using ( Pen tPen = GetPen( pane, scaleFactor, lastPt ) ) { if ( this.StepType == StepType.ForwardStep ) { g.DrawLine( tPen, lastX, lastY, tmpX, lastY ); g.DrawLine( tPen, tmpX, lastY, tmpX, tmpY ); } else if ( this.StepType == StepType.RearwardStep ) { g.DrawLine( tPen, lastX, lastY, lastX, tmpY ); g.DrawLine( tPen, lastX, tmpY, tmpX, tmpY ); } else // non-step g.DrawLine( tPen, lastX, lastY, tmpX, tmpY ); } } else { 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, curve, lastPt, scaleFactor, pen, lastX, lastY, tmpX, tmpY ); } } lastPt = curPt; lastX = tmpX; lastY = tmpY; lastBad = false; lastOut = isOut; } } } } } /// /// 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 ChartRect /// 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. /// private void InterpolatePoint( Graphics g, GraphPane pane, CurveItem curve, PointPair lastPt, float scaleFactor, Pen pen, float lastX, float lastY, float tmpX, float tmpY ) { try { RectangleF chartRect = pane.Chart._rect; // try to interpolate values bool lastIn = chartRect.Contains( lastX, lastY ); bool curIn = chartRect.Contains( tmpX, tmpY ); // If both points are outside the ChartRect, make a new point that is on the LastX/Y // side of the ChartRect, 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 ? chartRect.Left : chartRect.Right; newY = lastY + ( tmpY - lastY ) * ( newX - lastX ) / ( tmpX - lastX ); } else { newY = lastY < 0 ? chartRect.Top : chartRect.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 ? chartRect.Left : chartRect.Right; newY = tmpY + ( lastY - tmpY ) * ( newX - tmpX ) / ( lastX - tmpX ); } else { newY = tmpY < 0 ? chartRect.Top : chartRect.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 ); */ if ( !curve.IsSelected && this._gradientFill.IsGradientValueType ) { using ( Pen tPen = GetPen( pane, scaleFactor, lastPt ) ) { if ( this.StepType == StepType.ForwardStep ) { g.DrawLine( tPen, lastX, lastY, tmpX, lastY ); g.DrawLine( tPen, tmpX, lastY, tmpX, tmpY ); } else if ( this.StepType == StepType.RearwardStep ) { g.DrawLine( tPen, lastX, lastY, lastX, tmpY ); g.DrawLine( tPen, lastX, tmpY, tmpX, tmpY ); } else // non-step g.DrawLine( tPen, lastX, lastY, tmpX, tmpY ); } } else { 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 { } } /// /// Build an array of values (pixel coordinates) that represents /// the current curve. Note that this drawing routine ignores /// values, but it does not "break" the line to indicate values are missing. /// /// A reference to the object that is the parent or /// owner of this object. /// A representing this /// curve. /// An array of values in pixel /// coordinates representing the current curve. /// The number of points contained in the "arrPoints" /// parameter. /// true for a successful points array build, false for data problems 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[( _stepType == DrawGraph.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; } if ( x == PointPair.Missing || y == PointPair.Missing ) continue; // Transform the user scale values to pixel locations Axis xAxis = curve.GetXAxis( pane ); curX = 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 ( _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; } } /// /// Build an array of values (pixel coordinates) that represents /// the low values for the current curve. /// /// Note that this drawing routine ignores /// values, but it does not "break" the line to indicate values are missing. /// /// A reference to the object that is the parent or /// owner of this object. /// A representing this /// curve. /// An array of values in pixel /// coordinates representing the current curve. /// The number of points contained in the "arrPoints" /// parameter. /// true for a successful points array build, false for data problems 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[( _stepType == DrawGraph.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 ); if ( x == PointPair.Missing || y == PointPair.Missing ) continue; // Transform the user scale values to pixel locations Axis xAxis = curve.GetXAxis( pane ); curX = 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 ( _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; } } /// /// Close off a that defines a curve /// /// A reference to the object that is the parent or /// owner of this object. /// A representing this /// curve. /// An array of values in screen pixel /// coordinates representing the current curve. /// The number of points contained in the "arrPoints" /// parameter. /// The Y axis value location where the X axis crosses. /// The class that represents the curve. 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 = _isSmooth ? _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 } }