/*
 * 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;
using System.Collections;
using System.Drawing;
using System.Drawing.Drawing2D;

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 : ICloneable
    {
        private bool autoScaleText_;
        private bool autoScaleTicks_;
        private StringFormat drawFormat_;
        private bool flipTicksLabel_;
        private float fontScale_;
        private bool hidden_;
        private bool hideTickText_;
        private Brush labelBrush_;
        private Font labelFontScaled_;
        private Font labelFont_;
        private bool labelOffsetAbsolute_;
        private bool labelOffsetScaled_ = true;
        private float labelOffset_;
        private string label_;
        private int largeTickSize_;
        private Pen linePen_;
        private int minPhysicalLargeTickStep_ = 30;
        private string numberFormat_;
        private bool reversed_;
        private int smallTickSize_;
        private Brush tickTextBrush_;
        private Font tickTextFontScaled_;
        private Font tickTextFont_;
        private bool tickTextNextToAxis_;
        private float ticksAngle_ = (float) Math.PI/2.0f;
        private bool ticksCrossAxis_;
        private bool ticksIndependentOfPhysicalExtent_;
        private float ticksLabelAngle_;

        private double worldMax_;

        private double worldMin_;

        /// <summary>
        /// Default constructor
        /// </summary>
        public Axis()
        {
            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)
        {
            Init();
            WorldMin = worldMin;
            WorldMax = worldMax;
        }

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

        /// <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; }
        }

        /// <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
            {
                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));
                */
            }
        }

        /// <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 worldMin_; }
            set
            {
                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) );
                */
            }
        }

        /// <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; }
        }

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

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

        /// <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; }
        }

        /// <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; }
        }

        /// <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; }
        }

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

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

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

        /// <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; }
        }

        /// <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; }
        }

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

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

        /// <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; }
        }

        /// <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; }
        }

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

        /// <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; }
        }

        /// <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; }
        }

        /// <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; }
        }

        /// <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; }
        }

        /// <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; }
        }

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

        /// <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();
            }
        }

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

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

        /// <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; }
        }

        /// <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; }
        }

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

        /// <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 (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()
        {
            worldMax_ = double.NaN;
            worldMin_ = double.NaN;
            Hidden = false;
            SmallTickSize = 2;
            LargeTickSize = 6;
            FontScale = 1.0f;
            TickScale = 1.0f;
            AutoScaleTicks = false;
            AutoScaleText = false;
            TickTextNextToAxis = true;
            HideTickText = false;
            TicksCrossAxis = false;
            LabelOffset = 0.0f;
            LabelOffsetAbsolute = false;
            LabelOffsetScaled = true;

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

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

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

        /// <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 > WorldMax || coord < 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 (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 = ((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 (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*(WorldMax - WorldMin) + WorldMin;

            // if want clipped value, return extrema if outside range.
            if (clip)
            {
                world = Math.Max(world, WorldMin);
                world = Math.Min(world, 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 = LabelOffset;
                extraOffsetAmount += 2.0f; // empirically determed - text was too close to axis before this.

                if (AutoScaleText)
                {
                    if (LabelOffsetScaled)
                    {
                        extraOffsetAmount *= 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 (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(-TicksAngle)*axisDir.X + Math.Sin(-TicksAngle)*axisDir.Y);
            float y1 = (float) (-Math.Sin(-TicksAngle)*axisDir.X + Math.Cos(-TicksAngle)*axisDir.Y);

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

            if (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(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 (!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 (TicksLabelAngle != 0.0f)
                {
                    // determine the point we want to rotate text about.

                    PointF textScaledTickVector = new PointF(TickScale*x1*(textSize.Height/2.0f), TickScale*y1*(textSize.Height/2.0f));

                    PointF rotatePoint;
                    if (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)*TicksLabelAngle;
                        rotatePoint.X += textSize.Width*(float) Math.Cos(radAngle);
                        rotatePoint.Y += textSize.Width*(float) Math.Sin(radAngle);
                        actualAngle = TicksLabelAngle + 180;
                    }
                    else
                    {
                        actualAngle = 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(
            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(linePen_, physicalMin.X, physicalMin.Y, physicalMax.X, physicalMax.Y);

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

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

                // (3) draw the axis label
                object labelBounds = null;
                if (!HideTickText)
                {
                    labelBounds = 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>
        /// 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;
        }

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

            if (tickTextFont_ != null)
                tickTextFontScaled_ = Utils.ScaleFont(tickTextFont_, FontScale);
        }

        /// <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)
        {
            Rectangle tBoundingBox;
            Point tLabelOffset;

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

            return tLabelOffset;
        }

        /*

        // 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;

        */
    }
}