//============================================================================ //PointPairList Class //Copyright ?2004 Jerry Vos // //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.Collections.Generic; namespace DrawGraph { /// /// A collection class containing a list of objects /// that define the set of points to be displayed on the curve. /// /// /// /// /// Jerry Vos based on code by John Champion /// modified by John Champion /// $Revision: 3.36 $ $Date: 2007/02/18 05:51:54 $ [Serializable] public class PointPairList : List, IPointList, IPointListEdit { #region Fields /// Private field to maintain the sort status of this /// . Use the public property /// to access this value. /// protected bool _sorted = true; #endregion #region Properties /* /// /// Indexer to access the specified object by /// its ordinal position in the list. /// /// The ordinal position (zero-based) of the /// object to be accessed. /// A object reference. public PointPair this[ int index ] { get { return (PointPair) List[index]; } set { List[index] = value; } } */ /// /// true if the list is currently sorted. /// /// public bool Sorted { get { return _sorted; } } #endregion #region Constructors /// /// Default constructor for the collection class /// public PointPairList() { _sorted = false; } /// /// Constructor to initialize the PointPairList from two arrays of /// type double. /// public PointPairList( double[] x, double[] y ) { Add( x, y ); _sorted = false; } /// /// Constructor to initialize the PointPairList from an IPointList /// public PointPairList( IPointList list ) { int count = list.Count; for ( int i = 0; i < count; i++ ) Add( list[i] ); _sorted = false; } /// /// Constructor to initialize the PointPairList from three arrays of /// type double. /// public PointPairList( double[] x, double[] y, double[] baseVal ) { Add( x, y, baseVal ); _sorted = false; } /// /// The Copy Constructor /// /// The PointPairList from which to copy public PointPairList( PointPairList rhs ) { Add( rhs ); _sorted = false; } /// /// 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 PointPairList Clone() { return new PointPairList( this ); } #endregion #region Methods /// /// Add a object to the collection at the end of the list. /// /// The object to /// be added /// The zero-based ordinal index where the point was added in the list. public new void Add( PointPair point ) { _sorted = false; base.Add( new PointPair( point ) ); } /// /// Add a object to the collection at the end of the list. /// /// A reference to the object to /// be added /// The zero-based ordinal index where the last point was added in the list, /// or -1 if no points were added. public void Add( PointPairList pointList ) { foreach ( PointPair point in pointList ) Add( point ); _sorted = false; } /// /// Add a set of points to the PointPairList from two arrays of type double. /// If either array is null, then a set of ordinal values is automatically /// generated in its place (see . /// If the arrays are of different size, then the larger array prevails and the /// smaller array is padded with values. /// /// A double[] array of X values /// A double[] array of Y values /// The zero-based ordinal index where the last point was added in the list, /// or -1 if no points were added. public void Add( double[] x, double[] y ) { int len = 0; if ( x != null ) len = x.Length; if ( y != null && y.Length > len ) len = y.Length; for ( int i=0; i /// Add a set of points to the from three arrays of type double. /// If the X or Y array is null, then a set of ordinal values is automatically /// generated in its place (see . If the /// is null, then it is set to zero. /// If the arrays are of different size, then the larger array prevails and the /// smaller array is padded with values. /// /// A double[] array of X values /// A double[] array of Y values /// A double[] array of Z or lower-dependent axis values /// The zero-based ordinal index where the last point was added in the list, /// or -1 if no points were added. public void Add( double[] x, double[] y, double[] z ) { int len = 0; if ( x != null ) len = x.Length; if ( y != null && y.Length > len ) len = y.Length; if ( z != null && z.Length > len ) len = z.Length; for ( int i=0; i /// Add a single point to the from values of type double. /// /// The X value /// The Y value /// The zero-based ordinal index where the point was added in the list. public void Add( double x, double y ) { _sorted = false; PointPair point = new PointPair( x, y ); base.Add( point ); } /// /// Add a single point to the from values of type double. /// /// The X value /// The Y value /// The Tag value for the PointPair /// The zero-based ordinal index where the point was added in the list. public void Add( double x, double y, string tag ) { _sorted = false; PointPair point = new PointPair( x, y, tag ); base.Add( point ); } /// /// Add a single point to the from values of type double. /// /// The X value /// The Y value /// The Z or lower dependent axis value /// The zero-based ordinal index where the point was added /// in the list. public void Add( double x, double y, double z ) { _sorted = false; PointPair point = new PointPair( x, y, z ); base.Add( point ); } /// /// Add a single point to the from values of type double. /// /// The X value /// The Y value /// The Z or lower dependent axis value /// The Tag value for the PointPair /// The zero-based ordinal index where the point was added /// in the list. public void Add( double x, double y, double z, string tag ) { _sorted = false; PointPair point = new PointPair( x, y, z, tag ); base.Add( point ); } /// /// Add a object to the collection at the specified, /// zero-based, index location. /// /// /// The zero-based ordinal index where the point is to be added in the list. /// /// /// The object to be added. /// public new void Insert( int index, PointPair point ) { _sorted = false; base.Insert( index, point ); } /// /// Add a single point (from values of type double ) to the at the specified, /// zero-based, index location. /// /// /// The zero-based ordinal index where the point is to be added in the list. /// /// The X value /// The Y value public void Insert( int index, double x, double y ) { _sorted = false; base.Insert( index, new PointPair( x, y ) ); } /// /// Add a single point (from values of type double ) to the at the specified, /// zero-based, index location. /// /// /// The zero-based ordinal index where the point is to be added in the list. /// /// The X value /// The Y value /// The Z or lower dependent axis value public void Insert( int index, double x, double y, double z ) { _sorted = false; Insert( index, new PointPair( x, y, z ) ); } /* /// /// Remove the specified object from the collection based /// the point values (must match exactly). /// /// /// A that is to be removed by value. /// /// public void Remove( PointPair pt ) { List.Remove( pt ); } /// /// Return the zero-based position index of the specified /// in the collection. /// /// The object that is to be found. /// /// The zero-based index of the specified , or -1 if the /// is not in the list /// public int IndexOf( PointPair pt ) { return List.IndexOf( pt ); } */ /// /// Return the zero-based position index of the /// with the specified label . /// /// The object must be of type /// for this method to find it. /// The label that is in the /// attribute of the item to be found. /// /// The zero-based index of the specified , /// or -1 if the is not in the list public int IndexOfTag( string label ) { int iPt = 0; foreach ( PointPair p in this ) { if ( p.Tag is string && String.Compare( (string) p.Tag, label, true ) == 0 ) return iPt; iPt++; } return -1; } /// /// Compare two objects to see if they are equal. /// /// Equality is based on equal count of items, and /// each individual must be equal (as per the /// method. /// The to be compared with for equality. /// true if the objects are equal, false otherwise. public override bool Equals( object obj ) { PointPairList rhs = obj as PointPairList; if( this.Count != rhs.Count ) return false; for( int i=0; i /// Return the HashCode from the base class. /// /// public override int GetHashCode() { return base.GetHashCode (); } /// /// Sorts the list according to the point x values. Will not sort the /// list if the list is already sorted. /// /// If the list was sorted before sort was called public new bool Sort() { // if it is already sorted we don't have to sort again if ( _sorted ) return true; Sort( new PointPair.PointPairComparer( SortType.XValues ) ); return false; } /// /// Sorts the list according to the point values . Will not sort the /// list if the list is already sorted. /// /// The ///used to determine whether the X or Y values will be used to sort ///the list /// If the list was sorted before sort was called public bool Sort( SortType type) { // if it is already sorted we don't have to sort again if ( _sorted ) return true; this.Sort( new PointPair.PointPairComparer( type ) ); return false; } /// /// Set the X values for this from the specified /// array of double values. /// /// /// If has more values than /// this list, then the extra values will be ignored. If /// has less values, then the corresponding values /// will not be changed. That is, if the has 20 values /// and has 15 values, then the first 15 values of the /// will be changed, and the last 5 values will not be /// changed. /// /// An array of double values that will replace the existing X /// values in the . public void SetX( double[] x ) { for ( int i=0; i /// Set the Y values for this from the specified /// array of double values. /// /// /// If has more values than /// this list, then the extra values will be ignored. If /// has less values, then the corresponding values /// will not be changed. That is, if the has 20 values /// and has 15 values, then the first 15 values of the /// will be changed, and the last 5 values will not be /// changed. /// /// An array of double values that will replace the existing Y /// values in the . public void SetY( double[] y ) { for ( int i=0; i /// Set the Z values for this from the specified /// array of double values. /// /// /// If has more values than /// this list, then the extra values will be ignored. If /// has less values, then the corresponding values /// will not be changed. That is, if the has 20 values /// and has 15 values, then the first 15 values of the /// will be changed, and the last 5 values will not be /// changed. /// /// An array of double values that will replace the existing Z /// values in the . public void SetZ( double[] z ) { for ( int i=0; i /// Add the Y values from the specified object to this /// . If has more values than /// this list, then the extra values will be ignored. If /// has less values, the missing values are assumed to be zero. /// /// A reference to the object to /// be summed into the this . public void SumY( PointPairList sumList ) { for ( int i=0; i /// Add the X values from the specified object to this /// . If has more values than /// this list, then the extra values will be ignored. If /// has less values, the missing values are assumed to be zero. /// /// A reference to the object to /// be summed into the this . public void SumX( PointPairList sumList ) { for ( int i=0; i /// Linearly interpolate the data to find an arbitraty Y value that corresponds to the specified X value. /// /// /// This method uses linear interpolation with a binary search algorithm. It therefore /// requires that the x data be monotonically increasing. Missing values are not allowed. This /// method will extrapolate outside the range of the PointPairList if necessary. /// /// The target X value on which to interpolate /// The Y value that corresponds to the value. public double InterpolateX( double xTarget ) { int lo, mid, hi; if ( this.Count < 2 ) throw new Exception( "Error: Not enough points in curve to interpolate" ); if ( xTarget <= this[0].X ) { lo = 0; hi = 1; } else if ( xTarget >= this[this.Count-1].X ) { lo = this.Count - 2; hi = this.Count - 1; } else { // if x is within the bounds of the x table, then do a binary search // in the x table to find table entries that bound the x value lo = 0; hi = this.Count - 1; // limit to 1000 loops to avoid an infinite loop problem int j; for ( j=0; j<1000 && hi > lo + 1; j++ ) { mid = ( hi + lo ) / 2; if ( xTarget > this[mid].X ) lo = mid; else hi = mid; } if ( j >= 1000 ) throw new Exception( "Error: Infinite loop in interpolation" ); } return ( xTarget - this[lo].X ) / ( this[hi].X - this[lo].X ) * ( this[hi].Y - this[lo].Y ) + this[lo].Y; } /// /// Use Cardinal Splines to Interpolate the data to find an arbitraty Y value that corresponds to /// the specified X value. /// /// /// This method uses cardinal spline interpolation with a binary search algorithm. It therefore /// requires that the x data be monotonically increasing. Missing values are not allowed. This /// method will not extrapolate outside the range of the PointPairList (it returns /// if extrapolation would be required). WARNING: Cardinal /// spline interpolation can generate curves with non-unique X values for higher tension /// settings. That is, there may be multiple X values for the same Y value. This routine /// follows the path of the spline curve until it reaches the FIRST OCCURRENCE of the /// target X value. It does not check to see if other solutions are possible. /// /// The target X value on which to interpolate /// The tension setting that controls the curvature of the spline fit. /// Typical values are between 0 and 1, where 0 is a linear fit, and 1 is lots of "roundness". /// Values greater than 1 may give odd results. /// /// The Y value that corresponds to the value. public double SplineInterpolateX( double xTarget, double tension ) { // Scale the tension value to be compatible with the GDI+ values tension /= 3.0; int lo, mid, hi; if ( this.Count < 2 ) throw new Exception( "Error: Not enough points in curve to interpolate" ); // Extrapolation not allowed if ( xTarget <= this[0].X || xTarget >= this[this.Count-1].X ) return PointPair.Missing; else { // if x is within the bounds of the x table, then do a binary search // in the x table to find table entries that bound the x value lo = 0; hi = this.Count - 1; // limit to 1000 loops to avoid an infinite loop problem int j; for ( j=0; j<1000 && hi > lo + 1; j++ ) { mid = ( hi + lo ) / 2; if ( xTarget > this[mid].X ) lo = mid; else hi = mid; } if ( j >= 1000 ) throw new Exception( "Error: Infinite loop in interpolation" ); } // At this point, we know the two bounding points around our point of interest // We need the four points that surround our point double X0, X1, X2, X3; double Y0, Y1, Y2, Y3; double B0, B1, B2, B3; X1 = this[lo].X; X2 = this[hi].X; Y1 = this[lo].Y; Y2 = this[hi].Y; // if we are at either the beginning of the table or the end, then make up a before // and/or after point to fill in the four points if ( lo == 0 ) { X0 = X1 - ( X2 - X1 )/3; Y0 = Y1 - ( Y2 - Y1 )/3; } else { X0 = this[lo-1].X; Y0 = this[lo-1].Y; } if ( hi == this.Count - 1 ) { X3 = X2 + ( X2 - X1 )/3; Y3 = Y2 + ( Y2 - Y1 )/3; } else { X3 = this[hi+1].X; Y3 = this[hi+1].Y; } double newX, newY, lastX = X1, lastY = Y1; // Do 100 steps to find the result for ( double t=0.01; t<=1; t+=0.01 ) { B0 = (1 - t) * (1 - t) * (1 - t); B1 = 3.0 * t * (1 - t) * (1 - t); B2 = 3.0 * t * t * (1 - t); B3 = t * t * t; newX = X1 * B0 + (X1 + (X2 - X0) * tension) * B1 + (X2 - (X3 - X1) * tension) * B2 + X2 * B3; newY = Y1 * B0 + (Y1 + (Y2 - Y0) * tension) * B1 + (Y2 - (Y3 - Y1) * tension) * B2 + Y2 * B3; // We are looking for the first X that exceeds the target if ( newX >= xTarget ) { // We now have two bounding X values around our target // use linear interpolation to minimize the discretization // error. return ( xTarget - lastX ) / ( newX - lastX ) * ( newY - lastY ) + lastY; } lastX = newX; lastY = newY; } // This should never happen return Y2; } /// /// Linearly interpolate the data to find an arbitraty X value that corresponds to the specified Y value. /// /// /// This method uses linear interpolation with a binary search algorithm. It therefore /// requires that the Y data be monotonically increasing. Missing values are not allowed. This /// method will extrapolate outside the range of the PointPairList if necessary. /// /// The target Y value on which to interpolate /// The X value that corresponds to the value. public double InterpolateY( double yTarget ) { int lo, mid, hi; if ( this.Count < 2 ) throw new Exception( "Error: Not enough points in curve to interpolate" ); if ( yTarget <= this[0].Y ) { lo = 0; hi = 1; } else if ( yTarget >= this[this.Count-1].Y ) { lo = this.Count - 2; hi = this.Count - 1; } else { // if y is within the bounds of the y table, then do a binary search // in the y table to find table entries that bound the y value lo = 0; hi = this.Count - 1; // limit to 1000 loops to avoid an infinite loop problem int j; for ( j=0; j<1000 && hi > lo + 1; j++ ) { mid = ( hi + lo ) / 2; if ( yTarget > this[mid].Y ) lo = mid; else hi = mid; } if ( j >= 1000 ) throw new Exception( "Error: Infinite loop in interpolation" ); } return ( yTarget - this[lo].Y ) / ( this[hi].Y - this[lo].Y ) * ( this[hi].X - this[lo].X ) + this[lo].X; } /// /// Use linear regression to form a least squares fit of an existing /// instance. /// /// The output will cover the /// same X range of data as the original dataset. /// /// An instance containing /// the data to be regressed. /// The number of desired points to be included /// in the resultant . /// /// A new containing the resultant /// data fit. /// public PointPairList LinearRegression( IPointList points, int pointCount ) { double minX = double.MaxValue; double maxX = double.MinValue; for ( int i=0; i maxX ? pt.X : maxX; } } return LinearRegression( points, pointCount, minX, maxX ); } /// /// Use linear regression to form a least squares fit of an existing /// instance. /// /// An instance containing /// the data to be regressed. /// The number of desired points to be included /// in the resultant . /// /// The minimum X value of the resultant /// . /// The maximum X value of the resultant /// . /// A new containing the resultant /// data fit. /// /// Brian Chappell - lazarusds /// modified by John Champion public PointPairList LinearRegression( IPointList points, int pointCount, double minX, double maxX ) { double x = 0, y = 0, xx = 0, xy = 0, count = 0; for ( int i = 0; i < points.Count; i++ ) { PointPair pt = points[i]; if ( !pt.IsInvalid ) { x += points[i].X; y += points[i].Y; xx += points[i].X * points[i].X; xy += points[i].X * points[i].Y; count++; } } if ( count < 2 || maxX - minX < 1e-20 ) return null; double slope = ( count * xy - x * y ) / ( count * xx - x * x ); double intercept = ( y - slope * x ) / count; PointPairList newPoints = new PointPairList(); double stepSize = ( maxX - minX ) / pointCount; double value = minX; for ( int i = 0; i < pointCount; i++ ) { newPoints.Add( new PointPair( value, value * slope + intercept ) ); value += stepSize; } return newPoints; } #endregion } }