/*
 * NPlot - A charting library for .NET
 * 
 * Axis.cs
 * Copyright (C) 2003-2006 Matt Howlett and others.
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. Neither the name of NPlot nor the names of its contributors may
 *    be used to endorse or promote products derived from this software without
 *    specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System.Drawing.Drawing2D;
using System.Drawing;
using System;
using System.Collections;

namespace NPlot
{

	/// <summary>
	/// Encapsulates functionality common to all axis classes. All specific
	/// axis classes derive from Axis. Axis can be used as a concrete class
	/// itself - it is an Axis without any embilishments [tick marks or tick
	/// mark labels].<br></br><br></br>
	/// This class encapsulates no physical information about where the axes
	/// are drawn. 
	/// </summary>
	public class Axis : System.ICloneable
	{
	
		/// <summary>
		/// If true, tick marks will cross the axis, with their centre on the axis line.
		/// If false, tick marks will be drawn as a line with origin starting on the axis line.
		/// </summary>
		public bool TicksCrossAxis
		{
			get
			{
				return ticksCrossAxis_;
			}
			set
			{
				ticksCrossAxis_ = value;
			}
		}
		bool ticksCrossAxis_ = false;


		/// <summary>
		/// The maximum world extent of the axis. Note that it is sensical if 
		/// WorldMax is less than WorldMin - the axis would just be descending
		/// not ascending. Currently Axes won't display properly if you do 
		/// this - use the Axis.Reversed property instead to achieve the same
		/// result.
		/// 
        /// Setting this raises the WorldMinChanged event and the WorldExtentsChanged event.
        /// </summary>
		public virtual double WorldMax
		{
			get
			{
				return worldMax_;
			}
			set
			{
				this.worldMax_ = value;
                /*
                if (this.WorldExtentsChanged != null)
                    this.WorldExtentsChanged(this, new WorldValueChangedArgs(worldMax_, WorldValueChangedArgs.MinMaxType.Max));
                if (this.WorldMaxChanged != null)
                    this.WorldMaxChanged(this, new WorldValueChangedArgs(worldMax_, WorldValueChangedArgs.MinMaxType.Max));
                */
            }
		}
		private double worldMax_;
		

		/// <summary>
		/// The minumum world extent of the axis. Note that it is sensical if 
		/// WorldMax is less than WorldMin - the axis would just be descending
		/// not ascending. Currently Axes won't display properly if you do 
		/// this - use the Axis.Reversed property instead to achieve the same
		/// result.
		/// 
		/// Setting this raises the WorldMinChanged event and the WorldExtentsChanged event.
		/// </summary>
		public virtual double WorldMin
		{
			get
			{
				return this.worldMin_;
			}
			set
			{
				this.worldMin_ = value;
                /*
                if (this.WorldExtentsChanged != null)
                    this.WorldExtentsChanged( this, new WorldValueChangedArgs( worldMin_, WorldValueChangedArgs.MinMaxType.Min) );
                if (this.WorldMinChanged != null)
                    this.WorldMinChanged( this, new WorldValueChangedArgs(worldMin_, WorldValueChangedArgs.MinMaxType.Min) );
                */
            }
		}
		private double worldMin_;


		/// <summary>
		/// Length (in pixels) of a large tick. <b>Not</b> the distance 
		/// between large ticks. The length of the tick itself.
		/// </summary>
		public int LargeTickSize
		{
			get
			{
				return largeTickSize_;
			}
			set
			{
				largeTickSize_ = value;
			}
		}
		private int largeTickSize_;


		/// <summary>
		/// Length (in pixels) of the small ticks.
		/// </summary>
		public int SmallTickSize
		{
			get
			{
				return smallTickSize_;
			}
			set
			{
				smallTickSize_ = value;
			}
		}
		private int smallTickSize_;


		/// <summary>
		/// The Axis Label
		/// </summary>
		public string Label
		{
			get
			{
				return label_;
			}
			set
			{
				label_ = value;
			}
		}
		private string label_;


		/// <summary>
		/// If true, text associated with tick marks will be drawn on the other side of the
		/// axis line [next to the axis]. If false, tick mark text will be drawn at the end
		/// of the tick mark [on the same of the axis line as the tick].
		/// </summary>
		public bool TickTextNextToAxis
		{
			get
			{
				return tickTextNextToAxis_;
			}
			set
			{
				tickTextNextToAxis_ = value;
			}
		}
		bool tickTextNextToAxis_;


		/// <summary>
		/// If set to true, the axis is hidden. That is, the axis line, ticks, tick 
		/// labels and axis label will not be drawn. 
		/// </summary>
		public bool Hidden
		{
			get
			{
				return hidden_;
			}
			set
			{
				hidden_ = value;
			}
		}
		private bool hidden_;


		/// <summary>
		/// If set true, the axis will behave as though the WorldMin and WorldMax values
		/// have been swapped.
		/// </summary>
		public bool Reversed
		{
			get
			{
				return reversed_;
			}
			set
			{
				reversed_ = value;
			}
		}
		private bool reversed_;


		/// <summary>
		/// If true, no text will be drawn next to any axis tick marks.
		/// </summary>
		public bool HideTickText
		{
			get
			{
				return hideTickText_;
			}
			set
			{
				hideTickText_ = value;
			}
		}
		private bool hideTickText_;


		/// <summary>
		/// This font is used for the drawing of text next to the axis tick marks.
		/// </summary>
		public Font TickTextFont
		{
			get
			{
				return this.tickTextFont_;
			}
			set
			{
				this.tickTextFont_ = value;
				UpdateScale();
			}
		}
		private Font tickTextFont_;
		private Font tickTextFontScaled_;


		/// <summary>
		/// This font is used to draw the axis label.
		/// </summary>
		public Font LabelFont
		{
			get
			{
				return labelFont_;
			}
			set
			{
				labelFont_ = value;
				UpdateScale();
			}
		}
		private Font labelFont_;
		private Font labelFontScaled_;


		/// <summary>
		/// Specifies the format used for drawing tick labels. See 
		/// StringBuilder.AppendFormat for a description of this 
		/// string.
		/// </summary>
		public string NumberFormat
		{
			get
			{
				return numberFormat_;
			}
			set
			{
				numberFormat_ = value;
			}
		}
		private string numberFormat_;


		/// <summary>
		/// If LargeTickStep isn't specified, then this will be calculated 
		/// automatically. The calculated value will not be less than this
		/// amount.
		/// </summary>
		public int MinPhysicalLargeTickStep
		{
			get
			{
				return minPhysicalLargeTickStep_;
			}
			set
			{
				minPhysicalLargeTickStep_ = value;
			}
		}
		private int minPhysicalLargeTickStep_ = 30;


		/// <summary>
		/// The color of the pen used to draw the ticks and the axis line.
		/// </summary>
		public System.Drawing.Color AxisColor
		{
			get
			{
				return linePen_.Color;
			}
			set
			{
				linePen_ = new Pen( (Color)value );
			}
		}


		/// <summary>
		/// The pen used to draw the ticks and the axis line.
		/// </summary>
		public System.Drawing.Pen AxisPen
		{
			get
			{
				return linePen_;
			}
			set
			{
				linePen_ = value;
			}
		}
		private System.Drawing.Pen linePen_;


		/// <summary>
		/// If true, automated tick placement will be independent of the physical
		/// extent of the axis. Tick placement will look good for charts of typical
		/// size (say physical dimensions of 640x480). If you want to produce the
		/// same chart on two graphics surfaces of different sizes [eg Windows.Forms
		/// control and printer], then you will want to set this property to true.
		/// If false [default], the number of ticks and their placement will be 
		/// optimally calculated to look the best for the given axis extent. This 
		/// is very useful if you are creating a cart with particularly small or
		/// large physical dimensions.
		/// </summary>
		public bool TicksIndependentOfPhysicalExtent
		{
			get
			{
				return ticksIndependentOfPhysicalExtent_;
			}
			set
			{
				ticksIndependentOfPhysicalExtent_ = value;
			}
		}
		private bool ticksIndependentOfPhysicalExtent_ = false;


		/// <summary>
		/// If true label is flipped about the text center line parallel to the text.
		/// </summary>
		public bool FlipTicksLabel
		{
			get 
			{
				return flipTicksLabel_; 
			}
			set 
			{ 
				flipTicksLabel_ = value; 
			}
		}
 		private bool flipTicksLabel_ = false;


		/// <summary>
		/// Angle to draw ticks at (measured anti-clockwise from axis direction).
		/// </summary>
		public float TicksAngle
		{
			get
			{
				return ticksAngle_;
			}
			set
			{
				ticksAngle_ = value;
			}
		}
		private float ticksAngle_ = (float)Math.PI / 2.0f;


		/// <summary>
		/// Angle to draw large tick labels at (clockwise from horizontal). Note: 
		/// this is currently only implemented well for the lower x-axis. 
		/// </summary>
		public float TicksLabelAngle
		{
			get
			{
				return ticksLabelAngle_;
			}
			set
			{
				ticksLabelAngle_ = value;
			}
		}
		private float ticksLabelAngle_ = 0.0f;


		/// <summary>
		/// The color of the brush used to draw the axis label.
		/// </summary>
		public Color LabelColor
		{
			set
			{
				labelBrush_ = new SolidBrush( value );
			}
		}
		
		
		/// <summary>
		/// The brush used to draw the axis label.
		/// </summary>
		public Brush LabelBrush
		{
			get
			{
				return labelBrush_;
			}
			set
			{
				labelBrush_ = value;
			}
		}
		private Brush labelBrush_;

		
		/// <summary>
		/// The color of the brush used to draw the axis tick labels.
		/// </summary>
		public Color TickTextColor
		{
			set
			{
				tickTextBrush_ = new SolidBrush( value );
			}
		}


		/// <summary>
		/// The brush used to draw the tick text.
		/// </summary>
		public Brush TickTextBrush
		{
			get
			{
				return tickTextBrush_;
			}
			set
			{
				tickTextBrush_ = value;
			}
		}
		private Brush tickTextBrush_;


		/// <summary>
		/// If true, label and tick text will be scaled to match size
		/// of PlotSurface2D. If false, they won't be.
		/// </summary>
		/// <remarks>Could also be argued this belongs in PlotSurface2D</remarks>
		public bool AutoScaleText
		{
			get
			{
				return autoScaleText_;
			}
			set
			{
				autoScaleText_ = value;
			}
		}
		private bool autoScaleText_;


		/// <summary>
		/// If true, tick lengths will be scaled to match size
		/// of PlotSurface2D. If false, they won't be.
		/// </summary>
		/// <remarks>Could also be argued this belongs in PlotSurface2D</remarks>
		public bool AutoScaleTicks
		{
			get
			{
				return autoScaleTicks_;
			}
			set
			{
				autoScaleTicks_ = value;
			}
		}
		private bool autoScaleTicks_;


		/// <summary>
		/// Deep copy of Axis.
		/// </summary>
		/// <remarks>
		/// This method includes a check that guards against derived classes forgetting
		/// to implement their own Clone method. If Clone is called on a object derived
		/// from Axis, and the Clone method hasn't been overridden by that object, then
		/// the test this.GetType == typeof(Axis) will fail.
		/// </remarks>
		/// <returns>A copy of the Axis Class</returns>
		public virtual object Clone()
		{
			// ensure that this isn't being called on a derived type. If that is the case
			// then the derived type didn't override this method as it should have.
			if (this.GetType() != typeof(Axis))
			{
				throw new NPlotException( "Clone not defined in derived type." );
			}
			
			Axis a = new Axis();
			DoClone( this, a );
			return a;
		}


		/// <summary>
		/// Helper method for Clone. Does all the copying - can be called by derived
		/// types so they don't need to implement this part of the copying themselves.
		/// also useful in constructor of derived types that takes Axis class.
		/// </summary>
		protected static void DoClone( Axis b, Axis a )
		{

			// value items
			a.autoScaleText_ = b.autoScaleText_;
			a.autoScaleTicks_ = b.autoScaleTicks_;
			a.worldMax_ = b.worldMax_;
			a.worldMin_ = b.worldMin_;
			a.tickTextNextToAxis_ = b.tickTextNextToAxis_;
			a.hidden_ = b.hidden_;
			a.hideTickText_ = b.hideTickText_;
			a.reversed_ = b.reversed_;
			a.ticksAngle_ = b.ticksAngle_;
			a.ticksLabelAngle_ = b.ticksLabelAngle_;
			a.minPhysicalLargeTickStep_ = b.minPhysicalLargeTickStep_;
			a.ticksIndependentOfPhysicalExtent_ = b.ticksIndependentOfPhysicalExtent_;
			a.largeTickSize_ = b.largeTickSize_;
			a.smallTickSize_ = b.smallTickSize_;
			a.ticksCrossAxis_ = b.ticksCrossAxis_;
			a.labelOffset_ = b.labelOffset_;
            a.labelOffsetAbsolute_ = b.labelOffsetAbsolute_;
            a.labelOffsetScaled_ = b.labelOffsetScaled_;

			// reference items.
			a.tickTextFont_ = (Font)b.tickTextFont_.Clone();
			a.label_ = (string)b.label_.Clone();
			if (b.numberFormat_ != null) 
			{
				a.numberFormat_ = (string)b.numberFormat_.Clone();
			}
			else
			{
				a.numberFormat_ = null;
			}

			a.labelFont_ = (Font)b.labelFont_.Clone();
			a.linePen_ = (Pen)b.linePen_.Clone();
			a.tickTextBrush_ = (Brush)b.tickTextBrush_.Clone();
			a.labelBrush_ = (Brush)b.labelBrush_.Clone();

			a.FontScale = b.FontScale;
			a.TickScale = b.TickScale;

		}



		/// <summary>
		/// Helper function for constructors.
		/// Do initialization here so that Clear() method is handled properly
		/// </summary>
		private void Init()
		{
			this.worldMax_ = double.NaN;
			this.worldMin_ = double.NaN;
			this.Hidden = false;
			this.SmallTickSize = 2;
			this.LargeTickSize = 6;
			this.FontScale = 1.0f;
			this.TickScale = 1.0f;
			this.AutoScaleTicks = false;
			this.AutoScaleText = false;
			this.TickTextNextToAxis = true;
			this.HideTickText = false;
			this.TicksCrossAxis = false;
			this.LabelOffset = 0.0f;
            this.LabelOffsetAbsolute = false;
            this.LabelOffsetScaled = true;

			this.Label = "" ;
			this.NumberFormat = null;
			this.Reversed = false;

			FontFamily fontFamily = new FontFamily( "Arial" );
			this.TickTextFont = new Font( fontFamily, 10, FontStyle.Regular, GraphicsUnit.Pixel );
			this.LabelFont = new Font( fontFamily, 12, FontStyle.Regular, GraphicsUnit.Pixel );
			this.LabelColor = System.Drawing.Color.Black;
			this.TickTextColor = System.Drawing.Color.Black;
			this.linePen_ = new Pen( System.Drawing.Color.Black );
			this.linePen_.Width = 1.0f;
			this.FontScale = 1.0f;

			// saves constructing these in draw method.
			drawFormat_ = new StringFormat();
			drawFormat_.Alignment = StringAlignment.Center;
		}

		StringFormat drawFormat_;


		/// <summary>
		/// Default constructor
		/// </summary>
		public Axis( )
		{
			this.Init();
		}


		/// <summary>
		/// Constructor that takes only world min and max values.
		/// </summary>
		/// <param name="worldMin">The minimum world coordinate.</param>
		/// <param name="worldMax">The maximum world coordinate.</param>
		public Axis( double worldMin, double worldMax )
		{
			this.Init();
			this.WorldMin = worldMin;
			this.WorldMax = worldMax;
		}


		/// <summary>
		/// Copy constructor.
		/// </summary>
		/// <param name="a">The Axis to clone.</param>
		public Axis( Axis a )
		{
			Axis.DoClone( a, this );
		}


		/// <summary>
		/// Determines whether a world value is outside range WorldMin -> WorldMax
		/// </summary>
		/// <param name="coord">the world value to test</param>
		/// <returns>true if outside limits, false otherwise</returns>
		public bool OutOfRange( double coord )
		{
			if (double.IsNaN(WorldMin) || double.IsNaN(WorldMax))
			{
				throw new NPlotException( "world min / max not set" );
			}

			if (coord > this.WorldMax || coord < this.WorldMin)
			{
				return true;
			}
			else
			{
				return false;
			}
		}


		/// <summary>
		/// Sets the world extent of the current axis to be just large enough
		/// to encompas the current world extent of the axis, and the world
		/// extent of the passed in axis
		/// </summary>
		/// <param name="a">The other Axis instance.</param>
		public void LUB( Axis a )
		{
			if (a == null)
			{
				return;
			}

			// mins
			if (!double.IsNaN(a.worldMin_))
			{
				if (double.IsNaN(worldMin_))
				{
					WorldMin = a.WorldMin;
				}
				else
				{
					if (a.WorldMin < WorldMin)
					{
						WorldMin = a.WorldMin;
					}
				}
			}

			// maxs.
			if (!double.IsNaN(a.worldMax_))
			{
				if (double.IsNaN(worldMax_))
				{
					WorldMax = a.WorldMax;
				}
				else
				{
					if (a.WorldMax > WorldMax)
					{
						WorldMax = a.WorldMax;
					}
				}
			}
		}


		/// <summary>
		/// World to physical coordinate transform.
		/// </summary>
		/// <param name="coord">The coordinate value to transform.</param>
		/// <param name="physicalMin">The physical position corresponding to the world minimum of the axis.</param>
		/// <param name="physicalMax">The physical position corresponding to the world maximum of the axis.</param>
		/// <param name="clip">if false, then physical value may extend outside worldMin / worldMax. If true, the physical value returned will be clipped to physicalMin or physicalMax if it lies outside this range.</param>
		/// <returns>The transformed coordinates.</returns>
		/// <remarks>Not sure how much time is spent in this often called function. If it's lots, then
		/// worth optimizing (there is scope to do so).</remarks>
		public virtual PointF WorldToPhysical( 
			double coord, 
			PointF physicalMin, 
			PointF physicalMax, 
			bool clip )
		{

			// (1) account for reversed axis. Could be tricky and move
			// this out, but would be a little messy.

			PointF _physicalMin;
			PointF _physicalMax;

			if ( this.Reversed )
			{
				_physicalMin = physicalMax;
				_physicalMax = physicalMin;
			}
			else
			{
				_physicalMin = physicalMin;
				_physicalMax = physicalMax;
			}


			// (2) if want clipped value, return extrema if outside range.
			
			if ( clip )
			{
				if ( WorldMin < WorldMax )
				{
					if ( coord > WorldMax )
					{
						return _physicalMax;
					}
					if ( coord < WorldMin )
					{
						return _physicalMin;
					}
				}
				else
				{
					if ( coord < WorldMax )
					{
						return _physicalMax;
					}
					if ( coord > WorldMin )
					{
						return _physicalMin;
					}
				}
			}


			// (3) we are inside range or don't want to clip.

			double range = WorldMax - WorldMin;
			double prop = (double)((coord - WorldMin) / range);

			// Force clipping at bounding box largeClip times that of real bounding box 
			// anyway. This is effectively at infinity.
			const double largeClip = 100.0;
			if (prop > largeClip && clip)
				prop = largeClip;

			if (prop < -largeClip && clip)
				prop = -largeClip;

			if (range == 0)
			{
				if (coord >= WorldMin)
					prop = largeClip;

				if (coord < WorldMin)
					prop = -largeClip;
			}

			// calculate the physical coordinate.
			PointF offset = new PointF( 
				(float)(prop * (_physicalMax.X - _physicalMin.X)),
				(float)(prop * (_physicalMax.Y - _physicalMin.Y)) );

			return new PointF( (_physicalMin.X + offset.X), (_physicalMin.Y + offset.Y) );
		}


		/// <summary>
		/// Return the world coordinate of the projection of the point p onto
		/// the axis.
		/// </summary>
		/// <param name="p">The point to project onto the axis</param>
		/// <param name="physicalMin">The physical position corresponding to the world minimum of the axis.</param>
		/// <param name="physicalMax">The physical position corresponding to the world maximum of the axis.</param>
		/// <param name="clip">If true, the world value will be clipped to WorldMin or WorldMax as appropriate if it lies outside this range.</param>
		/// <returns>The world value corresponding to the projection of the point p onto the axis.</returns>
		public virtual double PhysicalToWorld( 
			PointF p, 
			PointF physicalMin, 
			PointF physicalMax,
			bool clip )
		{

			// (1) account for reversed axis. Could be tricky and move
			// this out, but would be a little messy.

			PointF _physicalMin;
			PointF _physicalMax;

			if ( this.Reversed )
			{
				_physicalMin = physicalMax;
				_physicalMax = physicalMin;
			}
			else
			{
				_physicalMin = physicalMin;
				_physicalMax = physicalMax;
			}

			// normalised axis dir vector
			float axis_X = _physicalMax.X - _physicalMin.X;
			float axis_Y = _physicalMax.Y - _physicalMin.Y;
			float len = (float)Math.Sqrt( axis_X * axis_X + axis_Y * axis_Y );
			axis_X /= len;
			axis_Y /= len;

			// point relative to axis physical minimum.
			PointF posRel = new PointF( p.X - _physicalMin.X, p.Y - _physicalMin.Y );

			// dist of point projection on axis, normalised.
			float prop = ( axis_X * posRel.X + axis_Y * posRel.Y ) / len;

			double world = prop * (this.WorldMax - this.WorldMin) + this.WorldMin;

			// if want clipped value, return extrema if outside range.
			if (clip)
			{
				world = Math.Max( world, this.WorldMin );
				world = Math.Min( world, this.WorldMax );
			}

			return world;
		}


		/// <summary>
		/// Draw the Axis Label
		/// </summary>
		/// <param name="g">The GDI+ drawing surface on which to draw.</param>
		/// <param name="offset">offset from axis. Should be calculated so as to make sure axis label misses tick labels.</param>
		/// <param name="axisPhysicalMin">The physical position corresponding to the world minimum of the axis.</param>
		/// <param name="axisPhysicalMax">The physical position corresponding to the world maximum of the axis.</param>
		/// <returns>boxed Rectangle indicating bounding box of label. null if no label printed.</returns>
		public object DrawLabel( 
			Graphics g, 
			Point offset, 
			Point axisPhysicalMin, 
			Point axisPhysicalMax )
		{

			if ( Label != "" )
			{
	
				// first calculate any extra offset for axis label spacing.
				float extraOffsetAmount = this.LabelOffset;
				extraOffsetAmount += 2.0f; // empirically determed - text was too close to axis before this.
				
				if (this.AutoScaleText)
				{
					if (this.LabelOffsetScaled)
					{
						extraOffsetAmount *= this.FontScale;
					}
				}

				// now extend offset.
				float offsetLength = (float)Math.Sqrt( offset.X*offset.X + offset.Y*offset.Y );
				if (offsetLength > 0.01)
				{
					float x_component = offset.X / offsetLength;
					float y_component = offset.Y / offsetLength;

					x_component *= extraOffsetAmount;
					y_component *= extraOffsetAmount;

                    if (this.LabelOffsetAbsolute)
                    {
                        offset.X = (int)x_component;
                        offset.Y = (int)y_component;
                    }
                    else
                    {
                        offset.X += (int)x_component;
                        offset.Y += (int)y_component;
                    }
                }
				
				// determine angle of axis in degrees
				double theta = Math.Atan2(
					axisPhysicalMax.Y - axisPhysicalMin.Y,
					axisPhysicalMax.X - axisPhysicalMin.X );
				theta = theta * 180.0f / Math.PI;

				PointF average = new PointF(
					(axisPhysicalMax.X + axisPhysicalMin.X)/2.0f,
					(axisPhysicalMax.Y + axisPhysicalMin.Y)/2.0f );

				g.TranslateTransform( offset.X , offset.Y );	// this is done last.
				g.TranslateTransform( average.X, average.Y );
				g.RotateTransform( (float)theta );				// this is done first.

				SizeF labelSize = g.MeasureString( Label, labelFontScaled_);

				//bounding box for label centered around zero.
				RectangleF drawRect = new RectangleF( 
					-labelSize.Width/2.0f,
					-labelSize.Height/2.0f,
					labelSize.Width,
					labelSize.Height );
				
				g.DrawString( 
					Label, 
					labelFontScaled_,
					labelBrush_, 
					drawRect,
					drawFormat_ );

				// now work out physical bounds of label. 
				Matrix m = g.Transform;
				PointF[] recPoints = new PointF[2];
				recPoints[0] = new PointF( -labelSize.Width/2.0f, -labelSize.Height/2.0f );
				recPoints[1] = new PointF( labelSize.Width/2.0f, labelSize.Height/2.0f );
				m.TransformPoints( recPoints );

				int x1 = (int)Math.Min( recPoints[0].X, recPoints[1].X );
				int x2 = (int)Math.Max( recPoints[0].X, recPoints[1].X );
				int y1 = (int)Math.Min( recPoints[0].Y, recPoints[1].Y );
				int y2 = (int)Math.Max( recPoints[0].Y, recPoints[1].Y );

				g.ResetTransform();

				// and return label bounding box.
				return new Rectangle( x1, y1, (x2-x1), (y2-y1) );
			}

			return null;
		}


		/// <summary>
		/// Draw a tick on the axis.
		/// </summary>
		/// <param name="g">The graphics surface on which to draw.</param>
		/// <param name="w">The tick position in world coordinates.</param>
		/// <param name="size">The size of the tick (in pixels)</param>
		/// <param name="text">The text associated with the tick</param>
		/// <param name="textOffset">The Offset to draw from the auto calculated position</param>
		/// <param name="axisPhysMin">The minimum physical extent of the axis</param>
		/// <param name="axisPhysMax">The maximum physical extent of the axis</param>
		/// <param name="boundingBox">out: The bounding rectangle for the tick and tickLabel drawn</param>
		/// <param name="labelOffset">out: offset from the axies required for axis label</param>
		public virtual void DrawTick( 
			Graphics g, 
			double w,
			float size,
			string text,
			Point textOffset,
			Point axisPhysMin,
			Point axisPhysMax,
			out Point labelOffset,
			out Rectangle boundingBox )
		{

			// determine physical location where tick touches axis. 
			PointF tickStart = WorldToPhysical( w, axisPhysMin, axisPhysMax, true );

			// determine offset from start point.
			PointF axisDir = Utils.UnitVector( axisPhysMin, axisPhysMax );

			// rotate axis dir clockwise by angle radians to get tick direction.
			float x1 = (float)(Math.Cos( -this.TicksAngle ) * axisDir.X + Math.Sin( -this.TicksAngle ) * axisDir.Y);
			float y1 = (float)(-Math.Sin( -this.TicksAngle ) * axisDir.X + Math.Cos( -this.TicksAngle ) * axisDir.Y);

			// now get the scaled tick vector.
			PointF tickVector = new PointF( this.TickScale * size * x1, this.TickScale * size * y1 );

			if (this.TicksCrossAxis)
			{
				tickStart = new PointF(
					tickStart.X - tickVector.X / 2.0f,
					tickStart.Y - tickVector.Y / 2.0f );
			}

			// and the end point [point off axis] of tick mark.
			PointF tickEnd = new PointF( tickStart.X + tickVector.X, tickStart.Y + tickVector.Y );

			// and draw it!
			if (g != null)
				g.DrawLine( this.linePen_, (int)tickStart.X, (int)tickStart.Y, (int)tickEnd.X, (int)tickEnd.Y );
			// note: casting to int for tick positions was necessary to ensure ticks drawn where we wanted
			// them. Not sure of the reason.

			// calculate bounds of tick.
			int minX = (int)Math.Min( tickStart.X, tickEnd.X );
			int minY = (int)Math.Min( tickStart.Y, tickEnd.Y );
			int maxX = (int)Math.Max( tickStart.X, tickEnd.X );
			int maxY = (int)Math.Max( tickStart.Y, tickEnd.Y );
			boundingBox = new Rectangle( minX, minY, maxX-minX, maxY-minY );
			
			// by default, label offset from axis is 0. TODO: revise this.
			labelOffset = new Point( 
				-(int)tickVector.X, 
				-(int)tickVector.Y );

			// ------------------------

			// now draw associated text.

			// **** TODO ****
			// The following code needs revising. A few things are hard coded when
			// they should not be. Also, angled tick text currently just works for
			// the bottom x-axis. Also, it's a bit hacky.

			if (text != "" && !HideTickText && g != null )
			{
				SizeF textSize = g.MeasureString( text, tickTextFontScaled_ );

				// determine the center point of the tick text.
				float textCenterX;
				float textCenterY;

				// if text is at pointy end of tick.
				if (!this.TickTextNextToAxis)
				{
					// offset due to tick.
					textCenterX = tickStart.X + tickVector.X*1.2f;
					textCenterY = tickStart.Y + tickVector.Y*1.2f;

					// offset due to text box size.
					textCenterX += 0.5f * x1 * textSize.Width;
					textCenterY += 0.5f * y1 * textSize.Height;
				}
					// else it's next to the axis.
				else
				{
					// start location.
					textCenterX = tickStart.X;
					textCenterY = tickStart.Y;

					// offset due to text box size.
					textCenterX -= 0.5f * x1 * textSize.Width;
					textCenterY -= 0.5f * y1 * textSize.Height;

					// bring text away from the axis a little bit.
					textCenterX -= x1*(2.0f+FontScale);
					textCenterY -= y1*(2.0f+FontScale);
				}

				// If tick text is angled.. 
				if (this.TicksLabelAngle != 0.0f)
				{

					// determine the point we want to rotate text about.
					
					PointF textScaledTickVector = new PointF( this.TickScale * x1 * (textSize.Height/2.0f), this.TickScale * y1 * (textSize.Height/2.0f) );

 					PointF rotatePoint;
 					if (this.TickTextNextToAxis) 
					{
 						rotatePoint = new PointF( tickStart.X - textScaledTickVector.X, tickStart.Y - textScaledTickVector.Y );
 					}
 					else 
					{
 						rotatePoint = new PointF( tickEnd.X + textScaledTickVector.X, tickEnd.Y + textScaledTickVector.Y );
 					}
 
 					float actualAngle;
					if (flipTicksLabel_) 
					{
 						double radAngle = (Math.PI / 180) * this.TicksLabelAngle;
 						rotatePoint.X += textSize.Width * (float)Math.Cos(radAngle);
 						rotatePoint.Y += textSize.Width * (float)Math.Sin(radAngle);
 						actualAngle = this.TicksLabelAngle + 180;
 					}
 					else 
					{
 						actualAngle = this.TicksLabelAngle;
					}
					

					g.TranslateTransform( rotatePoint.X, rotatePoint.Y );

					g.RotateTransform( actualAngle );
					
					Matrix m = g.Transform;
					PointF[] recPoints = new PointF[2];
					recPoints[0] = new PointF( 0.0f, -(textSize.Height / 2) );
					recPoints[1] = new PointF( textSize.Width, textSize.Height );
					m.TransformPoints( recPoints );

					float t_x1 = Math.Min( recPoints[0].X, recPoints[1].X );
					float t_x2 = Math.Max( recPoints[0].X, recPoints[1].X );
					float t_y1 = Math.Min( recPoints[0].Y, recPoints[1].Y );
					float t_y2 = Math.Max( recPoints[0].Y, recPoints[1].Y );
					
					boundingBox = Rectangle.Union(boundingBox, new Rectangle( (int)t_x1, (int)t_y1, (int)(t_x2-t_x1), (int)(t_y2-t_y1) ) );
					RectangleF drawRect = new RectangleF( 0.0f, -(textSize.Height / 2), textSize.Width, textSize.Height );

					g.DrawString( 
						text,
						tickTextFontScaled_,
						tickTextBrush_, 
						drawRect,
						drawFormat_ );

					t_x2 -= tickStart.X;
					t_y2 -= tickStart.Y;
					t_x2 *= 1.25f;
					t_y2 *= 1.25f;

					labelOffset = new Point( (int)t_x2, (int)t_y2 );

					g.ResetTransform();

					//g.DrawRectangle( new Pen(Color.Purple), boundingBox.X, boundingBox.Y, boundingBox.Width, boundingBox.Height );

				}
				else
				{

					float bx1 = (textCenterX - textSize.Width/2.0f);
					float by1 = (textCenterY - textSize.Height/2.0f);
					float bx2 = textSize.Width;
					float by2 = textSize.Height;

					RectangleF drawRect = new RectangleF( bx1, by1, bx2, by2 );
					Rectangle drawRect_int = new Rectangle( (int)bx1, (int)by1, (int)bx2, (int)by2 );
					// g.DrawRectangle( new Pen(Color.Green), bx1, by1, bx2, by2 );

					boundingBox = Rectangle.Union( boundingBox, drawRect_int );

					// g.DrawRectangle( new Pen(Color.Purple), boundingBox.X, boundingBox.Y, boundingBox.Width, boundingBox.Height );

					g.DrawString( 
						text,
						tickTextFontScaled_,
						tickTextBrush_,
						drawRect,
						drawFormat_ );

					textCenterX -= tickStart.X;
					textCenterY -= tickStart.Y;
					textCenterX *= 2.3f;
					textCenterY *= 2.3f;

					labelOffset = new Point( (int)textCenterX, (int)textCenterY );
				}
			} 

		}


		/// <summary>
		/// Draw the axis. This involves three steps:
		///  (1) Draw the axis line.
		///  (2) Draw the tick marks.
		///  (3) Draw the label.
		/// </summary>
		/// <param name="g">The drawing surface on which to draw.</param>
		/// <param name="physicalMin">The physical position corresponding to the world minimum of the axis.</param>
		/// <param name="physicalMax">The physical position corresponding to the world maximum of the axis.</param>
		/// <param name="boundingBox">out The bounding rectangle of the axis including axis line, label, tick marks and tick mark labels</param>
		public virtual void Draw( 
			System.Drawing.Graphics g,
			Point physicalMin,
			Point physicalMax, 
			out Rectangle boundingBox )
		{
			// calculate the bounds of the axis line only.
			int x1 = Math.Min( physicalMin.X, physicalMax.X );
			int x2 = Math.Max( physicalMin.X, physicalMax.X );
			int y1 = Math.Min( physicalMin.Y, physicalMax.Y );
			int y2 = Math.Max( physicalMin.Y, physicalMax.Y );
			Rectangle bounds = new Rectangle( x1, y1, x2-x1, y2-y1 );

			if (!Hidden)
			{
				
				// (1) Draw the axis line.
				g.DrawLine( this.linePen_, physicalMin.X, physicalMin.Y, physicalMax.X, physicalMax.Y );

				// (2) draw tick marks (subclass responsibility). 

				object labelOffset;
				object tickBounds;
				this.DrawTicks( g, physicalMin, physicalMax, out labelOffset, out tickBounds );

				// (3) draw the axis label
				object labelBounds = null;
				if (!this.HideTickText)
				{
					labelBounds = this.DrawLabel( g, (Point)labelOffset, physicalMin, physicalMax );
				}

				// (4) merge bounds and return.
				if (labelBounds != null)
					bounds = Rectangle.Union( bounds, (Rectangle)labelBounds );

				if (tickBounds != null)
					bounds = Rectangle.Union( bounds, (Rectangle)tickBounds );

			}

			boundingBox = bounds;
		}


		/// <summary>
		/// Update the bounding box and label offset associated with an axis
		/// to encompass the additionally specified mergeBoundingBox and 
		/// mergeLabelOffset respectively.
		/// </summary>
		/// <param name="labelOffset">Current axis label offset.</param>
		/// <param name="boundingBox">Current axis bounding box.</param>
		/// <param name="mergeLabelOffset">the label offset to merge. The current label offset will be replaced by this if it's norm is larger.</param>
		/// <param name="mergeBoundingBox">the bounding box to merge. The current bounding box will be replaced by this if null, or by the least upper bound of bother bounding boxes otherwise.</param>
		protected static void UpdateOffsetAndBounds( 
			ref object labelOffset, ref object boundingBox, 
			Point mergeLabelOffset, Rectangle mergeBoundingBox )
		{
			// determining largest label offset and use it.
			Point lo = (Point)labelOffset;
			double norm1 = Math.Sqrt( lo.X*lo.X + lo.Y*lo.Y );
			double norm2 = Math.Sqrt( mergeLabelOffset.X*mergeLabelOffset.X + mergeLabelOffset.Y*mergeLabelOffset.Y );
			if (norm1 < norm2)
			{
				labelOffset = mergeLabelOffset;
			}

			// determining bounding box.
			Rectangle b = mergeBoundingBox;
			if (boundingBox == null)
			{
				boundingBox = b;
			}
			else
			{
				boundingBox = Rectangle.Union( (Rectangle)boundingBox, b );
			}
		}


		/// <summary>
		/// DrawTicks method. In base axis class this does nothing.
		/// </summary>
		/// <param name="g">The graphics surface on which to draw</param>
		/// <param name="physicalMin">The physical position corresponding to the world minimum of the axis.</param>
		/// <param name="physicalMax">The physical position corresponding to the world maximum of the axis.</param>
		/// <param name="labelOffset">is set to a suitable offset from the axis to draw the axis label. In this base method, set to null.</param>
		/// <param name="boundingBox">is set to the smallest box that bounds the ticks and the tick text. In this base method, set to null.</param>
		protected virtual void DrawTicks( 
			Graphics g, 
			Point physicalMin, 
			Point physicalMax, 
			out object labelOffset,
			out object boundingBox )
		{
			labelOffset = null;
			boundingBox = null;
			// do nothing. This class is not abstract because a subclass may
			// want to override the Axis.Draw method to one that doesn't 
			// require DrawTicks.
		}



		/// <summary>
		/// World extent of the axis.
		/// </summary>
		public double WorldLength
		{
			get
			{
				return Math.Abs( worldMax_ - worldMin_ );
			}
		}

		/// <summary>
		/// Determines the positions, in world coordinates, of the large ticks. 
		/// When the physical extent of the axis is small, some of the positions 
		/// that were generated in this pass may be converted to small tick 
		/// positions and returned as well.
		/// 
		/// This default implementation returns empty large ticks list and null
		/// small tick list.
		/// </summary>
		/// <param name="physicalMin">The physical position corresponding to the world minimum of the axis.</param>
		/// <param name="physicalMax">The physical position corresponding to the world maximum of the axis.</param>
		/// <param name="largeTickPositions">ArrayList containing the positions of the large ticks.</param>
		/// <param name="smallTickPositions">ArrayList containing the positions of the small ticks if calculated, null otherwise.</param>
		internal virtual void WorldTickPositions_FirstPass(
			Point physicalMin, 
			Point physicalMax,
			out ArrayList largeTickPositions,
			out ArrayList smallTickPositions
			)
		{
			largeTickPositions = new ArrayList();
			smallTickPositions = null;
		}


		/// <summary>
		/// Determines the positions, in world coordinates, of the small ticks
		/// if they have not already been generated.
		/// 
		/// This default implementation creates an empty smallTickPositions list 
		/// if it doesn't already exist.
		/// </summary>
		/// <param name="physicalMin">The physical position corresponding to the world minimum of the axis.</param>
		/// <param name="physicalMax">The physical position corresponding to the world maximum of the axis.</param>
		/// <param name="largeTickPositions">The positions of the large ticks.</param>
		/// <param name="smallTickPositions">If null, small tick positions are returned via this parameter. Otherwise this function does nothing.</param>
		internal virtual void WorldTickPositions_SecondPass( 
			Point physicalMin,
			Point physicalMax,
			ArrayList largeTickPositions, 
			ref ArrayList smallTickPositions )
		{
			if (smallTickPositions == null)
				smallTickPositions = new ArrayList();
		}


		/// <summary>
		/// Determines the positions of all Large and Small ticks.
		/// </summary>
		/// <param name="physicalMin">The physical position corresponding to the world minimum of the axis.</param>
		/// <param name="physicalMax">The physical position corresponding to the world maximum of the axis.</param>
		/// <param name="largeTickPositions">ArrayList containing the positions of the large ticks.</param>
		/// <param name="smallTickPositions">ArrayList containing the positions of the small ticks.</param>
		public void WorldTickPositions(
			Point physicalMin,
			Point physicalMax,
			out ArrayList largeTickPositions,
			out ArrayList smallTickPositions
			)
		{
			WorldTickPositions_FirstPass( physicalMin, physicalMax, out largeTickPositions, out smallTickPositions );
			WorldTickPositions_SecondPass( physicalMin, physicalMax, largeTickPositions, ref smallTickPositions );
		}


		/// <summary>
		/// Moves the world min and max values so that the world axis
		/// length is [percent] bigger. If the current world
		/// max and min values are the same, they are moved appart 
		/// an arbitrary amount. This arbitrary amount is currently
		/// 0.01, and will probably be configurable in the future.
		/// </summary>
		/// <param name="percent">Percentage to increase world length by.</param>
		/// <remarks>Works for the case WorldMax is less than WorldMin.</remarks>
		public void IncreaseRange( double percent )
		{
			double range = WorldMax - WorldMin;
			
			if ( !Utils.DoubleEqual( range, 0.0 ) )
			{
				range *= percent;
			}
			else
			{
				// arbitrary number. 
				// TODO make this configurable.
				range = 0.01;
			}

			WorldMax += range;
			WorldMin -= range;
		}


		/// <summary>
		/// Scale label and tick fonts by this factor. Set by PlotSurface2D 
		/// Draw method.
		/// </summary>
		internal float FontScale 
		{
			get 
			{
				return fontScale_;
			}
 
			set 
			{
				fontScale_ = value;
				UpdateScale();
			}
		}
		private float fontScale_;


		/// <summary>
		/// Scale tick mark lengths by this factor. Set by PlotSurface2D
		/// Draw method.
		/// </summary>		
		internal float TickScale 
		{
			get 
			{
				return tickScale_;
			}
			set 
			{
				tickScale_ = value;
			}
		}
		private float tickScale_;


		private void UpdateScale()	
		{
			if (labelFont_ != null)
				this.labelFontScaled_ = Utils.ScaleFont( labelFont_, FontScale );
			
			if (tickTextFont_ != null)
				this.tickTextFontScaled_ = Utils.ScaleFont( tickTextFont_, FontScale );
		}


		/// <summary>
		/// Get whether or not this axis is linear.
		/// </summary>
		public virtual bool IsLinear
		{
			get
			{
				return true;
			}
		}

		private float labelOffset_ = 0;
		/// <summary>
		/// If LabelOffsetAbsolute is false (default) then this is the offset 
		/// added to default axis label position. If LabelOffsetAbsolute is 
		/// true, then this is the absolute offset of the label from the axis.
		/// 
		/// If positive, offset is further away from axis, if negative, towards
		/// the axis.
		/// </summary>
		public float LabelOffset
		{
			get
			{
				return labelOffset_;
			}
			set
			{
				labelOffset_ = value;
			}
		}

        private bool labelOffsetAbsolute_ = false;
        /// <summary>
        /// If true, the value specified by LabelOffset is the absolute distance
        /// away from the axis that the label is drawn. If false, the value 
        /// specified by LabelOffset is added to the pre-calculated value to 
        /// determine the axis label position.
        /// </summary>
        /// <value></value>
        public bool LabelOffsetAbsolute
        {
            get
            {
                return labelOffsetAbsolute_;
            }
            set
            {
                labelOffsetAbsolute_ = value;
            }
        }

        private bool labelOffsetScaled_ = true;
		/// <summary>
		/// Whether or not the supplied LabelOffset should be scaled by 
		/// a factor as specified by FontScale.
		/// </summary>
		public bool LabelOffsetScaled
		{
			get
			{
				return labelOffsetScaled_;
			}
			set
			{
				labelOffsetScaled_ = value;
			}
		}


		/// <summary>
		/// returns a suitable offset for the axis label in the case that there are no
		/// ticks or tick text in the way.
		/// </summary>
		/// <param name="physicalMin">physical point corresponding to the axis world maximum.</param>
		/// <param name="physicalMax">physical point corresponding to the axis world minimum.</param>
		/// <returns>axis label offset</returns>
		protected Point getDefaultLabelOffset( Point physicalMin, Point physicalMax )
		{
			System.Drawing.Rectangle tBoundingBox;
			System.Drawing.Point tLabelOffset;

			this.DrawTick( null, this.WorldMax, this.LargeTickSize, 
				"",
				new Point(0,0),
				physicalMin, physicalMax,
				out tLabelOffset, out tBoundingBox );

			return tLabelOffset;
		}


        /// <summary>
        /// Set the Axis color (sets all of axis line color, Tick text color, and label color).
        /// </summary>
        public Color Color
        {
            set
            {
                this.AxisColor = value;
                this.TickTextColor = value;
                this.LabelColor = value;
            }
        }


        /*

        // finish implementation of this at some point.

        public class WorldValueChangedArgs
        {
            public WorldValueChangedArgs( double value, MinMaxType minOrMax )
            {
                Value = value;
                MinOrMax = minOrMax;
            }

            public double Value;

            public enum MinMaxType
            {
                Min = 0,
                Max = 1
            }

            public MinMaxType MinOrMax;
        }


        public delegate void WorldValueChangedHandler( object sender, WorldValueChangedArgs e );

        public event WorldValueChangedHandler WorldMinChanged;
        public event WorldValueChangedHandler WorldMaxChanged;
        public event WorldValueChangedHandler WorldExtentsChanged;

        */

    }
}
