0001: /* ===========================================================
0002: * JFreeChart : a free chart library for the Java(tm) platform
0003: * ===========================================================
0004: *
0005: * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
0006: *
0007: * Project Info: http://www.jfree.org/jfreechart/index.html
0008: *
0009: * This library is free software; you can redistribute it and/or modify it
0010: * under the terms of the GNU Lesser General Public License as published by
0011: * the Free Software Foundation; either version 2.1 of the License, or
0012: * (at your option) any later version.
0013: *
0014: * This library is distributed in the hope that it will be useful, but
0015: * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
0016: * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
0017: * License for more details.
0018: *
0019: * You should have received a copy of the GNU Lesser General Public
0020: * License along with this library; if not, write to the Free Software
0021: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
0022: * USA.
0023: *
0024: * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
0025: * in the United States and other countries.]
0026: *
0027: * --------------
0028: * ValueAxis.java
0029: * --------------
0030: * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
0031: *
0032: * Original Author: David Gilbert (for Object Refinery Limited);
0033: * Contributor(s): Jonathan Nash;
0034: * Nicolas Brodu (for Astrium and EADS Corporate Research
0035: * Center);
0036: *
0037: * $Id: ValueAxis.java,v 1.10.2.5 2007/03/22 12:13:27 mungady Exp $
0038: *
0039: * Changes (from 18-Sep-2001)
0040: * --------------------------
0041: * 18-Sep-2001 : Added standard header and fixed DOS encoding problem (DG);
0042: * 23-Nov-2001 : Overhauled standard tick unit code (DG);
0043: * 04-Dec-2001 : Changed constructors to protected, and tidied up default
0044: * values (DG);
0045: * 12-Dec-2001 : Fixed vertical gridlines bug (DG);
0046: * 16-Jan-2002 : Added an optional crosshair, based on the implementation by
0047: * Jonathan Nash (DG);
0048: * 23-Jan-2002 : Moved the minimum and maximum values to here from NumberAxis,
0049: * and changed the type from Number to double (DG);
0050: * 25-Feb-2002 : Added default value for autoRange. Changed autoAdjustRange
0051: * from public to protected. Updated import statements (DG);
0052: * 23-Apr-2002 : Added setRange() method (DG);
0053: * 29-Apr-2002 : Added range adjustment methods (DG);
0054: * 13-Jun-2002 : Modified setCrosshairValue() to notify listeners only when the
0055: * crosshairs are visible, to avoid unnecessary repaints, as
0056: * suggested by Kees Kuip (DG);
0057: * 25-Jul-2002 : Moved lower and upper margin attributes from the NumberAxis
0058: * class (DG);
0059: * 05-Sep-2002 : Updated constructor for changes in Axis class (DG);
0060: * 01-Oct-2002 : Fixed errors reported by Checkstyle (DG);
0061: * 04-Oct-2002 : Moved standardTickUnits from NumberAxis --> ValueAxis (DG);
0062: * 08-Nov-2002 : Moved to new package com.jrefinery.chart.axis (DG);
0063: * 19-Nov-2002 : Removed grid settings (now controlled by the plot) (DG);
0064: * 27-Nov-2002 : Moved the 'inverted' attributed from NumberAxis to
0065: * ValueAxis (DG);
0066: * 03-Jan-2003 : Small fix to ensure auto-range minimum is observed
0067: * immediately (DG);
0068: * 14-Jan-2003 : Changed autoRangeMinimumSize from Number --> double (DG);
0069: * 20-Jan-2003 : Replaced monolithic constructor (DG);
0070: * 26-Mar-2003 : Implemented Serializable (DG);
0071: * 09-May-2003 : Added AxisLocation parameter to translation methods (DG);
0072: * 13-Aug-2003 : Implemented Cloneable (DG);
0073: * 01-Sep-2003 : Fixed bug 793167 (setMaximumAxisValue exception) (DG);
0074: * 02-Sep-2003 : Fixed bug 795366 (zooming on inverted axes) (DG);
0075: * 08-Sep-2003 : Completed Serialization support (NB);
0076: * 08-Sep-2003 : Renamed get/setMinimumValue --> get/setLowerBound,
0077: * and get/setMaximumValue --> get/setUpperBound (DG);
0078: * 27-Oct-2003 : Changed DEFAULT_AUTO_RANGE_MINIMUM_SIZE value - see bug ID
0079: * 829606 (DG);
0080: * 07-Nov-2003 : Changes to tick mechanism (DG);
0081: * 06-Jan-2004 : Moved axis line attributes to Axis class (DG);
0082: * 21-Jan-2004 : Removed redundant axisLineVisible attribute. Renamed
0083: * translateJava2DToValue --> java2DToValue, and
0084: * translateValueToJava2D --> valueToJava2D (DG);
0085: * 23-Jan-2004 : Fixed setAxisLinePaint() and setAxisLineStroke() which had no
0086: * effect (andreas.gawecki@coremedia.com);
0087: * 07-Apr-2004 : Changed text bounds calculation (DG);
0088: * 26-Apr-2004 : Added getter/setter methods for arrow shapes (DG);
0089: * 18-May-2004 : Added methods to set axis range *including* current
0090: * margins (DG);
0091: * 02-Jun-2004 : Fixed bug in setRangeWithMargins() method (DG);
0092: * 30-Sep-2004 : Moved drawRotatedString() from RefineryUtilities
0093: * --> TextUtilities (DG);
0094: * 11-Jan-2005 : Removed deprecated methods in preparation for 1.0.0
0095: * release (DG);
0096: * 21-Apr-2005 : Replaced Insets with RectangleInsets (DG);
0097: * ------------- JFREECHART 1.0.x ---------------------------------------------
0098: * 10-Oct-2006 : Source reformatting (DG);
0099: * 22-Mar-2007 : Added new defaultAutoRange attribute (DG);
0100: *
0101: */
0102:
0103: package org.jfree.chart.axis;
0104:
0105: import java.awt.Font;
0106: import java.awt.FontMetrics;
0107: import java.awt.Graphics2D;
0108: import java.awt.Polygon;
0109: import java.awt.Shape;
0110: import java.awt.font.LineMetrics;
0111: import java.awt.geom.AffineTransform;
0112: import java.awt.geom.Line2D;
0113: import java.awt.geom.Rectangle2D;
0114: import java.io.IOException;
0115: import java.io.ObjectInputStream;
0116: import java.io.ObjectOutputStream;
0117: import java.io.Serializable;
0118: import java.util.Iterator;
0119: import java.util.List;
0120:
0121: import org.jfree.chart.event.AxisChangeEvent;
0122: import org.jfree.chart.plot.Plot;
0123: import org.jfree.data.Range;
0124: import org.jfree.io.SerialUtilities;
0125: import org.jfree.text.TextUtilities;
0126: import org.jfree.ui.RectangleEdge;
0127: import org.jfree.ui.RectangleInsets;
0128: import org.jfree.util.ObjectUtilities;
0129: import org.jfree.util.PublicCloneable;
0130:
0131: /**
0132: * The base class for axes that display value data, where values are measured
0133: * using the <code>double</code> primitive. The two key subclasses are
0134: * {@link DateAxis} and {@link NumberAxis}.
0135: */
0136: public abstract class ValueAxis extends Axis implements Cloneable,
0137: PublicCloneable, Serializable {
0138:
0139: /** For serialization. */
0140: private static final long serialVersionUID = 3698345477322391456L;
0141:
0142: /** The default axis range. */
0143: public static final Range DEFAULT_RANGE = new Range(0.0, 1.0);
0144:
0145: /** The default auto-range value. */
0146: public static final boolean DEFAULT_AUTO_RANGE = true;
0147:
0148: /** The default inverted flag setting. */
0149: public static final boolean DEFAULT_INVERTED = false;
0150:
0151: /** The default minimum auto range. */
0152: public static final double DEFAULT_AUTO_RANGE_MINIMUM_SIZE = 0.00000001;
0153:
0154: /** The default value for the lower margin (0.05 = 5%). */
0155: public static final double DEFAULT_LOWER_MARGIN = 0.05;
0156:
0157: /** The default value for the upper margin (0.05 = 5%). */
0158: public static final double DEFAULT_UPPER_MARGIN = 0.05;
0159:
0160: /**
0161: * The default lower bound for the axis.
0162: *
0163: * @deprecated From 1.0.5 onwards, the axis defines a defaultRange
0164: * attribute (see {@link #getDefaultAutoRange()}).
0165: */
0166: public static final double DEFAULT_LOWER_BOUND = 0.0;
0167:
0168: /**
0169: * The default upper bound for the axis.
0170: *
0171: * @deprecated From 1.0.5 onwards, the axis defines a defaultRange
0172: * attribute (see {@link #getDefaultAutoRange()}).
0173: */
0174: public static final double DEFAULT_UPPER_BOUND = 1.0;
0175:
0176: /** The default auto-tick-unit-selection value. */
0177: public static final boolean DEFAULT_AUTO_TICK_UNIT_SELECTION = true;
0178:
0179: /** The maximum tick count. */
0180: public static final int MAXIMUM_TICK_COUNT = 500;
0181:
0182: /**
0183: * A flag that controls whether an arrow is drawn at the positive end of
0184: * the axis line.
0185: */
0186: private boolean positiveArrowVisible;
0187:
0188: /**
0189: * A flag that controls whether an arrow is drawn at the negative end of
0190: * the axis line.
0191: */
0192: private boolean negativeArrowVisible;
0193:
0194: /** The shape used for an up arrow. */
0195: private transient Shape upArrow;
0196:
0197: /** The shape used for a down arrow. */
0198: private transient Shape downArrow;
0199:
0200: /** The shape used for a left arrow. */
0201: private transient Shape leftArrow;
0202:
0203: /** The shape used for a right arrow. */
0204: private transient Shape rightArrow;
0205:
0206: /** A flag that affects the orientation of the values on the axis. */
0207: private boolean inverted;
0208:
0209: /** The axis range. */
0210: private Range range;
0211:
0212: /**
0213: * Flag that indicates whether the axis automatically scales to fit the
0214: * chart data.
0215: */
0216: private boolean autoRange;
0217:
0218: /** The minimum size for the 'auto' axis range (excluding margins). */
0219: private double autoRangeMinimumSize;
0220:
0221: /**
0222: * The default range is used when the dataset is empty and the axis needs
0223: * to determine the auto range.
0224: *
0225: * @since 1.0.5
0226: */
0227: private Range defaultAutoRange;
0228:
0229: /**
0230: * The upper margin percentage. This indicates the amount by which the
0231: * maximum axis value exceeds the maximum data value (as a percentage of
0232: * the range on the axis) when the axis range is determined automatically.
0233: */
0234: private double upperMargin;
0235:
0236: /**
0237: * The lower margin. This is a percentage that indicates the amount by
0238: * which the minimum axis value is "less than" the minimum data value when
0239: * the axis range is determined automatically.
0240: */
0241: private double lowerMargin;
0242:
0243: /**
0244: * If this value is positive, the amount is subtracted from the maximum
0245: * data value to determine the lower axis range. This can be used to
0246: * provide a fixed "window" on dynamic data.
0247: */
0248: private double fixedAutoRange;
0249:
0250: /**
0251: * Flag that indicates whether or not the tick unit is selected
0252: * automatically.
0253: */
0254: private boolean autoTickUnitSelection;
0255:
0256: /** The standard tick units for the axis. */
0257: private TickUnitSource standardTickUnits;
0258:
0259: /** An index into an array of standard tick values. */
0260: private int autoTickIndex;
0261:
0262: /** A flag indicating whether or not tick labels are rotated to vertical. */
0263: private boolean verticalTickLabels;
0264:
0265: /**
0266: * Constructs a value axis.
0267: *
0268: * @param label the axis label (<code>null</code> permitted).
0269: * @param standardTickUnits the source for standard tick units
0270: * (<code>null</code> permitted).
0271: */
0272: protected ValueAxis(String label, TickUnitSource standardTickUnits) {
0273:
0274: super (label);
0275:
0276: this .positiveArrowVisible = false;
0277: this .negativeArrowVisible = false;
0278:
0279: this .range = DEFAULT_RANGE;
0280: this .autoRange = DEFAULT_AUTO_RANGE;
0281: this .defaultAutoRange = DEFAULT_RANGE;
0282:
0283: this .inverted = DEFAULT_INVERTED;
0284: this .autoRangeMinimumSize = DEFAULT_AUTO_RANGE_MINIMUM_SIZE;
0285:
0286: this .lowerMargin = DEFAULT_LOWER_MARGIN;
0287: this .upperMargin = DEFAULT_UPPER_MARGIN;
0288:
0289: this .fixedAutoRange = 0.0;
0290:
0291: this .autoTickUnitSelection = DEFAULT_AUTO_TICK_UNIT_SELECTION;
0292: this .standardTickUnits = standardTickUnits;
0293:
0294: Polygon p1 = new Polygon();
0295: p1.addPoint(0, 0);
0296: p1.addPoint(-2, 2);
0297: p1.addPoint(2, 2);
0298:
0299: this .upArrow = p1;
0300:
0301: Polygon p2 = new Polygon();
0302: p2.addPoint(0, 0);
0303: p2.addPoint(-2, -2);
0304: p2.addPoint(2, -2);
0305:
0306: this .downArrow = p2;
0307:
0308: Polygon p3 = new Polygon();
0309: p3.addPoint(0, 0);
0310: p3.addPoint(-2, -2);
0311: p3.addPoint(-2, 2);
0312:
0313: this .rightArrow = p3;
0314:
0315: Polygon p4 = new Polygon();
0316: p4.addPoint(0, 0);
0317: p4.addPoint(2, -2);
0318: p4.addPoint(2, 2);
0319:
0320: this .leftArrow = p4;
0321:
0322: this .verticalTickLabels = false;
0323:
0324: }
0325:
0326: /**
0327: * Returns <code>true</code> if the tick labels should be rotated (to
0328: * vertical), and <code>false</code> otherwise.
0329: *
0330: * @return <code>true</code> or <code>false</code>.
0331: *
0332: * @see #setVerticalTickLabels(boolean)
0333: */
0334: public boolean isVerticalTickLabels() {
0335: return this .verticalTickLabels;
0336: }
0337:
0338: /**
0339: * Sets the flag that controls whether the tick labels are displayed
0340: * vertically (that is, rotated 90 degrees from horizontal). If the flag
0341: * is changed, an {@link AxisChangeEvent} is sent to all registered
0342: * listeners.
0343: *
0344: * @param flag the flag.
0345: *
0346: * @see #isVerticalTickLabels()
0347: */
0348: public void setVerticalTickLabels(boolean flag) {
0349: if (this .verticalTickLabels != flag) {
0350: this .verticalTickLabels = flag;
0351: notifyListeners(new AxisChangeEvent(this ));
0352: }
0353: }
0354:
0355: /**
0356: * Returns a flag that controls whether or not the axis line has an arrow
0357: * drawn that points in the positive direction for the axis.
0358: *
0359: * @return A boolean.
0360: *
0361: * @see #setPositiveArrowVisible(boolean)
0362: */
0363: public boolean isPositiveArrowVisible() {
0364: return this .positiveArrowVisible;
0365: }
0366:
0367: /**
0368: * Sets a flag that controls whether or not the axis lines has an arrow
0369: * drawn that points in the positive direction for the axis, and sends an
0370: * {@link AxisChangeEvent} to all registered listeners.
0371: *
0372: * @param visible the flag.
0373: *
0374: * @see #isPositiveArrowVisible()
0375: */
0376: public void setPositiveArrowVisible(boolean visible) {
0377: this .positiveArrowVisible = visible;
0378: notifyListeners(new AxisChangeEvent(this ));
0379: }
0380:
0381: /**
0382: * Returns a flag that controls whether or not the axis line has an arrow
0383: * drawn that points in the negative direction for the axis.
0384: *
0385: * @return A boolean.
0386: *
0387: * @see #setNegativeArrowVisible(boolean)
0388: */
0389: public boolean isNegativeArrowVisible() {
0390: return this .negativeArrowVisible;
0391: }
0392:
0393: /**
0394: * Sets a flag that controls whether or not the axis lines has an arrow
0395: * drawn that points in the negative direction for the axis, and sends an
0396: * {@link AxisChangeEvent} to all registered listeners.
0397: *
0398: * @param visible the flag.
0399: *
0400: * @see #setNegativeArrowVisible(boolean)
0401: */
0402: public void setNegativeArrowVisible(boolean visible) {
0403: this .negativeArrowVisible = visible;
0404: notifyListeners(new AxisChangeEvent(this ));
0405: }
0406:
0407: /**
0408: * Returns a shape that can be displayed as an arrow pointing upwards at
0409: * the end of an axis line.
0410: *
0411: * @return A shape (never <code>null</code>).
0412: *
0413: * @see #setUpArrow(Shape)
0414: */
0415: public Shape getUpArrow() {
0416: return this .upArrow;
0417: }
0418:
0419: /**
0420: * Sets the shape that can be displayed as an arrow pointing upwards at
0421: * the end of an axis line and sends an {@link AxisChangeEvent} to all
0422: * registered listeners.
0423: *
0424: * @param arrow the arrow shape (<code>null</code> not permitted).
0425: *
0426: * @see #getUpArrow()
0427: */
0428: public void setUpArrow(Shape arrow) {
0429: if (arrow == null) {
0430: throw new IllegalArgumentException("Null 'arrow' argument.");
0431: }
0432: this .upArrow = arrow;
0433: notifyListeners(new AxisChangeEvent(this ));
0434: }
0435:
0436: /**
0437: * Returns a shape that can be displayed as an arrow pointing downwards at
0438: * the end of an axis line.
0439: *
0440: * @return A shape (never <code>null</code>).
0441: *
0442: * @see #setDownArrow(Shape)
0443: */
0444: public Shape getDownArrow() {
0445: return this .downArrow;
0446: }
0447:
0448: /**
0449: * Sets the shape that can be displayed as an arrow pointing downwards at
0450: * the end of an axis line and sends an {@link AxisChangeEvent} to all
0451: * registered listeners.
0452: *
0453: * @param arrow the arrow shape (<code>null</code> not permitted).
0454: *
0455: * @see #getDownArrow()
0456: */
0457: public void setDownArrow(Shape arrow) {
0458: if (arrow == null) {
0459: throw new IllegalArgumentException("Null 'arrow' argument.");
0460: }
0461: this .downArrow = arrow;
0462: notifyListeners(new AxisChangeEvent(this ));
0463: }
0464:
0465: /**
0466: * Returns a shape that can be displayed as an arrow pointing left at the
0467: * end of an axis line.
0468: *
0469: * @return A shape (never <code>null</code>).
0470: *
0471: * @see #setLeftArrow(Shape)
0472: */
0473: public Shape getLeftArrow() {
0474: return this .leftArrow;
0475: }
0476:
0477: /**
0478: * Sets the shape that can be displayed as an arrow pointing left at the
0479: * end of an axis line and sends an {@link AxisChangeEvent} to all
0480: * registered listeners.
0481: *
0482: * @param arrow the arrow shape (<code>null</code> not permitted).
0483: *
0484: * @see #getLeftArrow()
0485: */
0486: public void setLeftArrow(Shape arrow) {
0487: if (arrow == null) {
0488: throw new IllegalArgumentException("Null 'arrow' argument.");
0489: }
0490: this .leftArrow = arrow;
0491: notifyListeners(new AxisChangeEvent(this ));
0492: }
0493:
0494: /**
0495: * Returns a shape that can be displayed as an arrow pointing right at the
0496: * end of an axis line.
0497: *
0498: * @return A shape (never <code>null</code>).
0499: *
0500: * @see #setRightArrow(Shape)
0501: */
0502: public Shape getRightArrow() {
0503: return this .rightArrow;
0504: }
0505:
0506: /**
0507: * Sets the shape that can be displayed as an arrow pointing rightwards at
0508: * the end of an axis line and sends an {@link AxisChangeEvent} to all
0509: * registered listeners.
0510: *
0511: * @param arrow the arrow shape (<code>null</code> not permitted).
0512: *
0513: * @see #getRightArrow()
0514: */
0515: public void setRightArrow(Shape arrow) {
0516: if (arrow == null) {
0517: throw new IllegalArgumentException("Null 'arrow' argument.");
0518: }
0519: this .rightArrow = arrow;
0520: notifyListeners(new AxisChangeEvent(this ));
0521: }
0522:
0523: /**
0524: * Draws an axis line at the current cursor position and edge.
0525: *
0526: * @param g2 the graphics device.
0527: * @param cursor the cursor position.
0528: * @param dataArea the data area.
0529: * @param edge the edge.
0530: */
0531: protected void drawAxisLine(Graphics2D g2, double cursor,
0532: Rectangle2D dataArea, RectangleEdge edge) {
0533: Line2D axisLine = null;
0534: if (edge == RectangleEdge.TOP) {
0535: axisLine = new Line2D.Double(dataArea.getX(), cursor,
0536: dataArea.getMaxX(), cursor);
0537: } else if (edge == RectangleEdge.BOTTOM) {
0538: axisLine = new Line2D.Double(dataArea.getX(), cursor,
0539: dataArea.getMaxX(), cursor);
0540: } else if (edge == RectangleEdge.LEFT) {
0541: axisLine = new Line2D.Double(cursor, dataArea.getY(),
0542: cursor, dataArea.getMaxY());
0543: } else if (edge == RectangleEdge.RIGHT) {
0544: axisLine = new Line2D.Double(cursor, dataArea.getY(),
0545: cursor, dataArea.getMaxY());
0546: }
0547: g2.setPaint(getAxisLinePaint());
0548: g2.setStroke(getAxisLineStroke());
0549: g2.draw(axisLine);
0550:
0551: boolean drawUpOrRight = false;
0552: boolean drawDownOrLeft = false;
0553: if (this .positiveArrowVisible) {
0554: if (this .inverted) {
0555: drawDownOrLeft = true;
0556: } else {
0557: drawUpOrRight = true;
0558: }
0559: }
0560: if (this .negativeArrowVisible) {
0561: if (this .inverted) {
0562: drawUpOrRight = true;
0563: } else {
0564: drawDownOrLeft = true;
0565: }
0566: }
0567: if (drawUpOrRight) {
0568: double x = 0.0;
0569: double y = 0.0;
0570: Shape arrow = null;
0571: if (edge == RectangleEdge.TOP
0572: || edge == RectangleEdge.BOTTOM) {
0573: x = dataArea.getMaxX();
0574: y = cursor;
0575: arrow = this .rightArrow;
0576: } else if (edge == RectangleEdge.LEFT
0577: || edge == RectangleEdge.RIGHT) {
0578: x = cursor;
0579: y = dataArea.getMinY();
0580: arrow = this .upArrow;
0581: }
0582:
0583: // draw the arrow...
0584: AffineTransform transformer = new AffineTransform();
0585: transformer.setToTranslation(x, y);
0586: Shape shape = transformer.createTransformedShape(arrow);
0587: g2.fill(shape);
0588: g2.draw(shape);
0589: }
0590:
0591: if (drawDownOrLeft) {
0592: double x = 0.0;
0593: double y = 0.0;
0594: Shape arrow = null;
0595: if (edge == RectangleEdge.TOP
0596: || edge == RectangleEdge.BOTTOM) {
0597: x = dataArea.getMinX();
0598: y = cursor;
0599: arrow = this .leftArrow;
0600: } else if (edge == RectangleEdge.LEFT
0601: || edge == RectangleEdge.RIGHT) {
0602: x = cursor;
0603: y = dataArea.getMaxY();
0604: arrow = this .downArrow;
0605: }
0606:
0607: // draw the arrow...
0608: AffineTransform transformer = new AffineTransform();
0609: transformer.setToTranslation(x, y);
0610: Shape shape = transformer.createTransformedShape(arrow);
0611: g2.fill(shape);
0612: g2.draw(shape);
0613: }
0614:
0615: }
0616:
0617: /**
0618: * Calculates the anchor point for a tick label.
0619: *
0620: * @param tick the tick.
0621: * @param cursor the cursor.
0622: * @param dataArea the data area.
0623: * @param edge the edge on which the axis is drawn.
0624: *
0625: * @return The x and y coordinates of the anchor point.
0626: */
0627: protected float[] calculateAnchorPoint(ValueTick tick,
0628: double cursor, Rectangle2D dataArea, RectangleEdge edge) {
0629:
0630: RectangleInsets insets = getTickLabelInsets();
0631: float[] result = new float[2];
0632: if (edge == RectangleEdge.TOP) {
0633: result[0] = (float) valueToJava2D(tick.getValue(),
0634: dataArea, edge);
0635: result[1] = (float) (cursor - insets.getBottom() - 2.0);
0636: } else if (edge == RectangleEdge.BOTTOM) {
0637: result[0] = (float) valueToJava2D(tick.getValue(),
0638: dataArea, edge);
0639: result[1] = (float) (cursor + insets.getTop() + 2.0);
0640: } else if (edge == RectangleEdge.LEFT) {
0641: result[0] = (float) (cursor - insets.getLeft() - 2.0);
0642: result[1] = (float) valueToJava2D(tick.getValue(),
0643: dataArea, edge);
0644: } else if (edge == RectangleEdge.RIGHT) {
0645: result[0] = (float) (cursor + insets.getRight() + 2.0);
0646: result[1] = (float) valueToJava2D(tick.getValue(),
0647: dataArea, edge);
0648: }
0649: return result;
0650: }
0651:
0652: /**
0653: * Draws the axis line, tick marks and tick mark labels.
0654: *
0655: * @param g2 the graphics device.
0656: * @param cursor the cursor.
0657: * @param plotArea the plot area.
0658: * @param dataArea the data area.
0659: * @param edge the edge that the axis is aligned with.
0660: *
0661: * @return The width or height used to draw the axis.
0662: */
0663: protected AxisState drawTickMarksAndLabels(Graphics2D g2,
0664: double cursor, Rectangle2D plotArea, Rectangle2D dataArea,
0665: RectangleEdge edge) {
0666:
0667: AxisState state = new AxisState(cursor);
0668:
0669: if (isAxisLineVisible()) {
0670: drawAxisLine(g2, cursor, dataArea, edge);
0671: }
0672:
0673: double ol = getTickMarkOutsideLength();
0674: double il = getTickMarkInsideLength();
0675:
0676: List ticks = refreshTicks(g2, state, dataArea, edge);
0677: state.setTicks(ticks);
0678: g2.setFont(getTickLabelFont());
0679: Iterator iterator = ticks.iterator();
0680: while (iterator.hasNext()) {
0681: ValueTick tick = (ValueTick) iterator.next();
0682: if (isTickLabelsVisible()) {
0683: g2.setPaint(getTickLabelPaint());
0684: float[] anchorPoint = calculateAnchorPoint(tick,
0685: cursor, dataArea, edge);
0686: TextUtilities.drawRotatedString(tick.getText(), g2,
0687: anchorPoint[0], anchorPoint[1], tick
0688: .getTextAnchor(), tick.getAngle(), tick
0689: .getRotationAnchor());
0690: }
0691:
0692: if (isTickMarksVisible()) {
0693: float xx = (float) valueToJava2D(tick.getValue(),
0694: dataArea, edge);
0695: Line2D mark = null;
0696: g2.setStroke(getTickMarkStroke());
0697: g2.setPaint(getTickMarkPaint());
0698: if (edge == RectangleEdge.LEFT) {
0699: mark = new Line2D.Double(cursor - ol, xx, cursor
0700: + il, xx);
0701: } else if (edge == RectangleEdge.RIGHT) {
0702: mark = new Line2D.Double(cursor + ol, xx, cursor
0703: - il, xx);
0704: } else if (edge == RectangleEdge.TOP) {
0705: mark = new Line2D.Double(xx, cursor - ol, xx,
0706: cursor + il);
0707: } else if (edge == RectangleEdge.BOTTOM) {
0708: mark = new Line2D.Double(xx, cursor + ol, xx,
0709: cursor - il);
0710: }
0711: g2.draw(mark);
0712: }
0713: }
0714:
0715: // need to work out the space used by the tick labels...
0716: // so we can update the cursor...
0717: double used = 0.0;
0718: if (isTickLabelsVisible()) {
0719: if (edge == RectangleEdge.LEFT) {
0720: used += findMaximumTickLabelWidth(ticks, g2, plotArea,
0721: isVerticalTickLabels());
0722: state.cursorLeft(used);
0723: } else if (edge == RectangleEdge.RIGHT) {
0724: used = findMaximumTickLabelWidth(ticks, g2, plotArea,
0725: isVerticalTickLabels());
0726: state.cursorRight(used);
0727: } else if (edge == RectangleEdge.TOP) {
0728: used = findMaximumTickLabelHeight(ticks, g2, plotArea,
0729: isVerticalTickLabels());
0730: state.cursorUp(used);
0731: } else if (edge == RectangleEdge.BOTTOM) {
0732: used = findMaximumTickLabelHeight(ticks, g2, plotArea,
0733: isVerticalTickLabels());
0734: state.cursorDown(used);
0735: }
0736: }
0737:
0738: return state;
0739: }
0740:
0741: /**
0742: * Returns the space required to draw the axis.
0743: *
0744: * @param g2 the graphics device.
0745: * @param plot the plot that the axis belongs to.
0746: * @param plotArea the area within which the plot should be drawn.
0747: * @param edge the axis location.
0748: * @param space the space already reserved (for other axes).
0749: *
0750: * @return The space required to draw the axis (including pre-reserved
0751: * space).
0752: */
0753: public AxisSpace reserveSpace(Graphics2D g2, Plot plot,
0754: Rectangle2D plotArea, RectangleEdge edge, AxisSpace space) {
0755:
0756: // create a new space object if one wasn't supplied...
0757: if (space == null) {
0758: space = new AxisSpace();
0759: }
0760:
0761: // if the axis is not visible, no additional space is required...
0762: if (!isVisible()) {
0763: return space;
0764: }
0765:
0766: // if the axis has a fixed dimension, return it...
0767: double dimension = getFixedDimension();
0768: if (dimension > 0.0) {
0769: space.ensureAtLeast(dimension, edge);
0770: }
0771:
0772: // calculate the max size of the tick labels (if visible)...
0773: double tickLabelHeight = 0.0;
0774: double tickLabelWidth = 0.0;
0775: if (isTickLabelsVisible()) {
0776: g2.setFont(getTickLabelFont());
0777: List ticks = refreshTicks(g2, new AxisState(), plotArea,
0778: edge);
0779: if (RectangleEdge.isTopOrBottom(edge)) {
0780: tickLabelHeight = findMaximumTickLabelHeight(ticks, g2,
0781: plotArea, isVerticalTickLabels());
0782: } else if (RectangleEdge.isLeftOrRight(edge)) {
0783: tickLabelWidth = findMaximumTickLabelWidth(ticks, g2,
0784: plotArea, isVerticalTickLabels());
0785: }
0786: }
0787:
0788: // get the axis label size and update the space object...
0789: Rectangle2D labelEnclosure = getLabelEnclosure(g2, edge);
0790: double labelHeight = 0.0;
0791: double labelWidth = 0.0;
0792: if (RectangleEdge.isTopOrBottom(edge)) {
0793: labelHeight = labelEnclosure.getHeight();
0794: space.add(labelHeight + tickLabelHeight, edge);
0795: } else if (RectangleEdge.isLeftOrRight(edge)) {
0796: labelWidth = labelEnclosure.getWidth();
0797: space.add(labelWidth + tickLabelWidth, edge);
0798: }
0799:
0800: return space;
0801:
0802: }
0803:
0804: /**
0805: * A utility method for determining the height of the tallest tick label.
0806: *
0807: * @param ticks the ticks.
0808: * @param g2 the graphics device.
0809: * @param drawArea the area within which the plot and axes should be drawn.
0810: * @param vertical a flag that indicates whether or not the tick labels
0811: * are 'vertical'.
0812: *
0813: * @return The height of the tallest tick label.
0814: */
0815: protected double findMaximumTickLabelHeight(List ticks,
0816: Graphics2D g2, Rectangle2D drawArea, boolean vertical) {
0817:
0818: RectangleInsets insets = getTickLabelInsets();
0819: Font font = getTickLabelFont();
0820: double maxHeight = 0.0;
0821: if (vertical) {
0822: FontMetrics fm = g2.getFontMetrics(font);
0823: Iterator iterator = ticks.iterator();
0824: while (iterator.hasNext()) {
0825: Tick tick = (Tick) iterator.next();
0826: Rectangle2D labelBounds = TextUtilities.getTextBounds(
0827: tick.getText(), g2, fm);
0828: if (labelBounds.getWidth() + insets.getTop()
0829: + insets.getBottom() > maxHeight) {
0830: maxHeight = labelBounds.getWidth()
0831: + insets.getTop() + insets.getBottom();
0832: }
0833: }
0834: } else {
0835: LineMetrics metrics = font.getLineMetrics("ABCxyz", g2
0836: .getFontRenderContext());
0837: maxHeight = metrics.getHeight() + insets.getTop()
0838: + insets.getBottom();
0839: }
0840: return maxHeight;
0841:
0842: }
0843:
0844: /**
0845: * A utility method for determining the width of the widest tick label.
0846: *
0847: * @param ticks the ticks.
0848: * @param g2 the graphics device.
0849: * @param drawArea the area within which the plot and axes should be drawn.
0850: * @param vertical a flag that indicates whether or not the tick labels
0851: * are 'vertical'.
0852: *
0853: * @return The width of the tallest tick label.
0854: */
0855: protected double findMaximumTickLabelWidth(List ticks,
0856: Graphics2D g2, Rectangle2D drawArea, boolean vertical) {
0857:
0858: RectangleInsets insets = getTickLabelInsets();
0859: Font font = getTickLabelFont();
0860: double maxWidth = 0.0;
0861: if (!vertical) {
0862: FontMetrics fm = g2.getFontMetrics(font);
0863: Iterator iterator = ticks.iterator();
0864: while (iterator.hasNext()) {
0865: Tick tick = (Tick) iterator.next();
0866: Rectangle2D labelBounds = TextUtilities.getTextBounds(
0867: tick.getText(), g2, fm);
0868: if (labelBounds.getWidth() + insets.getLeft()
0869: + insets.getRight() > maxWidth) {
0870: maxWidth = labelBounds.getWidth()
0871: + insets.getLeft() + insets.getRight();
0872: }
0873: }
0874: } else {
0875: LineMetrics metrics = font.getLineMetrics("ABCxyz", g2
0876: .getFontRenderContext());
0877: maxWidth = metrics.getHeight() + insets.getTop()
0878: + insets.getBottom();
0879: }
0880: return maxWidth;
0881:
0882: }
0883:
0884: /**
0885: * Returns a flag that controls the direction of values on the axis.
0886: * <P>
0887: * For a regular axis, values increase from left to right (for a horizontal
0888: * axis) and bottom to top (for a vertical axis). When the axis is
0889: * 'inverted', the values increase in the opposite direction.
0890: *
0891: * @return The flag.
0892: *
0893: * @see #setInverted(boolean)
0894: */
0895: public boolean isInverted() {
0896: return this .inverted;
0897: }
0898:
0899: /**
0900: * Sets a flag that controls the direction of values on the axis, and
0901: * notifies registered listeners that the axis has changed.
0902: *
0903: * @param flag the flag.
0904: *
0905: * @see #isInverted()
0906: */
0907: public void setInverted(boolean flag) {
0908:
0909: if (this .inverted != flag) {
0910: this .inverted = flag;
0911: notifyListeners(new AxisChangeEvent(this ));
0912: }
0913:
0914: }
0915:
0916: /**
0917: * Returns the flag that controls whether or not the axis range is
0918: * automatically adjusted to fit the data values.
0919: *
0920: * @return The flag.
0921: *
0922: * @see #setAutoRange(boolean)
0923: */
0924: public boolean isAutoRange() {
0925: return this .autoRange;
0926: }
0927:
0928: /**
0929: * Sets a flag that determines whether or not the axis range is
0930: * automatically adjusted to fit the data, and notifies registered
0931: * listeners that the axis has been modified.
0932: *
0933: * @param auto the new value of the flag.
0934: *
0935: * @see #isAutoRange()
0936: */
0937: public void setAutoRange(boolean auto) {
0938: setAutoRange(auto, true);
0939: }
0940:
0941: /**
0942: * Sets the auto range attribute. If the <code>notify</code> flag is set,
0943: * an {@link AxisChangeEvent} is sent to registered listeners.
0944: *
0945: * @param auto the flag.
0946: * @param notify notify listeners?
0947: *
0948: * @see #isAutoRange()
0949: */
0950: protected void setAutoRange(boolean auto, boolean notify) {
0951: if (this .autoRange != auto) {
0952: this .autoRange = auto;
0953: if (this .autoRange) {
0954: autoAdjustRange();
0955: }
0956: if (notify) {
0957: notifyListeners(new AxisChangeEvent(this ));
0958: }
0959: }
0960: }
0961:
0962: /**
0963: * Returns the minimum size allowed for the axis range when it is
0964: * automatically calculated.
0965: *
0966: * @return The minimum range.
0967: *
0968: * @see #setAutoRangeMinimumSize(double)
0969: */
0970: public double getAutoRangeMinimumSize() {
0971: return this .autoRangeMinimumSize;
0972: }
0973:
0974: /**
0975: * Sets the auto range minimum size and sends an {@link AxisChangeEvent}
0976: * to all registered listeners.
0977: *
0978: * @param size the size.
0979: *
0980: * @see #getAutoRangeMinimumSize()
0981: */
0982: public void setAutoRangeMinimumSize(double size) {
0983: setAutoRangeMinimumSize(size, true);
0984: }
0985:
0986: /**
0987: * Sets the minimum size allowed for the axis range when it is
0988: * automatically calculated.
0989: * <p>
0990: * If requested, an {@link AxisChangeEvent} is forwarded to all registered
0991: * listeners.
0992: *
0993: * @param size the new minimum.
0994: * @param notify notify listeners?
0995: */
0996: public void setAutoRangeMinimumSize(double size, boolean notify) {
0997: if (size <= 0.0) {
0998: throw new IllegalArgumentException(
0999: "NumberAxis.setAutoRangeMinimumSize(double): must be > 0.0.");
1000: }
1001: if (this .autoRangeMinimumSize != size) {
1002: this .autoRangeMinimumSize = size;
1003: if (this .autoRange) {
1004: autoAdjustRange();
1005: }
1006: if (notify) {
1007: notifyListeners(new AxisChangeEvent(this ));
1008: }
1009: }
1010:
1011: }
1012:
1013: /**
1014: * Returns the default auto range.
1015: *
1016: * @return The default auto range (never <code>null</code>).
1017: *
1018: * @see #setDefaultAutoRange(Range)
1019: * @since 1.0.5
1020: */
1021: public Range getDefaultAutoRange() {
1022: return this .defaultAutoRange;
1023: }
1024:
1025: /**
1026: * Sets the default auto range and sends an {@link AxisChangeEvent} to all
1027: * registered listeners.
1028: *
1029: * @param range the range (<code>null</code> not permitted).
1030: *
1031: * @see #getDefaultAutoRange()
1032: *
1033: * @since 1.0.5
1034: */
1035: public void setDefaultAutoRange(Range range) {
1036: if (range == null) {
1037: throw new IllegalArgumentException("Null 'range' argument.");
1038: }
1039: this .defaultAutoRange = range;
1040: notifyListeners(new AxisChangeEvent(this ));
1041: }
1042:
1043: /**
1044: * Returns the lower margin for the axis, expressed as a percentage of the
1045: * axis range. This controls the space added to the lower end of the axis
1046: * when the axis range is automatically calculated (it is ignored when the
1047: * axis range is set explicitly). The default value is 0.05 (five percent).
1048: *
1049: * @return The lower margin.
1050: *
1051: * @see #setLowerMargin(double)
1052: */
1053: public double getLowerMargin() {
1054: return this .lowerMargin;
1055: }
1056:
1057: /**
1058: * Sets the lower margin for the axis (as a percentage of the axis range)
1059: * and sends an {@link AxisChangeEvent} to all registered listeners. This
1060: * margin is added only when the axis range is auto-calculated - if you set
1061: * the axis range manually, the margin is ignored.
1062: *
1063: * @param margin the margin percentage (for example, 0.05 is five percent).
1064: *
1065: * @see #getLowerMargin()
1066: * @see #setUpperMargin(double)
1067: */
1068: public void setLowerMargin(double margin) {
1069: this .lowerMargin = margin;
1070: if (isAutoRange()) {
1071: autoAdjustRange();
1072: }
1073: notifyListeners(new AxisChangeEvent(this ));
1074: }
1075:
1076: /**
1077: * Returns the upper margin for the axis, expressed as a percentage of the
1078: * axis range. This controls the space added to the lower end of the axis
1079: * when the axis range is automatically calculated (it is ignored when the
1080: * axis range is set explicitly). The default value is 0.05 (five percent).
1081: *
1082: * @return The upper margin.
1083: *
1084: * @see #setUpperMargin(double)
1085: */
1086: public double getUpperMargin() {
1087: return this .upperMargin;
1088: }
1089:
1090: /**
1091: * Sets the upper margin for the axis (as a percentage of the axis range)
1092: * and sends an {@link AxisChangeEvent} to all registered listeners. This
1093: * margin is added only when the axis range is auto-calculated - if you set
1094: * the axis range manually, the margin is ignored.
1095: *
1096: * @param margin the margin percentage (for example, 0.05 is five percent).
1097: *
1098: * @see #getLowerMargin()
1099: * @see #setLowerMargin(double)
1100: */
1101: public void setUpperMargin(double margin) {
1102: this .upperMargin = margin;
1103: if (isAutoRange()) {
1104: autoAdjustRange();
1105: }
1106: notifyListeners(new AxisChangeEvent(this ));
1107: }
1108:
1109: /**
1110: * Returns the fixed auto range.
1111: *
1112: * @return The length.
1113: *
1114: * @see #setFixedAutoRange(double)
1115: */
1116: public double getFixedAutoRange() {
1117: return this .fixedAutoRange;
1118: }
1119:
1120: /**
1121: * Sets the fixed auto range for the axis.
1122: *
1123: * @param length the range length.
1124: *
1125: * @see #getFixedAutoRange()
1126: */
1127: public void setFixedAutoRange(double length) {
1128: this .fixedAutoRange = length;
1129: if (isAutoRange()) {
1130: autoAdjustRange();
1131: }
1132: notifyListeners(new AxisChangeEvent(this ));
1133: }
1134:
1135: /**
1136: * Returns the lower bound of the axis range.
1137: *
1138: * @return The lower bound.
1139: *
1140: * @see #setLowerBound(double)
1141: */
1142: public double getLowerBound() {
1143: return this .range.getLowerBound();
1144: }
1145:
1146: /**
1147: * Sets the lower bound for the axis range. An {@link AxisChangeEvent} is
1148: * sent to all registered listeners.
1149: *
1150: * @param min the new minimum.
1151: *
1152: * @see #getLowerBound()
1153: */
1154: public void setLowerBound(double min) {
1155: if (this .range.getUpperBound() > min) {
1156: setRange(new Range(min, this .range.getUpperBound()));
1157: } else {
1158: setRange(new Range(min, min + 1.0));
1159: }
1160: }
1161:
1162: /**
1163: * Returns the upper bound for the axis range.
1164: *
1165: * @return The upper bound.
1166: *
1167: * @see #setUpperBound(double)
1168: */
1169: public double getUpperBound() {
1170: return this .range.getUpperBound();
1171: }
1172:
1173: /**
1174: * Sets the upper bound for the axis range, and sends an
1175: * {@link AxisChangeEvent} to all registered listeners.
1176: *
1177: * @param max the new maximum.
1178: *
1179: * @see #getUpperBound()
1180: */
1181: public void setUpperBound(double max) {
1182: if (this .range.getLowerBound() < max) {
1183: setRange(new Range(this .range.getLowerBound(), max));
1184: } else {
1185: setRange(max - 1.0, max);
1186: }
1187: }
1188:
1189: /**
1190: * Returns the range for the axis.
1191: *
1192: * @return The axis range (never <code>null</code>).
1193: *
1194: * @see #setRange(Range)
1195: */
1196: public Range getRange() {
1197: return this .range;
1198: }
1199:
1200: /**
1201: * Sets the range attribute and sends an {@link AxisChangeEvent} to all
1202: * registered listeners. As a side-effect, the auto-range flag is set to
1203: * <code>false</code>.
1204: *
1205: * @param range the range (<code>null</code> not permitted).
1206: *
1207: * @see #getRange()
1208: */
1209: public void setRange(Range range) {
1210: // defer argument checking
1211: setRange(range, true, true);
1212: }
1213:
1214: /**
1215: * Sets the range for the axis, if requested, sends an
1216: * {@link AxisChangeEvent} to all registered listeners. As a side-effect,
1217: * the auto-range flag is set to <code>false</code> (optional).
1218: *
1219: * @param range the range (<code>null</code> not permitted).
1220: * @param turnOffAutoRange a flag that controls whether or not the auto
1221: * range is turned off.
1222: * @param notify a flag that controls whether or not listeners are
1223: * notified.
1224: *
1225: * @see #getRange()
1226: */
1227: public void setRange(Range range, boolean turnOffAutoRange,
1228: boolean notify) {
1229: if (range == null) {
1230: throw new IllegalArgumentException("Null 'range' argument.");
1231: }
1232: if (turnOffAutoRange) {
1233: this .autoRange = false;
1234: }
1235: this .range = range;
1236: if (notify) {
1237: notifyListeners(new AxisChangeEvent(this ));
1238: }
1239: }
1240:
1241: /**
1242: * Sets the axis range and sends an {@link AxisChangeEvent} to all
1243: * registered listeners. As a side-effect, the auto-range flag is set to
1244: * <code>false</code>.
1245: *
1246: * @param lower the lower axis limit.
1247: * @param upper the upper axis limit.
1248: *
1249: * @see #getRange()
1250: * @see #setRange(Range)
1251: */
1252: public void setRange(double lower, double upper) {
1253: setRange(new Range(lower, upper));
1254: }
1255:
1256: /**
1257: * Sets the range for the axis (after first adding the current margins to
1258: * the specified range) and sends an {@link AxisChangeEvent} to all
1259: * registered listeners.
1260: *
1261: * @param range the range (<code>null</code> not permitted).
1262: */
1263: public void setRangeWithMargins(Range range) {
1264: setRangeWithMargins(range, true, true);
1265: }
1266:
1267: /**
1268: * Sets the range for the axis after first adding the current margins to
1269: * the range and, if requested, sends an {@link AxisChangeEvent} to all
1270: * registered listeners. As a side-effect, the auto-range flag is set to
1271: * <code>false</code> (optional).
1272: *
1273: * @param range the range (excluding margins, <code>null</code> not
1274: * permitted).
1275: * @param turnOffAutoRange a flag that controls whether or not the auto
1276: * range is turned off.
1277: * @param notify a flag that controls whether or not listeners are
1278: * notified.
1279: */
1280: public void setRangeWithMargins(Range range,
1281: boolean turnOffAutoRange, boolean notify) {
1282: if (range == null) {
1283: throw new IllegalArgumentException("Null 'range' argument.");
1284: }
1285: setRange(Range
1286: .expand(range, getLowerMargin(), getUpperMargin()),
1287: turnOffAutoRange, notify);
1288: }
1289:
1290: /**
1291: * Sets the axis range (after first adding the current margins to the
1292: * range) and sends an {@link AxisChangeEvent} to all registered listeners.
1293: * As a side-effect, the auto-range flag is set to <code>false</code>.
1294: *
1295: * @param lower the lower axis limit.
1296: * @param upper the upper axis limit.
1297: */
1298: public void setRangeWithMargins(double lower, double upper) {
1299: setRangeWithMargins(new Range(lower, upper));
1300: }
1301:
1302: /**
1303: * Sets the axis range, where the new range is 'size' in length, and
1304: * centered on 'value'.
1305: *
1306: * @param value the central value.
1307: * @param length the range length.
1308: */
1309: public void setRangeAboutValue(double value, double length) {
1310: setRange(new Range(value - length / 2, value + length / 2));
1311: }
1312:
1313: /**
1314: * Returns a flag indicating whether or not the tick unit is automatically
1315: * selected from a range of standard tick units.
1316: *
1317: * @return A flag indicating whether or not the tick unit is automatically
1318: * selected.
1319: *
1320: * @see #setAutoTickUnitSelection(boolean)
1321: */
1322: public boolean isAutoTickUnitSelection() {
1323: return this .autoTickUnitSelection;
1324: }
1325:
1326: /**
1327: * Sets a flag indicating whether or not the tick unit is automatically
1328: * selected from a range of standard tick units. If the flag is changed,
1329: * registered listeners are notified that the chart has changed.
1330: *
1331: * @param flag the new value of the flag.
1332: *
1333: * @see #isAutoTickUnitSelection()
1334: */
1335: public void setAutoTickUnitSelection(boolean flag) {
1336: setAutoTickUnitSelection(flag, true);
1337: }
1338:
1339: /**
1340: * Sets a flag indicating whether or not the tick unit is automatically
1341: * selected from a range of standard tick units.
1342: *
1343: * @param flag the new value of the flag.
1344: * @param notify notify listeners?
1345: *
1346: * @see #isAutoTickUnitSelection()
1347: */
1348: public void setAutoTickUnitSelection(boolean flag, boolean notify) {
1349:
1350: if (this .autoTickUnitSelection != flag) {
1351: this .autoTickUnitSelection = flag;
1352: if (notify) {
1353: notifyListeners(new AxisChangeEvent(this ));
1354: }
1355: }
1356: }
1357:
1358: /**
1359: * Returns the source for obtaining standard tick units for the axis.
1360: *
1361: * @return The source (possibly <code>null</code>).
1362: *
1363: * @see #setStandardTickUnits(TickUnitSource)
1364: */
1365: public TickUnitSource getStandardTickUnits() {
1366: return this .standardTickUnits;
1367: }
1368:
1369: /**
1370: * Sets the source for obtaining standard tick units for the axis and sends
1371: * an {@link AxisChangeEvent} to all registered listeners. The axis will
1372: * try to select the smallest tick unit from the source that does not cause
1373: * the tick labels to overlap (see also the
1374: * {@link #setAutoTickUnitSelection(boolean)} method.
1375: *
1376: * @param source the source for standard tick units (<code>null</code>
1377: * permitted).
1378: *
1379: * @see #getStandardTickUnits()
1380: */
1381: public void setStandardTickUnits(TickUnitSource source) {
1382: this .standardTickUnits = source;
1383: notifyListeners(new AxisChangeEvent(this ));
1384: }
1385:
1386: /**
1387: * Converts a data value to a coordinate in Java2D space, assuming that the
1388: * axis runs along one edge of the specified dataArea.
1389: * <p>
1390: * Note that it is possible for the coordinate to fall outside the area.
1391: *
1392: * @param value the data value.
1393: * @param area the area for plotting the data.
1394: * @param edge the edge along which the axis lies.
1395: *
1396: * @return The Java2D coordinate.
1397: *
1398: * @see #java2DToValue(double, Rectangle2D, RectangleEdge)
1399: */
1400: public abstract double valueToJava2D(double value,
1401: Rectangle2D area, RectangleEdge edge);
1402:
1403: /**
1404: * Converts a length in data coordinates into the corresponding length in
1405: * Java2D coordinates.
1406: *
1407: * @param length the length.
1408: * @param area the plot area.
1409: * @param edge the edge along which the axis lies.
1410: *
1411: * @return The length in Java2D coordinates.
1412: */
1413: public double lengthToJava2D(double length, Rectangle2D area,
1414: RectangleEdge edge) {
1415: double zero = valueToJava2D(0.0, area, edge);
1416: double l = valueToJava2D(length, area, edge);
1417: return Math.abs(l - zero);
1418: }
1419:
1420: /**
1421: * Converts a coordinate in Java2D space to the corresponding data value,
1422: * assuming that the axis runs along one edge of the specified dataArea.
1423: *
1424: * @param java2DValue the coordinate in Java2D space.
1425: * @param area the area in which the data is plotted.
1426: * @param edge the edge along which the axis lies.
1427: *
1428: * @return The data value.
1429: *
1430: * @see #valueToJava2D(double, Rectangle2D, RectangleEdge)
1431: */
1432: public abstract double java2DToValue(double java2DValue,
1433: Rectangle2D area, RectangleEdge edge);
1434:
1435: /**
1436: * Automatically sets the axis range to fit the range of values in the
1437: * dataset. Sometimes this can depend on the renderer used as well (for
1438: * example, the renderer may "stack" values, requiring an axis range
1439: * greater than otherwise necessary).
1440: */
1441: protected abstract void autoAdjustRange();
1442:
1443: /**
1444: * Centers the axis range about the specified value and sends an
1445: * {@link AxisChangeEvent} to all registered listeners.
1446: *
1447: * @param value the center value.
1448: */
1449: public void centerRange(double value) {
1450:
1451: double central = this .range.getCentralValue();
1452: Range adjusted = new Range(this .range.getLowerBound() + value
1453: - central, this .range.getUpperBound() + value - central);
1454: setRange(adjusted);
1455:
1456: }
1457:
1458: /**
1459: * Increases or decreases the axis range by the specified percentage about
1460: * the central value and sends an {@link AxisChangeEvent} to all registered
1461: * listeners.
1462: * <P>
1463: * To double the length of the axis range, use 200% (2.0).
1464: * To halve the length of the axis range, use 50% (0.5).
1465: *
1466: * @param percent the resize factor.
1467: */
1468: public void resizeRange(double percent) {
1469: resizeRange(percent, this .range.getCentralValue());
1470: }
1471:
1472: /**
1473: * Increases or decreases the axis range by the specified percentage about
1474: * the specified anchor value and sends an {@link AxisChangeEvent} to all
1475: * registered listeners.
1476: * <P>
1477: * To double the length of the axis range, use 200% (2.0).
1478: * To halve the length of the axis range, use 50% (0.5).
1479: *
1480: * @param percent the resize factor.
1481: * @param anchorValue the new central value after the resize.
1482: */
1483: public void resizeRange(double percent, double anchorValue) {
1484: if (percent > 0.0) {
1485: double halfLength = this .range.getLength() * percent / 2;
1486: Range adjusted = new Range(anchorValue - halfLength,
1487: anchorValue + halfLength);
1488: setRange(adjusted);
1489: } else {
1490: setAutoRange(true);
1491: }
1492: }
1493:
1494: /**
1495: * Zooms in on the current range.
1496: *
1497: * @param lowerPercent the new lower bound.
1498: * @param upperPercent the new upper bound.
1499: */
1500: public void zoomRange(double lowerPercent, double upperPercent) {
1501: double start = this .range.getLowerBound();
1502: double length = this .range.getLength();
1503: Range adjusted = null;
1504: if (isInverted()) {
1505: adjusted = new Range(start + (length * (1 - upperPercent)),
1506: start + (length * (1 - lowerPercent)));
1507: } else {
1508: adjusted = new Range(start + length * lowerPercent, start
1509: + length * upperPercent);
1510: }
1511: setRange(adjusted);
1512: }
1513:
1514: /**
1515: * Returns the auto tick index.
1516: *
1517: * @return The auto tick index.
1518: *
1519: * @see #setAutoTickIndex(int)
1520: */
1521: protected int getAutoTickIndex() {
1522: return this .autoTickIndex;
1523: }
1524:
1525: /**
1526: * Sets the auto tick index.
1527: *
1528: * @param index the new value.
1529: *
1530: * @see #getAutoTickIndex()
1531: */
1532: protected void setAutoTickIndex(int index) {
1533: this .autoTickIndex = index;
1534: }
1535:
1536: /**
1537: * Tests the axis for equality with an arbitrary object.
1538: *
1539: * @param obj the object (<code>null</code> permitted).
1540: *
1541: * @return <code>true</code> or <code>false</code>.
1542: */
1543: public boolean equals(Object obj) {
1544:
1545: if (obj == this ) {
1546: return true;
1547: }
1548: if (!(obj instanceof ValueAxis)) {
1549: return false;
1550: }
1551:
1552: ValueAxis that = (ValueAxis) obj;
1553:
1554: if (this .positiveArrowVisible != that.positiveArrowVisible) {
1555: return false;
1556: }
1557: if (this .negativeArrowVisible != that.negativeArrowVisible) {
1558: return false;
1559: }
1560: if (this .inverted != that.inverted) {
1561: return false;
1562: }
1563: if (!ObjectUtilities.equal(this .range, that.range)) {
1564: return false;
1565: }
1566: if (this .autoRange != that.autoRange) {
1567: return false;
1568: }
1569: if (this .autoRangeMinimumSize != that.autoRangeMinimumSize) {
1570: return false;
1571: }
1572: if (!this .defaultAutoRange.equals(that.defaultAutoRange)) {
1573: return false;
1574: }
1575: if (this .upperMargin != that.upperMargin) {
1576: return false;
1577: }
1578: if (this .lowerMargin != that.lowerMargin) {
1579: return false;
1580: }
1581: if (this .fixedAutoRange != that.fixedAutoRange) {
1582: return false;
1583: }
1584: if (this .autoTickUnitSelection != that.autoTickUnitSelection) {
1585: return false;
1586: }
1587: if (!ObjectUtilities.equal(this .standardTickUnits,
1588: that.standardTickUnits)) {
1589: return false;
1590: }
1591: if (this .verticalTickLabels != that.verticalTickLabels) {
1592: return false;
1593: }
1594:
1595: return super .equals(obj);
1596:
1597: }
1598:
1599: /**
1600: * Returns a clone of the object.
1601: *
1602: * @return A clone.
1603: *
1604: * @throws CloneNotSupportedException if some component of the axis does
1605: * not support cloning.
1606: */
1607: public Object clone() throws CloneNotSupportedException {
1608: ValueAxis clone = (ValueAxis) super .clone();
1609: return clone;
1610: }
1611:
1612: /**
1613: * Provides serialization support.
1614: *
1615: * @param stream the output stream.
1616: *
1617: * @throws IOException if there is an I/O error.
1618: */
1619: private void writeObject(ObjectOutputStream stream)
1620: throws IOException {
1621: stream.defaultWriteObject();
1622: SerialUtilities.writeShape(this .upArrow, stream);
1623: SerialUtilities.writeShape(this .downArrow, stream);
1624: SerialUtilities.writeShape(this .leftArrow, stream);
1625: SerialUtilities.writeShape(this .rightArrow, stream);
1626: }
1627:
1628: /**
1629: * Provides serialization support.
1630: *
1631: * @param stream the input stream.
1632: *
1633: * @throws IOException if there is an I/O error.
1634: * @throws ClassNotFoundException if there is a classpath problem.
1635: */
1636: private void readObject(ObjectInputStream stream)
1637: throws IOException, ClassNotFoundException {
1638:
1639: stream.defaultReadObject();
1640: this.upArrow = SerialUtilities.readShape(stream);
1641: this.downArrow = SerialUtilities.readShape(stream);
1642: this.leftArrow = SerialUtilities.readShape(stream);
1643: this.rightArrow = SerialUtilities.readShape(stream);
1644:
1645: }
1646:
1647: }
|