mirror of https://github.com/mhowlett/nplot.git
1150 lines
40 KiB
C#
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_;
|
|
}
|
|
}
|
|
} |