//============================================================================ //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.Runtime.Serialization; using System.Security.Permissions; namespace DrawGraph { /// /// This class encapsulates the chart that is displayed /// in the /// /// /// John Champion /// $Revision: 3.38 $ $Date: 2007/03/11 02:08:16 $ [Serializable] public class Legend : ICloneable, ISerializable { #region private Fields /// Private field to hold the bounding rectangle around the legend. /// This bounding rectangle varies with the number of legend entries, font sizes, /// etc., and is re-calculated by at each redraw. /// Use the public readonly property to access this /// rectangle. /// private RectangleF _rect; /// Private field to hold the legend location setting. This field /// contains the enum type to specify the area of /// the graph where the legend will be positioned. Use the public property /// to access this value. /// /// private LegendPos _position; /// /// Private field to enable/disable horizontal stacking of the legend entries. /// If this value is false, then the legend entries will always be a single column. /// Use the public property to access this value. /// /// private bool _isHStack; /// /// Private field to enable/disable drawing of the entire legend. /// If this value is false, then the legend will not be drawn. /// Use the public property to access this value. /// private bool _isVisible; /// /// Private field that stores the data for this /// . Use the public property to /// access this value. /// private Fill _fill; /// /// Private field that stores the data for this /// . Use the public property to /// access this value. /// private Border _border; /// /// Private field to maintain the class that /// maintains font attributes for the entries in this legend. Use /// the property to access this class. /// private FontSpec _fontSpec; /// /// Private field to maintain the location. This object /// is only applicable if the property is set to /// . /// private Location _location; /// /// Private temporary field to maintain the number of columns (horizontal stacking) to be used /// for drawing the . This value is only valid during a draw operation. /// private int _hStack; /// /// Private temporary field to maintain the width of each column in the /// . This value is only valid during a draw operation. /// private float _legendItemWidth; /// /// Private temporary field to maintain the height of each row in the /// . This value is only valid during a draw operation. /// private float _legendItemHeight; /// /// Private field to store the gap between the legend and the chart rectangle. /// private float _gap; // CJBL /// /// Private field to select output order of legend entries. /// private bool _isReverse; /// /// Private temporary field to maintain the characteristic "gap" for the legend. /// This is normal the height of the largest font in the legend. /// This value is only valid during a draw operation. /// private float _tmpSize; #endregion #region Defaults /// /// A simple struct that defines the /// default property values for the class. /// public struct Default { // Default Legend properties /// /// The default pen width for the border border. /// ( property). Units are in pixels. /// public static float BorderWidth = 1; /// /// The default color for the border border. /// ( property). /// public static Color BorderColor = Color.Black; /// /// The default color for the background. /// ( property). Use of this /// color depends on the status of the /// property. /// public static Color FillColor = Color.White; /// /// The default custom brush for filling in this . /// public static Brush FillBrush = null; /// /// The default fill mode for the background. /// public static FillType FillType = FillType.Brush; /// /// The default location for the on the graph /// ( property). This property is /// defined as a enumeration. /// public static LegendPos Position = LegendPos.Top; /// /// The default border mode for the . /// ( property). true /// to draw a border around the , /// false otherwise. /// public static bool IsBorderVisible = true; /// /// The default display mode for the . /// ( property). true /// to show the legend, /// false to hide it. /// public static bool IsVisible = true; /// /// The default fill mode for the background /// ( property). /// true to fill-in the background with color, /// false to leave the background transparent. /// public static bool IsFilled = true; /// /// The default horizontal stacking mode for the /// ( property). /// true to allow horizontal legend item stacking, false to allow /// only vertical legend orientation. /// public static bool IsHStack = true; /// /// The default font family for the entries /// ( property). /// public static string FontFamily = "Arial"; /// /// The default font size for the entries /// ( property). Units are /// in points (1/72 inch). /// public static float FontSize = 12; /// /// The default font color for the entries /// ( property). /// public static Color FontColor = Color.Black; /// /// The default font bold mode for the entries /// ( property). true /// for a bold typeface, false otherwise. /// public static bool FontBold = false; /// /// The default font italic mode for the entries /// ( property). true /// for an italic typeface, false otherwise. /// public static bool FontItalic = false; /// /// The default font underline mode for the entries /// ( property). true /// for an underlined typeface, false otherwise. /// public static bool FontUnderline = false; /// /// The default color for filling in the scale text background /// (see property). /// public static Color FontFillColor = Color.White; /// /// The default custom brush for filling in the scale text background /// (see property). /// public static Brush FontFillBrush = null; /// /// The default fill mode for filling in the scale text background /// (see property). /// public static FillType FontFillType = FillType.None; /// /// The default gap size between the legend and the . /// This is the default value of . /// public static float Gap = 0.5f; /// /// Default value for the property. /// public static bool IsReverse = false; } #endregion #region Properties /// /// Get the bounding rectangle for the in screen coordinates /// /// A screen rectangle in pixel units public RectangleF Rect { get { return _rect; } } /// /// Access to the class used to render /// the entries /// /// A reference to a object /// /// /// /// /// /// public FontSpec FontSpec { get { return _fontSpec; } set { if ( value == null ) throw new ArgumentNullException( "Uninitialized FontSpec in Legend" ); _fontSpec = value; } } /// /// Gets or sets a property that shows or hides the entirely /// /// true to show the , false to hide it /// public bool IsVisible { get { return _isVisible; } set { _isVisible = value; } } /// /// The class used to draw the border border around this . /// public Border Border { get { return _border; } set { _border = value; } } /// /// Gets or sets the data for this /// background. /// public Fill Fill { get { return _fill; } set { _fill = value; } } /// /// Sets or gets a property that allows the items to /// stack horizontally in addition to the vertical stacking /// /// true to allow horizontal stacking, false otherwise /// /// public bool IsHStack { get { return _isHStack; } set { _isHStack = value; } } /// /// Sets or gets the location of the on the /// using the enum type /// /// public LegendPos Position { get { return _position; } set { _position = value; } } /// /// Gets or sets the data for the . /// This property is only applicable if is set /// to . /// public Location Location { get { return _location; } set { _location = value; } } /// /// Gets or sets the gap size between the legend and the . /// /// /// This is expressed as a fraction of the largest scaled character height for any /// of the fonts used in the legend. Each in the legend can /// optionally have its own specification. /// public float Gap { get { return _gap; } set { _gap = value; } } /// /// Gets or sets a value that determines if the legend entries are displayed in normal order /// (matching the order in the , or in reverse order. /// public bool IsReverse { get { return _isReverse; } set { _isReverse = value; } } #endregion #region Constructors /// /// Default constructor that sets all properties to default /// values as defined in the class. /// public Legend() { _position = Default.Position; _isHStack = Default.IsHStack; _isVisible = Default.IsVisible; this.Location = new Location( 0, 0, CoordType.PaneFraction ); _fontSpec = new FontSpec( Default.FontFamily, Default.FontSize, Default.FontColor, Default.FontBold, Default.FontItalic, Default.FontUnderline, Default.FontFillColor, Default.FontFillBrush, Default.FontFillType ); _fontSpec.Border.IsVisible = false; _border = new Border( Default.IsBorderVisible, Default.BorderColor, Default.BorderWidth ); _fill = new Fill( Default.FillColor, Default.FillBrush, Default.FillType ); _gap = Default.Gap; _isReverse = Default.IsReverse; } /// /// The Copy Constructor /// /// The XAxis object from which to copy public Legend( Legend rhs ) { _rect = rhs.Rect; _position = rhs.Position; _isHStack = rhs.IsHStack; _isVisible = rhs.IsVisible; _location = rhs.Location; _border = rhs.Border.Clone(); _fill = rhs.Fill.Clone(); _fontSpec = rhs.FontSpec.Clone(); _gap = rhs._gap; _isReverse = rhs._isReverse; } /// /// 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 Legend Clone() { return new Legend( this ); } #endregion #region Serialization /// /// Current schema value that defines the version of the serialized file /// public const int schema = 11; /// /// Constructor for deserializing objects /// /// A instance that defines the serialized data /// /// A instance that contains the serialized data /// protected Legend( 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" ); _position = (LegendPos) info.GetValue( "position", typeof(LegendPos) ); _isHStack = info.GetBoolean( "isHStack" ); _isVisible = info.GetBoolean( "isVisible" ); _fill = (Fill) info.GetValue( "fill", typeof(Fill) ); _border = (Border) info.GetValue( "border", typeof(Border) ); _fontSpec = (FontSpec) info.GetValue( "fontSpec", typeof(FontSpec) ); _location = (Location) info.GetValue( "location", typeof(Location) ); _gap = info.GetSingle( "gap" ); if ( schema >= 11 ) _isReverse = info.GetBoolean( "isReverse" ); } /// /// 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( "position", _position ); info.AddValue( "isHStack", _isHStack ); info.AddValue( "isVisible", _isVisible ); info.AddValue( "fill", _fill ); info.AddValue( "border", _border ); info.AddValue( "fontSpec", _fontSpec ); info.AddValue( "location", _location ); info.AddValue( "gap", _gap ); info.AddValue( "isReverse", _isReverse ); } #endregion #region Rendering Methods /// /// Render the to the specified device. /// /// /// This method is normally only called by the Draw method /// of the parent 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, PaneBase pane, float scaleFactor ) { // if the legend is not visible, do nothing if ( ! _isVisible ) return; // Fill the background with the specified color if required _fill.Draw( g, _rect ); PaneList paneList = GetPaneList( pane ); float halfGap = _tmpSize / 2.0F; // Check for bad data values if ( _hStack <= 0 ) _hStack = 1; if ( _legendItemWidth <= 0 ) _legendItemWidth = 100; if ( _legendItemHeight <= 0 ) _legendItemHeight = _tmpSize; //float gap = pane.ScaledGap( scaleFactor ); int iEntry = 0; float x, y; // Get a brush for the legend label text using ( SolidBrush brushB = new SolidBrush( Color.Black ) ) { foreach ( GraphPane tmpPane in paneList ) { // Loop for each curve in the CurveList collection //foreach ( CurveItem curve in tmpPane.CurveList ) int count = tmpPane.CurveList.Count; for ( int i = 0; i < count; i++ ) { CurveItem curve = tmpPane.CurveList[_isReverse ? count - i - 1 : i]; if ( curve._label._text != "" && curve._label._isVisible ) { // Calculate the x,y (TopLeft) location of the current // curve legend label // assuming: // charHeight/2 for the left margin, plus legendWidth for each // horizontal column // legendHeight is the line spacing, with no extra margin above x = _rect.Left + halfGap / 2.0F + ( iEntry % _hStack ) * _legendItemWidth; y = _rect.Top + (int)( iEntry / _hStack ) * _legendItemHeight; // Draw the legend label for the current curve FontSpec tmpFont = ( curve._label._fontSpec != null ) ? curve._label._fontSpec : this.FontSpec; // This is required because, for long labels, the centering can affect the // position in GDI+. tmpFont.StringAlignment = StringAlignment.Near; tmpFont.Draw( g, pane, curve._label._text, x + 2.5F * _tmpSize, y + _legendItemHeight / 2.0F, AlignH.Left, AlignV.Center, scaleFactor ); RectangleF rect = new RectangleF( x, y + _legendItemHeight / 4.0F, 2 * _tmpSize, _legendItemHeight / 2.0F ); curve.DrawLegendKey( g, tmpPane, rect, scaleFactor ); // maintain a curve count for positioning iEntry++; } } if ( pane is MasterPane && ( (MasterPane)pane ).IsUniformLegendEntries ) break; } // Draw a border around the legend if required if ( iEntry > 0 ) this.Border.Draw( g, pane, scaleFactor, _rect ); } } private float GetMaxHeight( PaneList paneList, Graphics g, float scaleFactor ) { // Set up some scaled dimensions for calculating sizes and locations float defaultCharHeight = this.FontSpec.GetHeight( scaleFactor ); float maxCharHeight = defaultCharHeight; // Find the largest charHeight, just in case the curves have individual fonts defined foreach ( GraphPane tmpPane in paneList ) { foreach ( CurveItem curve in tmpPane.CurveList ) { if ( curve._label._text != string.Empty && curve._label._isVisible ) { float tmpHeight = defaultCharHeight; if ( curve._label._fontSpec != null ) tmpHeight = curve._label._fontSpec.GetHeight( scaleFactor ); // Account for multiline legend entries tmpHeight *= curve._label._text.Split( '\n' ).Length; if ( tmpHeight > maxCharHeight ) maxCharHeight = tmpHeight; } } } return maxCharHeight; } /// /// Determine if a mouse point is within the legend, and if so, which legend /// entry () is nearest. /// /// The screen point, in pixel coordinates. /// /// 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. /// /// The index number of the legend /// entry that is under the mouse point. The object is /// accessible via CurveList[index]. /// /// true if the mouse point is within the bounding /// box, false otherwise. /// public bool FindPoint( PointF mousePt, PaneBase pane, float scaleFactor, out int index ) { index = -1; if ( _rect.Contains( mousePt ) ) { int j = (int) ( ( mousePt.Y - _rect.Top ) / _legendItemHeight ); int i = (int) ( ( mousePt.X - _rect.Left - _tmpSize / 2.0f ) / _legendItemWidth ); if ( i < 0 ) i = 0; if ( i >= _hStack ) i = _hStack - 1; int pos = i + j * _hStack; index = 0; PaneList paneList = GetPaneList( pane ); foreach ( GraphPane tmpPane in paneList ) { foreach ( CurveItem curve in tmpPane.CurveList ) { if ( curve._label._isVisible && curve._label._text != string.Empty ) { if ( pos == 0 ) return true; pos--; } index++; } } return true; } else return false; } private PaneList GetPaneList( PaneBase pane ) { // For a single GraphPane, create a PaneList to contain it // Otherwise, just use the paneList from the MasterPane PaneList paneList; if ( pane is GraphPane ) { paneList = new PaneList(); paneList.Add( (GraphPane) pane ); } else paneList = ((MasterPane)pane).PaneList; return paneList; } /// /// Calculate the rectangle (), /// taking into account the number of required legend /// entries, and the legend drawing preferences. /// /// Adjust the size of the /// for the parent to accomodate the /// space required by the legend. /// /// /// 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. /// /// /// The rectangle that contains the area bounded by the axes, in pixel units. /// /// public void CalcRect( Graphics g, PaneBase pane, float scaleFactor, ref RectangleF tChartRect ) { // Start with an empty rectangle _rect = Rectangle.Empty; _hStack = 1; _legendItemWidth = 1; _legendItemHeight = 0; RectangleF clientRect = pane.CalcClientRect( g, scaleFactor ); // If the legend is invisible, don't do anything if ( !_isVisible ) return; int nCurve = 0; PaneList paneList = GetPaneList( pane ); _tmpSize = GetMaxHeight( paneList, g, scaleFactor ); float halfGap = _tmpSize / 2.0F, maxWidth = 0, tmpWidth, gapPix = _gap * _tmpSize; foreach ( GraphPane tmpPane in paneList ) { // Loop through each curve in the curve list // Find the maximum width of the legend labels //foreach ( CurveItem curve in tmpPane.CurveList ) //foreach ( CurveItem curve in GetIterator( tmpPane.CurveList, _isReverse ) ) int count = tmpPane.CurveList.Count; for ( int i = 0; i < count; i++ ) { CurveItem curve = tmpPane.CurveList[_isReverse ? count - i - 1 : i]; if ( curve._label._text != string.Empty && curve._label._isVisible ) { // Calculate the width of the label save the max width FontSpec tmpFont = ( curve._label._fontSpec != null ) ? curve._label._fontSpec : this.FontSpec; tmpWidth = tmpFont.GetWidth( g, curve._label._text, scaleFactor ); if ( tmpWidth > maxWidth ) maxWidth = tmpWidth; // Save the maximum symbol height for line-type curves if ( curve is LineItem && ( (LineItem) curve ).Symbol.Size > _legendItemHeight ) _legendItemHeight = ( (LineItem) curve ).Symbol.Size; nCurve++; } } if ( pane is MasterPane && ((MasterPane)pane).IsUniformLegendEntries ) break ; } float widthAvail; // Is this legend horizontally stacked? if ( _isHStack ) { // Determine the available space for horizontal stacking switch( _position ) { // Never stack if the legend is to the right or left case LegendPos.Right: case LegendPos.Left: widthAvail = 0; break; // for the top & bottom, the axis border width is available case LegendPos.Top: case LegendPos.TopCenter: case LegendPos.Bottom: case LegendPos.BottomCenter : widthAvail = tChartRect.Width; break; // for the top & bottom flush left, the panerect less margins is available case LegendPos.TopFlushLeft: case LegendPos.BottomFlushLeft: widthAvail = clientRect.Width; break; // for inside the axis area or Float, use 1/2 of the axis border width case LegendPos.InsideTopRight: case LegendPos.InsideTopLeft: case LegendPos.InsideBotRight: case LegendPos.InsideBotLeft: case LegendPos.Float: widthAvail = tChartRect.Width / 2; break; // shouldn't ever happen default: widthAvail = 0; break; } // width of one legend entry _legendItemWidth = 3 * _tmpSize + maxWidth; // Calculate the number of columns in the legend // Normally, the legend is: // available width / ( max width of any entry + space for line&symbol ) if ( maxWidth > 0 ) _hStack = (int) ( (widthAvail - halfGap) / _legendItemWidth ); // You can never have more columns than legend entries if ( _hStack > nCurve ) _hStack = nCurve; // a saftey check if ( _hStack == 0 ) _hStack = 1; } else _legendItemWidth = 3.5F * _tmpSize + maxWidth; // legend is: // item: space line space text space // width: wid 4*wid wid maxWid wid // The symbol is centered on the line // // legend begins 3 * wid to the right of the plot rect // // The height of the legend is the actual height of the lines of text // (nCurve * hite) plus wid on top and wid on the bottom // total legend width float totLegWidth = _hStack * _legendItemWidth; // The total legend height _legendItemHeight = _legendItemHeight * (float) scaleFactor + halfGap; if ( _tmpSize > _legendItemHeight ) _legendItemHeight = _tmpSize; float totLegHeight = (float) Math.Ceiling( (double) nCurve / (double) _hStack ) * _legendItemHeight; RectangleF newRect = new RectangleF(); // Now calculate the legend rect based on the above determined parameters // Also, adjust the ChartRect to reflect the space for the legend if ( nCurve > 0 ) { newRect = new RectangleF( 0, 0, totLegWidth, totLegHeight ); // The switch statement assigns the left and top edges, and adjusts the ChartRect // as required. The right and bottom edges are calculated at the bottom of the switch. switch( _position ) { case LegendPos.Right: newRect.X = clientRect.Right - totLegWidth; newRect.Y = tChartRect.Top; tChartRect.Width -= totLegWidth + gapPix; break; case LegendPos.Top: newRect.X = tChartRect.Left; newRect.Y = clientRect.Top; tChartRect.Y += totLegHeight + gapPix; tChartRect.Height -= totLegHeight + gapPix; break; case LegendPos.TopFlushLeft: newRect.X = clientRect.Left; newRect.Y = clientRect.Top; tChartRect.Y += totLegHeight + gapPix * 1.5f; tChartRect.Height -= totLegHeight + gapPix * 1.5f; break; case LegendPos.TopCenter: newRect.X = tChartRect.Left + ( tChartRect.Width - totLegWidth ) / 2; newRect.Y = tChartRect.Top; tChartRect.Y += totLegHeight + gapPix; tChartRect.Height -= totLegHeight + gapPix; break; case LegendPos.Bottom: newRect.X = tChartRect.Left; newRect.Y = clientRect.Bottom - totLegHeight; tChartRect.Height -= totLegHeight + gapPix; break; case LegendPos.BottomFlushLeft: newRect.X = clientRect.Left; newRect.Y = clientRect.Bottom - totLegHeight; tChartRect.Height -= totLegHeight + gapPix; break; case LegendPos.BottomCenter: newRect.X = tChartRect.Left + ( tChartRect.Width - totLegWidth ) / 2; newRect.Y = clientRect.Bottom - totLegHeight; tChartRect.Height -= totLegHeight + gapPix; break; case LegendPos.Left: newRect.X = clientRect.Left; newRect.Y = tChartRect.Top; tChartRect.X += totLegWidth + halfGap; tChartRect.Width -= totLegWidth + gapPix; break; case LegendPos.InsideTopRight: newRect.X = tChartRect.Right - totLegWidth; newRect.Y = tChartRect.Top; break; case LegendPos.InsideTopLeft: newRect.X = tChartRect.Left; newRect.Y = tChartRect.Top; break; case LegendPos.InsideBotRight: newRect.X = tChartRect.Right - totLegWidth; newRect.Y = tChartRect.Bottom - totLegHeight; break; case LegendPos.InsideBotLeft: newRect.X = tChartRect.Left; newRect.Y = tChartRect.Bottom - totLegHeight; break; case LegendPos.Float: newRect.Location = this.Location.TransformTopLeft( pane, totLegWidth, totLegHeight ); break; } } _rect = newRect; } // /// // /// Private method to the render region that gives the iterator depending on the attribute // /// // /// // /// // /// // private IEnumerable GetIterator(CurveList c, bool forward) // { // return forward ? c.Forward : c.Backward; // } #endregion } }