nplot/src/AxesConstraint.cs

469 lines
22 KiB
C#

/*
* NPlot - A charting library for .NET
*
* AxesConstraint.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>
/// Classes derived from this abstract base class define and can apply
/// some form of constraint to the positioning and length of one or more
/// of the four axes of a PlotSurface2D.
/// </summary>
public abstract class AxesConstraint
{
/// <summary>
/// Applies the constraint to the axes. Must be overriden.
/// </summary>
/// <param name="pXAxis1">The bottom x-axis.</param>
/// <param name="pYAxis1">The left y-axis.</param>
/// <param name="pXAxis2">The top x-axis.</param>
/// <param name="pYAxis2">The right y-axis.</param>
public abstract void ApplyConstraint(
PhysicalAxis pXAxis1, PhysicalAxis pYAxis1,
PhysicalAxis pXAxis2, PhysicalAxis pYAxis2);
/// <summary>
/// Defines an axes constraint that forces the world width and height pixel lengths
/// to be at the provided ratio. For example, an aspect ratio of 3:2 or
/// 1.5 indicates that there should be 1.5 times as many pixels per fixed
/// world length along the x direction than for the same world length along
/// the y direction. In other words, the world length of one pixel along
/// the x direction is 2/3rds that of the world length of one pixel height
/// in the y direction.
/// </summary>
/// <remarks>
/// This class will never increase the size of the plot bounding box. It
/// will always be made smaller.
/// </remarks>
public class AspectRatio : AxesConstraint
{
private readonly double a_;
private readonly object holdFixedX_;
private readonly object holdFixedY_;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="a">Aspect Ratio</param>
public AspectRatio(double a)
{
a_ = a;
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="a">Aspect Ratio</param>
/// <param name="holdFixedX">
/// When adjusting the position of axes, the specified axis will never
/// be moved.
/// </param>
public AspectRatio(double a, PlotSurface2D.XAxisPosition holdFixedX)
{
a_ = a;
holdFixedX_ = holdFixedX;
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="a">Aspect Ratio</param>
/// <param name="holdFixedY">
/// When adjusting the position of axes, the
/// specified axis will never be moved.
/// </param>
public AspectRatio(double a, PlotSurface2D.YAxisPosition holdFixedY)
{
a_ = a;
holdFixedY_ = holdFixedY;
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="a">Aspect Ratio</param>
/// <param name="holdFixedX">When adjusting the position of axes, the specified axis will never be moved.</param>
/// <param name="holdFixedY">When adjusting the position of axes, the specified axis will never be moved.</param>
public AspectRatio(
double a,
PlotSurface2D.XAxisPosition holdFixedX,
PlotSurface2D.YAxisPosition holdFixedY)
{
a_ = a;
holdFixedX_ = holdFixedX;
holdFixedY_ = holdFixedY;
}
/// <summary>
/// Applies the constraint to the axes.
/// </summary>
/// <param name="pXAxis1">The bottom x-axis.</param>
/// <param name="pYAxis1">The left y-axis.</param>
/// <param name="pXAxis2">The top x-axis.</param>
/// <param name="pYAxis2">The right y-axis.</param>
public override void ApplyConstraint(
PhysicalAxis pXAxis1, PhysicalAxis pYAxis1,
PhysicalAxis pXAxis2, PhysicalAxis pYAxis2)
{
double xWorldRange = Math.Abs(pXAxis1.Axis.WorldMax - pXAxis1.Axis.WorldMin);
double xPhysicalRange = Math.Abs(pXAxis1.PhysicalMax.X - pXAxis1.PhysicalMin.X);
double xDirPixelSize = xWorldRange/xPhysicalRange;
double yWorldRange = Math.Abs(pYAxis1.Axis.WorldMax - pYAxis1.Axis.WorldMin);
double yPhysicalRange = Math.Abs(pYAxis1.PhysicalMax.Y - pYAxis1.PhysicalMin.Y);
double yDirPixelSize = yWorldRange/yPhysicalRange;
double currentAspectRatio = yDirPixelSize/xDirPixelSize;
// we want to change the current aspect ratio to be the desired.
// to do this, we may only add the world pixel lengths.
if (a_ > currentAspectRatio)
{
// want to increase aspect ratio. Therefore, want to add some amount
// to yDirPixelSize (numerator).
double toAdd = (a_ - currentAspectRatio)*xDirPixelSize;
int newHeight =
(int) (Math.Abs(pYAxis1.Axis.WorldMax - pYAxis1.Axis.WorldMin)/(yDirPixelSize + toAdd));
int changeInHeight = (int) yPhysicalRange - newHeight;
int changeBottom = changeInHeight/2;
int changeTop = changeInHeight/2;
if (holdFixedX_ != null)
{
if ((PlotSurface2D.XAxisPosition) holdFixedX_ == PlotSurface2D.XAxisPosition.Bottom)
{
changeBottom = 0;
changeTop = changeInHeight;
}
else
{
changeBottom = changeInHeight;
changeTop = 0;
}
}
pYAxis1.PhysicalMin = new Point(pYAxis1.PhysicalMin.X, pYAxis1.PhysicalMin.Y - changeBottom);
pYAxis1.PhysicalMax = new Point(pYAxis1.PhysicalMax.X, pYAxis1.PhysicalMax.Y + changeTop);
pYAxis2.PhysicalMin = new Point(pYAxis2.PhysicalMin.X, pYAxis2.PhysicalMin.Y - changeBottom);
pYAxis2.PhysicalMax = new Point(pYAxis2.PhysicalMax.X, pYAxis2.PhysicalMax.Y + changeTop);
pXAxis1.PhysicalMin = new Point(pXAxis1.PhysicalMin.X, pXAxis1.PhysicalMin.Y - changeBottom);
pXAxis1.PhysicalMax = new Point(pXAxis1.PhysicalMax.X, pXAxis1.PhysicalMax.Y - changeBottom);
pXAxis2.PhysicalMin = new Point(pXAxis2.PhysicalMin.X, pXAxis2.PhysicalMin.Y + changeTop);
pXAxis2.PhysicalMax = new Point(pXAxis2.PhysicalMax.X, pXAxis2.PhysicalMax.Y + changeTop);
}
else
{
// want to decrease aspect ratio. Therefore, want to add some amount
// to xDirPixelSize (denominator).
double toAdd = yDirPixelSize/a_ - xDirPixelSize;
int newWidth =
(int) (Math.Abs(pXAxis1.Axis.WorldMax - pXAxis1.Axis.WorldMin)/(xDirPixelSize + toAdd));
int changeInWidth = (int) xPhysicalRange - newWidth;
int changeLeft = changeInWidth/2;
int changeRight = changeInWidth/2;
if (holdFixedY_ != null)
{
if ((PlotSurface2D.YAxisPosition) holdFixedY_ == PlotSurface2D.YAxisPosition.Left)
{
changeLeft = 0;
changeRight = changeInWidth;
}
else
{
changeLeft = changeInWidth;
changeRight = 0;
}
}
pXAxis1.PhysicalMin = new Point(pXAxis1.PhysicalMin.X + changeLeft, pXAxis1.PhysicalMin.Y);
pXAxis1.PhysicalMax = new Point(pXAxis1.PhysicalMax.X - changeRight, pXAxis1.PhysicalMax.Y);
pXAxis2.PhysicalMin = new Point(pXAxis2.PhysicalMin.X + changeLeft, pXAxis2.PhysicalMin.Y);
pXAxis2.PhysicalMax = new Point(pXAxis2.PhysicalMax.X - changeRight, pXAxis2.PhysicalMax.Y);
pYAxis1.PhysicalMin = new Point(pYAxis1.PhysicalMin.X + changeLeft, pYAxis1.PhysicalMin.Y);
pYAxis1.PhysicalMax = new Point(pYAxis1.PhysicalMax.X + changeLeft, pYAxis1.PhysicalMax.Y);
pYAxis2.PhysicalMin = new Point(pYAxis2.PhysicalMin.X - changeRight, pYAxis2.PhysicalMin.Y);
pYAxis2.PhysicalMax = new Point(pYAxis2.PhysicalMax.X - changeRight, pYAxis2.PhysicalMax.Y);
}
}
}
/// <summary>
/// Defines an AxisConstraint that forces the specified axis to be placed at a
/// specific physical position. The position of the axis opposite is held
/// constant.
/// </summary>
public class AxisPosition : AxesConstraint
{
private readonly int position_;
private readonly object xAxisPosition_;
private readonly object yAxisPosition_;
/// <summary>
/// Constructor, which defines an horizontal axis and the physical
/// y position it should be drawn at.
/// </summary>
/// <param name="axis">The x-axis for which the y position is to be specified.</param>
/// <param name="yPosition">The [physical] y position of the axis.</param>
public AxisPosition(PlotSurface2D.XAxisPosition axis, int yPosition)
{
position_ = yPosition;
xAxisPosition_ = axis;
}
/// <summary>
/// Constructor, which defines a vertical axis and the physical
/// x position it should be drawn at.
/// </summary>
/// <param name="axis">The y-axis for which the x position is to be specified.</param>
/// <param name="xPosition">The [physical] x position of the axis.</param>
public AxisPosition(PlotSurface2D.YAxisPosition axis, int xPosition)
{
position_ = xPosition;
yAxisPosition_ = axis;
}
/// <summary>
/// Applies the constraint to the axes.
/// </summary>
/// <param name="pXAxis1">The bottom x-axis.</param>
/// <param name="pYAxis1">The left y-axis.</param>
/// <param name="pXAxis2">The top x-axis.</param>
/// <param name="pYAxis2">The right y-axis.</param>
public override void ApplyConstraint(
PhysicalAxis pXAxis1, PhysicalAxis pYAxis1,
PhysicalAxis pXAxis2, PhysicalAxis pYAxis2)
{
if (xAxisPosition_ != null)
{
if ((PlotSurface2D.XAxisPosition) xAxisPosition_ == PlotSurface2D.XAxisPosition.Bottom)
{
pXAxis1.PhysicalMin = new Point(pXAxis1.PhysicalMin.X, position_);
pXAxis1.PhysicalMax = new Point(pXAxis1.PhysicalMax.X, position_);
pYAxis1.PhysicalMin = new Point(pYAxis1.PhysicalMin.X, position_);
pYAxis2.PhysicalMin = new Point(pYAxis2.PhysicalMin.X, position_);
}
else
{
pXAxis2.PhysicalMin = new Point(pXAxis2.PhysicalMin.X, position_);
pXAxis2.PhysicalMax = new Point(pXAxis2.PhysicalMax.X, position_);
pYAxis1.PhysicalMax = new Point(pYAxis1.PhysicalMax.X, position_);
pYAxis2.PhysicalMax = new Point(pYAxis2.PhysicalMax.X, position_);
}
}
else if (yAxisPosition_ != null)
{
if ((PlotSurface2D.YAxisPosition) yAxisPosition_ == PlotSurface2D.YAxisPosition.Left)
{
pYAxis1.PhysicalMin = new Point(position_, pYAxis1.PhysicalMin.Y);
pYAxis1.PhysicalMax = new Point(position_, pYAxis1.PhysicalMax.Y);
pXAxis1.PhysicalMin = new Point(position_, pXAxis1.PhysicalMin.Y);
pXAxis2.PhysicalMin = new Point(position_, pXAxis2.PhysicalMin.Y);
}
else
{
pYAxis2.PhysicalMin = new Point(position_, pYAxis2.PhysicalMin.Y);
pYAxis2.PhysicalMax = new Point(position_, pYAxis2.PhysicalMax.Y);
pXAxis1.PhysicalMax = new Point(position_, pXAxis1.PhysicalMax.Y);
pXAxis2.PhysicalMax = new Point(position_, pXAxis2.PhysicalMax.Y);
}
}
}
}
/// <summary>
/// Defines an AxisConstraint that forces the world length corresponding
/// to one pixel on the bottom x-axis to be a certain value.
/// TODO: Allow the pixel world length to be set for the top axis.
/// </summary>
public class XPixelWorldLength : AxesConstraint
{
private readonly object holdFixedY_;
private readonly double pWorldLength_;
/// <summary>
/// Constructor, which defines the world pixel length only. Both
/// y-axes will be moved by equal amounts in order to force this
/// constraint.
/// </summary>
/// <param name="p">The world pixel length</param>
public XPixelWorldLength(double p)
{
pWorldLength_ = p;
}
/// <summary>
/// Constructor, which defines the world pixel length together with
/// the y-axis that should be held constant when forcing this
/// constraint [the other y-axis only will be moved].
/// </summary>
/// <param name="p">The world pixel length</param>
/// <param name="holdFixedY">
/// The position of this y-axis will be
/// held constant. The other y-axis will be moved in order to
/// force the constraint.
/// </param>
public XPixelWorldLength(double p, PlotSurface2D.YAxisPosition holdFixedY)
{
pWorldLength_ = p;
holdFixedY_ = holdFixedY;
}
/// <summary>
/// Applies the constraint to the axes.
/// </summary>
/// <param name="pXAxis1">The bottom x-axis.</param>
/// <param name="pYAxis1">The left y-axis.</param>
/// <param name="pXAxis2">The top x-axis.</param>
/// <param name="pYAxis2">The right y-axis.</param>
public override void ApplyConstraint(
PhysicalAxis pXAxis1, PhysicalAxis pYAxis1,
PhysicalAxis pXAxis2, PhysicalAxis pYAxis2)
{
int desiredLength = (int) (pXAxis1.Axis.WorldLength/pWorldLength_);
int currentLength = pXAxis1.PhysicalLength;
int delta = currentLength - desiredLength;
int changeLeft = delta/2;
int changeRight = delta/2;
if (holdFixedY_ != null)
{
if ((PlotSurface2D.YAxisPosition) holdFixedY_ == PlotSurface2D.YAxisPosition.Left)
{
changeLeft = 0;
changeRight = delta;
}
else
{
changeLeft = delta;
changeRight = 0;
}
}
pXAxis1.PhysicalMin = new Point(pXAxis1.PhysicalMin.X + changeLeft, pXAxis1.PhysicalMin.Y);
pXAxis1.PhysicalMax = new Point(pXAxis1.PhysicalMax.X - changeRight, pXAxis1.PhysicalMax.Y);
pXAxis2.PhysicalMin = new Point(pXAxis2.PhysicalMin.X + changeLeft, pXAxis2.PhysicalMin.Y);
pXAxis2.PhysicalMax = new Point(pXAxis2.PhysicalMax.X - changeRight, pXAxis2.PhysicalMax.Y);
pYAxis1.PhysicalMin = new Point(pYAxis1.PhysicalMin.X + changeLeft, pYAxis1.PhysicalMin.Y);
pYAxis1.PhysicalMax = new Point(pYAxis1.PhysicalMax.X + changeLeft, pYAxis1.PhysicalMax.Y);
pYAxis2.PhysicalMin = new Point(pYAxis2.PhysicalMin.X - changeRight, pYAxis2.PhysicalMin.Y);
pYAxis2.PhysicalMax = new Point(pYAxis2.PhysicalMax.X - changeRight, pYAxis2.PhysicalMax.Y);
}
}
/// <summary>
/// Defines an AxisConstraint that forces the world length corresponding
/// to one pixel on the left y-axis to be a certain value.
/// TODO: Allow the pixel world length to be set for the right axis.
/// </summary>
public class YPixelWorldLength : AxesConstraint
{
private readonly object holdFixedX_;
private readonly double pWorldLength_;
/// <summary>
/// Constructor, which defines the world pixel length only. Both
/// x-axes will be moved by equal amounts in order to force this
/// constraint.
/// </summary>
/// <param name="p">The world pixel length</param>
public YPixelWorldLength(double p)
{
pWorldLength_ = p;
}
/// <summary>
/// Constructor, which defines the world pixel length together with
/// the x-axis that should be held constant when forcing this
/// constraint [the other x-axis only will be moved].
/// </summary>
/// <param name="p">The world pixel length</param>
/// <param name="holdFixedX">The position of this x-axis will be held constant. The other x-axis will be moved in order to force the constraint.</param>
public YPixelWorldLength(double p, PlotSurface2D.XAxisPosition holdFixedX)
{
pWorldLength_ = p;
holdFixedX_ = holdFixedX;
}
/// <summary>
/// Applies the constraint to the axes.
/// </summary>
/// <param name="pXAxis1">The bottom x-axis.</param>
/// <param name="pYAxis1">The left y-axis.</param>
/// <param name="pXAxis2">The top x-axis.</param>
/// <param name="pYAxis2">The right y-axis.</param>
public override void ApplyConstraint(
PhysicalAxis pXAxis1, PhysicalAxis pYAxis1,
PhysicalAxis pXAxis2, PhysicalAxis pYAxis2)
{
int desiredLength = (int) (pYAxis1.Axis.WorldLength/pWorldLength_);
int currentLength = pYAxis1.PhysicalLength;
int delta = currentLength - desiredLength;
int changeBottom = -delta/2;
int changeTop = -delta/2;
if (holdFixedX_ != null)
{
if ((PlotSurface2D.XAxisPosition) holdFixedX_ == PlotSurface2D.XAxisPosition.Bottom)
{
changeBottom = 0;
changeTop = -delta;
}
else
{
changeBottom = -delta;
changeTop = 0;
}
}
pYAxis1.PhysicalMin = new Point(pYAxis1.PhysicalMin.X, pYAxis1.PhysicalMin.Y + changeBottom);
pYAxis1.PhysicalMax = new Point(pYAxis1.PhysicalMax.X, pYAxis1.PhysicalMax.Y - changeTop);
pYAxis2.PhysicalMin = new Point(pYAxis2.PhysicalMin.X, pYAxis2.PhysicalMin.Y + changeBottom);
pYAxis2.PhysicalMax = new Point(pYAxis2.PhysicalMax.X, pYAxis2.PhysicalMax.Y - changeTop);
pXAxis1.PhysicalMin = new Point(pXAxis1.PhysicalMin.X, pXAxis1.PhysicalMin.Y + changeBottom);
pXAxis1.PhysicalMax = new Point(pXAxis1.PhysicalMax.X, pXAxis1.PhysicalMax.Y + changeBottom);
pXAxis2.PhysicalMin = new Point(pXAxis2.PhysicalMin.X, pXAxis2.PhysicalMin.Y - changeTop);
pXAxis2.PhysicalMax = new Point(pXAxis2.PhysicalMax.X, pXAxis2.PhysicalMax.Y - changeTop);
}
}
}
}