/* * NPlot - A charting library for .NET * * LabelAxis.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.Collections; using System.Drawing; namespace NPlot { /// /// Allows the creation of axes with any number of user defined labels at /// user defined world values along the axis. /// public class LabelAxis : Axis { /// /// Deep copy of LabelAxis. /// /// A copy of the LinearAxis Class. public override object Clone() { LabelAxis a = new LabelAxis(); // ensure that this isn't being called on a derived type. If it is, then oh no! if (this.GetType() != a.GetType()) { throw new NPlotException( "Error. Clone method is not defined in derived type." ); } DoClone( this, a ); return a; } /// /// Helper method for Clone. /// /// The original object to clone. /// The cloned object. protected static void DoClone( LabelAxis b, LabelAxis a ) { Axis.DoClone( b, a ); a.labels_ = (ArrayList)b.labels_.Clone(); a.numbers_ = (ArrayList)b.numbers_.Clone(); a.ticksBetweenText_ = b.ticksBetweenText_; a.sortDataIfNecessary_ = b.sortDataIfNecessary_; } /// /// Initialise LabelAxis to default state. /// private void Init() { labels_ = new ArrayList(); numbers_ = new ArrayList(); } /// /// Copy constructor /// /// The Axis to clone. /// TODO: [review notes] I don't think this will work as desired. public LabelAxis( Axis a ) : base( a ) { Init(); } /// /// Default constructor /// public LabelAxis() : base() { Init(); } /// /// Constructor /// /// Minimum world value /// Maximum world value public LabelAxis( double worldMin, double worldMax ) : base( worldMin, worldMax ) { Init(); } /// /// Adds a label to the axis /// /// The label /// The world value at which to place the label public void AddLabel( string name, double val ) { labels_.Add( name ); numbers_.Add( val ); } /// /// Given Graphics surface, and physical extents of axis, draw ticks and /// associated labels. /// /// The GDI+ Graphics surface on which to draw. /// The physical location of the world min point /// The physical location of the world max point /// out: smallest box that completely encompasses all of the ticks and tick labels. /// out: a suitable offset from the axis to draw the axis label. protected override void DrawTicks( Graphics g, Point physicalMin, Point physicalMax, out object labelOffset, out object boundingBox ) { Point tLabelOffset; Rectangle tBoundingBox; labelOffset = this.getDefaultLabelOffset( physicalMin, physicalMax ); boundingBox = null; // draw the tick labels (but not the ticks). PointF lastPos = WorldToPhysical( (double)numbers_[0], physicalMin, physicalMax, true ); for (int i=0; i WorldMin && (double)numbers_[i] < WorldMax) { // check to make sure labels are far enough appart. PointF thisPos = WorldToPhysical( (double)numbers_[i], physicalMin, physicalMax, true ); float dist = Utils.Distance( thisPos, lastPos ); if ( i==0 || (dist > this.PhysicalSpacingMin) ) { lastPos = thisPos; this.DrawTick( g, (double)numbers_[i], 0, (string)labels_[i], new Point(0,0), physicalMin, physicalMax, out tLabelOffset, out tBoundingBox ); Axis.UpdateOffsetAndBounds( ref labelOffset, ref boundingBox, tLabelOffset, tBoundingBox ); } } } // now draw the ticks (which might not be aligned with the tick text). ArrayList largeTickPositions; ArrayList smallTickPositions; WorldTickPositions_FirstPass( physicalMin, physicalMax, out largeTickPositions, out smallTickPositions ); lastPos = WorldToPhysical( (double)largeTickPositions[0], physicalMin, physicalMax, true ); for (int i=0; i this.PhysicalSpacingMin) ) { lastPos = thisPos; this.DrawTick( g, tickPos, LargeTickSize, "", new Point(0,0), physicalMin, physicalMax, out tLabelOffset, out tBoundingBox ); Axis.UpdateOffsetAndBounds( ref labelOffset, ref boundingBox, tLabelOffset, tBoundingBox ); } } } /// /// Determines the positions, in world coordinates, of the large ticks. /// /// Label axes do not have small ticks. /// /// The physical position corresponding to the world minimum of the axis. /// The physical position corresponding to the world maximum of the axis. /// ArrayList containing the positions of the large ticks. /// null internal override void WorldTickPositions_FirstPass( Point physicalMin, Point physicalMax, out ArrayList largeTickPositions, out ArrayList smallTickPositions ) { smallTickPositions = null; largeTickPositions = new ArrayList(); // if ticks correspond to position of text if (!ticksBetweenText_) { for (int i=0; i WorldMin && (double)numbers_[i] < WorldMax) { largeTickPositions.Add( numbers_[i] ); } } } // if ticks correspond to gaps between text else { ArrayList numbers_copy; if (sortDataIfNecessary_) { numbers_copy = (ArrayList)numbers_.Clone(); // shallow copy. numbers_copy.Sort(); } else { numbers_copy = numbers_; } for (int i=1; i WorldMin && worldPosition < WorldMax) { largeTickPositions.Add( worldPosition ); } } } } /// /// If true, large ticks are drawn between the labels, rather /// than at the position of the labels. /// public bool TicksBetweenText { get { return ticksBetweenText_; } set { ticksBetweenText_ = value; } } private bool ticksBetweenText_ = false; /// /// If your data may be be specified out of order (that is /// abscissa values with a higher index may be less than /// abscissa values of a lower index), then data sorting /// may be necessary to implement some of the functionality /// of this object. If you know your data is already /// ordered with abscissa values lowest -> highest, then /// you may set this to false. It's default is true. /// public bool SortDataIfNecessary { get { return sortDataIfNecessary_; } set { sortDataIfNecessary_ = value; } } private bool sortDataIfNecessary_ = true; /// /// If consecutive labels are less than this number of pixels appart, /// some of the labels will not be drawn. /// public int PhysicalSpacingMin { get { return physicalSpacingMin_; } set { physicalSpacingMin_ = value; } } private int physicalSpacingMin_ = 0; private ArrayList labels_; private ArrayList numbers_; } }