//============================================================================
//ZedGraph Class Library - A Flexible Line Graph/Bar Graph Library in C#
//Copyright ?2006 John Champion
//RollingPointPairList class Copyright ?2006 by Colin Green
//
//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.Text;
using System.Runtime.Serialization;
using System.Security.Permissions;
namespace DrawGraph
{
///
/// A class that provides a rolling list of objects.
/// This is essentially a
/// first-in-first-out (FIFO) queue with a fixed capacity which allows 'rolling'
/// (or oscilloscope like) graphs to be be animated without having the overhead of an
/// ever-growing ArrayList.
///
/// The queue is constructed with a fixed capacity and new points can be enqueued. When the
/// capacity is reached the oldest (first in) PointPair is overwritten. However, when
/// accessing via , the objects are
/// seen in the order in which they were enqeued.
///
/// RollingPointPairList supports data editing through the
/// interface.
///
/// Colin Green with mods by John Champion
/// $Date: 2007/02/18 05:51:54 $
///
[Serializable]
public class RollingPointPairList : IPointList, ISerializable, IPointListEdit
{
#region Fields
///
/// An array of PointPair objects that acts as the underlying buffer.
///
protected PointPair[] _mBuffer;
///
/// The index of the previously enqueued item. -1 if buffer is empty.
///
protected int _headIdx;
///
/// The index of the next item to be dequeued. -1 if buffer is empty.
///
protected int _tailIdx;
#endregion
#region Constructors
///
/// Constructs an empty buffer with the specified capacity.
///
/// Number of elements in the rolling list. This number
/// cannot be changed once the RollingPointPairList is constructed.
public RollingPointPairList( int capacity )
: this( capacity, false )
{
_mBuffer = new PointPair[capacity];
_headIdx = _tailIdx = -1;
}
///
/// Constructs an empty buffer with the specified capacity. Pre-allocates space
/// for all PointPair's in the list if is true.
///
/// Number of elements in the rolling list. This number
/// cannot be changed once the RollingPointPairList is constructed.
/// true to pre-allocate all PointPair instances in
/// the list, false otherwise. Note that in order to be memory efficient,
/// the method should be used to add
/// data. Avoid the method.
///
///
public RollingPointPairList( int capacity, bool preLoad )
{
_mBuffer = new PointPair[capacity];
_headIdx = _tailIdx = -1;
if ( preLoad )
for ( int i = 0; i < capacity; i++ )
_mBuffer[i] = new PointPair();
}
///
/// Constructs a buffer with a copy of the items within the provided
/// .
/// The is set to the length of the provided list.
///
/// The to be copied.
public RollingPointPairList( IPointList rhs )
{
_mBuffer = new PointPair[rhs.Count];
for ( int i = 0; i < rhs.Count; i++ )
{
_mBuffer[i] = new PointPair( rhs[i] );
}
_headIdx = rhs.Count - 1;
_tailIdx = 0;
}
#endregion
#region Properties
///
/// Gets the capacity of the rolling buffer.
///
public int Capacity
{
get { return _mBuffer.Length; }
}
///
/// Gets the count of items within the rolling buffer. Note that this may be less than
/// the capacity.
///
public int Count
{
get
{
if ( _headIdx == -1 )
return 0;
if ( _headIdx > _tailIdx )
return ( _headIdx - _tailIdx ) + 1;
if ( _tailIdx > _headIdx )
return ( _mBuffer.Length - _tailIdx ) + _headIdx + 1;
return 1;
}
}
///
/// Gets a bolean that indicates if the buffer is empty.
/// Alternatively you can test Count==0.
///
public bool IsEmpty
{
get { return _headIdx == -1; }
}
///
/// Gets or sets the at the specified index in the buffer.
///
///
/// Index must be within the current size of the buffer, e.g., the set
/// method will not expand the buffer even if is available
///
public PointPair this[int index]
{
get
{
if ( index >= Count || index < 0 )
throw new ArgumentOutOfRangeException();
index += _tailIdx;
if ( index >= _mBuffer.Length )
index -= _mBuffer.Length;
return _mBuffer[index];
}
set
{
if ( index >= Count || index < 0 )
throw new ArgumentOutOfRangeException();
index += _tailIdx;
if ( index >= _mBuffer.Length )
index -= _mBuffer.Length;
_mBuffer[index] = value;
}
}
#endregion
#region Public Methods
///
/// 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 RollingPointPairList Clone()
{
return new RollingPointPairList( this );
}
///
/// Clear the buffer of all objects.
/// Note that the remains unchanged.
///
public void Clear()
{
_headIdx = _tailIdx = -1;
}
///
/// Calculate that the next index in the buffer that should receive a new data point.
/// Note that this method actually advances the buffer, so a datapoint should be
/// added at _mBuffer[_headIdx].
///
/// The index position of the new head element
private int GetNextIndex()
{
if ( _headIdx == -1 )
{ // buffer is currently empty.
_headIdx = _tailIdx = 0;
}
else
{
// Determine the index to write to.
if ( ++_headIdx == _mBuffer.Length )
{ // Wrap around.
_headIdx = 0;
}
if ( _headIdx == _tailIdx )
{ // Buffer overflow. Increment tailIdx.
if ( ++_tailIdx == _mBuffer.Length )
{ // Wrap around.
_tailIdx = 0;
}
}
}
return _headIdx;
}
///
/// Add a onto the head of the queue,
/// overwriting old values if the buffer is full.
///
/// The to be added.
public void Add( PointPair item )
{
_mBuffer[ GetNextIndex() ] = item;
}
///
/// Add an object to the head of the queue.
///
/// A reference to the object to
/// be added
public void Add( IPointList pointList )
{ // A slightly more efficient approach would be to determine where the new points should placed within
// the buffer and to then copy them in directly - updating the head and tail indexes appropriately.
for ( int i = 0; i < pointList.Count; i++ )
Add( pointList[i] );
}
///
/// Remove an old item from the tail of the queue.
///
/// The removed item. Throws an
/// if the buffer was empty.
/// Check the buffer's length () or the
/// property to avoid exceptions.
public PointPair Remove()
{
if ( _tailIdx == -1 )
{ // buffer is currently empty.
throw new InvalidOperationException( "buffer is empty." );
}
PointPair o = _mBuffer[_tailIdx];
if ( _tailIdx == _headIdx )
{ // The buffer is now empty.
_headIdx = _tailIdx = -1;
return o;
}
if ( ++_tailIdx == _mBuffer.Length )
{ // Wrap around.
_tailIdx = 0;
}
return o;
}
///
/// Remove the at the specified index
///
///
/// All items in the queue that lie after will
/// be shifted back by one, and the queue will be one item shorter.
///
/// The ordinal position of the item to be removed.
/// Throws an if index is less than
/// zero or greater than or equal to
///
public void RemoveAt( int index )
{
int count = this.Count;
if ( index >= count || index < 0 )
throw new ArgumentOutOfRangeException();
// shift all the items that lie after index back by 1
for ( int i = index + _tailIdx; i < _tailIdx + count - 1; i++ )
{
i = ( i >= _mBuffer.Length ) ? 0 : i;
int j = i + 1;
j = ( j >= _mBuffer.Length ) ? 0 : j;
_mBuffer[i] = _mBuffer[j];
}
// Remove the item from the head (it's been duplicated already)
Pop();
}
///
/// Pop an item off the head of the queue.
///
/// The popped item. Throws an exception if the buffer was empty.
public PointPair Pop()
{
if ( _tailIdx == -1 )
{ // buffer is currently empty.
throw new InvalidOperationException( "buffer is empty." );
}
PointPair o = _mBuffer[_headIdx];
if ( _tailIdx == _headIdx )
{ // The buffer is now empty.
_headIdx = _tailIdx = -1;
return o;
}
if ( --_headIdx == -1 )
{ // Wrap around.
_headIdx = _mBuffer.Length - 1;
}
return o;
}
///
/// Peek at the item at the head of the queue.
///
/// The item at the head of the queue.
/// Throws an if the buffer was empty.
///
public PointPair Peek()
{
if ( _headIdx == -1 )
{ // buffer is currently empty.
throw new InvalidOperationException( "buffer is empty." );
}
return _mBuffer[_headIdx];
}
#endregion
#region Auxilliary Methods
///
/// Add a set of values onto the head of the queue,
/// overwriting old values if the buffer is full.
///
///
/// This method is much more efficient that the Add(PointPair)
/// method, since it does not require that a new PointPair instance be provided.
/// If the buffer already contains a at the head position,
/// then the x, y, z, and tag values will be copied into the existing PointPair.
/// Otherwise, a new PointPair instance must be created.
/// In this way, each PointPair position in the rolling list will only be allocated one time.
/// To truly be memory efficient, the , ,
/// and methods should be avoided. Also, the property
/// for this method should be null, since it is a reference type.
///
/// The X value
/// The Y value
/// The Z value
/// The Tag value for the PointPair
public void Add( double x, double y, double z, object tag )
{
// advance the rolling list
GetNextIndex();
if ( _mBuffer[_headIdx] == null )
_mBuffer[_headIdx] = new PointPair( x, y, z, tag );
else
{
_mBuffer[_headIdx].X = x;
_mBuffer[_headIdx].Y = y;
_mBuffer[_headIdx].Z = z;
_mBuffer[_headIdx].Tag = tag;
}
}
///
/// Add a set of values onto the head of the queue,
/// overwriting old values if the buffer is full.
///
///
/// This method is much more efficient that the Add(PointPair)
/// method, since it does not require that a new PointPair instance be provided.
/// If the buffer already contains a at the head position,
/// then the x, y, z, and tag values will be copied into the existing PointPair.
/// Otherwise, a new PointPair instance must be created.
/// In this way, each PointPair position in the rolling list will only be allocated one time.
/// To truly be memory efficient, the , ,
/// and methods should be avoided.
///
/// The X value
/// The Y value
public void Add( double x, double y )
{
Add( x, y, PointPair.Missing, null );
}
///
/// Add a set of values onto the head of the queue,
/// overwriting old values if the buffer is full.
///
///
/// This method is much more efficient that the Add(PointPair)
/// method, since it does not require that a new PointPair instance be provided.
/// If the buffer already contains a at the head position,
/// then the x, y, z, and tag values will be copied into the existing PointPair.
/// Otherwise, a new PointPair instance must be created.
/// In this way, each PointPair position in the rolling list will only be allocated one time.
/// To truly be memory efficient, the , ,
/// and methods should be avoided. Also, the property
/// for this method should be null, since it is a reference type.
///
/// The X value
/// The Y value
/// The Tag value for the PointPair
public void Add( double x, double y, object tag )
{
Add( x, y, PointPair.Missing, tag );
}
///
/// Add a set of values onto the head of the queue,
/// overwriting old values if the buffer is full.
///
///
/// This method is much more efficient that the Add(PointPair)
/// method, since it does not require that a new PointPair instance be provided.
/// If the buffer already contains a at the head position,
/// then the x, y, z, and tag values will be copied into the existing PointPair.
/// Otherwise, a new PointPair instance must be created.
/// In this way, each PointPair position in the rolling list will only be allocated one time.
/// To truly be memory efficient, the , ,
/// and methods should be avoided.
///
/// The X value
/// The Y value
/// The Z value
public void Add( double x, double y, double z )
{
Add( x, y, z, null );
}
///
/// Add a set of points to the
/// 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
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 < len; i++ )
{
PointPair point = new PointPair( 0, 0, 0 );
if ( x == null )
point.X = (double)i + 1.0;
else if ( i < x.Length )
point.X = x[i];
else
point.X = PointPair.Missing;
if ( y == null )
point.Y = (double)i + 1.0;
else if ( i < y.Length )
point.Y = y[i];
else
point.Y = PointPair.Missing;
Add( point );
}
}
///
/// 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 value
/// 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 values
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 < len; i++ )
{
PointPair point = new PointPair();
if ( x == null )
point.X = (double)i + 1.0;
else if ( i < x.Length )
point.X = x[i];
else
point.X = PointPair.Missing;
if ( y == null )
point.Y = (double)i + 1.0;
else if ( i < y.Length )
point.Y = y[i];
else
point.Y = PointPair.Missing;
if ( z == null )
point.Z = (double)i + 1.0;
else if ( i < z.Length )
point.Z = z[i];
else
point.Z = PointPair.Missing;
Add( point );
}
}
#endregion
#region Serialization
///
/// Current schema value that defines the version of the serialized file
///
public const int schema = 10;
///
/// Constructor for deserializing objects
///
/// A instance that defines the serialized data
///
/// A instance that contains the serialized data
///
protected RollingPointPairList( 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" );
_headIdx = info.GetInt32( "headIdx" );
_tailIdx = info.GetInt32( "tailIdx" );
_mBuffer = (PointPair[])info.GetValue( "mBuffer", typeof( PointPair[] ) );
}
///
/// 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 virtual void GetObjectData( SerializationInfo info, StreamingContext context )
{
info.AddValue( "schema", schema );
info.AddValue( "headIdx", _headIdx );
info.AddValue( "tailIdx", _tailIdx );
info.AddValue( "mBuffer", _mBuffer );
}
#endregion
}
}