//============================================================================ //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.Drawing.Drawing2D; using System.Runtime.Serialization; using System.Security.Permissions; namespace DrawGraph { /// /// The Axis class is an abstract base class that encompasses all properties /// and methods required to define a graph Axis. /// /// This class is inherited by the /// , , and classes /// to define specific characteristics for those types. /// /// /// John Champion modified by Jerry Vos /// $Revision: 3.73 $ $Date: 2007/05/19 06:06:05 $ [Serializable] abstract public class Axis : ISerializable, ICloneable { #region Class Fields /// /// private field that stores the class, which implements all the /// calculations and methods associated with the numeric scale for this /// . See the public property to access this class. /// internal Scale _scale; /// /// Private field that stores the class, which handles all /// the minor tic information. See the public property to access this class. /// internal MinorTic _minorTic; /// /// Private field that stores the class, which handles all /// the major tic information. See the public property to access this class. /// internal MajorTic _majorTic; /// /// Private field that stores the class, which handles all /// the major grid information. See the public property to access this class. /// internal MajorGrid _majorGrid; /// /// Private field that stores the class, which handles all /// the minor grid information. See the public property to access this class. /// internal MinorGrid _minorGrid; /// Private fields for the scale rendering properties. /// Use the public properties and /// for access to these values. /// internal double _cross; /// Private field for the automatic cross position mode. /// Use the public property for access to this value. /// internal bool _crossAuto; /// Private fields for the attributes. /// Use the public properties , /// for access to these values. /// protected bool _isVisible, _isAxisSegmentVisible; /// Private field for the title string. /// Use the public property for access to this value. /// protected AxisLabel _title; /// /// A tag object for use by the user. This can be used to store additional /// information associated with the . ZedGraph does /// not use this value for any purpose. /// /// /// Note that, if you are going to Serialize ZedGraph data, then any type /// that you store in must be a serializable type (or /// it will cause an exception). /// public object Tag; /// Private field for the drawing dimensions. /// Use the public property /// for access to these values. private float _axisGap; /// /// Private field for the minimum allowable space allocation. /// Use the public property to access this value. /// /// private float _minSpace; /// Private fields for the colors. /// Use the public property for access to this values. /// private Color _color; /// /// Temporary values for axis space calculations (see ). /// internal float _tmpSpace; #endregion #region Events /// /// A delegate that allows full custom formatting of the Axis labels /// /// The for which the label is to be /// formatted /// The of interest. /// The value to be formatted /// The zero-based index of the label to be formatted /// /// A string value representing the label, or null if the ZedGraph should go ahead /// and generate the label according to the current settings /// public delegate string ScaleFormatHandler( GraphPane pane, Axis axis, double val, int index ); /// /// Subscribe to this event to handle custom formatting of the scale labels. /// public event ScaleFormatHandler ScaleFormatEvent; // Revision: JCarpenter 10/06 /// /// Allow customization of title based on user preferences. /// /// The of interest. /// /// A string value representing the label, or null if the ZedGraph should go ahead /// and generate the label according to the current settings. To make the title /// blank, return "". /// public delegate string ScaleTitleEventHandler( Axis axis ); //Revision: JCarpenter 10/06 /// /// Allow customization of the title when the scale is very large /// Subscribe to this event to handle custom formatting of the scale axis label. /// public event ScaleTitleEventHandler ScaleTitleEvent; #endregion #region Defaults /// /// A simple struct that defines the /// default property values for the class. /// public struct Default { /// /// The default size for the gap between multiple axes /// ( property). Units are in points (1/72 inch). /// public static float AxisGap = 5; /// /// The default setting for the gap between the scale labels and the axis title. /// public static float TitleGap = 0.0f; /// /// The default font family for the text /// font specification /// ( property). /// public static string TitleFontFamily = "Arial"; /// /// The default font size for the text /// font specification /// ( property). Units are /// in points (1/72 inch). /// public static float TitleFontSize = 14; /// /// The default font color for the text /// font specification /// ( property). /// public static Color TitleFontColor = Color.Black; /// /// The default font bold mode for the text /// font specification /// ( property). true /// for a bold typeface, false otherwise. /// public static bool TitleFontBold = true; /// /// The default font italic mode for the text /// font specification /// ( property). true /// for an italic typeface, false otherwise. /// public static bool TitleFontItalic = false; /// /// The default font underline mode for the text /// font specification /// ( property). true /// for an underlined typeface, false otherwise. /// public static bool TitleFontUnderline = false; /// /// The default color for filling in the text background /// (see property). /// public static Color TitleFillColor = Color.White; /// /// The default custom brush for filling in the text background /// (see property). /// public static Brush TitleFillBrush = null; /// /// The default fill mode for filling in the text background /// (see property). /// public static FillType TitleFillType = FillType.None; /// /// The default color for the itself /// ( property). This color only affects the /// the axis border. /// public static Color BorderColor = Color.Black; /// /// The default value for , which determines /// whether or not the scale segment itself is visible /// public static bool IsAxisSegmentVisible = true; /// /// The default setting for the scale axis type /// ( property). This value is set as per /// the enumeration /// public static AxisType Type = AxisType.Linear; /// /// The default color for the axis segment. /// public static Color Color = Color.Black; /// /// The default setting for the axis space allocation. This term, expressed in /// points (1/72 inch) and scaled according to for the /// , determines the minimum amount of space an axis must /// have between the and the /// . This minimum space /// applies whether is true or false. /// public static float MinSpace = 0f; } #endregion #region Constructors /// /// Default constructor for that sets all axis properties /// to default values as defined in the class. /// public Axis() { _scale = new LinearScale( this ); _cross = 0.0; _crossAuto = true; _majorTic = new MajorTic(); _minorTic = new MinorTic(); _majorGrid = new MajorGrid(); _minorGrid = new MinorGrid(); _axisGap = Default.AxisGap; _minSpace = Default.MinSpace; _isVisible = true; _isAxisSegmentVisible = Default.IsAxisSegmentVisible; _title = new AxisLabel( "", Default.TitleFontFamily, Default.TitleFontSize, Default.TitleFontColor, Default.TitleFontBold, Default.TitleFontUnderline, Default.TitleFontItalic ); _title.FontSpec.Fill = new Fill( Default.TitleFillColor, Default.TitleFillBrush, Default.TitleFillType ); _title.FontSpec.Border.IsVisible = false; _color = Default.Color; } /// /// Constructor for that sets all axis properties /// to default values as defined in the class, /// except for the . /// /// A string containing the axis title public Axis( string title ) : this() { _title._text = title; } /// /// The Copy Constructor. /// /// The Axis object from which to copy public Axis( Axis rhs ) { _scale = rhs._scale.Clone( this ); _cross = rhs._cross; _crossAuto = rhs._crossAuto; _majorTic = rhs.MajorTic.Clone(); _minorTic = rhs.MinorTic.Clone(); _majorGrid = rhs._majorGrid.Clone(); _minorGrid = rhs._minorGrid.Clone(); _isVisible = rhs.IsVisible; _isAxisSegmentVisible = rhs._isAxisSegmentVisible; _title = (AxisLabel) rhs.Title.Clone(); _axisGap = rhs._axisGap; _minSpace = rhs.MinSpace; _color = rhs.Color; } /// /// Implement the interface in a typesafe manner by just /// calling the typed version of Clone. /// /// /// Note that this method must be called with an explicit cast to ICloneable, and /// that it is inherently virtual. For example: /// /// ParentClass foo = new ChildClass(); /// ChildClass bar = (ChildClass) ((ICloneable)foo).Clone(); /// /// Assume that ChildClass is inherited from ParentClass. Even though foo is declared with /// ParentClass, it is actually an instance of ChildClass. Calling the ICloneable implementation /// of Clone() on foo actually calls ChildClass.Clone() as if it were a virtual function. /// /// A deep copy of this object object ICloneable.Clone() { throw new NotImplementedException( "Can't clone an abstract base type -- child types must implement ICloneable" ); //return new PaneBase( this ); } #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 Axis( 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" ); _cross = info.GetDouble( "cross" ); _crossAuto = info.GetBoolean( "crossAuto" ); _majorTic = (MajorTic)info.GetValue( "MajorTic", typeof( MajorTic ) ); _minorTic = (MinorTic)info.GetValue( "MinorTic", typeof( MinorTic ) ); _majorGrid = (MajorGrid)info.GetValue( "majorGrid", typeof( MajorGrid ) ); _minorGrid = (MinorGrid)info.GetValue( "minorGrid", typeof( MinorGrid ) ); _isVisible = info.GetBoolean( "isVisible" ); _title = (AxisLabel) info.GetValue( "title", typeof( AxisLabel ) ); _minSpace = info.GetSingle( "minSpace" ); _color = (Color)info.GetValue( "color", typeof( Color ) ); _isAxisSegmentVisible = info.GetBoolean( "isAxisSegmentVisible" ); _axisGap = info.GetSingle( "axisGap" ); _scale = (Scale)info.GetValue( "scale", typeof( Scale ) ); _scale._ownerAxis = this; } /// /// 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( "cross", _cross ); info.AddValue( "crossAuto", _crossAuto ); info.AddValue( "MajorTic", MajorTic ); info.AddValue( "MinorTic", MinorTic ); info.AddValue( "majorGrid", _majorGrid ); info.AddValue( "minorGrid", _minorGrid ); info.AddValue( "isVisible", _isVisible ); info.AddValue( "title", _title ); info.AddValue( "minSpace", _minSpace ); info.AddValue( "color", _color ); info.AddValue( "isAxisSegmentVisible", _isAxisSegmentVisible ); info.AddValue( "axisGap", _axisGap ); info.AddValue( "scale", _scale ); } #endregion #region Scale Properties /// /// Gets the instance associated with this . /// public Scale Scale { get { return _scale; } } /// /// Gets or sets the scale value at which this axis should cross the "other" axis. /// /// This property allows the axis to be shifted away from its default location. /// For example, for a graph with an X range from -100 to +100, the Y Axis can be located /// at the X=0 value rather than the left edge of the ChartRect. This value can be set /// automatically based on the state of . If /// this value is set manually, then will /// also be set to false. The "other" axis is the axis the handles the second dimension /// for the graph. For the XAxis, the "other" axis is the YAxis. For the YAxis or /// Y2Axis, the "other" axis is the XAxis. /// /// The value is defined in user scale units /// /// /// /// public double Cross { get { return _cross; } set { _cross = value; _crossAuto = false; } } /// /// Gets or sets a value that determines whether or not the value /// is set automatically. /// /// Set to true to have ZedGraph put the axis in the default location, or false /// to specify the axis location manually with a value. /// /// /// /// public bool CrossAuto { get { return _crossAuto; } set { _crossAuto = value; } } /// /// Gets or sets the minimum axis space allocation. /// /// /// This term, expressed in /// points (1/72 inch) and scaled according to /// for the , determines the minimum amount of space /// an axis must have between the Chart.Rect and the /// GraphPane.Rect. This minimum space /// applies whether is true or false. /// public float MinSpace { get { return _minSpace; } set { _minSpace = value; } } #endregion #region Tic Properties /// /// The color to use for drawing this . /// /// /// This affects only the axis segment (see ), /// since the , /// , , , /// , and /// all have their own color specification. /// /// The color is defined using the /// class /// . /// public Color Color { get { return _color; } set { _color = value; } } /// /// Gets a reference to the class instance /// for this . This class stores all the major tic settings. /// public MajorTic MajorTic { get { return _majorTic; } } /// /// Gets a reference to the class instance /// for this . This class stores all the minor tic settings. /// public MinorTic MinorTic { get { return _minorTic; } } #endregion #region Grid Properties /// /// Gets a reference to the class that contains the properties /// of the major grid. /// public MajorGrid MajorGrid { get { return _majorGrid; } } /// /// Gets a reference to the class that contains the properties /// of the minor grid. /// public MinorGrid MinorGrid { get { return _minorGrid; } } #endregion #region Type Properties /// /// This property determines whether or not the is shown. /// /// /// Note that even if /// the axis is not visible, it can still be actively used to draw curves on a /// graph, it will just be invisible to the user /// /// true to show the axis, false to disable all drawing of this axis /// . /// . /// . /// . public bool IsVisible { get { return _isVisible; } set { _isVisible = value; } } /// /// Gets or sets a property that determines whether or not the axis segment (the line that /// represents the axis itself) is drawn. /// /// /// Under normal circumstances, this value won't affect the appearance of the display because /// the Axis segment is overlain by the Axis border (see ). /// However, when the border is not visible, or when is set to /// false, this value will make a difference. /// public bool IsAxisSegmentVisible { get { return _isAxisSegmentVisible; } set { _isAxisSegmentVisible = value; } } /// /// Gets or sets the for this . /// /// /// The type can be either , /// , , /// or . /// /// /// /// /// /// public AxisType Type { get { return _scale.Type; } set { _scale = Scale.MakeNewScale( _scale, value ); } } #endregion #region Label Properties /// /// Gets or sets the class that contains the title of this /// . /// /// The title normally shows the basis and dimensions of /// the scale range, such as "Time (Years)". The title is only shown if the /// property is set to true. If the Title text is empty, /// then no title is shown, and no space is "reserved" for the title on the graph. /// /// the title is a string value /// public AxisLabel Title { get { return _title; } set { _title = value; } } /// /// The size of the gap between multiple axes (see and /// ). /// /// /// This size will be scaled /// according to the for the /// /// /// The axis gap is measured in points (1/72 inch) /// . public float AxisGap { get { return _axisGap; } set { _axisGap = value; } } #endregion #region Rendering Methods /// /// Restore the scale ranging to automatic mode, and recalculate the /// scale ranges /// /// /// A reference to the object that is the parent or /// owner of this object. /// /// /// A graphic device object to be drawn into. This is normally e.Graphics from the /// PaintEventArgs argument to the Paint() method. /// /// /// /// /// /// public void ResetAutoScale( GraphPane pane, Graphics g ) { _scale._minAuto = true; _scale._maxAuto = true; _scale._majorStepAuto = true; _scale._minorStepAuto = true; _crossAuto = true; _scale._magAuto = true; //this.numDecAuto = true; _scale._formatAuto = true; pane.AxisChange( g ); } /// /// Do all rendering associated with this 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. /// /// /// The number of pixels to shift to account for non-primary axis position (e.g., /// the second, third, fourth, etc. or . /// public void Draw( Graphics g, GraphPane pane, float scaleFactor, float shiftPos ) { Matrix saveMatrix = g.Transform; _scale.SetupScaleData( pane, this ); if ( _isVisible ) { SetTransformMatrix( g, pane, scaleFactor ); shiftPos = CalcTotalShift( pane, scaleFactor, shiftPos ); _scale.Draw( g, pane, scaleFactor, shiftPos ); //DrawTitle( g, pane, scaleFactor ); g.Transform = saveMatrix; } } internal void DrawGrid( Graphics g, GraphPane pane, float scaleFactor, float shiftPos ) { if ( _isVisible ) { Matrix saveMatrix = g.Transform; SetTransformMatrix( g, pane, scaleFactor ); double baseVal = _scale.CalcBaseTic(); float topPix, rightPix; _scale.GetTopRightPix( pane, out topPix, out rightPix ); shiftPos = CalcTotalShift( pane, scaleFactor, shiftPos ); _scale.DrawGrid( g, pane, baseVal, topPix, scaleFactor ); DrawMinorTics( g, pane, baseVal, shiftPos, scaleFactor, topPix ); g.Transform = saveMatrix; } } /// /// This method will set the property for this /// using the currently required space multiplied by a fraction (bufferFraction). /// /// /// The currently required space is calculated using , and is /// based on current data ranges, font sizes, etc. The "space" is actually the amount of space /// required to fit the tic marks, scale labels, and axis title. /// /// 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 amount of space to allocate for the axis, expressed /// as a fraction of the currently required space. For example, a value of 1.2 would /// allow for 20% extra above the currently required space. /// If true, then this method will only modify the /// property if the calculated result is more than the current value. public void SetMinSpaceBuffer( Graphics g, GraphPane pane, float bufferFraction, bool isGrowOnly ) { // save the original value of minSpace float oldSpace = this.MinSpace; // set minspace to zero, since we don't want it to affect the CalcSpace() result this.MinSpace = 0; // Calculate the space required for the current graph assuming scalefactor = 1.0 // and apply the bufferFraction float fixedSpace; float space = this.CalcSpace( g, pane, 1.0F, out fixedSpace ) * bufferFraction; // isGrowOnly indicates the minSpace can grow but not shrink if ( isGrowOnly ) space = Math.Max( oldSpace, space ); // Set the minSpace this.MinSpace = space; } /// /// Setup the Transform Matrix to handle drawing of this /// /// /// 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. /// abstract public void SetTransformMatrix( Graphics g, GraphPane pane, float scaleFactor ); /// /// Calculate the "shift" size, in pixels, in order to shift the axis from its default /// location to the value specified by . /// /// /// A reference to the object that is the parent or /// owner of this object. /// /// The shift amount measured in pixels abstract internal float CalcCrossShift( GraphPane pane ); /// /// Gets the "Cross" axis that corresponds to this axis. /// /// /// The cross axis is the axis which determines the of this Axis when the /// Axis.Cross property is used. The /// cross axis for any or /// is always the primary , and /// the cross axis for any or is /// always the primary . /// /// /// A reference to the object that is the parent or /// owner of this object. /// abstract public Axis GetCrossAxis( GraphPane pane ); // abstract internal float GetMinPix( GraphPane pane ); //abstract internal float CalcCrossFraction( GraphPane pane ); /// /// Returns the linearized actual cross position for this axis, reflecting the settings of /// , , and . /// /// /// If the value of lies outside the axis range, it is /// limited to the axis range. /// internal double EffectiveCrossValue( GraphPane pane ) { Axis crossAxis = GetCrossAxis( pane ); // Use Linearize here instead of _minLinTemp because this method is called // as part of CalcRect() before scale is fully setup double min = crossAxis._scale.Linearize( _scale._min ); double max = crossAxis._scale.Linearize( _scale._max ); if ( _crossAuto ) { if ( crossAxis._scale.IsReverse == ( this is Y2Axis || this is X2Axis ) ) return max; else return min; } else if ( _cross < min ) return min; else if ( _cross > max ) return max; else return _scale.Linearize( _cross ); } /// /// Returns true if the axis is shifted at all due to the setting of /// . This function will always return false if /// is true. /// internal bool IsCrossShifted( GraphPane pane ) { if ( _crossAuto ) return false; else { Axis crossAxis = GetCrossAxis( pane ); if ( ( ( this is XAxis || this is YAxis ) && !crossAxis._scale.IsReverse ) || ( ( this is X2Axis || this is Y2Axis ) && crossAxis._scale.IsReverse ) ) { if ( _cross <= crossAxis._scale._min ) return false; } else { if ( _cross >= crossAxis._scale._max ) return false; } } return true; } /// /// Calculates the proportional fraction of the total cross axis width at which /// this axis is located. /// /// /// internal float CalcCrossFraction( GraphPane pane ) { // if this axis is not shifted due to the Cross value if ( !this.IsCrossShifted( pane ) ) { // if it's the primary axis and the scale labels are on the inside, then we // don't need to save any room for the axis labels (they will be inside the chart rect) if ( IsPrimary( pane ) && _scale._isLabelsInside ) return 1.0f; // otherwise, it's a secondary (outboard) axis and we always save room for the axis and labels. else return 0.0f; } double effCross = EffectiveCrossValue( pane ); Axis crossAxis = GetCrossAxis( pane ); // Use Linearize here instead of _minLinTemp because this method is called // as part of CalcRect() before scale is fully setup // double max = crossAxis._scale._maxLinTemp; // double min = crossAxis._scale._minLinTemp; double max = crossAxis._scale.Linearize( _scale._min ); double min = crossAxis._scale.Linearize( _scale._max ); float frac; if ( ( ( this is XAxis || this is YAxis ) && _scale._isLabelsInside == crossAxis._scale.IsReverse ) || ( ( this is X2Axis || this is Y2Axis ) && _scale._isLabelsInside != crossAxis._scale.IsReverse ) ) frac = (float)( ( effCross - min ) / ( max - min ) ); else frac = (float)( ( max - effCross ) / ( max - min ) ); if ( frac < 0.0f ) frac = 0.0f; if ( frac > 1.0f ) frac = 1.0f; return frac; } private float CalcTotalShift( GraphPane pane, float scaleFactor, float shiftPos ) { if ( !IsPrimary( pane ) ) { // if ( CalcCrossFraction( pane ) != 0.0 ) if ( IsCrossShifted( pane ) ) { shiftPos = 0; } else { // Scaled size (pixels) of a tic float ticSize = _majorTic.ScaledTic( scaleFactor ); // if the scalelabels are on the inside, shift everything so the axis is drawn, // for example, to the left side of the available space for a YAxis type if ( _scale._isLabelsInside ) { shiftPos += _tmpSpace; // shift the axis to leave room for the outside tics if ( _majorTic.IsOutside || _majorTic._isCrossOutside || _minorTic.IsOutside || _minorTic._isCrossOutside ) shiftPos -= ticSize; } else { // if it's not the primary axis, add a tic space for the spacing between axes shiftPos += _axisGap * scaleFactor; // if it has inside tics, leave another tic space if ( _majorTic.IsInside || _majorTic._isCrossInside || _minorTic.IsInside || _minorTic._isCrossInside ) shiftPos += ticSize; } } } // shift is the position of the actual axis line itself // everything else is based on that position. float crossShift = CalcCrossShift( pane ); shiftPos += crossShift; return shiftPos; } /// /// Calculate the space required (pixels) for this object. /// /// /// This is the total space (vertical space for the X axis, horizontal space for /// the Y axes) required to contain the axis. If is zero, then /// this space will be the space required between the and /// the . This method sets the internal values of /// for use by the /// method. /// /// /// 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 amount of space (pixels) at the edge of the ChartRect /// that is always required for this axis, even if the axis is shifted by the /// value. /// Returns the space, in pixels, required for this axis (between the /// rect and ChartRect) public float CalcSpace( Graphics g, GraphPane pane, float scaleFactor, out float fixedSpace ) { //fixedSpace = 0; //Typical character height for the scale font float charHeight = _scale._fontSpec.GetHeight( scaleFactor ); // Scaled size (pixels) of a tic float ticSize = _majorTic.ScaledTic( scaleFactor ); // Scaled size (pixels) of the axis gap float axisGap = _axisGap * scaleFactor; float scaledLabelGap = _scale._labelGap * charHeight; float scaledTitleGap = _title.GetScaledGap( scaleFactor ); // The minimum amount of space to reserve for the NORMAL position of the axis. This would // be the left side of the chart rect for the Y axis, the right side for the Y2 axis, etc. // This amount of space is based on the need to reserve space for tics, etc., even if the // Axis.Cross property causes the axis to be in a different location. fixedSpace = 0; // The actual space needed for this axis (ignoring the setting of Axis.Cross) _tmpSpace = 0; // Account for the Axis if ( _isVisible ) { bool hasTic = this.MajorTic.IsOutside || this.MajorTic._isCrossOutside || this.MinorTic.IsOutside || this.MinorTic._isCrossOutside; // account for the tic space. Leave the tic space for any type of outside tic (Outside Tic Space) if ( hasTic ) _tmpSpace += ticSize; // if this is not the primary axis if ( !IsPrimary( pane ) ) { // always leave an extra tic space for the space between the multi-axes (Axis Gap) _tmpSpace += axisGap; // if it has inside tics, leave another tic space (Inside Tic Space) if ( this.MajorTic._isInside || this.MajorTic._isCrossInside || this.MinorTic._isInside || this.MinorTic._isCrossInside ) _tmpSpace += ticSize; } // tic takes up 1x tic // space between tic and scale label is 0.5 tic // scale label is GetScaleMaxSpace() // space between scale label and axis label is 0.5 tic // account for the tic labels + 'LabelGap' tic gap between the tic and the label _tmpSpace += _scale.GetScaleMaxSpace( g, pane, scaleFactor, true ).Height + scaledLabelGap; string str = MakeTitle(); // Only add space for the title if there is one // Axis Title gets actual height if ( str.Length > 0 && _title._isVisible ) { //tmpSpace += this.TitleFontSpec.BoundingBox( g, str, scaleFactor ).Height; fixedSpace = this.Title.FontSpec.BoundingBox( g, str, scaleFactor ).Height + scaledTitleGap; _tmpSpace += fixedSpace; fixedSpace += scaledTitleGap; } if ( hasTic ) fixedSpace += ticSize; } // for the Y axes, make sure that enough space is left to fit the first // and last X axis scale label if ( this.IsPrimary( pane ) && ( ( ( this is YAxis && ( ( !pane.XAxis._scale._isSkipFirstLabel && !pane.XAxis._scale._isReverse ) || ( !pane.XAxis._scale._isSkipLastLabel && pane.XAxis._scale._isReverse ) ) ) || ( this is Y2Axis && ( ( !pane.XAxis._scale._isSkipFirstLabel && pane.XAxis._scale._isReverse ) || ( !pane.XAxis._scale._isSkipLastLabel && !pane.XAxis._scale._isReverse ) ) ) ) && pane.XAxis.IsVisible && pane.XAxis._scale._isVisible ) ) { // half the width of the widest item, plus a gap of 1/2 the charheight float tmp = pane.XAxis._scale.GetScaleMaxSpace( g, pane, scaleFactor, true ).Width / 2.0F; //+ charHeight / 2.0F; //if ( tmp > tmpSpace ) // tmpSpace = tmp; fixedSpace = Math.Max( tmp, fixedSpace ); } // Verify that the minSpace property was satisfied _tmpSpace = Math.Max( _tmpSpace, _minSpace * (float)scaleFactor ); fixedSpace = Math.Max( fixedSpace, _minSpace * (float)scaleFactor ); return _tmpSpace; } /// /// Determines if this object is a "primary" one. /// /// /// The primary axes are the (always), the first /// in the /// ( = 0), and the first /// in the /// ( = 0). Note that /// and /// always reference the primary axes. /// /// /// A reference to the object that is the parent or /// owner of this object. /// /// true for a primary (for the , /// this is always true), false otherwise abstract internal bool IsPrimary( GraphPane pane ); internal void FixZeroLine( Graphics g, GraphPane pane, float scaleFactor, float left, float right ) { // restore the zero line if needed (since the fill tends to cover it up) if ( _isVisible && _majorGrid._isZeroLine && _scale._min < 0.0 && _scale._max > 0.0 ) { float zeroPix = _scale.Transform( 0.0 ); using ( Pen zeroPen = new Pen( _color, pane.ScaledPenWidth( _majorGrid._penWidth, scaleFactor ) ) ) { g.DrawLine( zeroPen, left, zeroPix, right, zeroPix ); //zeroPen.Dispose(); } } } /// /// Draw the minor tic marks as required for this . /// /// /// 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 scale value for the first major tic position. This is the reference point /// for all other tic marks. /// /// The number of pixels to shift this axis, based on the /// value of . A positive value is into the ChartRect relative to /// the default axis position. /// /// 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 pixel location of the far side of the ChartRect from this axis. /// This value is the ChartRect.Height for the XAxis, or the ChartRect.Width /// for the YAxis and Y2Axis. /// public void DrawMinorTics( Graphics g, GraphPane pane, double baseVal, float shift, float scaleFactor, float topPix ) { if ( ( this.MinorTic.IsOutside || this.MinorTic.IsOpposite || this.MinorTic.IsInside || this.MinorTic._isCrossOutside || this.MinorTic._isCrossInside || _minorGrid._isVisible ) && _isVisible ) { double tMajor = _scale._majorStep * _scale.MajorUnitMultiplier, tMinor = _scale._minorStep * _scale.MinorUnitMultiplier; if ( _scale.IsLog || tMinor < tMajor ) { float minorScaledTic = this.MinorTic.ScaledTic( scaleFactor ); // Minor tics start at the minimum value and step all the way thru // the full scale. This means that if the minor step size is not // an even division of the major step size, the minor tics won't // line up with all of the scale labels and major tics. double first = _scale._minLinTemp, last = _scale._maxLinTemp; double dVal = first; float pixVal; int iTic = _scale.CalcMinorStart( baseVal ); int MajorTic = 0; double majorVal = _scale.CalcMajorTicValue( baseVal, MajorTic ); using ( Pen pen = new Pen( _minorTic._color, pane.ScaledPenWidth( MinorTic._penWidth, scaleFactor ) ) ) using ( Pen minorGridPen = _minorGrid.GetPen( pane, scaleFactor ) ) { // Draw the minor tic marks while ( dVal < last && iTic < 5000 ) { // Calculate the scale value for the current tic dVal = _scale.CalcMinorTicValue( baseVal, iTic ); // Maintain a value for the current major tic if ( dVal > majorVal ) majorVal = _scale.CalcMajorTicValue( baseVal, ++MajorTic ); // Make sure that the current value does not match up with a major tic if ( ( ( Math.Abs( dVal ) < 1e-20 && Math.Abs( dVal - majorVal ) > 1e-20 ) || ( Math.Abs( dVal ) > 1e-20 && Math.Abs( ( dVal - majorVal ) / dVal ) > 1e-10 ) ) && ( dVal >= first && dVal <= last ) ) { pixVal = _scale.LocalTransform( dVal ); _minorGrid.Draw( g, minorGridPen, pixVal, topPix ); _minorTic.Draw( g, pane, pen, pixVal, topPix, shift, minorScaledTic ); } iTic++; } } } } } /// /// Draw the title for this . /// /// On entry, it is assumed that the /// graphics transform has been configured so that the origin is at the left side /// of this axis, and the axis is aligned along the X coordinate direction. /// /// /// 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 number of pixels to shift this axis, based on the /// value of . A positive value is into the ChartRect relative to /// the default axis position. /// /// 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 DrawTitle( Graphics g, GraphPane pane, float shiftPos, float scaleFactor ) { string str = MakeTitle(); // If the Axis is visible, draw the title if ( _isVisible && _title._isVisible && str.Length > 0 ) { bool hasTic = ( _scale._isLabelsInside ? ( this.MajorTic.IsInside || this.MajorTic._isCrossInside || this.MinorTic.IsInside || this.MinorTic._isCrossInside ) : ( this.MajorTic.IsOutside || this.MajorTic._isCrossOutside || this.MinorTic.IsOutside || this.MinorTic._isCrossOutside ) ); // Calculate the title position in screen coordinates float x = ( _scale._maxPix - _scale._minPix ) / 2; float scaledTic = MajorTic.ScaledTic( scaleFactor ); float scaledLabelGap = _scale._fontSpec.GetHeight( scaleFactor ) * _scale._labelGap; float scaledTitleGap = _title.GetScaledGap( scaleFactor ); // The space for the scale labels is only reserved if the axis is not shifted due to the // cross value. Note that this could be a problem if the axis is only shifted slightly, // since the scale value labels may overlap the axis title. However, it's not possible to // calculate that actual shift amount at this point, because the ChartRect rect has not yet been // calculated, and the cross value is determined using a transform of scale values (which // rely on ChartRect). float gap = scaledTic * ( hasTic ? 1.0f : 0.0f ) + this.Title.FontSpec.BoundingBox( g, str, scaleFactor ).Height / 2.0F; float y = ( _scale._isVisible ? _scale.GetScaleMaxSpace( g, pane, scaleFactor, true ).Height + scaledLabelGap : 0 ); if ( _scale._isLabelsInside ) y = shiftPos - y - gap; else y = shiftPos + y + gap; if ( !_crossAuto && !_title._isTitleAtCross ) y = Math.Max( y, gap ); AlignV alignV = AlignV.Center; // Add in the TitleGap space y += scaledTitleGap; // Draw the title this.Title.FontSpec.Draw( g, pane, str, x, y, AlignH.Center, alignV, scaleFactor ); } } private string MakeTitle() { // Revision: JCarpenter 10/06 // Allow customization of the modified title when the scale is very large // The event handler can edit the full label. If the handler returns // null, then the title will be the default. if ( ScaleTitleEvent != null ) { string label = ScaleTitleEvent( this ); if ( label != null ) return label; } // If the Mag is non-zero and IsOmitMag == false, and IsLog == false, // then add the mag indicator to the title. if ( _scale._mag != 0 && !_title._isOmitMag && !_scale.IsLog ) return _title._text + String.Format( " (10^{0})", _scale._mag ); else return _title._text; } /// /// Make a value label for the axis at the specified ordinal position. /// /// /// This method properly accounts for , /// , /// and other axis format settings. It also implements the ScaleFormatEvent such that /// custom labels can be created. /// /// /// A reference to the object that is the parent or /// owner of this object. /// /// /// The zero-based, ordinal index of the label to be generated. For example, a value of 2 would /// cause the third value label on the axis to be generated. /// /// /// The numeric value associated with the label. This value is ignored for log /// () /// and text () type axes. /// /// The resulting value label as a internal string MakeLabelEventWorks( GraphPane pane, int index, double dVal ) { // if there is a valid ScaleFormatEvent, then try to use it to create the label // the label will be non-null if it's to be used if ( this.ScaleFormatEvent != null ) { string label; label = this.ScaleFormatEvent( pane, this, dVal, index ); if ( label != null ) return label; } // second try. If there's no custom ScaleFormatEvent, then just call // _scale.MakeLabel according to the type of scale if ( this.Scale != null ) return _scale.MakeLabel( pane, index, dVal ); else return "?"; } #endregion } }