Files
ntpa/NPlot/PlotSurface2D.cs
T
2016-03-05 14:06:38 +01:00

1314 lines
36 KiB
C#
Executable File

/*
* NPlot - A charting library for .NET
*
* PlotSurface2D.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.
*/
// #define DEBUG_BOUNDING_BOXES
using System;
using System.Drawing;
using System.Diagnostics;
using System.Collections;
namespace NPlot
{
/// <summary>
/// Implements the surface on which IDrawables are drawn. Is extended
/// by Bitmap.PlotSurface2D, Windows.PlotSurface2D etc. TODO: better explanation.
/// </summary>
public class PlotSurface2D : IPlotSurface2D
{
/// <summary>
/// Possible positions of the X axis.
/// </summary>
public enum XAxisPosition
{
/// <summary>
/// X axis is on the top.
/// </summary>
Top = 1,
//Center = 2,
/// <summary>
/// X axis is on the bottom.
/// </summary>
Bottom = 3,
}
/// <summary>
/// Possible positions of the Y axis.
/// </summary>
public enum YAxisPosition
{
/// <summary>
/// Y axis on the left.
/// </summary>
Left = 1,
// Center
/// <summary>
/// Y axis on the right.
/// </summary>
Right = 3,
}
private System.Drawing.StringFormat titleDrawFormat_;
private Font titleFont_;
private string title_;
private Brush titleBrush_;
private int padding_;
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 autoScaleAutoGeneratedAxes_ = false;
private bool autoScaleTitle_ = false;
private object plotAreaBoundingBoxCache_;
private object bbXAxis1Cache_;
private object bbXAxis2Cache_;
private object bbYAxis1Cache_;
private object bbYAxis2Cache_;
private object bbTitleCache_;
private object plotBackColor_ = null;
private System.Drawing.Bitmap plotBackImage_ = null;
private IRectangleBrush plotBackBrush_ = null;
private System.Collections.ArrayList drawables_;
private System.Collections.ArrayList xAxisPositions_;
private System.Collections.ArrayList yAxisPositions_;
private System.Collections.ArrayList zPositions_;
private System.Collections.SortedList ordering_;
private System.Drawing.Drawing2D.SmoothingMode smoothingMode_;
private ArrayList axesConstraints_ = null;
private Legend legend_;
/// <summary>
/// The physical bounding box of the last drawn plot surface area is available here.
/// </summary>
public Rectangle PlotAreaBoundingBoxCache
{
get
{
if (plotAreaBoundingBoxCache_ == null)
{
return Rectangle.Empty;
}
else
{
return (Rectangle)plotAreaBoundingBoxCache_;
}
}
}
/// <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();
// this is the case if PlotSurface has been cleared.
if (bbXAxis1Cache_ == null)
{
return a;
}
else if (bbXAxis1Cache_ != null && ((Rectangle) bbXAxis1Cache_ ).Contains(p))
{
a.Add( this.xAxis1_ );
return a;
}
else if (bbYAxis1Cache_ != null && ((Rectangle) bbYAxis1Cache_ ).Contains(p))
{
a.Add( this.yAxis1_ );
return a;
}
else if (bbXAxis2Cache_ != null && ((Rectangle) bbXAxis2Cache_ ).Contains(p))
{
a.Add( this.xAxis2_ );
return a;
}
else if (bbXAxis2Cache_ != null && ((Rectangle) bbYAxis2Cache_ ).Contains(p))
{
a.Add( this.yAxis2_ );
return a;
}
else if (bbTitleCache_ != null && ((Rectangle) bbTitleCache_ ).Contains(p))
{
a.Add( this );
return a;
}
else if (plotAreaBoundingBoxCache_ != null && ((Rectangle) plotAreaBoundingBoxCache_ ).Contains(p))
{
a.Add( this );
return a;
}
return a;
}
/// <summary>
/// The bottom abscissa axis.
/// </summary>
public Axis XAxis1
{
get
{
return xAxis1_;
}
set
{
xAxis1_ = value;
}
}
/// <summary>
/// The left ordinate axis.
/// </summary>
public Axis YAxis1
{
get
{
return yAxis1_;
}
set
{
yAxis1_ = value;
}
}
/// <summary>
/// The top abscissa axis.
/// </summary>
public Axis XAxis2
{
get
{
return xAxis2_;
}
set
{
xAxis2_ = value;
}
}
/// <summary>
/// The right ordinate axis.
/// </summary>
public Axis YAxis2
{
get
{
return yAxis2_;
}
set
{
yAxis2_ = value;
}
}
/// <summary>
/// The physical XAxis1 that was last drawn.
/// </summary>
public PhysicalAxis PhysicalXAxis1Cache
{
get
{
return pXAxis1Cache_;
}
}
/// <summary>
/// The physical YAxis1 that was last drawn.
/// </summary>
public PhysicalAxis PhysicalYAxis1Cache
{
get
{
return pYAxis1Cache_;
}
}
/// <summary>
/// The physical XAxis2 that was last drawn.
/// </summary>
public PhysicalAxis PhysicalXAxis2Cache
{
get
{
return pXAxis2Cache_;
}
}
/// <summary>
/// The physical YAxis2 that was last drawn.
/// </summary>
public PhysicalAxis PhysicalYAxis2Cache
{
get
{
return pYAxis2Cache_;
}
}
/// <summary>
/// The chart title.
/// </summary>
public string Title
{
get
{
return title_;
}
set
{
title_ = value;
}
}
/// <summary>
/// The plot title font.
/// </summary>
public Font TitleFont
{
get
{
return titleFont_;
}
set
{
titleFont_ = value;
}
}
/// <summary>
/// The distance in pixels to leave between of the edge of the bounding rectangle
/// supplied to the Draw method, and the markings that make up the plot.
/// </summary>
public int Padding
{
get
{
return padding_;
}
set
{
padding_ = value;
}
}
/// <summary>
/// Sets the title to be drawn using a solid brush of this color.
/// </summary>
public Color TitleColor
{
set
{
titleBrush_ = new SolidBrush( value );
}
}
/// <summary>
/// The brush used for drawing the title.
/// </summary>
public Brush TitleBrush
{
get
{
return titleBrush_;
}
set
{
titleBrush_ = value;
}
}
/// <summary>
/// A color used to paint the plot background. Mutually exclusive with PlotBackImage and PlotBackBrush
/// </summary>
public System.Drawing.Color PlotBackColor
{
set
{
plotBackColor_ = value;
plotBackBrush_ = null;
plotBackImage_ = null;
}
}
/// <summary>
/// An imaged used to paint the plot background. Mutually exclusive with PlotBackColor and PlotBackBrush
/// </summary>
public System.Drawing.Bitmap PlotBackImage
{
set
{
plotBackImage_ = value;
plotBackColor_ = null;
plotBackBrush_ = null;
}
}
/// <summary>
/// A Rectangle brush used to paint the plot background. Mutually exclusive with PlotBackColor and PlotBackBrush
/// </summary>
public IRectangleBrush PlotBackBrush
{
set
{
plotBackBrush_ = value;
plotBackColor_ = null;
plotBackImage_ = null;
}
}
/// <summary>
/// Smoothing mode to use when drawing plots.
/// </summary>
public System.Drawing.Drawing2D.SmoothingMode SmoothingMode
{
get
{
return smoothingMode_;
}
set
{
this.smoothingMode_ = value;
}
}
private void Init()
{
drawables_ = new ArrayList();
xAxisPositions_ = new ArrayList();
yAxisPositions_ = new ArrayList();
zPositions_ = new ArrayList();
ordering_ = new SortedList();
FontFamily fontFamily = new FontFamily("Arial");
TitleFont = new Font(fontFamily, 14, FontStyle.Regular, GraphicsUnit.Pixel);
padding_ = 10;
title_ = "";
autoScaleTitle_ = false;
autoScaleAutoGeneratedAxes_ = false;
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.None;
axesConstraints_ = new ArrayList();
}
/// <summary>
/// Default constructor.
/// </summary>
public PlotSurface2D()
{
// only create this once.
titleDrawFormat_ = new StringFormat();
titleDrawFormat_.Alignment = StringAlignment.Center;
Init();
}
private float DetermineScaleFactor( int w, int h )
{
float diag = (float)Math.Sqrt( w*w + h*h );
float scaleFactor = (diag / 1400.0f)*2.4f;
if ( scaleFactor > 1.0f )
{
return scaleFactor;
}
else
{
return 1.0f;
}
}
/// <summary>
/// Adds a drawable object to the plot surface with z-order 0. If the object is an IPlot,
/// the PlotSurface2D axes will also be updated.
/// </summary>
/// <param name="p">The IDrawable object to add to the plot surface.</param>
public void Add(IDrawable p)
{
Add(p, 0);
}
/// <summary>
/// Adds a drawable object to the plot surface. If the object is an IPlot,
/// the PlotSurface2D axes will also be updated.
/// </summary>
/// <param name="p">The IDrawable object to add to the plot surface.</param>
/// <param name="zOrder">The z-ordering when drawing (objects with lower numbers are drawn first)</param>
public void Add( IDrawable p, int zOrder )
{
Add( p, XAxisPosition.Bottom, YAxisPosition.Left, zOrder );
}
/// <summary>
/// Adds a drawable object to the plot surface against the specified axes with
/// z-order of 0. If the object is an IPlot, the PlotSurface2D axes will also
/// be updated.
/// </summary>
/// <param name="p">the IDrawable object to add to the plot surface</param>
/// <param name="xp">the x-axis to add the plot against.</param>
/// <param name="yp">the y-axis to add the plot against.</param>
public void Add(IDrawable p, XAxisPosition xp, YAxisPosition yp)
{
Add(p, xp, yp, 0);
}
/// <summary>
/// Adds a drawable object to the plot surface against the specified axes. If
/// the object is an IPlot, the PlotSurface2D axes will also be updated.
/// </summary>
/// <param name="p">the IDrawable object to add to the plot surface</param>
/// <param name="xp">the x-axis to add the plot against.</param>
/// <param name="yp">the y-axis to add the plot against.</param>
/// <param name="zOrder">The z-ordering when drawing (objects with lower numbers are drawn first)</param>
public void Add( IDrawable p, XAxisPosition xp, YAxisPosition yp, int zOrder )
{
drawables_.Add( p );
xAxisPositions_.Add( xp );
yAxisPositions_.Add( yp );
zPositions_.Add((double)zOrder);
// fraction is to make key unique. With 10 million plots at same z, this buggers up..
double fraction = (double)(++uniqueCounter_)/10000000.0f;
ordering_.Add( (double)zOrder + fraction, drawables_.Count - 1 );
// if p is just an IDrawable, then it can't affect the axes.
if ( p is IPlot )
{
UpdateAxes( false );
}
}
private int uniqueCounter_ = 0;
private void UpdateAxes( bool recalculateAll )
{
if (drawables_.Count != xAxisPositions_.Count || drawables_.Count != yAxisPositions_.Count)
{
throw new NPlotException("plots and axis position arrays our of sync");
}
int position = 0;
// if we're not recalculating axes using all iplots then set
// position to last one in list.
if (!recalculateAll)
{
position = drawables_.Count - 1;
if (position < 0) position = 0;
}
if (recalculateAll)
{
this.xAxis1_ = null;
this.yAxis1_ = null;
this.xAxis2_ = null;
this.yAxis2_ = null;
}
for (int i = position; i < drawables_.Count; ++i)
{
// only update axes if this drawable is an IPlot.
if (!(drawables_[position] is IPlot))
continue;
IPlot p = (IPlot)drawables_[position];
XAxisPosition xap = (XAxisPosition)xAxisPositions_[position];
YAxisPosition yap = (YAxisPosition)yAxisPositions_[position];
if (xap == XAxisPosition.Bottom)
{
if (this.xAxis1_ == null)
{
this.xAxis1_ = p.SuggestXAxis();
if (this.xAxis1_ != null)
{
this.xAxis1_.TicksAngle = -(float)Math.PI / 2.0f;
}
}
else
{
this.xAxis1_.LUB(p.SuggestXAxis());
}
if (this.xAxis1_ != null)
{
this.xAxis1_.MinPhysicalLargeTickStep = 50;
if (this.AutoScaleAutoGeneratedAxes)
{
this.xAxis1_.AutoScaleText = true;
this.xAxis1_.AutoScaleTicks = true;
this.xAxis1_.TicksIndependentOfPhysicalExtent = true;
}
else
{
this.xAxis1_.AutoScaleText = false;
this.xAxis1_.AutoScaleTicks = false;
this.xAxis1_.TicksIndependentOfPhysicalExtent = false;
}
}
}
if (xap == XAxisPosition.Top)
{
if (this.xAxis2_ == null)
{
this.xAxis2_ = p.SuggestXAxis();
if (this.xAxis2_ != null)
{
this.xAxis2_.TicksAngle = (float)Math.PI / 2.0f;
}
}
else
{
this.xAxis2_.LUB(p.SuggestXAxis());
}
if (this.xAxis2_ != null)
{
this.xAxis2_.MinPhysicalLargeTickStep = 50;
if (this.AutoScaleAutoGeneratedAxes)
{
this.xAxis2_.AutoScaleText = true;
this.xAxis2_.AutoScaleTicks = true;
this.xAxis2_.TicksIndependentOfPhysicalExtent = true;
}
else
{
this.xAxis2_.AutoScaleText = false;
this.xAxis2_.AutoScaleTicks = false;
this.xAxis2_.TicksIndependentOfPhysicalExtent = false;
}
}
}
if (yap == YAxisPosition.Left)
{
if (this.yAxis1_ == null)
{
this.yAxis1_ = p.SuggestYAxis();
if (this.yAxis1_ != null)
{
this.yAxis1_.TicksAngle = (float)Math.PI / 2.0f;
}
}
else
{
this.yAxis1_.LUB(p.SuggestYAxis());
}
if (this.yAxis1_ != null)
{
if (this.AutoScaleAutoGeneratedAxes)
{
this.yAxis1_.AutoScaleText = true;
this.yAxis1_.AutoScaleTicks = true;
this.yAxis1_.TicksIndependentOfPhysicalExtent = true;
}
else
{
this.yAxis1_.AutoScaleText = false;
this.yAxis1_.AutoScaleTicks = false;
this.yAxis1_.TicksIndependentOfPhysicalExtent = false;
}
}
}
if (yap == YAxisPosition.Right)
{
if (this.yAxis2_ == null)
{
this.yAxis2_ = p.SuggestYAxis();
if (this.yAxis2_ != null)
{
this.yAxis2_.TicksAngle = -(float)Math.PI / 2.0f;
}
}
else
{
this.yAxis2_.LUB(p.SuggestYAxis());
}
if (this.yAxis2_ != null)
{
if (this.AutoScaleAutoGeneratedAxes)
{
this.yAxis2_.AutoScaleText = true;
this.yAxis2_.AutoScaleTicks = true;
this.yAxis2_.TicksIndependentOfPhysicalExtent = true;
}
else
{
this.yAxis2_.AutoScaleText = false;
this.yAxis2_.AutoScaleTicks = false;
this.yAxis2_.TicksIndependentOfPhysicalExtent = false;
}
}
}
}
}
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 NPlotException( "Error: No X-Axis specified" );
}
xAxis1 = (Axis)this.xAxis2_.Clone();
xAxis1.HideTickText = true;
xAxis1.TicksAngle = -(float)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 = (float)Math.PI / 2.0f;
}
if (this.yAxis1_ == null)
{
if (this.yAxis2_ == null)
{
throw new NPlotException( "Error: No Y-Axis specified" );
}
yAxis1 = (Axis)this.yAxis2_.Clone();
yAxis1.HideTickText = true;
yAxis1.TicksAngle = (float)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 = -(float)Math.PI / 2.0f;
}
}
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 = padding_;
if (!pXAxis1.Axis.Hidden)
{
// evaluate its bounding box
Rectangle bb = pXAxis1.GetBoundingBox();
// finally determine its indentation from the bottom
bottomIndent = bottomIndent + bb.Bottom - cb.Bottom;
}
int leftIndent = padding_;
if (!pYAxis1.Axis.Hidden)
{
// evaluate its bounding box
Rectangle bb = pYAxis1.GetBoundingBox();
// finally determine its indentation from the left
leftIndent = leftIndent - bb.Left + cb.Left;
}
int topIndent = padding_;
float scale = this.DetermineScaleFactor( bounds.Width, bounds.Height );
int titleHeight;
if (this.AutoScaleTitle)
{
titleHeight = Utils.ScaleFont(titleFont_, scale).Height;
}
else
{
titleHeight = titleFont_.Height;
}
//count number of new lines in title.
int nlCount = 0;
for (int i=0; i<title_.Length; ++i)
{
if (title_[i] == '\n')
nlCount += 1;
}
titleHeight = (int)( ((float)nlCount*0.75 + 1.0f) * (float)titleHeight);
if (!pXAxis2.Axis.Hidden)
{
// evaluate its bounding box
Rectangle bb = pXAxis2.GetBoundingBox();
topIndent = 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)(titleHeight * 1.3f);
}
}
int rightIndent = padding_;
if (!pYAxis2.Axis.Hidden)
{
// evaluate its bounding box
Rectangle 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 );
}
/// <summary>
/// Draw the the PlotSurface2D and all contents [axes, drawables, and legend] on the
/// supplied graphics surface.
/// </summary>
/// <param name="g">The graphics surface on which to draw.</param>
/// <param name="bounds">A bounding box on this surface that denotes the area on the
/// surface to confine drawing to.</param>
public void Draw( Graphics g, Rectangle bounds )
{
// determine font sizes and tick scale factor.
float scale = DetermineScaleFactor( bounds.Width, bounds.Height );
// if there is nothing to plot, return.
if ( drawables_.Count == 0 )
{
// draw title
float x_center = (bounds.Left + bounds.Right)/2.0f;
float y_center = (bounds.Top + bounds.Bottom)/2.0f;
Font scaled_font;
if (this.AutoScaleTitle)
{
scaled_font = Utils.ScaleFont( titleFont_, scale );
}
else
{
scaled_font = titleFont_;
}
g.DrawString( title_, scaled_font, this.titleBrush_, new PointF(x_center,y_center), titleDrawFormat_ );
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 );
// apply scale factor to axes as desired.
if (xAxis1.AutoScaleTicks)
xAxis1.TickScale = scale;
if (xAxis1.AutoScaleText)
xAxis1.FontScale = scale;
if (yAxis1.AutoScaleTicks)
yAxis1.TickScale = scale;
if (yAxis1.AutoScaleText)
yAxis1.FontScale = scale;
if (xAxis2.AutoScaleTicks)
xAxis2.TickScale = scale;
if (xAxis2.AutoScaleText)
xAxis2.FontScale = scale;
if (yAxis2.AutoScaleTicks)
yAxis2.TickScale = scale;
if (yAxis2.AutoScaleText)
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(
pXAxis1, pYAxis1, pXAxis2, pYAxis2 );
}
/////////////////////////////////////////////////////////////////////////
// draw legend if have one.
// Note: this will update axes if necessary.
Point legendPosition = new Point(0,0);
if (this.legend_ != null)
{
legend_.UpdateAxesPositions(
pXAxis1, pYAxis1, pXAxis2, pYAxis2,
this.drawables_, scale, this.padding_, bounds,
out legendPosition );
}
float newXAxis2Height = pXAxis2.PhysicalMin.Y;
float titleExtraOffset = oldXAxis2Height - newXAxis2Height;
// now we are ready to define the bounding box for the plot area (to use in clipping
// operations.
plotAreaBoundingBoxCache_ = new Rectangle(
Math.Min( pXAxis1.PhysicalMin.X, pXAxis1.PhysicalMax.X ),
Math.Min( pYAxis1.PhysicalMax.Y, pYAxis1.PhysicalMin.Y ),
Math.Abs( pXAxis1.PhysicalMax.X - pXAxis1.PhysicalMin.X + 1 ),
Math.Abs( pYAxis1.PhysicalMin.Y - pYAxis1.PhysicalMax.Y + 1 )
);
bbXAxis1Cache_ = pXAxis1.GetBoundingBox();
bbXAxis2Cache_ = pXAxis2.GetBoundingBox();
bbYAxis1Cache_ = pYAxis1.GetBoundingBox();
bbYAxis2Cache_ = pYAxis2.GetBoundingBox();
// Fill in the background.
if ( this.plotBackColor_ != null )
{
g.FillRectangle(
new System.Drawing.SolidBrush( (Color)this.plotBackColor_ ),
(Rectangle)plotAreaBoundingBoxCache_ );
}
else if (this.plotBackBrush_ != null)
{
g.FillRectangle(
this.plotBackBrush_.Get( (Rectangle)plotAreaBoundingBoxCache_ ),
(Rectangle)plotAreaBoundingBoxCache_ );
}
else if (this.plotBackImage_ != null)
{
g.DrawImage(
Utils.TiledImage( this.plotBackImage_ , new Size(
((Rectangle)plotAreaBoundingBoxCache_).Width,
((Rectangle)plotAreaBoundingBoxCache_).Height ) ),
(Rectangle)plotAreaBoundingBoxCache_ );
}
// draw title
float xt = (pXAxis2.PhysicalMax.X + pXAxis2.PhysicalMin.X)/2.0f;
float yt = bounds.Top + this.padding_ - titleExtraOffset;
Font scaledFont;
if (this.AutoScaleTitle)
{
scaledFont = Utils.ScaleFont( titleFont_, scale );
}
else
{
scaledFont = titleFont_;
}
g.DrawString( title_, scaledFont, this.titleBrush_, new PointF(xt,yt), titleDrawFormat_ );
//count number of new lines in title.
int nlCount = 0;
for (int i=0; i<title_.Length; ++i)
{
if (title_[i] == '\n')
nlCount += 1;
}
SizeF s = g.MeasureString(title_,scaledFont);
bbTitleCache_ = new Rectangle( (int)(xt-s.Width/2), (int)(yt), (int)(s.Width), (int)(s.Height)*(nlCount+1) );
// draw drawables..
System.Drawing.Drawing2D.SmoothingMode smoothSave = g.SmoothingMode;
g.SmoothingMode = this.smoothingMode_;
bool legendDrawn = false;
for ( int i_o = 0; i_o < ordering_.Count; ++i_o )
{
int i = (int)ordering_.GetByIndex(i_o);
double zOrder = (double)ordering_.GetKey( i_o );
if (zOrder > this.legendZOrder_)
{
// draw legend.
if ( !legendDrawn && this.legend_ != null )
{
legend_.Draw( g, legendPosition, this.drawables_, scale );
legendDrawn = true;
}
}
IDrawable drawable = (IDrawable)drawables_[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.
drawable.Draw( g, drawXAxis, drawYAxis );
// reset it..
g.ResetClip();
}
if ( !legendDrawn && this.legend_ != null )
{
legend_.Draw( g, legendPosition, this.drawables_, scale );
}
// 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.
Rectangle axisBounds;
pXAxis1.Draw( g, out axisBounds );
pXAxis2.Draw( g, out axisBounds );
pYAxis1.Draw( g, out axisBounds );
pYAxis2.Draw( g, out axisBounds );
#if DEBUG_BOUNDING_BOXES
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
}
/// <summary>
/// Clears the plot and resets all state to the default.
/// </summary>
public void Clear()
{
Init();
}
/// <summary>
/// Legend to use. If this property is null [default], then the plot
/// surface will have no corresponding legend.
/// </summary>
public NPlot.Legend Legend
{
get
{
return this.legend_;
}
set
{
this.legend_ = value;
}
}
/// <summary>
/// Add an axis constraint to the plot surface. Axes constraints give you
/// control over where NPlot positions each axes, and the world - pixel
/// ratio.
/// </summary>
/// <param name="constraint">The axis constraint to add.</param>
public void AddAxesConstraint( AxesConstraint constraint )
{
this.axesConstraints_.Add( constraint );
}
/// <summary>
/// Whether or not the title will be scaled according to size of the plot surface.
/// </summary>
public bool AutoScaleTitle
{
get
{
return autoScaleTitle_;
}
set
{
autoScaleTitle_ = value;
}
}
/// <summary>
/// When plots are added to the plot surface, the axes they are attached to
/// are immediately modified to reflect data of the plot. If
/// AutoScaleAutoGeneratedAxes is true when a plot is added, the axes will
/// be turned in to auto scaling ones if they are not already [tick marks,
/// tick text and label size scaled to size of plot surface]. If false,
/// axes will not be autoscaling.
/// </summary>
public bool AutoScaleAutoGeneratedAxes
{
get
{
return autoScaleAutoGeneratedAxes_;
}
set
{
autoScaleAutoGeneratedAxes_ = value;
}
}
/// <summary>
/// Remove a drawable object.
/// Note that axes are not updated.
/// </summary>
/// <param name="p">Drawable to remove.</param>
/// <param name="updateAxes">if true, the axes are updated.</param>
public void Remove( IDrawable p, bool updateAxes )
{
int index = drawables_.IndexOf( p );
if (index < 0)
return;
drawables_.RemoveAt( index );
xAxisPositions_.RemoveAt( index );
yAxisPositions_.RemoveAt( index );
zPositions_.RemoveAt(index);
if (updateAxes)
{
this.UpdateAxes(true);
}
this.RefreshZOrdering();
}
/// <summary>
/// If a plot is removed, then the ordering_ list needs to be
/// recalculated.
/// </summary>
private void RefreshZOrdering()
{
uniqueCounter_ = 0;
ordering_ = new SortedList();
for (int i = 0; i < zPositions_.Count; ++i)
{
double zpos = Convert.ToDouble(zPositions_[i]);
double fraction = (double)(++uniqueCounter_) / 10000000.0f;
double d = zpos + fraction;
ordering_.Add(d, i);
}
}
/// <summary>
/// Gets an array list containing all drawables currently added to the PlotSurface2D.
/// </summary>
public ArrayList Drawables
{
get
{
return this.drawables_;
}
}
/// <summary>
/// Returns the x-axis associated with a given plot.
/// </summary>
/// <param name="plot">the plot to get associated x-axis.</param>
/// <returns>the axis associated with the plot.</returns>
public Axis WhichXAxis( IPlot plot )
{
int index = drawables_.IndexOf( plot );
XAxisPosition p = (XAxisPosition)xAxisPositions_[index];
if ( p == XAxisPosition.Bottom )
return this.xAxis1_;
else
return this.xAxis2_;
}
/// <summary>
/// Returns the y-axis associated with a given plot.
/// </summary>
/// <param name="plot">the plot to get associated y-axis.</param>
/// <returns>the axis associated with the plot.</returns>
public Axis WhichYAxis( IPlot plot )
{
int index = drawables_.IndexOf( plot );
YAxisPosition p = (YAxisPosition)yAxisPositions_[index];
if ( p == YAxisPosition.Left )
return this.yAxis1_;
else
return this.yAxis2_;
}
/// <summary>
/// Setting this value determines the order (relative to IDrawables added to the plot surface)
/// that the legend is drawn.
/// </summary>
public int LegendZOrder
{
get
{
return legendZOrder_;
}
set
{
legendZOrder_ = value;
}
}
int legendZOrder_ = -1;
}
}