359 lines
12 KiB
C#
359 lines
12 KiB
C#
//============================================================================
|
|
//ZedGraph Class Library - A Flexible Line Graph/Bar Graph Library in C#
|
|
//Copyright ?2006 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.Collections.Generic;
|
|
using System.Text;
|
|
using System.Drawing;
|
|
|
|
namespace DrawGraph
|
|
{
|
|
/// <summary>
|
|
/// A simple storage struct to maintain an individual sampling of data. This only
|
|
/// contains two data values in order to reduce to memory load for large datasets.
|
|
/// (e.g., no Tag or Z property)
|
|
/// </summary>
|
|
public struct DataPoint
|
|
{
|
|
/// <summary>
|
|
/// The X value for the point, stored as a double type.
|
|
/// </summary>
|
|
public double X;
|
|
/// <summary>
|
|
/// The Y value for the point, stored as a double type.
|
|
/// </summary>
|
|
public double Y;
|
|
}
|
|
|
|
/// <summary>
|
|
/// A collection class to maintain a set of samples.
|
|
/// </summary>
|
|
/// <remarks>This type, intended for very
|
|
/// large datasets, will reduce the number of points displayed by eliminating
|
|
/// individual points that overlay (at the same pixel location) on the graph.
|
|
/// Note that this type probably does not make sense for line plots, but is intended
|
|
/// primarily for scatter plots.
|
|
/// </remarks>
|
|
///
|
|
/// <author> John Champion </author>
|
|
/// <version> $Revision: 3.5 $ $Date: 2007/06/02 06:56:03 $ </version>
|
|
[Serializable]
|
|
public class NoDupePointList : List<DataPoint>, IPointList, IPointListEdit
|
|
{
|
|
/// <summary>
|
|
/// Protected field that stores a value indicating whether or not the data have been filtered.
|
|
/// If the data have not been filtered, then <see cref="Count" /> will be equal to
|
|
/// <see cref="TotalCount" />. Use the public property <see cref="IsFiltered" /> to
|
|
/// access this value.
|
|
/// </summary>
|
|
protected bool _isFiltered;
|
|
/// <summary>
|
|
/// Protected field that stores the number of data points after filtering (e.g.,
|
|
/// <see cref="FilterData" /> has been called). The <see cref="Count" /> property
|
|
/// returns the total count for an unfiltered dataset, or <see cref="_filteredCount" />
|
|
/// for a dataset that has been filtered.
|
|
/// </summary>
|
|
protected int _filteredCount;
|
|
/// <summary>
|
|
/// Protected array of indices for all the points that are currently visible. This only
|
|
/// applies if <see cref="IsFiltered" /> is true.
|
|
/// </summary>
|
|
protected int[] _visibleIndicies;
|
|
|
|
|
|
/// <summary>
|
|
/// Protected field that stores a value that determines how close a point must be to a prior
|
|
/// neighbor in order to be filtered out. Use the public property <see cref="FilterMode" />
|
|
/// to access this value.
|
|
/// </summary>
|
|
protected int _filterMode;
|
|
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value that determines how close a point must be to a prior
|
|
/// neighbor in order to be filtered out.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// A value of 0 indicates that subsequent
|
|
/// points must coincide exactly at the same pixel location. A value of 1 or more
|
|
/// indicates that number of pixels distance from a prior point that will cause
|
|
/// a new point to be filtered out. For example, a value of 2 means that, once
|
|
/// a particular pixel location is taken, any subsequent point that lies within 2
|
|
/// pixels of that location will be filtered out.
|
|
/// </remarks>
|
|
public int FilterMode
|
|
{
|
|
get { return _filterMode; }
|
|
set { _filterMode = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether or not the data have been filtered. If the data
|
|
/// have not been filtered, then <see cref="Count" /> will be equal to
|
|
/// <see cref="TotalCount" />.
|
|
/// </summary>
|
|
public bool IsFiltered
|
|
{
|
|
get { return _isFiltered; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Indexer: get the DataPoint instance at the specified ordinal position in the list
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This method will throw an exception if the index is out of range. This can happen
|
|
/// if the index is less than the number of filtered values, or if data points are
|
|
/// removed from a filtered dataset with updating the filter (by calling
|
|
/// <see cref="FilterData" />).
|
|
/// </remarks>
|
|
/// <param name="index">The ordinal position in the list of points</param>
|
|
/// <returns>Returns a <see cref="PointPair" /> instance. The <see cref="PointPair.Z" />
|
|
/// and <see cref="PointPair.Tag" /> properties will be defaulted to
|
|
/// <see cref="PointPairBase.Missing" /> and null, respectively.
|
|
/// </returns>
|
|
public new PointPair this[int index]
|
|
{
|
|
get
|
|
{
|
|
int j = index;
|
|
if ( _isFiltered )
|
|
j = _visibleIndicies[index];
|
|
|
|
DataPoint dp = base[j];
|
|
PointPair pt = new PointPair( dp.X, dp.Y );
|
|
return pt;
|
|
}
|
|
set
|
|
{
|
|
int j = index;
|
|
if ( _isFiltered )
|
|
j = _visibleIndicies[index];
|
|
|
|
DataPoint dp;
|
|
dp.X = value.X;
|
|
dp.Y = value.Y;
|
|
base[j] = dp;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the number of active samples in the collection. This is the number of
|
|
/// samples that are non-duplicates. See the <see cref="TotalCount" /> property
|
|
/// to get the total number of samples in the list.
|
|
/// </summary>
|
|
public new int Count
|
|
{
|
|
get
|
|
{
|
|
if ( !_isFiltered )
|
|
return base.Count;
|
|
else
|
|
return _filteredCount;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the total number of samples in the collection. See the <see cref="Count" />
|
|
/// property to get the number of active (non-duplicate) samples in the list.
|
|
/// </summary>
|
|
public int TotalCount
|
|
{
|
|
get { return base.Count; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Append a data point to the collection
|
|
/// </summary>
|
|
/// <param name="pt">The <see cref="PointPair" /> value to append</param>
|
|
public void Add( PointPair pt )
|
|
{
|
|
DataPoint dp = new DataPoint();
|
|
dp.X = pt.X;
|
|
dp.Y = pt.Y;
|
|
Add( dp );
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Append a point to the collection
|
|
/// </summary>
|
|
/// <param name="x">The x value of the point to append</param>
|
|
/// <param name="y">The y value of the point to append</param>
|
|
public void Add( double x, double y )
|
|
{
|
|
DataPoint dp = new DataPoint();
|
|
dp.X = x;
|
|
dp.Y = y;
|
|
Add( dp );
|
|
}
|
|
|
|
|
|
// generic Clone: just call the typesafe version
|
|
object ICloneable.Clone()
|
|
{
|
|
return this.Clone();
|
|
}
|
|
|
|
/// <summary>
|
|
/// typesafe clone method
|
|
/// </summary>
|
|
/// <returns>A new cloned NoDupePointList. This returns a copy of the structure,
|
|
/// but it does not duplicate the data (it just keeps a reference to the original)
|
|
/// </returns>
|
|
public NoDupePointList Clone()
|
|
{
|
|
return new NoDupePointList( this );
|
|
}
|
|
|
|
/// <summary>
|
|
/// default constructor
|
|
/// </summary>
|
|
public NoDupePointList()
|
|
{
|
|
_isFiltered = false;
|
|
_filteredCount = 0;
|
|
_visibleIndicies = null;
|
|
_filterMode = 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// copy constructor -- this returns a copy of the structure,
|
|
/// but it does not duplicate the data (it just keeps a reference to the original)
|
|
/// </summary>
|
|
/// <param name="rhs">The NoDupePointList to be copied</param>
|
|
public NoDupePointList( NoDupePointList rhs )
|
|
{
|
|
int count = rhs.TotalCount;
|
|
for ( int i = 0; i < count; i++ )
|
|
Add( rhs.GetDataPointAt( i ) );
|
|
|
|
_filteredCount = rhs._filteredCount;
|
|
_isFiltered = rhs._isFiltered;
|
|
_filterMode = rhs._filterMode;
|
|
|
|
if ( rhs._visibleIndicies != null )
|
|
_visibleIndicies = (int[]) rhs._visibleIndicies.Clone();
|
|
else
|
|
_visibleIndicies = null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Protected method to access the internal DataPoint collection, without any
|
|
/// translation to a PointPair.
|
|
/// </summary>
|
|
/// <param name="index">The ordinal position of the DataPoint of interest</param>
|
|
protected DataPoint GetDataPointAt( int index )
|
|
{
|
|
return base[index];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clears any filtering previously done by a call to <see cref="FilterData" />.
|
|
/// After calling this method, all data points will be visible, and
|
|
/// <see cref="Count" /> will be equal to <see cref="TotalCount" />.
|
|
/// </summary>
|
|
public void ClearFilter()
|
|
{
|
|
_isFiltered = false;
|
|
_filteredCount = 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Go through the collection, and hide (filter out) any points that fall on the
|
|
/// same pixel location as a previously included point.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This method does not delete any points, it just temporarily hides them until
|
|
/// the next call to <see cref="FilterData" /> or <see cref="ClearFilter" />.
|
|
/// You should call <see cref="FilterData" /> once your collection of points has
|
|
/// been constructed. You may need to call <see cref="FilterData" /> again if
|
|
/// you add points, or if the chart rect changes size (by resizing, printing,
|
|
/// image save, etc.), or if the scale range changes.
|
|
/// You must call <see cref="GraphPane.AxisChange()" /> before calling
|
|
/// this method so that the <see cref="Chart.Rect">GraphPane.Chart.Rect</see>
|
|
/// and the scale ranges are valid. This method is not valid for
|
|
/// ordinal axes (but ordinal axes don't make sense for very large datasets
|
|
/// anyway).
|
|
/// </remarks>
|
|
/// <param name="pane">The <see cref="GraphPane" /> into which the data
|
|
/// will be plotted. </param>
|
|
/// <param name="yAxis">The <see cref="Axis" /> class to be used in the Y direction
|
|
/// for plotting these data. This can be a <see cref="YAxis" /> or a
|
|
/// <see cref="Y2Axis" />, and can be a primary or secondary axis (if multiple Y or Y2
|
|
/// axes are being used).
|
|
/// </param>
|
|
/// <param name="xAxis">The <see cref="Axis" /> class to be used in the X direction
|
|
/// for plotting these data. This can be an <see cref="XAxis" /> or a
|
|
/// <see cref="X2Axis" />.
|
|
/// </param>
|
|
public void FilterData( GraphPane pane, Axis xAxis, Axis yAxis )
|
|
{
|
|
if ( _visibleIndicies == null || _visibleIndicies.Length < base.Count )
|
|
_visibleIndicies = new int[base.Count];
|
|
|
|
_filteredCount = 0;
|
|
_isFiltered = true;
|
|
|
|
int width = (int)pane.Chart.Rect.Width;
|
|
int height = (int)pane.Chart.Rect.Height;
|
|
if ( width <= 0 || height <= 0 )
|
|
throw new IndexOutOfRangeException( "Error in FilterData: Chart rect not valid" );
|
|
|
|
bool[,] usedArray = new bool[width, height];
|
|
for ( int i = 0; i < width; i++ )
|
|
for ( int j = 0; j < height; j++ )
|
|
usedArray[i, j] = false;
|
|
|
|
xAxis.Scale.SetupScaleData( pane, xAxis );
|
|
yAxis.Scale.SetupScaleData( pane, yAxis );
|
|
|
|
int n = _filterMode < 0 ? 0 : _filterMode;
|
|
int left = (int)pane.Chart.Rect.Left;
|
|
int top = (int)pane.Chart.Rect.Top;
|
|
|
|
for ( int i=0; i<base.Count; i++ )
|
|
{
|
|
DataPoint dp = base[i];
|
|
int x = (int)( xAxis.Scale.Transform( dp.X ) + 0.5 ) - left;
|
|
int y = (int)( yAxis.Scale.Transform( dp.Y ) + 0.5 ) - top;
|
|
|
|
if ( x >= 0 && x < width && y >= 0 && y < height )
|
|
{
|
|
bool used = false;
|
|
if ( n <= 0 )
|
|
used = usedArray[x, y];
|
|
else
|
|
{
|
|
for ( int ix = x - n; ix <= x + n; ix++ )
|
|
for ( int iy = y - n; iy <= y + n; iy++ )
|
|
used |= ( ix >= 0 && ix < width && iy >= 0 && iy < height && usedArray[ix, iy] );
|
|
}
|
|
|
|
if ( !used )
|
|
{
|
|
usedArray[x, y] = true;
|
|
_visibleIndicies[_filteredCount] = i;
|
|
_filteredCount++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|