//============================================================================
//ZedGraph Class Library - A Flexible Line Graph/Bar Graph Library in C#
//Copyright ?2004 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.Drawing;
using System.Collections.Generic;
namespace DrawGraph
{
///
/// A collection class containing a list of objects
/// that define the set of curves to be displayed on the graph.
///
///
/// John Champion
/// modified by Jerry Vos
/// $Revision: 3.41 $ $Date: 2007/04/16 00:03:01 $
[Serializable]
public class CurveList : List, ICloneable
{
#region Properties
// internal temporary value that keeps
// the max number of points for any curve
// associated with this curveList
private int maxPts;
///
/// Read only value for the maximum number of points in any of the curves
/// in the list.
///
public int MaxPts
{
get { return maxPts; }
}
///
/// Read only property that returns the number of curves in the list that are of
/// type .
///
public int NumBars
{
get
{
int count = 0;
foreach ( CurveItem curve in this )
{
if ( curve.IsBar )
count++;
}
return count;
}
}
///
/// Read only property that returns the number of pie slices in the list (class type is
/// ).
///
public int NumPies
{
get
{
int count = 0;
foreach ( CurveItem curve in this )
{
if ( curve.IsPie )
count++;
}
return count;
}
}
///
/// Read only property that determines if all items in the are
/// Pies.
///
public bool IsPieOnly
{
get
{
bool hasPie = false;
foreach ( CurveItem curve in this )
{
if ( !curve.IsPie )
return false;
else
hasPie = true;
}
return hasPie;
}
}
///
/// Determine if there is any data in any of the
/// objects for this graph. This method does not verify valid data, it
/// only checks to see if > 0.
///
/// true if there is any data, false otherwise
public bool HasData()
{
foreach( CurveItem curve in this )
{
if ( curve.Points.Count > 0 )
return true;
}
return false;
}
#endregion
#region Constructors
///
/// Default constructor for the collection class
///
public CurveList()
{
maxPts = 1;
}
///
/// The Copy Constructor
///
/// The XAxis object from which to copy
public CurveList( CurveList rhs )
{
this.maxPts = rhs.maxPts;
foreach ( CurveItem item in rhs )
{
this.Add( (CurveItem) ((ICloneable)item).Clone() );
}
}
///
/// 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 CurveList Clone()
{
return new CurveList( this );
}
#endregion
#region IEnumerable Methods
//CJBL
///
/// Iterate backwards through the items.
///
public IEnumerable Backward
{
get
{
for ( int i = this.Count - 1; i >= 0; i-- )
yield return this[i];
}
}
///
/// Iterate forward through the items.
///
public IEnumerable Forward
{
get
{
for ( int i = 0; i < this.Count; i++ )
yield return this[i];
}
}
#endregion
#region List Methods
/*
///
/// 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 CurveItem this[ int index ]
{
get { return( (CurveItem) List[index] ); }
set { List[index] = value; }
}
*/
///
/// Indexer to access the specified object by
/// its string.
///
/// The string label of the
/// object to be accessed.
/// A object reference.
public CurveItem this[ string label ]
{
get
{
int index = IndexOf( label );
if ( index >= 0 )
return( this[index] );
else
return null;
}
}
/*
///
/// Add a object to the collection at the end of the list.
///
/// A reference to the object to
/// be added
///
public void Add( CurveItem curve )
{
List.Add( curve );
}
*/
/*
///
/// Remove a object from the collection based on an object reference.
///
/// A reference to the object that is to be
/// removed.
///
public void Remove( CurveItem curve )
{
List.Remove( curve );
}
*/
/*
///
/// Insert a object into the collection at the specified
/// zero-based index location.
///
/// The zero-based index location for insertion.
/// A reference to the object that is to be
/// inserted.
///
public void Insert( int index, CurveItem curve )
{
List.Insert( index, curve );
}
*/
///
/// Return the zero-based position index of the
/// with the specified .
///
/// 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 IndexOf( string label )
{
int index = 0;
foreach ( CurveItem p in this )
{
if ( String.Compare( p._label._text, label, true ) == 0 )
return index;
index++;
}
return -1;
}
///
/// Return the zero-based position index of the
/// with the specified .
///
/// In order for this method to work, the
/// property must be of type .
/// The tag 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 tag )
{
int index = 0;
foreach ( CurveItem p in this )
{
if ( p.Tag is string &&
String.Compare( (string) p.Tag, tag, true ) == 0 )
return index;
index++;
}
return -1;
}
///
/// Sorts the list according to the point values at the specified index and
/// for the specified axis.
///
public void Sort( SortType type, int index )
{
this.Sort( new CurveItem.Comparer( type, index ) );
}
///
/// Move the position of the object at the specified index
/// to the new relative position in the list.
/// For Graphic type objects, this method controls the
/// Z-Order of the items. Objects at the beginning of the list
/// appear in front of objects at the end of the list.
/// The zero-based index of the object
/// to be moved.
/// The relative number of positions to move
/// the object. A value of -1 will move the
/// object one position earlier in the list, a value
/// of 1 will move it one position later. To move an item to the
/// beginning of the list, use a large negative value (such as -999).
/// To move it to the end of the list, use a large positive value.
///
/// The new position for the object, or -1 if the object
/// was not found.
public int Move( int index, int relativePos )
{
if ( index < 0 || index >= Count )
return -1;
CurveItem curve = this[index];
this.RemoveAt( index );
index += relativePos;
if ( index < 0 )
index = 0;
if ( index > Count )
index = Count;
Insert( index, curve );
return index;
}
#endregion
#region Rendering Methods
///
/// Go through each object in the collection,
/// calling the member to
/// determine the minimum and maximum values in the
/// list of data value pairs. If the curves include
/// a stack bar, handle within the current GetRange method. In the event that no
/// data are available, a default range of min=0.0 and max=1.0 are returned.
/// If the Y axis has a valid data range and the Y2 axis not, then the Y2
/// range will be a duplicate of the Y range. Vice-versa for the Y2 axis
/// having valid data when the Y axis does not.
/// If any in the list has a missing
/// , a new empty one will be generated.
///
/// ignoreInitial is a boolean value that
/// affects the data range that is considered for the automatic scale
/// ranging (see ). If true, then initial
/// data points where the Y value is zero are not included when
/// automatically determining the scale ,
/// , and size. All data after
/// the first non-zero Y value are included.
///
///
/// Determines if the auto-scaled axis ranges will subset the
/// data points based on any manually set scale range values.
///
///
/// A reference to the object that is the parent or
/// owner of this object.
///
///
public void GetRange( bool bIgnoreInitial, bool isBoundedRanges, GraphPane pane )
{
double tXMinVal,
tXMaxVal,
tYMinVal,
tYMaxVal;
InitScale( pane.XAxis.Scale, isBoundedRanges );
InitScale( pane.X2Axis.Scale, isBoundedRanges );
foreach ( YAxis axis in pane.YAxisList )
InitScale( axis.Scale, isBoundedRanges );
foreach ( Y2Axis axis in pane.Y2AxisList )
InitScale( axis.Scale, isBoundedRanges );
maxPts = 1;
// Loop over each curve in the collection and examine the data ranges
foreach ( CurveItem curve in this )
{
if ( curve.IsVisible )
{
// For stacked types, use the GetStackRange() method which accounts for accumulated values
// rather than simple curve values.
if ( ( ( curve is BarItem ) && ( pane._barSettings.Type == BarType.Stack ||
pane._barSettings.Type == BarType.PercentStack ) ) ||
( ( curve is LineItem ) && pane.LineType == LineType.Stack ) )
{
GetStackRange( pane, curve, out tXMinVal, out tYMinVal,
out tXMaxVal, out tYMaxVal );
}
else
{
// Call the GetRange() member function for the current
// curve to get the min and max values
curve.GetRange( out tXMinVal, out tXMaxVal,
out tYMinVal, out tYMaxVal, bIgnoreInitial, true, pane );
}
// isYOrd is true if the Y axis is an ordinal type
Scale yScale = curve.GetYAxis( pane ).Scale;
Scale xScale = curve.GetXAxis( pane ).Scale;
bool isYOrd = yScale.IsAnyOrdinal;
// isXOrd is true if the X axis is an ordinal type
bool isXOrd = xScale.IsAnyOrdinal;
// For ordinal Axes, the data range is just 1 to Npts
if ( isYOrd && !curve.IsOverrideOrdinal )
{
tYMinVal = 1.0;
tYMaxVal = curve.NPts;
}
if ( isXOrd && !curve.IsOverrideOrdinal )
{
tXMinVal = 1.0;
tXMaxVal = curve.NPts;
}
// Bar types always include the Y=0 value
if ( curve.IsBar )
{
if ( pane._barSettings.Base == BarBase.X )
{
if ( pane._barSettings.Type != BarType.ClusterHiLow )
{
if ( tYMinVal > 0 )
tYMinVal = 0;
else if ( tYMaxVal < 0 )
tYMaxVal = 0;
}
// for non-ordinal axes, expand the data range slightly for bar charts to
// account for the fact that the bar clusters have a width
if ( !isXOrd )
{
tXMinVal -= pane._barSettings._clusterScaleWidth / 2.0;
tXMaxVal += pane._barSettings._clusterScaleWidth / 2.0;
}
}
else
{
if ( pane._barSettings.Type != BarType.ClusterHiLow )
{
if ( tXMinVal > 0 )
tXMinVal = 0;
else if ( tXMaxVal < 0 )
tXMaxVal = 0;
}
// for non-ordinal axes, expand the data range slightly for bar charts to
// account for the fact that the bar clusters have a width
if ( !isYOrd )
{
tYMinVal -= pane._barSettings._clusterScaleWidth / 2.0;
tYMaxVal += pane._barSettings._clusterScaleWidth / 2.0;
}
}
}
// determine which curve has the maximum number of points
if ( curve.NPts > maxPts )
maxPts = curve.NPts;
// If the min and/or max values from the current curve
// are the absolute min and/or max, then save the values
// Also, differentiate between Y and Y2 values
if ( tYMinVal < yScale._rangeMin )
yScale._rangeMin = tYMinVal;
if ( tYMaxVal > yScale._rangeMax )
yScale._rangeMax = tYMaxVal;
if ( tXMinVal < xScale._rangeMin )
xScale._rangeMin = tXMinVal;
if ( tXMaxVal > xScale._rangeMax )
xScale._rangeMax = tXMaxVal;
}
}
pane.XAxis.Scale.SetRange( pane, pane.XAxis );
pane.X2Axis.Scale.SetRange( pane, pane.X2Axis );
foreach ( YAxis axis in pane.YAxisList )
axis.Scale.SetRange( pane, axis );
foreach ( Y2Axis axis in pane.Y2AxisList )
axis.Scale.SetRange( pane, axis );
}
private void InitScale( Scale scale, bool isBoundedRanges )
{
scale._rangeMin = double.MaxValue;
scale._rangeMax = double.MinValue;
scale._lBound = ( isBoundedRanges && !scale._minAuto ) ?
scale._min : double.MinValue;
scale._uBound = ( isBoundedRanges && !scale._maxAuto ) ?
scale._max : double.MaxValue;
}
///
/// Calculate the range for stacked bars and lines.
///
/// This method is required for the stacked
/// types because (for bars), the negative values are a separate stack than the positive
/// values. If you just sum up the bars, you will get the sum of the positive plus negative,
/// which is less than the maximum positive value and greater than the maximum negative value.
///
///
/// A reference to the object that is the parent or
/// owner of this object.
///
/// The for which to calculate the range
/// The minimum X value so far
/// The minimum Y value so far
/// The maximum X value so far
/// The maximum Y value so far
///
private void GetStackRange( GraphPane pane, CurveItem curve, out double tXMinVal,
out double tYMinVal, out double tXMaxVal, out double tYMaxVal )
{
// initialize the values to outrageous ones to start
tXMinVal = tYMinVal = Double.MaxValue;
tXMaxVal = tYMaxVal = Double.MinValue;
ValueHandler valueHandler = new ValueHandler( pane, false );
Axis baseAxis = curve.BaseAxis( pane );
bool isXBase = baseAxis is XAxis || baseAxis is X2Axis;
double lowVal, baseVal, hiVal;
for ( int i=0; i tXMaxVal )
tXMaxVal = x;
if ( y < tYMinVal )
tYMinVal = y;
if ( y > tYMaxVal )
tYMaxVal = y;
if ( !isXBase )
{
if ( lowVal < tXMinVal )
tXMinVal = lowVal;
if ( lowVal > tXMaxVal )
tXMaxVal = lowVal;
}
else
{
if ( lowVal < tYMinVal )
tYMinVal = lowVal;
if ( lowVal > tYMaxVal )
tYMaxVal = lowVal;
}
}
}
}
///
/// Render all the objects in the list to the
/// specified
/// device by calling the member function of
/// each object.
///
///
/// A graphic device object to be drawn into. This is normally e.Graphics from the
/// PaintEventArgs argument to the Paint() method.
///
///
/// A reference to the object that is the parent or
/// owner of this object.
///
///
/// The scaling factor to be used for rendering objects. This is calculated and
/// passed down by the parent object using the
/// method, and is used to proportionally adjust
/// font sizes, etc. according to the actual size of the graph.
///
public void Draw( Graphics g, GraphPane pane, float scaleFactor )
{
// Configure the accumulator for stacked bars
//Bar.ResetBarStack();
// Count the number of BarItems in the curvelist
int pos = this.NumBars;
// sorted overlay bars are a special case, since they are sorted independently at each
// ordinal position.
if ( pane._barSettings.Type == BarType.SortedOverlay )
{
// First, create a new curveList with references (not clones) of the curves
CurveList tempList = new CurveList();
foreach ( CurveItem curve in this )
if ( curve.IsBar )
tempList.Add( (CurveItem) curve );
// Loop through the bars, graphing each ordinal position separately
for ( int i=0; i= 0; i-- )
{
CurveItem curve = this[i];
if ( curve.IsBar )
pos--;
// Render the curve
// if it's a sorted overlay bar type, it's already been done above
if ( !( curve.IsBar && pane._barSettings.Type == BarType.SortedOverlay ) )
{
curve.Draw( g, pane, pos, scaleFactor );
}
}
}
///
/// Find the ordinal position of the specified within
/// the . This position only counts
/// types, ignoring all other types.
///
/// The of interest
/// The for which to search.
/// The ordinal position of the specified bar, or -1 if the bar
/// was not found.
public int GetBarItemPos( GraphPane pane, BarItem barItem )
{
if ( pane._barSettings.Type == BarType.Overlay ||
pane._barSettings.Type == BarType.Stack ||
pane._barSettings.Type == BarType.PercentStack)
return 0;
int i = 0;
foreach ( CurveItem curve in this )
{
if ( curve == barItem )
return i;
else if ( curve is BarItem )
i++;
}
return -1;
}
#endregion
}
}