1335 строки
36 KiB
C#
1335 строки
36 KiB
C#
/*
|
|
NPlot - A charting library for .NET
|
|
|
|
PlotSurface2D.cs
|
|
Copyright (C) 2003
|
|
Matt Howlett, Paolo Pierini
|
|
|
|
Redistribution and use of NPlot or parts there-of in source and
|
|
binary forms, with or without modification, are permitted provided
|
|
that the following conditions are met:
|
|
|
|
1. Re-distributions in source form must retain at the head of each
|
|
source file the above copyright notice, this list of conditions
|
|
and the following disclaimer.
|
|
|
|
2. Any product ("the product") that makes use NPlot or parts
|
|
there-of must either:
|
|
|
|
(a) allow any user of the product to obtain a complete machine-
|
|
readable copy of the corresponding source code for the
|
|
product and the version of NPlot used for a charge no more
|
|
than your cost of physically performing source distribution,
|
|
on a medium customarily used for software interchange, or:
|
|
|
|
(b) reproduce the following text in the documentation, about
|
|
box or other materials intended to be read by human users
|
|
of the product that is provided to every human user of the
|
|
product:
|
|
|
|
"This product includes software developed as
|
|
part of the NPlot library project available
|
|
from: http://www.nplot.com/"
|
|
|
|
The words "This product" may optionally be replace with
|
|
the actual name of the product.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
// #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;
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|