nplot/src/PhysicalAxis.cs

202 lines
8.5 KiB
C#

/*
* NPlot - A charting library for .NET
*
* PhysicalAxis.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.
*/
using System;
using System.Drawing;
namespace NPlot
{
/// <summary>
/// This class adds physical positioning information [PhysicalMin, PhysicalMax]
/// and related functionality on top of a specific Axis class.
/// It's an interesting
/// question where to put this information. It belongs with every specific axis
/// type, but on the other hand, users of the library as it is normally used
/// should not see it because
/// positioning of axes is handled internally by PlotSurface2D. Therefore it doesn't make sense
/// to put it in the Axis class unless it is internal. But if this were done it would restrict
/// use of this information outside the library always, which is not what is wanted.
/// The main disadvantage with the method chosen is that there is a lot of passing
/// of the positional information between physical axis and the underlying logical
/// axis type.
/// C# doesn't have templates. If it did, I might derive PhysicalAxis from the
/// templated Axis type (LinearAxis etc). Instead, have used a has-a relationship
/// with an Axis superclass.
/// </summary>
public class PhysicalAxis
{
/// <summary>
/// Prevent default construction.
/// </summary>
private PhysicalAxis()
{
}
/// <summary>
/// Construct
/// </summary>
/// <param name="a">The axis this is a physical representation of.</param>
/// <param name="physicalMin">the physical position of the world minimum axis value.</param>
/// <param name="physicalMax">the physical position of the world maximum axis value.</param>
public PhysicalAxis(Axis a, Point physicalMin, Point physicalMax)
{
Axis = a;
PhysicalMin = physicalMin;
PhysicalMax = physicalMax;
}
/// <summary>
/// The physical position corresponding to WorldMin.
/// </summary>
public Point PhysicalMin { get; set; }
/// <summary>
/// The physical position corresponding to WorldMax.
/// </summary>
public Point PhysicalMax { get; set; }
/// <summary>
/// The axis this object adds physical extents to.
/// </summary>
public Axis Axis { get; set; }
/// <summary>
/// The length in pixels of the axis.
/// </summary>
public int PhysicalLength
{
get { return Utils.Distance(PhysicalMin, PhysicalMax); }
}
/// <summary>
/// The length in world coordinates of one pixel.
/// </summary>
public double PixelWorldLength
{
get { return Axis.WorldLength/PhysicalLength; }
}
/// <summary>
/// Returns the smallest rectangle that completely contains all parts of the axis [including ticks and label].
/// </summary>
/// <returns>the smallest rectangle that completely contains all parts of the axis [including ticks and label].</returns>
public virtual Rectangle GetBoundingBox()
{
System.Drawing.Bitmap scratchArea_ = new System.Drawing.Bitmap(1, 1);
Graphics g = Graphics.FromImage(scratchArea_);
Rectangle bounds;
Draw(g, out bounds);
return bounds;
}
/// <summary>
/// Draws the axis on the given graphics surface.
/// </summary>
/// <param name="g">The graphics surface on which to draw.</param>
/// <param name="boundingBox">
/// out: the axis bounding box - the smallest rectangle that
/// completely contains all parts of the axis [including ticks and label].
/// </param>
public virtual void Draw(Graphics g, out Rectangle boundingBox)
{
Axis.Draw(g, PhysicalMin, PhysicalMax, out boundingBox);
}
/// <summary>
/// Given a world coordinate value, returns the physical position of the
/// coordinate along the axis.
/// </summary>
/// <param name="coord">the world coordinate</param>
/// <param name="clip">if true, the physical position returned will be clipped to the physical max / min position as appropriate if the world value is outside the limits of the axis.</param>
/// <returns>the physical position of the coordinate along the axis.</returns>
public PointF WorldToPhysical(double coord, bool clip)
{
return Axis.WorldToPhysical(coord, PhysicalMin, PhysicalMax, clip);
}
/// <summary>
/// Given a physical point on the graphics surface, returns the world
/// value of it's projection onto the axis [i.e. closest point on the axis].
/// The function is implemented for axes of arbitrary orientation.
/// </summary>
/// <param name="p">Physical point to find corresponding world value of.</param>
/// <param name="clip">
/// if true, returns a world position outside WorldMin / WorldMax
/// range if this is closer to the axis line. If false, such values will
/// be clipped to be either WorldMin or WorldMax as appropriate.
/// </param>
/// <returns>the world value of the point's projection onto the axis.</returns>
public double PhysicalToWorld(Point p, bool clip)
{
return Axis.PhysicalToWorld(p, PhysicalMin, PhysicalMax, clip);
}
/// <summary>
/// This sets new world limits for the axis from two physical points
/// selected within the plot area.
/// </summary>
/// <param name="min">The upper left point of the selection.</param>
/// <param name="max">The lower right point of the selection.</param>
public void SetWorldLimitsFromPhysical(Point min, Point max)
{
double minc;
double maxc;
if (Axis != null)
{
minc = Axis.WorldMin;
maxc = Axis.WorldMax;
if (!Axis.Reversed)
{
double tmp = PhysicalToWorld(min, true);
Axis.WorldMax = PhysicalToWorld(max, true);
Axis.WorldMin = tmp;
}
else
{
double tmp = PhysicalToWorld(min, true);
Axis.WorldMin = PhysicalToWorld(max, true);
Axis.WorldMax = tmp;
}
// need to trap somehow if the user selects an
// arbitrarily small range. Otherwise the GDI+
// drawing routines lead to an overflow in painting
// the picture. This may be not the optimal solution,
// but if the GDI+ draw leads to an overflow the
// graphic surface becomes unusable anymore and I
// had difficulty to trap the error.
double half = (Axis.WorldMin + Axis.WorldMax)/2;
double width = Axis.WorldMax - Axis.WorldMin;
if (Math.Abs(half/width) > 1.0e12)
{
Axis.WorldMin = minc;
Axis.WorldMax = maxc;
}
}
}
}
}