nplot/src/PlotSurface2D.cs

1150 lines
40 KiB
C#

/*
* 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.
*
* 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.Collections;
using System.Drawing;
using System.Drawing.Drawing2D;
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 readonly StringFormat titleDrawFormat_;
private bool autoScaleAutoGeneratedAxes_;
private bool autoScaleTitle_;
private ArrayList axesConstraints_;
private object bbTitleCache_;
private object bbXAxis1Cache_;
private object bbXAxis2Cache_;
private object bbYAxis1Cache_;
private object bbYAxis2Cache_;
private ArrayList drawables_;
private int legendZOrder_ = -1;
private Legend legend_;
private SortedList ordering_;
private PhysicalAxis pXAxis1Cache_;
private PhysicalAxis pXAxis2Cache_;
private PhysicalAxis pYAxis1Cache_;
private PhysicalAxis pYAxis2Cache_;
private int padding_;
private object plotAreaBoundingBoxCache_;
private IRectangleBrush plotBackBrush_;
private object plotBackColor_;
private System.Drawing.Bitmap plotBackImage_;
private SmoothingMode smoothingMode_;
private Brush titleBrush_;
private Font titleFont_;
private string title_;
private int uniqueCounter_;
private Axis xAxis1_;
private Axis xAxis2_;
private ArrayList xAxisPositions_;
private Axis yAxis1_;
private Axis yAxis2_;
private ArrayList yAxisPositions_;
private ArrayList zPositions_;
/// <summary>
/// Default constructor.
/// </summary>
public PlotSurface2D()
{
// only create this once.
titleDrawFormat_ = new StringFormat();
titleDrawFormat_.Alignment = StringAlignment.Center;
Init();
}
/// <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>
/// 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 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 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 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 SmoothingMode SmoothingMode
{
get { return smoothingMode_; }
set { smoothingMode_ = value; }
}
/// <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(zOrder + fraction, drawables_.Count - 1);
// if p is just an IDrawable, then it can't affect the axes.
if (p is IPlot)
{
UpdateAxes(false);
}
}
/// <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 Legend Legend
{
get { return legend_; }
set { 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)
{
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)
{
UpdateAxes(true);
}
RefreshZOrdering();
}
/// <summary>
/// Gets an array list containing all drawables currently added to the PlotSurface2D.
/// </summary>
public ArrayList Drawables
{
get { return drawables_; }
}
/// <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; }
}
/// <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 ArrayList HitTest(Point p)
{
ArrayList a = new 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(xAxis1_);
return a;
}
else if (bbYAxis1Cache_ != null && ((Rectangle) bbYAxis1Cache_).Contains(p))
{
a.Add(yAxis1_);
return a;
}
else if (bbXAxis2Cache_ != null && ((Rectangle) bbXAxis2Cache_).Contains(p))
{
a.Add(xAxis2_);
return a;
}
else if (bbXAxis2Cache_ != null && ((Rectangle) bbYAxis2Cache_).Contains(p))
{
a.Add(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;
}
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;
legend_ = null;
smoothingMode_ = SmoothingMode.None;
axesConstraints_ = new ArrayList();
}
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;
}
}
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)
{
xAxis1_ = null;
yAxis1_ = null;
xAxis2_ = null;
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 (xAxis1_ == null)
{
xAxis1_ = p.SuggestXAxis();
if (xAxis1_ != null)
{
xAxis1_.TicksAngle = -(float) Math.PI/2.0f;
}
}
else
{
xAxis1_.LUB(p.SuggestXAxis());
}
if (xAxis1_ != null)
{
xAxis1_.MinPhysicalLargeTickStep = 50;
if (AutoScaleAutoGeneratedAxes)
{
xAxis1_.AutoScaleText = true;
xAxis1_.AutoScaleTicks = true;
xAxis1_.TicksIndependentOfPhysicalExtent = true;
}
else
{
xAxis1_.AutoScaleText = false;
xAxis1_.AutoScaleTicks = false;
xAxis1_.TicksIndependentOfPhysicalExtent = false;
}
}
}
if (xap == XAxisPosition.Top)
{
if (xAxis2_ == null)
{
xAxis2_ = p.SuggestXAxis();
if (xAxis2_ != null)
{
xAxis2_.TicksAngle = (float) Math.PI/2.0f;
}
}
else
{
xAxis2_.LUB(p.SuggestXAxis());
}
if (xAxis2_ != null)
{
xAxis2_.MinPhysicalLargeTickStep = 50;
if (AutoScaleAutoGeneratedAxes)
{
xAxis2_.AutoScaleText = true;
xAxis2_.AutoScaleTicks = true;
xAxis2_.TicksIndependentOfPhysicalExtent = true;
}
else
{
xAxis2_.AutoScaleText = false;
xAxis2_.AutoScaleTicks = false;
xAxis2_.TicksIndependentOfPhysicalExtent = false;
}
}
}
if (yap == YAxisPosition.Left)
{
if (yAxis1_ == null)
{
yAxis1_ = p.SuggestYAxis();
if (yAxis1_ != null)
{
yAxis1_.TicksAngle = (float) Math.PI/2.0f;
}
}
else
{
yAxis1_.LUB(p.SuggestYAxis());
}
if (yAxis1_ != null)
{
if (AutoScaleAutoGeneratedAxes)
{
yAxis1_.AutoScaleText = true;
yAxis1_.AutoScaleTicks = true;
yAxis1_.TicksIndependentOfPhysicalExtent = true;
}
else
{
yAxis1_.AutoScaleText = false;
yAxis1_.AutoScaleTicks = false;
yAxis1_.TicksIndependentOfPhysicalExtent = false;
}
}
}
if (yap == YAxisPosition.Right)
{
if (yAxis2_ == null)
{
yAxis2_ = p.SuggestYAxis();
if (yAxis2_ != null)
{
yAxis2_.TicksAngle = -(float) Math.PI/2.0f;
}
}
else
{
yAxis2_.LUB(p.SuggestYAxis());
}
if (yAxis2_ != null)
{
if (AutoScaleAutoGeneratedAxes)
{
yAxis2_.AutoScaleText = true;
yAxis2_.AutoScaleTicks = true;
yAxis2_.TicksIndependentOfPhysicalExtent = true;
}
else
{
yAxis2_.AutoScaleText = false;
yAxis2_.AutoScaleTicks = false;
yAxis2_.TicksIndependentOfPhysicalExtent = false;
}
}
}
}
}
private void DetermineAxesToDraw(out Axis xAxis1, out Axis xAxis2, out Axis yAxis1, out Axis yAxis2)
{
xAxis1 = xAxis1_;
xAxis2 = xAxis2_;
yAxis1 = yAxis1_;
yAxis2 = yAxis2_;
if (xAxis1_ == null)
{
if (xAxis2_ == null)
{
throw new NPlotException("Error: No X-Axis specified");
}
xAxis1 = (Axis) xAxis2_.Clone();
xAxis1.HideTickText = true;
xAxis1.TicksAngle = -(float) Math.PI/2.0f;
}
if (xAxis2_ == null)
{
// don't need to check if xAxis1_ == null, as case already handled above.
xAxis2 = (Axis) xAxis1_.Clone();
xAxis2.HideTickText = true;
xAxis2.TicksAngle = (float) Math.PI/2.0f;
}
if (yAxis1_ == null)
{
if (yAxis2_ == null)
{
throw new NPlotException("Error: No Y-Axis specified");
}
yAxis1 = (Axis) yAxis2_.Clone();
yAxis1.HideTickText = true;
yAxis1.TicksAngle = (float) Math.PI/2.0f;
}
if (yAxis2_ == null)
{
// don't need to check if yAxis1_ == null, as case already handled above.
yAxis2 = (Axis) 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)
{
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 = DetermineScaleFactor(bounds.Width, bounds.Height);
int titleHeight;
if (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) ((nlCount*0.75 + 1.0f)*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 = (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 (AutoScaleTitle)
{
scaled_font = Utils.ScaleFont(titleFont_, scale);
}
else
{
scaled_font = titleFont_;
}
g.DrawString(title_, scaled_font, 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;
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;
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 (legend_ != null)
{
legend_.UpdateAxesPositions(
pXAxis1, pYAxis1, pXAxis2, pYAxis2,
drawables_, scale, 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 (plotBackColor_ != null)
{
g.FillRectangle(
new SolidBrush((Color) plotBackColor_),
(Rectangle) plotAreaBoundingBoxCache_);
}
else if (plotBackBrush_ != null)
{
g.FillRectangle(
plotBackBrush_.Get((Rectangle) plotAreaBoundingBoxCache_),
(Rectangle) plotAreaBoundingBoxCache_);
}
else if (plotBackImage_ != null)
{
g.DrawImage(
Utils.TiledImage(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 + padding_ - titleExtraOffset;
Font scaledFont;
if (AutoScaleTitle)
{
scaledFont = Utils.ScaleFont(titleFont_, scale);
}
else
{
scaledFont = titleFont_;
}
g.DrawString(title_, scaledFont, 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..
SmoothingMode smoothSave = g.SmoothingMode;
g.SmoothingMode = 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 > legendZOrder_)
{
// draw legend.
if (!legendDrawn && legend_ != null)
{
legend_.Draw(g, legendPosition, 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 && legend_ != null)
{
legend_.Draw(g, legendPosition, drawables_, scale);
}
// cache the physical axes we used on this draw;
pXAxis1Cache_ = pXAxis1;
pYAxis1Cache_ = pYAxis1;
pXAxis2Cache_ = pXAxis2;
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>
/// 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>
/// 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 xAxis1_;
else
return 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 yAxis1_;
else
return yAxis2_;
}
}
}