Files
ntpa/NPlot/AxesConstraint.cs
T

500 lines
18 KiB
C#
Raw Normal View History

2016-03-05 14:06:38 +01:00
/*
* 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.
* 3. Neither the name of NPlot nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* 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;
using System.Drawing.Drawing2D;
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>
/// 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 double pWorldLength_ = 0.0f;
private object holdFixedY_ = null;
/// <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 )
{
this.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 )
{
this.pWorldLength_ = p;
this.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 / (double)this.pWorldLength_);
int currentLength = pXAxis1.PhysicalLength;
int delta = currentLength - desiredLength;
int changeLeft = delta / 2;
int changeRight = delta / 2;
if (this.holdFixedY_ != null)
{
if ( (PlotSurface2D.YAxisPosition)this.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 double pWorldLength_ = 0.0;
private object holdFixedX_ = null;
/// <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 )
{
this.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 )
{
this.pWorldLength_ = p;
this.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 / this.pWorldLength_);
int currentLength = pYAxis1.PhysicalLength;
int delta = currentLength - desiredLength;
int changeBottom = -delta / 2;
int changeTop = -delta / 2;
if (this.holdFixedX_ != null)
{
if ( (PlotSurface2D.XAxisPosition)this.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 );
}
}
/// <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 object xAxisPosition_;
private object yAxisPosition_;
private int position_;
/// <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 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 double a_;
private object holdFixedX_ = null;
private object holdFixedY_ = null;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="a">Aspect Ratio</param>
public AspectRatio( double a )
{
this.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 )
{
this.a_ = a;
this.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 )
{
this.a_ = a;
this.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 )
{
this.a_ = a;
this.holdFixedX_ = holdFixedX;
this.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 ( this.a_ > currentAspectRatio )
{
// want to increase aspect ratio. Therefore, want to add some amount
// to yDirPixelSize (numerator).
double toAdd = ( this.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 (this.holdFixedX_ != null)
{
if ( (PlotSurface2D.XAxisPosition)this.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 / this.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 (this.holdFixedY_ != null)
{
if ( (PlotSurface2D.YAxisPosition)this.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>
/// 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 );
}
}