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