/*
ScPl - A plotting library for .NET

PlotSurface2D.cs
Copyright (C) 2003
Matt Howlett, Paolo Pierini

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 following text in 
   the documentation and / or other materials provided with the 
   distribution: 
   
   "This product includes software developed as part of 
   the ScPl plotting library project available from: 
   http://www.netcontrols.org/scpl/" 

------------------------------------------------------------------------

THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.

$Id: PlotSurface2D.cs,v 1.49 2004/05/17 09:16:13 mhowlett Exp $

*/

// This can be used for "graphical debugging of the
// plot construction. If needed define the symbol
#undef _IWANNASEE_

using System;
using System.Drawing;
using System.Diagnostics;
using System.Collections;

namespace scpl
{
	/// <summary>
	/// Class providing the main capabilities of a plot object.
	/// </summary>
	public class PlotSurface2D : IPlotSurface2D
	{

		#region enum XAxisPosition
		/// <summary>
		/// Possible positions of the X axis (enum).
		/// </summary>
		public enum XAxisPosition
		{
			/// <summary>
			/// X axis is at top.
			/// </summary>
			Top = 1,
			//Center = 2,
			/// <summary>
			/// X axis is at bottom.
			/// </summary>
			Bottom = 3,
		}
		#endregion
		#region enum YAxisPosition
		/// <summary>
		/// Possible positions of the Y axis (enum).
		/// </summary>
		public enum YAxisPosition
		{
			/// <summary>
			/// Y axis is to the left.
			/// </summary>
			Left = 1,
			// Center
			/// <summary>
			/// Y axis is to the right.
			/// </summary>
			Right = 3,
		}
		#endregion

		#region Private members
		private Font titleFont_;
		private string title_;
		private Brush titleBrush_;
		private int padding_; // todo: change to float.
		private Axis xAxis1_;
		private Axis yAxis1_;
		private Axis xAxis2_;
		private Axis yAxis2_;
		private PhysicalAxis pXAxis1Cache_;
		private PhysicalAxis pYAxis1Cache_;
		private PhysicalAxis pXAxis2Cache_;
		private PhysicalAxis pYAxis2Cache_;
		private bool autoScale_;

		private object plotAreaBoundingBoxCache_;
		private object bbXAxis1Cache_;
		private object bbXAxis2Cache_;
		private object bbYAxis1Cache_;
		private object bbYAxis2Cache_;
		//private object bbLegendCache_;
		private object bbTitleCache_;

		private object plotBackColor_;
		private System.Collections.ArrayList plots_;
		private System.Collections.ArrayList xAxisPositions_;
		private System.Collections.ArrayList yAxisPositions_;
		private System.Drawing.Drawing2D.SmoothingMode smoothingMode_;

		private ArrayList axesConstraints_ = null;

		private Legend legend_;
		#endregion

		#region PlotAreaBoundingBoxCache and Hit test
		internal Rectangle PlotAreaBoundingBoxCache
		{
			get
			{
				if (plotAreaBoundingBoxCache_ == null) 
				{
					return Rectangle.Empty;
				}
				else
				{
					return (Rectangle)plotAreaBoundingBoxCache_;
				}
			}
		}
//		private Rectangle [] GetHitRectangles()
//		{
//			Rectangle[] r = new Rectangle[7];
//			r[0]=bbLegendCache_;
//			r[1]=bbXAxis1Cache_;
//			r[2]=bbYAxis1Cache_;
//			r[3]=bbXAxis2Cache_;
//			r[4]=bbYAxis2Cache_;
//			r[5]=bbTitleCache_;
//			r[6]=plotAreaBoundingBoxCache_;
//			return r;
//		}

		/// <summary>
		/// Performs a hit test with the given point and returns information 
		/// about the object being hit.
		/// </summary>
		/// <param name="p">The point to test.</param>
		/// <returns></returns>
		public System.Collections.ArrayList HitTest(Point p)
		{
			System.Collections.ArrayList a = new System.Collections.ArrayList();

			/*
			if (bbLegendCache_ != null && ((Rectangle) bbLegendCache_).Contains(p))
			{
				a.Add( typeof(scpl.Legend ));
				a.Add( null );
				return a;
			}
			*/
			if (bbXAxis1Cache_ != null && ((Rectangle) bbXAxis1Cache_ ).Contains(p))
			{
				a.Add( typeof(scpl.Axis ));
				a.Add( this.xAxis1_ );
				return a;
			}
			if (bbYAxis1Cache_ != null && ((Rectangle) bbYAxis1Cache_ ).Contains(p))
			{
				a.Add( typeof(scpl.Axis ));
				a.Add( this.yAxis1_ );
				return a;
			}
			if (bbXAxis2Cache_ != null && ((Rectangle) bbXAxis2Cache_ ).Contains(p))
			{
				a.Add( typeof(scpl.Axis ));
				a.Add( this.xAxis2_ );
				return a;
			}
			if (bbXAxis2Cache_ != null && ((Rectangle) bbYAxis2Cache_ ).Contains(p))
			{
				a.Add( typeof(scpl.Axis ));
				a.Add( this.yAxis2_ );
				return a;
			}
			if (bbTitleCache_ != null && ((Rectangle) bbTitleCache_ ).Contains(p))
			{
				a.Add( typeof(scpl.PlotSurface2D));
				a.Add( this );
				return a;
			}
			if (plotAreaBoundingBoxCache_ != null && ((Rectangle) plotAreaBoundingBoxCache_ ).Contains(p))
			{
				a.Add( typeof(scpl.PlotSurface2D ));
				a.Add( this );
				return a;
			}
			return a;
		}
		#endregion

		#region get/set XAxis1
			/// <summary>
			/// The first abscissa axis.
			/// </summary>
		public Axis XAxis1
		{
			get
			{
				return xAxis1_;
			}
			set
			{
				xAxis1_ = value;
			}
		}
		#endregion
		#region get/set YAxis1
		/// <summary>
		/// The first ordinate axis.
		/// </summary>
		public Axis YAxis1
		{
			get
			{
				return yAxis1_;
			}
			set
			{
				yAxis1_ = value;
			}
		}
		#endregion
		#region get/set XAxis2
		/// <summary>
		/// The second abscissa axis.
		/// </summary>
		public Axis XAxis2
		{
			get
			{
				return xAxis2_;
			}
			set
			{
				xAxis2_ = value;
			}
		}
		#endregion
		#region get/set YAxis2
		/// <summary>
		/// The second ordinate axis.
		/// </summary>
		public Axis YAxis2
		{
			get
			{
				return yAxis2_;
			}
			set
			{
				yAxis2_ = value;
			}
		}
		#endregion

		#region get PhysicalXAxis1Cache
		/// <summary>
		/// Returns the physical information of Xaxis1.
		/// </summary>
		public PhysicalAxis PhysicalXAxis1Cache
		{
			get
			{
				return pXAxis1Cache_;
			}
		}
		#endregion
		#region get PhysicalYAxis1Cache
		/// <summary>
		/// Returns the physical information of Yaxis1.
		/// </summary>
		public PhysicalAxis PhysicalYAxis1Cache
		{
			get
			{
				return pYAxis1Cache_;
			}
		}
		#endregion
		#region get PhysicalXAxis2Cache
		/// <summary>
		/// Returns the physical information of Xaxis2.
		/// </summary>
		public PhysicalAxis PhysicalXAxis2Cache
		{
			get
			{
				return pXAxis2Cache_;
			}
		}
		#endregion
		#region get PhysicalYAxis2Cache
		/// <summary>
		/// Returns the physical information of Yaxis2.
		/// </summary>
		public PhysicalAxis PhysicalYAxis2Cache
		{
			get
			{
				return pYAxis2Cache_;
			}
		}
		#endregion

		#region get/set Title
		/// <summary>
		/// The plot title.
		/// </summary>
		public string Title
		{
			get
			{
				return title_;
			}
			set
			{
				title_ = value;
			}
		}
		#endregion
		#region get/set TitleFont
		/// <summary>
		/// The plot title font.
		/// </summary>
		public Font TitleFont
		{
			get
			{
				return titleFont_;
			}
			set
			{
				titleFont_ = value;
			}
		}
		#endregion
		#region get/set Padding
		/// <summary>
		/// The padding distance.
		/// </summary>
		public int Padding
		{
			get
			{
				return padding_;
			}
			set
			{
				padding_ = value;
			}
		}
		#endregion
		#region get/set AutoScale
		/// <summary>
		/// Flag for plot automatic scaling.
		/// </summary>
		public bool AutoScale
		{
			get
			{
				return autoScale_;
			}
			set
			{
				autoScale_ = value;
			}
		}
		#endregion
		#region get/set TitleBrush
		/// <summary>
		/// The Bush used for drawing the title.
		/// </summary>
		public Brush TitleBrush
		{
			get
			{
				return titleBrush_;
			}
			set
			{
				titleBrush_ = value;
			}
		}
		#endregion
		#region get/set PlotBackColor
		/// <summary>
		/// The plot background color.
		/// </summary>
		public System.Drawing.Color PlotBackColor
		{
			get
			{
				return (Color)plotBackColor_;
			}
			set
			{
				plotBackColor_ = value;
			}
		}
		#endregion
		#region get/set SmoothingMode
		public System.Drawing.Drawing2D.SmoothingMode SmoothingMode 
		{ 
			get
			{
				return smoothingMode_;
			}
			set
			{
				this.smoothingMode_ = value;
			}
		}
		#endregion
		
		#region Init
		private void Init()
		{
			plots_ = new System.Collections.ArrayList();
			xAxisPositions_ = new System.Collections.ArrayList();
			yAxisPositions_ = new System.Collections.ArrayList();

			FontFamily fontFamily = new FontFamily("Arial");
			TitleFont = new Font(fontFamily, 14, FontStyle.Regular, GraphicsUnit.Pixel);
			padding_ = 10;
			title_ = "";
			autoScale_ = true;
			xAxis1_ = null;
			xAxis2_ = null;
			yAxis1_ = null;
			yAxis2_ = null;
			pXAxis1Cache_ = null;
			pYAxis1Cache_ = null;
			pXAxis2Cache_ = null;
			pYAxis2Cache_ = null;
			titleBrush_ = new SolidBrush( Color.Black );
			plotBackColor_ = Color.White;
			
			this.legend_ = null;

			smoothingMode_ = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;

			axesConstraints_ = new ArrayList();
		}
		#endregion
		#region PlotSurface2D
		/// <summary>
		/// Default parameterless constructor.
		/// </summary>
		public PlotSurface2D()
		{
			Init();
		}
		#endregion
		#region DetermineScaleFactor
		private double DetermineScaleFactor( double w, double h )
		{
			if ( !this.autoScale_ )
			{
				return 1.0f;
			}

			double diag = Math.Sqrt( w*w +  h*h );
			double scaleFactor = (diag / 1400.0f)*2.4;
			
			if ( scaleFactor > 1.0f )
			{
				return scaleFactor;
			}
			else
			{
				return 1.0f;
			}
		}
		#endregion
		#region method Add
		/// <summary>
		/// Adds a trace plot to the plot surface.
		/// </summary>
		/// <param name="p">The trace plot to add.</param>
		public void Add( IDrawable p )
		{
			Add( p, XAxisPosition.Bottom, YAxisPosition.Left);
		}
		#endregion
		#region method Add
		/// <summary>
		/// Adds a trace plot to the plot surface.
		/// </summary>
		/// <param name="p">The trace plot to add.</param>
		/// <param name="xp">The position of the X axis.</param>
		/// <param name="yp">The position of the Y axis.</param>
		public void Add( IDrawable p, XAxisPosition xp, YAxisPosition yp )
		{
			plots_.Add( p );
			xAxisPositions_.Add( xp );
			yAxisPositions_.Add( yp );
			// if p is just an IDrawable, then it can't affect the axes.
			if ( p is IPlot )
			{
				UpdateAxes();
			}
		}
		#endregion
		#region method UpdateAxes
		private void UpdateAxes()
		{
			// make sure our lists exist
			if (plots_.Count==0 || xAxisPositions_.Count==0 || yAxisPositions_.Count==0)
			{
				throw new System.Exception("UpdateAxes called from function other than Add.");
			}
			int last = plots_.Count - 1;
			if ( last != xAxisPositions_.Count-1 || last != yAxisPositions_.Count-1 )
			{
				throw new System.Exception( "plots and axis position arrays our of sync" );
			}

			IPlot p = (IPlot)plots_[last];
			XAxisPosition xap = (XAxisPosition)xAxisPositions_[last];
			YAxisPosition yap = (YAxisPosition)yAxisPositions_[last];

			if ( xap == XAxisPosition.Bottom )
			{
				if (this.xAxis1_ == null)
				{
					this.xAxis1_ = p.SuggestXAxis();
					this.xAxis1_.TicksAngle = -Math.PI/2.0f;
				}
				else
				{
					this.xAxis1_.LUB( p.SuggestXAxis() );
				}
			}

			if ( xap == XAxisPosition.Top )
			{
				if (this.xAxis2_ == null)
				{
					this.xAxis2_ = p.SuggestXAxis();
					this.xAxis2_.TicksAngle = Math.PI/2.0f;
				}
				else
				{
					this.xAxis2_.LUB( p.SuggestXAxis() );
				}
			}

			if ( yap == YAxisPosition.Left )
			{
				if ( this.yAxis1_ == null )
				{
					this.yAxis1_ = p.SuggestYAxis();
					this.yAxis1_.TicksAngle = Math.PI/2.0f;
				}
				else
				{
					this.yAxis1_.LUB( p.SuggestYAxis() );
				}
			}

			if ( yap == YAxisPosition.Right )
			{
				if (this.yAxis2_ == null )
				{
					this.yAxis2_ = p.SuggestYAxis();
					this.yAxis2_.TicksAngle = -Math.PI/2.0f;
				}
				else
				{
					this.yAxis2_.LUB( p.SuggestYAxis() );
				}
			}
		}
		#endregion

		#region method DetermineAxesToDraw
		private void DetermineAxesToDraw( out Axis xAxis1, out Axis xAxis2, out Axis yAxis1, out Axis yAxis2 )
		{
			xAxis1 = this.xAxis1_;
			xAxis2 = this.xAxis2_;
			yAxis1 = this.yAxis1_;
			yAxis2 = this.yAxis2_;

			if (this.xAxis1_ == null)
			{
				if (this.xAxis2_ == null)
				{
					throw new System.Exception( "Error: No X-Axis specified" );
				}
				xAxis1 = (Axis)this.xAxis2_.Clone();
				xAxis1.HideTickText = true;
				xAxis1.TicksAngle = -Math.PI/2.0f;
			}

			if (this.xAxis2_ == null)
			{
				// don't need to check if xAxis1_ == null, as case already handled above.
				xAxis2 = (Axis)this.xAxis1_.Clone();
				xAxis2.HideTickText = true;
				xAxis2.TicksAngle = Math.PI/2.0f;
			}

			if (this.yAxis1_ == null)
			{
				if (this.yAxis2_ == null)
				{
					throw new System.Exception( "Error: No Y-Axis specified" );
				}
				yAxis1 = (Axis)this.yAxis2_.Clone();
				yAxis1.HideTickText = true;
				yAxis1.TicksAngle = Math.PI/2.0f;
			}

			if (this.yAxis2_ == null)
			{
				// don't need to check if yAxis1_ == null, as case already handled above.
				yAxis2 = (Axis)this.yAxis1_.Clone();
				yAxis2.HideTickText = true;
				yAxis2.TicksAngle = -Math.PI/2.0f;
			}

		}
		#endregion
		#region method DeterminePhysicalAxesToDraw
		private void DeterminePhysicalAxesToDraw( Rectangle bounds, 
			Axis xAxis1, Axis xAxis2, Axis yAxis1, Axis yAxis2,
			out PhysicalAxis pXAxis1, out PhysicalAxis pXAxis2, 
			out PhysicalAxis pYAxis1, out PhysicalAxis pYAxis2 )
		{

			System.Drawing.Rectangle cb = bounds;

			pXAxis1 = new PhysicalAxis( xAxis1,
				new Point( cb.Left, cb.Bottom ), new Point( cb.Right, cb.Bottom ) );
			pYAxis1 = new PhysicalAxis( yAxis1,
				new Point( cb.Left, cb.Bottom ), new Point( cb.Left, cb.Top ) );
			pXAxis2 = new PhysicalAxis( xAxis2,
				new Point( cb.Left, cb.Top), new Point( cb.Right, cb.Top) );
			pYAxis2 = new PhysicalAxis( yAxis2,
				new Point( cb.Right, cb.Bottom ), new Point( cb.Right, cb.Top ) );

			int bottomIndent = (int)(padding_);
			if (!pXAxis1.Axis.Hidden) 
			{
				// evaluate its bounding box
				RectangleF bb = pXAxis1.GetBoundingBox();
				// finally determine its indentation from the bottom
				bottomIndent = (int)(bottomIndent + bb.Bottom-cb.Bottom);
			}

			int leftIndent = (int)(padding_);
			if (!pYAxis1.Axis.Hidden) 
			{
				// evaluate its bounding box
				RectangleF bb = pYAxis1.GetBoundingBox();
				// finally determine its indentation from the left
				leftIndent = (int)(leftIndent - bb.Left + cb.Left);
			}

			int topIndent = (int)(padding_);
			float scale = (float)DetermineScaleFactor( bounds.Width, bounds.Height );
			double titleHeight = FontScaler.scaleFont(titleFont_, scale).Height;
			if (!pXAxis2.Axis.Hidden)  
			{
				// evaluate its bounding box
				RectangleF bb = pXAxis2.GetBoundingBox();
				topIndent = (int)(topIndent - bb.Top + cb.Top);

				// finally determine its indentation from the top
				// correct top indendation to take into account plot title
				if (title_ != "" )
				{
					topIndent += (int)((double)titleHeight * 1.3f);
				}
			}

			int rightIndent = (int)(padding_);
			if (!pYAxis2.Axis.Hidden) 
			{
				// evaluate its bounding box
				RectangleF bb = pYAxis2.GetBoundingBox();

				// finally determine its indentation from the right
				rightIndent = (int)(rightIndent + bb.Right-cb.Right);
			}


			// now we have all the default calculated positions and we can proceed to
			// "move" the axes to their right places

			// primary axes (bottom, left)
			pXAxis1.PhysicalMin = new Point( cb.Left+leftIndent, cb.Bottom-bottomIndent );
			pXAxis1.PhysicalMax = new Point( cb.Right-rightIndent, cb.Bottom-bottomIndent );
			pYAxis1.PhysicalMin = new Point( cb.Left+leftIndent, cb.Bottom-bottomIndent);
			pYAxis1.PhysicalMax = new Point( cb.Left+leftIndent, cb.Top+topIndent );

			// secondary axes (top, right)
			pXAxis2.PhysicalMin = new Point( cb.Left+leftIndent, cb.Top+topIndent);
			pXAxis2.PhysicalMax = new Point( cb.Right-rightIndent, cb.Top+topIndent);
			pYAxis2.PhysicalMin = new Point( cb.Right-rightIndent, cb.Bottom-bottomIndent);
			pYAxis2.PhysicalMax = new Point( cb.Right-rightIndent, cb.Top+topIndent );

		}
		#endregion

		#region Draw
		/// <summary>
		/// Renders the plot.
		/// </summary>
		/// <param name="g">The Graphics surface.</param>
		/// <param name="bounds">The rectangle storing the bounds for rendering.</param>
		public void Draw( Graphics g, Rectangle bounds )
		{

			// if there is nothing to plot, return.
			if ( plots_.Count == 0 )
			{
				return;
			}

			// determine the [non physical] axes to draw based on the axis properties set.
			Axis xAxis1 = null;
			Axis xAxis2 = null;
			Axis yAxis1 = null;
			Axis yAxis2 = null;
			this.DetermineAxesToDraw( out xAxis1, out xAxis2, out yAxis1, out yAxis2 );


			// determine font sizes and tick sizes for physical axes.
			float scale = (float)DetermineScaleFactor( bounds.Width, bounds.Height );

			xAxis1.TickScale = scale;
			xAxis1.FontScale = scale;
			yAxis1.TickScale = scale;
			yAxis1.FontScale = scale;
			xAxis2.TickScale = scale;
			xAxis2.FontScale = scale;
			yAxis2.TickScale = scale;
			yAxis2.FontScale = scale;


			// determine the default physical positioning of those axes.
			PhysicalAxis pXAxis1 = null;
			PhysicalAxis pYAxis1 = null;
			PhysicalAxis pXAxis2 = null;
			PhysicalAxis pYAxis2 = null;
			this.DeterminePhysicalAxesToDraw( bounds, xAxis1, xAxis2, yAxis1, yAxis2,
				out pXAxis1, out pXAxis2, out pYAxis1, out pYAxis2 );
			
			float oldXAxis2Height = pXAxis2.PhysicalMin.Y;

			// Apply axes constraints
			for (int i=0; i<axesConstraints_.Count; ++i)
			{
				((AxesConstraint)axesConstraints_[i]).ApplyConstraint( 
					ref pXAxis1, ref pYAxis1, ref pXAxis2, ref pYAxis2 );
			}

			/////////////////////////////////////////////////////////////////////////
			// draw legend if have one.
			// Note: this will update axes if necessary. 

			float legendXPos = 0.0f;
			float legendYPos = 0.0f;
			if (this.legend_ != null)
			{
				legend_.UpdateAxesPositions( 
					pXAxis1, pYAxis1, pXAxis2, pYAxis2,
					this.plots_, scale, this.padding_, bounds, 
					out legendXPos, out legendYPos );
			}

			float newXAxis2Height = pXAxis2.PhysicalMin.Y;

			int titleExtraOffset = (int)(oldXAxis2Height - newXAxis2Height);
	
			// now we are ready to define the bounding box for the plot area (to use in clipping
			// operations.
			plotAreaBoundingBoxCache_ = new Rectangle( 
				(int)Math.Min(pXAxis1.PhysicalMin.X,pXAxis1.PhysicalMax.X),
				(int)Math.Min(pYAxis1.PhysicalMax.Y,pYAxis1.PhysicalMin.Y),
				(int)Math.Abs(pXAxis1.PhysicalMax.X-pXAxis1.PhysicalMin.X+1),
				(int)Math.Abs(pYAxis1.PhysicalMin.Y-pYAxis1.PhysicalMax.Y+1)
			);
			bbXAxis1Cache_ = RectangleConverter.ToRectangle(pXAxis1.GetBoundingBox());
			bbXAxis2Cache_ = RectangleConverter.ToRectangle(pXAxis2.GetBoundingBox());
			bbYAxis1Cache_ = RectangleConverter.ToRectangle(pYAxis1.GetBoundingBox());
			bbYAxis2Cache_ = RectangleConverter.ToRectangle(pYAxis2.GetBoundingBox());

			// Fill in the background. 
			if ( this.plotBackColor_ != null )
			{
				g.FillRectangle( new System.Drawing.SolidBrush( (Color)this.plotBackColor_ ),
					(Rectangle)plotAreaBoundingBoxCache_ );
			}

			// draw legend.
			if ( this.legend_ != null )
			{
				legend_.Draw( g, legendXPos, legendYPos, this.plots_, scale );
			}

			// draw title
			StringFormat drawFormat = new StringFormat();
			drawFormat.Alignment = StringAlignment.Center;
			float xt = (pXAxis2.PhysicalMax.X + pXAxis2.PhysicalMin.X)/2.0f;
			float yt = bounds.Top + this.padding_ - titleExtraOffset;
			Font scaledFont = FontScaler.scaleFont(titleFont_,scale);
			g.DrawString( title_, scaledFont, this.titleBrush_,	new PointF(xt,yt), drawFormat );

			SizeF s = g.MeasureString(title_,scaledFont);
			bbTitleCache_ = new Rectangle((int) (xt-s.Width/2.0F),(int) (yt), (int) s.Width, (int) s.Height);

			// draw plots.
			System.Drawing.Drawing2D.SmoothingMode smoothSave = g.SmoothingMode;

			g.SmoothingMode = this.smoothingMode_;

			for ( int i = 0; i < plots_.Count; ++i )
			{

				IDrawable plot = (IDrawable)plots_[i];
				XAxisPosition xap = (XAxisPosition)xAxisPositions_[i];
				YAxisPosition yap = (YAxisPosition)yAxisPositions_[i];

				PhysicalAxis drawXAxis;
				PhysicalAxis drawYAxis;

				if ( xap == XAxisPosition.Bottom )
				{
					drawXAxis = pXAxis1;
				}
				else
				{
					drawXAxis = pXAxis2;
				}

				if ( yap == YAxisPosition.Left )
				{
					drawYAxis = pYAxis1;
				}
				else
				{
					drawYAxis = pYAxis2;
				}
	
				// set the clipping region.. (necessary for zoom)
				g.Clip = new Region((Rectangle)plotAreaBoundingBoxCache_);
				// plot..
				plot.Draw( g, drawXAxis, drawYAxis );
				// reset it..
				g.ResetClip();

				// cache the physical axes we used on this draw;
				this.pXAxis1Cache_ = pXAxis1;
				this.pYAxis1Cache_ = pYAxis1;
				this.pXAxis2Cache_ = pXAxis2;
				this.pYAxis2Cache_ = pYAxis2;
			}

			g.SmoothingMode = smoothSave;

			// now draw axes.
			pXAxis1.Draw( g );
			pXAxis2.Draw( g );
			pYAxis1.Draw( g );
			pYAxis2.Draw( g );

#if _IWANNASEE_
			g.DrawRectangle( new Pen(Color.Orange), (Rectangle) bbXAxis1Cache_ );
			g.DrawRectangle( new Pen(Color.Orange), (Rectangle) bbXAxis2Cache_ );
			g.DrawRectangle( new Pen(Color.Orange), (Rectangle) bbYAxis1Cache_ );
			g.DrawRectangle( new Pen(Color.Orange), (Rectangle) bbYAxis2Cache_ );
			g.DrawRectangle( new Pen(Color.Red,5.0F),(Rectangle) plotAreaBoundingBoxCache_);
			if(this.ShowLegend)g.DrawRectangle( new Pen(Color.Chocolate, 3.0F), (Rectangle) bbLegendCache_);
			g.DrawRectangle( new Pen(Color.DeepPink,2.0F), (Rectangle) bbTitleCache_);
#endif

		}
		#endregion
		#region Clear
		/// <summary>
		/// Clears the plot and resets to default values.
		/// </summary>
		public void Clear()
		{
			Init();
		}
		#endregion

		#region Save Methods
		private System.Drawing.Imaging.ImageFormat imageFormat_ = System.Drawing.Imaging.ImageFormat.Png;
		/// <summary>
		/// The File Format to use for the default.
		/// </summary>
		public System.Drawing.Imaging.ImageFormat ImageFormatDefault
		{
			get
			{
				return imageFormat_;
			}
			set
			{
				imageFormat_ = value;
			}
		}
		/// <summary>
		/// Save as a picture file.
		/// </summary>
		/// <param name="strFilePath">File name.</param>
		/// <param name="width">Image width.</param>
		/// <param name="height">Image height.</param>
		/// <param name="imageFormat">Image format.</param>
		/// <returns>True if file saved.</returns>
		public bool SaveAsFile( string strFilePath, int width, int height,
			System.Drawing.Imaging.ImageFormat imageFormat )
		{
			if ( (null == strFilePath) || ("" == strFilePath) )
			{
				return false;
			}
			if (strFilePath.IndexOf('.') == -1)
			{
				strFilePath = strFilePath + "." + imageFormat.ToString();
			}
			System.Drawing.Rectangle MyRect = new System.Drawing.Rectangle(0, 0, width, height);
			System.Drawing.Bitmap MyBitmap = new System.Drawing.Bitmap(width, height);
			this.Draw(Graphics.FromImage(MyBitmap), MyRect);
			MyBitmap.Save(strFilePath, imageFormat);
			return true;
		}
		/// <summary>
		/// Save as a picture file.
		/// </summary>
		/// <param name="strFilePath">File name.</param>
		/// <param name="width">Image width.</param>
		/// <param name="height">Image height.</param>
		/// <returns>True if file saved.</returns>
		public bool SaveAsFile( string strFilePath, int width, int height )
		{
			return this.SaveAsFile(strFilePath, width, height, imageFormat_);
		}
		#endregion

		#region Legend
		public scpl.Legend Legend
		{
			get
			{
				return this.legend_;
			}
			set
			{
				this.legend_ = value;
			}
		}
		#endregion

		#region RectangleConverter
		private class RectangleConverter
		{
			public static Rectangle ToRectangle(RectangleF r)
			{
				return new Rectangle((int) r.X, (int)r.Y, (int)r.Width, (int) r.Height);
			}
		}
		#endregion

		#region AddAxesConstraint
		public void AddAxesConstraint( AxesConstraint c )
		{
			this.axesConstraints_.Add( c );
		}
		#endregion
	} 
} 


