/* * 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; namespace NPlot { /// /// 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. /// public abstract class AxesConstraint { /// /// Applies the constraint to the axes. Must be overriden. /// /// The bottom x-axis. /// The left y-axis. /// The top x-axis. /// The right y-axis. public abstract void ApplyConstraint( PhysicalAxis pXAxis1, PhysicalAxis pYAxis1, PhysicalAxis pXAxis2, PhysicalAxis pYAxis2); /// /// 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. /// /// /// This class will never increase the size of the plot bounding box. It /// will always be made smaller. /// public class AspectRatio : AxesConstraint { private readonly double a_; private readonly object holdFixedX_; private readonly object holdFixedY_; /// /// Constructor. /// /// Aspect Ratio public AspectRatio(double a) { a_ = a; } /// /// Constructor /// /// Aspect Ratio /// /// When adjusting the position of axes, the specified axis will never /// be moved. /// public AspectRatio(double a, PlotSurface2D.XAxisPosition holdFixedX) { a_ = a; holdFixedX_ = holdFixedX; } /// /// Constructor /// /// Aspect Ratio /// /// When adjusting the position of axes, the /// specified axis will never be moved. /// public AspectRatio(double a, PlotSurface2D.YAxisPosition holdFixedY) { a_ = a; holdFixedY_ = holdFixedY; } /// /// Constructor /// /// Aspect Ratio /// When adjusting the position of axes, the specified axis will never be moved. /// When adjusting the position of axes, the specified axis will never be moved. public AspectRatio( double a, PlotSurface2D.XAxisPosition holdFixedX, PlotSurface2D.YAxisPosition holdFixedY) { a_ = a; holdFixedX_ = holdFixedX; holdFixedY_ = holdFixedY; } /// /// Applies the constraint to the axes. /// /// The bottom x-axis. /// The left y-axis. /// The top x-axis. /// The right y-axis. 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); } } } /// /// 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. /// public class AxisPosition : AxesConstraint { private readonly int position_; private readonly object xAxisPosition_; private readonly object yAxisPosition_; /// /// Constructor, which defines an horizontal axis and the physical /// y position it should be drawn at. /// /// The x-axis for which the y position is to be specified. /// The [physical] y position of the axis. public AxisPosition(PlotSurface2D.XAxisPosition axis, int yPosition) { position_ = yPosition; xAxisPosition_ = axis; } /// /// Constructor, which defines a vertical axis and the physical /// x position it should be drawn at. /// /// The y-axis for which the x position is to be specified. /// The [physical] x position of the axis. public AxisPosition(PlotSurface2D.YAxisPosition axis, int xPosition) { position_ = xPosition; yAxisPosition_ = axis; } /// /// Applies the constraint to the axes. /// /// The bottom x-axis. /// The left y-axis. /// The top x-axis. /// The right y-axis. 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); } } } } /// /// 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. /// public class XPixelWorldLength : AxesConstraint { private readonly object holdFixedY_; private readonly double pWorldLength_; /// /// Constructor, which defines the world pixel length only. Both /// y-axes will be moved by equal amounts in order to force this /// constraint. /// /// The world pixel length public XPixelWorldLength(double p) { pWorldLength_ = p; } /// /// 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]. /// /// The world pixel length /// /// The position of this y-axis will be /// held constant. The other y-axis will be moved in order to /// force the constraint. /// public XPixelWorldLength(double p, PlotSurface2D.YAxisPosition holdFixedY) { pWorldLength_ = p; holdFixedY_ = holdFixedY; } /// /// Applies the constraint to the axes. /// /// The bottom x-axis. /// The left y-axis. /// The top x-axis. /// The right y-axis. 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); } } /// /// 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. /// public class YPixelWorldLength : AxesConstraint { private readonly object holdFixedX_; private readonly double pWorldLength_; /// /// Constructor, which defines the world pixel length only. Both /// x-axes will be moved by equal amounts in order to force this /// constraint. /// /// The world pixel length public YPixelWorldLength(double p) { pWorldLength_ = p; } /// /// 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]. /// /// The world pixel length /// The position of this x-axis will be held constant. The other x-axis will be moved in order to force the constraint. public YPixelWorldLength(double p, PlotSurface2D.XAxisPosition holdFixedX) { pWorldLength_ = p; holdFixedX_ = holdFixedX; } /// /// Applies the constraint to the axes. /// /// The bottom x-axis. /// The left y-axis. /// The top x-axis. /// The right y-axis. 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); } } } }