//============================================================================
//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
}
}