    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
    006     *
    007     * Project Info:  http://www.jfree.org/jfreechart/index.html
    008     *
    009     * This library is free software; you can redistribute it and/or modify it
    010     * under the terms of the GNU Lesser General Public License as published by
    011     * the Free Software Foundation; either version 2.1 of the License, or
    012     * (at your option) any later version.
    013     *
    014     * This library is distributed in the hope that it will be useful, but
    015     * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
    016     * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
    017     * License for more details.
    018     *
    019     * You should have received a copy of the GNU Lesser General Public
    020     * License along with this library; if not, write to the Free Software
    021     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
    022     * USA.
    023     *
    024     * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
    025     * in the United States and other countries.]
    026     *
    027     * --------------
    028     * ValueAxis.java
    029     * --------------
    030     * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
    031     *
    032     * Original Author:  David Gilbert (for Object Refinery Limited);
    033     * Contributor(s):   Jonathan Nash;
    034     *                   Nicolas Brodu (for Astrium and EADS Corporate Research
    035     *                   Center);
    036     *
    037     * $Id: ValueAxis.java,v 1.10.2.5 2007/03/22 12:13:27 mungady Exp $
    038     *
    039     * Changes (from 18-Sep-2001)
    040     * --------------------------
    041     * 18-Sep-2001 : Added standard header and fixed DOS encoding problem (DG);
    042     * 23-Nov-2001 : Overhauled standard tick unit code (DG);
    043     * 04-Dec-2001 : Changed constructors to protected, and tidied up default
    044     *               values (DG);
    045     * 12-Dec-2001 : Fixed vertical gridlines bug (DG);
    046     * 16-Jan-2002 : Added an optional crosshair, based on the implementation by
    047     *               Jonathan Nash (DG);
    048     * 23-Jan-2002 : Moved the minimum and maximum values to here from NumberAxis,
    049     *               and changed the type from Number to double (DG);
    050     * 25-Feb-2002 : Added default value for autoRange. Changed autoAdjustRange
    051     *               from public to protected. Updated import statements (DG);
    052     * 23-Apr-2002 : Added setRange() method (DG);
    053     * 29-Apr-2002 : Added range adjustment methods (DG);
    054     * 13-Jun-2002 : Modified setCrosshairValue() to notify listeners only when the
    055     *               crosshairs are visible, to avoid unnecessary repaints, as
    056     *               suggested by Kees Kuip (DG);
    057     * 25-Jul-2002 : Moved lower and upper margin attributes from the NumberAxis
    058     *               class (DG);
    059     * 05-Sep-2002 : Updated constructor for changes in Axis class (DG);
    060     * 01-Oct-2002 : Fixed errors reported by Checkstyle (DG);
    061     * 04-Oct-2002 : Moved standardTickUnits from NumberAxis --> ValueAxis (DG);
    062     * 08-Nov-2002 : Moved to new package com.jrefinery.chart.axis (DG);
    063     * 19-Nov-2002 : Removed grid settings (now controlled by the plot) (DG);
    064     * 27-Nov-2002 : Moved the 'inverted' attributed from NumberAxis to
    065     *               ValueAxis (DG);
    066     * 03-Jan-2003 : Small fix to ensure auto-range minimum is observed
    067     *               immediately (DG);
    068     * 14-Jan-2003 : Changed autoRangeMinimumSize from Number --> double (DG);
    069     * 20-Jan-2003 : Replaced monolithic constructor (DG);
    070     * 26-Mar-2003 : Implemented Serializable (DG);
    071     * 09-May-2003 : Added AxisLocation parameter to translation methods (DG);
    072     * 13-Aug-2003 : Implemented Cloneable (DG);
    073     * 01-Sep-2003 : Fixed bug 793167 (setMaximumAxisValue exception) (DG);
    074     * 02-Sep-2003 : Fixed bug 795366 (zooming on inverted axes) (DG);
    075     * 08-Sep-2003 : Completed Serialization support (NB);
    076     * 08-Sep-2003 : Renamed get/setMinimumValue --> get/setLowerBound,
    077     *               and get/setMaximumValue --> get/setUpperBound (DG);
    078     * 27-Oct-2003 : Changed DEFAULT_AUTO_RANGE_MINIMUM_SIZE value - see bug ID
    079     *               829606 (DG);
    080     * 07-Nov-2003 : Changes to tick mechanism (DG);
    081     * 06-Jan-2004 : Moved axis line attributes to Axis class (DG);
    082     * 21-Jan-2004 : Removed redundant axisLineVisible attribute.  Renamed
    083     *               translateJava2DToValue --> java2DToValue, and
    084     *               translateValueToJava2D --> valueToJava2D (DG);
    085     * 23-Jan-2004 : Fixed setAxisLinePaint() and setAxisLineStroke() which had no
    086     *               effect (andreas.gawecki@coremedia.com);
    087     * 07-Apr-2004 : Changed text bounds calculation (DG);
    088     * 26-Apr-2004 : Added getter/setter methods for arrow shapes (DG);
    089     * 18-May-2004 : Added methods to set axis range *including* current
    090     *               margins (DG);
    091     * 02-Jun-2004 : Fixed bug in setRangeWithMargins() method (DG);
    092     * 30-Sep-2004 : Moved drawRotatedString() from RefineryUtilities
    093     *               --> TextUtilities (DG);
    094     * 11-Jan-2005 : Removed deprecated methods in preparation for 1.0.0
    095     *               release (DG);
    096     * 21-Apr-2005 : Replaced Insets with RectangleInsets (DG);
    097     * ------------- JFREECHART 1.0.x ---------------------------------------------
    098     * 10-Oct-2006 : Source reformatting (DG);
    099     * 22-Mar-2007 : Added new defaultAutoRange attribute (DG);
    100     *
    101     */
    102
    103    package org.jfree.chart.axis;
    104
    105    import java.awt.Font;
    106    import java.awt.FontMetrics;
    107    import java.awt.Graphics2D;
    108    import java.awt.Polygon;
    109    import java.awt.Shape;
    110    import java.awt.font.LineMetrics;
    111    import java.awt.geom.AffineTransform;
    112    import java.awt.geom.Line2D;
    113    import java.awt.geom.Rectangle2D;
    114    import java.io.IOException;
    115    import java.io.ObjectInputStream;
    116    import java.io.ObjectOutputStream;
    117    import java.io.Serializable;
    118    import java.util.Iterator;
    119    import java.util.List;
    120
    121    import org.jfree.chart.event.AxisChangeEvent;
    122    import org.jfree.chart.plot.Plot;
    123    import org.jfree.data.Range;
    124    import org.jfree.io.SerialUtilities;
    125    import org.jfree.text.TextUtilities;
    126    import org.jfree.ui.RectangleEdge;
    127    import org.jfree.ui.RectangleInsets;
    128    import org.jfree.util.ObjectUtilities;
    129    import org.jfree.util.PublicCloneable;
    130
    131    /**
    132     * The base class for axes that display value data, where values are measured
    133     * using the <code>double</code> primitive.  The two key subclasses are
    134     * {@link DateAxis} and {@link NumberAxis}.
    135     */
    136    public abstract class ValueAxis extends Axis
    137                                    implements Cloneable, PublicCloneable,
    138                                               Serializable {
    139
    140        /** For serialization. */
    141        private static final long serialVersionUID = 3698345477322391456L;
    142
    143        /** The default axis range. */
    144        public static final Range DEFAULT_RANGE = new Range(0.0, 1.0);
    145
    146        /** The default auto-range value. */
    147        public static final boolean DEFAULT_AUTO_RANGE = true;
    148
    149        /** The default inverted flag setting. */
    150        public static final boolean DEFAULT_INVERTED = false;
    151
    152        /** The default minimum auto range. */
    153        public static final double DEFAULT_AUTO_RANGE_MINIMUM_SIZE = 0.00000001;
    154
    155        /** The default value for the lower margin (0.05 = 5%). */
    156        public static final double DEFAULT_LOWER_MARGIN = 0.05;
    157
    158        /** The default value for the upper margin (0.05 = 5%). */
    159        public static final double DEFAULT_UPPER_MARGIN = 0.05;
    160
    161        /**
    162         * The default lower bound for the axis.
    163         *
    164         * @deprecated From 1.0.5 onwards, the axis defines a defaultRange
    165         *     attribute (see {@link #getDefaultAutoRange()}).
    166         */
    167        public static final double DEFAULT_LOWER_BOUND = 0.0;
    168
    169        /**
    170         * The default upper bound for the axis.
    171         *
    172         * @deprecated From 1.0.5 onwards, the axis defines a defaultRange
    173         *     attribute (see {@link #getDefaultAutoRange()}).
    174         */
    175        public static final double DEFAULT_UPPER_BOUND = 1.0;
    176
    177        /** The default auto-tick-unit-selection value. */
    178        public static final boolean DEFAULT_AUTO_TICK_UNIT_SELECTION = true;
    179
    180        /** The maximum tick count. */
    181        public static final int MAXIMUM_TICK_COUNT = 500;
    182
    183        /**
    184         * A flag that controls whether an arrow is drawn at the positive end of
    185         * the axis line.
    186         */
    187        private boolean positiveArrowVisible;
    188
    189        /**
    190         * A flag that controls whether an arrow is drawn at the negative end of
    191         * the axis line.
    192         */
    193        private boolean negativeArrowVisible;
    194
    195        /** The shape used for an up arrow. */
    196        private transient Shape upArrow;
    197
    198        /** The shape used for a down arrow. */
    199        private transient Shape downArrow;
    200
    201        /** The shape used for a left arrow. */
    202        private transient Shape leftArrow;
    203
    204        /** The shape used for a right arrow. */
    205        private transient Shape rightArrow;
    206
    207        /** A flag that affects the orientation of the values on the axis. */
    208        private boolean inverted;
    209
    210        /** The axis range. */
    211        private Range range;
    212
    213        /**
    214         * Flag that indicates whether the axis automatically scales to fit the
    215         * chart data.
    216         */
    217        private boolean autoRange;
    218
    219        /** The minimum size for the 'auto' axis range (excluding margins). */
    220        private double autoRangeMinimumSize;
    221
    222        /**
    223         * The default range is used when the dataset is empty and the axis needs
    224         * to determine the auto range.
    225         *
    226         * @since 1.0.5
    227         */
    228        private Range defaultAutoRange;
    229
    230        /**
    231         * The upper margin percentage.  This indicates the amount by which the
    232         * maximum axis value exceeds the maximum data value (as a percentage of
    233         * the range on the axis) when the axis range is determined automatically.
    234         */
    235        private double upperMargin;
    236
    237        /**
    238         * The lower margin.  This is a percentage that indicates the amount by
    239         * which the minimum axis value is "less than" the minimum data value when
    240         * the axis range is determined automatically.
    241         */
    242        private double lowerMargin;
    243
    244        /**
    245         * If this value is positive, the amount is subtracted from the maximum
    246         * data value to determine the lower axis range.  This can be used to
    247         * provide a fixed "window" on dynamic data.
    248         */
    249        private double fixedAutoRange;
    250
    251        /**
    252         * Flag that indicates whether or not the tick unit is selected
    253         * automatically.
    254         */
    255        private boolean autoTickUnitSelection;
    256
    257        /** The standard tick units for the axis. */
    258        private TickUnitSource standardTickUnits;
    259
    260        /** An index into an array of standard tick values. */
    261        private int autoTickIndex;
    262
    263        /** A flag indicating whether or not tick labels are rotated to vertical. */
    264        private boolean verticalTickLabels;
    265
    266        /**
    267         * Constructs a value axis.
    268         *
    269         * @param label  the axis label (<code>null</code> permitted).
    270         * @param standardTickUnits  the source for standard tick units
    271         *                           (<code>null</code> permitted).
    272         */
    273        protected ValueAxis(String label, TickUnitSource standardTickUnits) {
    274
    275            super(label);
    276
    277            this.positiveArrowVisible = false;
    278            this.negativeArrowVisible = false;
    279
    280            this.range = DEFAULT_RANGE;
    281            this.autoRange = DEFAULT_AUTO_RANGE;
    282            this.defaultAutoRange = DEFAULT_RANGE;
    283
    284            this.inverted = DEFAULT_INVERTED;
    285            this.autoRangeMinimumSize = DEFAULT_AUTO_RANGE_MINIMUM_SIZE;
    286
    287            this.lowerMargin = DEFAULT_LOWER_MARGIN;
    288            this.upperMargin = DEFAULT_UPPER_MARGIN;
    289
    290            this.fixedAutoRange = 0.0;
    291
    292            this.autoTickUnitSelection = DEFAULT_AUTO_TICK_UNIT_SELECTION;
    293            this.standardTickUnits = standardTickUnits;
    294
    295            Polygon p1 = new Polygon();
    296            p1.addPoint(0, 0);
    297            p1.addPoint(-2, 2);
    298            p1.addPoint(2, 2);
    299
    300            this.upArrow = p1;
    301
    302            Polygon p2 = new Polygon();
    303            p2.addPoint(0, 0);
    304            p2.addPoint(-2, -2);
    305            p2.addPoint(2, -2);
    306
    307            this.downArrow = p2;
    308
    309            Polygon p3 = new Polygon();
    310            p3.addPoint(0, 0);
    311            p3.addPoint(-2, -2);
    312            p3.addPoint(-2, 2);
    313
    314            this.rightArrow = p3;
    315
    316            Polygon p4 = new Polygon();
    317            p4.addPoint(0, 0);
    318            p4.addPoint(2, -2);
    319            p4.addPoint(2, 2);
    320
    321            this.leftArrow = p4;
    322
    323            this.verticalTickLabels = false;
    324
    325        }
    326
    327        /**
    328         * Returns <code>true</code> if the tick labels should be rotated (to
    329         * vertical), and <code>false</code> otherwise.
    330         *
    331         * @return <code>true</code> or <code>false</code>.
    332         *
    333         * @see #setVerticalTickLabels(boolean)
    334         */
    335        public boolean isVerticalTickLabels() {
    336            return this.verticalTickLabels;
    337        }
    338
    339        /**
    340         * Sets the flag that controls whether the tick labels are displayed
    341         * vertically (that is, rotated 90 degrees from horizontal).  If the flag
    342         * is changed, an {@link AxisChangeEvent} is sent to all registered
    343         * listeners.
    344         *
    345         * @param flag  the flag.
    346         *
    347         * @see #isVerticalTickLabels()
    348         */
    349        public void setVerticalTickLabels(boolean flag) {
    350            if (this.verticalTickLabels != flag) {
    351                this.verticalTickLabels = flag;
    352                notifyListeners(new AxisChangeEvent(this));
    353            }
    354        }
    355
    356        /**
    357         * Returns a flag that controls whether or not the axis line has an arrow
    358         * drawn that points in the positive direction for the axis.
    359         *
    360         * @return A boolean.
    361         *
    362         * @see #setPositiveArrowVisible(boolean)
    363         */
    364        public boolean isPositiveArrowVisible() {
    365            return this.positiveArrowVisible;
    366        }
    367
    368        /**
    369         * Sets a flag that controls whether or not the axis lines has an arrow
    370         * drawn that points in the positive direction for the axis, and sends an
    371         * {@link AxisChangeEvent} to all registered listeners.
    372         *
    373         * @param visible  the flag.
    374         *
    375         * @see #isPositiveArrowVisible()
    376         */
    377        public void setPositiveArrowVisible(boolean visible) {
    378            this.positiveArrowVisible = visible;
    379            notifyListeners(new AxisChangeEvent(this));
    380        }
    381
    382        /**
    383         * Returns a flag that controls whether or not the axis line has an arrow
    384         * drawn that points in the negative direction for the axis.
    385         *
    386         * @return A boolean.
    387         *
    388         * @see #setNegativeArrowVisible(boolean)
    389         */
    390        public boolean isNegativeArrowVisible() {
    391            return this.negativeArrowVisible;
    392        }
    393
    394        /**
    395         * Sets a flag that controls whether or not the axis lines has an arrow
    396         * drawn that points in the negative direction for the axis, and sends an
    397         * {@link AxisChangeEvent} to all registered listeners.
    398         *
    399         * @param visible  the flag.
    400         *
    401         * @see #setNegativeArrowVisible(boolean)
    402         */
    403        public void setNegativeArrowVisible(boolean visible) {
    404            this.negativeArrowVisible = visible;
    405            notifyListeners(new AxisChangeEvent(this));
    406        }
    407
    408        /**
    409         * Returns a shape that can be displayed as an arrow pointing upwards at
    410         * the end of an axis line.
    411         *
    412         * @return A shape (never <code>null</code>).
    413         *
    414         * @see #setUpArrow(Shape)
    415         */
    416        public Shape getUpArrow() {
    417            return this.upArrow;
    418        }
    419
    420        /**
    421         * Sets the shape that can be displayed as an arrow pointing upwards at
    422         * the end of an axis line and sends an {@link AxisChangeEvent} to all
    423         * registered listeners.
    424         *
    425         * @param arrow  the arrow shape (<code>null</code> not permitted).
    426         *
    427         * @see #getUpArrow()
    428         */
    429        public void setUpArrow(Shape arrow) {
    430            if (arrow == null) {
    431                throw new IllegalArgumentException("Null 'arrow' argument.");
    432            }
    433            this.upArrow = arrow;
    434            notifyListeners(new AxisChangeEvent(this));
    435        }
    436
    437        /**
    438         * Returns a shape that can be displayed as an arrow pointing downwards at
    439         * the end of an axis line.
    440         *
    441         * @return A shape (never <code>null</code>).
    442         *
    443         * @see #setDownArrow(Shape)
    444         */
    445        public Shape getDownArrow() {
    446            return this.downArrow;
    447        }
    448
    449        /**
    450         * Sets the shape that can be displayed as an arrow pointing downwards at
    451         * the end of an axis line and sends an {@link AxisChangeEvent} to all
    452         * registered listeners.
    453         *
    454         * @param arrow  the arrow shape (<code>null</code> not permitted).
    455         *
    456         * @see #getDownArrow()
    457         */
    458        public void setDownArrow(Shape arrow) {
    459            if (arrow == null) {
    460                throw new IllegalArgumentException("Null 'arrow' argument.");
    461            }
    462            this.downArrow = arrow;
    463            notifyListeners(new AxisChangeEvent(this));
    464        }
    465
    466        /**
    467         * Returns a shape that can be displayed as an arrow pointing left at the
    468         * end of an axis line.
    469         *
    470         * @return A shape (never <code>null</code>).
    471         *
    472         * @see #setLeftArrow(Shape)
    473         */
    474        public Shape getLeftArrow() {
    475            return this.leftArrow;
    476        }
    477
    478        /**
    479         * Sets the shape that can be displayed as an arrow pointing left at the
    480         * end of an axis line and sends an {@link AxisChangeEvent} to all
    481         * registered listeners.
    482         *
    483         * @param arrow  the arrow shape (<code>null</code> not permitted).
    484         *
    485         * @see #getLeftArrow()
    486         */
    487        public void setLeftArrow(Shape arrow) {
    488            if (arrow == null) {
    489                throw new IllegalArgumentException("Null 'arrow' argument.");
    490            }
    491            this.leftArrow = arrow;
    492            notifyListeners(new AxisChangeEvent(this));
    493        }
    494
    495        /**
    496         * Returns a shape that can be displayed as an arrow pointing right at the
    497         * end of an axis line.
    498         *
    499         * @return A shape (never <code>null</code>).
    500         *
    501         * @see #setRightArrow(Shape)
    502         */
    503        public Shape getRightArrow() {
    504            return this.rightArrow;
    505        }
    506
    507        /**
    508         * Sets the shape that can be displayed as an arrow pointing rightwards at
    509         * the end of an axis line and sends an {@link AxisChangeEvent} to all
    510         * registered listeners.
    511         *
    512         * @param arrow  the arrow shape (<code>null</code> not permitted).
    513         *
    514         * @see #getRightArrow()
    515         */
    516        public void setRightArrow(Shape arrow) {
    517            if (arrow == null) {
    518                throw new IllegalArgumentException("Null 'arrow' argument.");
    519            }
    520            this.rightArrow = arrow;
    521            notifyListeners(new AxisChangeEvent(this));
    522        }
    523
    524        /**
    525         * Draws an axis line at the current cursor position and edge.
    526         *
    527         * @param g2  the graphics device.
    528         * @param cursor  the cursor position.
    529         * @param dataArea  the data area.
    530         * @param edge  the edge.
    531         */
    532        protected void drawAxisLine(Graphics2D g2, double cursor,
    533                                    Rectangle2D dataArea, RectangleEdge edge) {
    534            Line2D axisLine = null;
    535            if (edge == RectangleEdge.TOP) {
    536                axisLine = new Line2D.Double(dataArea.getX(), cursor,
    537                        dataArea.getMaxX(), cursor);
    538            }
    539            else if (edge == RectangleEdge.BOTTOM) {
    540                axisLine = new Line2D.Double(dataArea.getX(), cursor,
    541                        dataArea.getMaxX(), cursor);
    542            }
    543            else if (edge == RectangleEdge.LEFT) {
    544                axisLine = new Line2D.Double(cursor, dataArea.getY(), cursor,
    545                        dataArea.getMaxY());
    546            }
    547            else if (edge == RectangleEdge.RIGHT) {
    548                axisLine = new Line2D.Double(cursor, dataArea.getY(), cursor,
    549                        dataArea.getMaxY());
    550            }
    551            g2.setPaint(getAxisLinePaint());
    552            g2.setStroke(getAxisLineStroke());
    553            g2.draw(axisLine);
    554
    555            boolean drawUpOrRight = false;
    556            boolean drawDownOrLeft = false;
    557            if (this.positiveArrowVisible) {
    558                if (this.inverted) {
    559                    drawDownOrLeft = true;
    560                }
    561                else {
    562                    drawUpOrRight = true;
    563                }
    564            }
    565            if (this.negativeArrowVisible) {
    566                if (this.inverted) {
    567                    drawUpOrRight = true;
    568                }
    569                else {
    570                    drawDownOrLeft = true;
    571                }
    572            }
    573            if (drawUpOrRight) {
    574                double x = 0.0;
    575                double y = 0.0;
    576                Shape arrow = null;
    577                if (edge == RectangleEdge.TOP || edge == RectangleEdge.BOTTOM) {
    578                    x = dataArea.getMaxX();
    579                    y = cursor;
    580                    arrow = this.rightArrow;
    581                }
    582                else if (edge == RectangleEdge.LEFT
    583                        || edge == RectangleEdge.RIGHT) {
    584                    x = cursor;
    585                    y = dataArea.getMinY();
    586                    arrow = this.upArrow;
    587                }
    588
    589                // draw the arrow...
    590                AffineTransform transformer = new AffineTransform();
    591                transformer.setToTranslation(x, y);
    592                Shape shape = transformer.createTransformedShape(arrow);
    593                g2.fill(shape);
    594                g2.draw(shape);
    595            }
    596
    597            if (drawDownOrLeft) {
    598                double x = 0.0;
    599                double y = 0.0;
    600                Shape arrow = null;
    601                if (edge == RectangleEdge.TOP || edge == RectangleEdge.BOTTOM) {
    602                    x = dataArea.getMinX();
    603                    y = cursor;
    604                    arrow = this.leftArrow;
    605                }
    606                else if (edge == RectangleEdge.LEFT
    607                        || edge == RectangleEdge.RIGHT) {
    608                    x = cursor;
    609                    y = dataArea.getMaxY();
    610                    arrow = this.downArrow;
    611                }
    612
    613                // draw the arrow...
    614                AffineTransform transformer = new AffineTransform();
    615                transformer.setToTranslation(x, y);
    616                Shape shape = transformer.createTransformedShape(arrow);
    617                g2.fill(shape);
    618                g2.draw(shape);
    619            }
    620
    621        }
    622
    623        /**
    624         * Calculates the anchor point for a tick label.
    625         *
    626         * @param tick  the tick.
    627         * @param cursor  the cursor.
    628         * @param dataArea  the data area.
    629         * @param edge  the edge on which the axis is drawn.
    630         *
    631         * @return The x and y coordinates of the anchor point.
    632         */
    633        protected float[] calculateAnchorPoint(ValueTick tick,
    634                                               double cursor,
    635                                               Rectangle2D dataArea,
    636                                               RectangleEdge edge) {
    637
    638            RectangleInsets insets = getTickLabelInsets();
    639            float[] result = new float[2];
    640            if (edge == RectangleEdge.TOP) {
    641                result[0] = (float) valueToJava2D(tick.getValue(), dataArea, edge);
    642                result[1] = (float) (cursor - insets.getBottom() - 2.0);
    643            }
    644            else if (edge == RectangleEdge.BOTTOM) {
    645                result[0] = (float) valueToJava2D(tick.getValue(), dataArea, edge);
    646                result[1] = (float) (cursor + insets.getTop() + 2.0);
    647            }
    648            else if (edge == RectangleEdge.LEFT) {
    649                result[0] = (float) (cursor - insets.getLeft() - 2.0);
    650                result[1] = (float) valueToJava2D(tick.getValue(), dataArea, edge);
    651            }
    652            else if (edge == RectangleEdge.RIGHT) {
    653                result[0] = (float) (cursor + insets.getRight() + 2.0);
    654                result[1] = (float) valueToJava2D(tick.getValue(), dataArea, edge);
    655            }
    656            return result;
    657        }
    658
    659        /**
    660         * Draws the axis line, tick marks and tick mark labels.
    661         *
    662         * @param g2  the graphics device.
    663         * @param cursor  the cursor.
    664         * @param plotArea  the plot area.
    665         * @param dataArea  the data area.
    666         * @param edge  the edge that the axis is aligned with.
    667         *
    668         * @return The width or height used to draw the axis.
    669         */
    670        protected AxisState drawTickMarksAndLabels(Graphics2D g2,
    671                                                   double cursor,
    672                                                   Rectangle2D plotArea,
    673                                                   Rectangle2D dataArea,
    674                                                   RectangleEdge edge) {
    675
    676            AxisState state = new AxisState(cursor);
    677
    678            if (isAxisLineVisible()) {
    679                drawAxisLine(g2, cursor, dataArea, edge);
    680            }
    681
    682            double ol = getTickMarkOutsideLength();
    683            double il = getTickMarkInsideLength();
    684
    685            List ticks = refreshTicks(g2, state, dataArea, edge);
    686            state.setTicks(ticks);
    687            g2.setFont(getTickLabelFont());
    688            Iterator iterator = ticks.iterator();
    689            while (iterator.hasNext()) {
    690                ValueTick tick = (ValueTick) iterator.next();
    691                if (isTickLabelsVisible()) {
    692                    g2.setPaint(getTickLabelPaint());
    693                    float[] anchorPoint = calculateAnchorPoint(tick, cursor,
    694                            dataArea, edge);
    695                    TextUtilities.drawRotatedString(tick.getText(), g2,
    696                            anchorPoint[0], anchorPoint[1], tick.getTextAnchor(),
    697                            tick.getAngle(), tick.getRotationAnchor());
    698                }
    699
    700                if (isTickMarksVisible()) {
    701                    float xx = (float) valueToJava2D(tick.getValue(), dataArea,
    702                            edge);
    703                    Line2D mark = null;
    704                    g2.setStroke(getTickMarkStroke());
    705                    g2.setPaint(getTickMarkPaint());
    706                    if (edge == RectangleEdge.LEFT) {
    707                        mark = new Line2D.Double(cursor - ol, xx, cursor + il, xx);
    708                    }
    709                    else if (edge == RectangleEdge.RIGHT) {
    710                        mark = new Line2D.Double(cursor + ol, xx, cursor - il, xx);
    711                    }
    712                    else if (edge == RectangleEdge.TOP) {
    713                        mark = new Line2D.Double(xx, cursor - ol, xx, cursor + il);
    714                    }
    715                    else if (edge == RectangleEdge.BOTTOM) {
    716                        mark = new Line2D.Double(xx, cursor + ol, xx, cursor - il);
    717                    }
    718                    g2.draw(mark);
    719                }
    720            }
    721
    722            // need to work out the space used by the tick labels...
    723            // so we can update the cursor...
    724            double used = 0.0;
    725            if (isTickLabelsVisible()) {
    726                if (edge == RectangleEdge.LEFT) {
    727                    used += findMaximumTickLabelWidth(ticks, g2, plotArea,
    728                            isVerticalTickLabels());
    729                    state.cursorLeft(used);
    730                }
    731                else if (edge == RectangleEdge.RIGHT) {
    732                    used = findMaximumTickLabelWidth(ticks, g2, plotArea,
    733                            isVerticalTickLabels());
    734                    state.cursorRight(used);
    735                }
    736                else if (edge == RectangleEdge.TOP) {
    737                    used = findMaximumTickLabelHeight(ticks, g2, plotArea,
    738                            isVerticalTickLabels());
    739                    state.cursorUp(used);
    740                }
    741                else if (edge == RectangleEdge.BOTTOM) {
    742                    used = findMaximumTickLabelHeight(ticks, g2, plotArea,
    743                            isVerticalTickLabels());
    744                    state.cursorDown(used);
    745                }
    746            }
    747
    748            return state;
    749        }
    750
    751        /**
    752         * Returns the space required to draw the axis.
    753         *
    754         * @param g2  the graphics device.
    755         * @param plot  the plot that the axis belongs to.
    756         * @param plotArea  the area within which the plot should be drawn.
    757         * @param edge  the axis location.
    758         * @param space  the space already reserved (for other axes).
    759         *
    760         * @return The space required to draw the axis (including pre-reserved
    761         *         space).
    762         */
    763        public AxisSpace reserveSpace(Graphics2D g2, Plot plot,
    764                                      Rectangle2D plotArea,
    765                                      RectangleEdge edge, AxisSpace space) {
    766
    767            // create a new space object if one wasn't supplied...
    768            if (space == null) {
    769                space = new AxisSpace();
    770            }
    771
    772            // if the axis is not visible, no additional space is required...
    773            if (!isVisible()) {
    774                return space;
    775            }
    776
    777            // if the axis has a fixed dimension, return it...
    778            double dimension = getFixedDimension();
    779            if (dimension > 0.0) {
    780                space.ensureAtLeast(dimension, edge);
    781            }
    782
    783            // calculate the max size of the tick labels (if visible)...
    784            double tickLabelHeight = 0.0;
    785            double tickLabelWidth = 0.0;
    786            if (isTickLabelsVisible()) {
    787                g2.setFont(getTickLabelFont());
    788                List ticks = refreshTicks(g2, new AxisState(), plotArea, edge);
    789                if (RectangleEdge.isTopOrBottom(edge)) {
    790                    tickLabelHeight = findMaximumTickLabelHeight(ticks, g2,
    791                            plotArea, isVerticalTickLabels());
    792                }
    793                else if (RectangleEdge.isLeftOrRight(edge)) {
    794                    tickLabelWidth = findMaximumTickLabelWidth(ticks, g2, plotArea,
    795                            isVerticalTickLabels());
    796                }
    797            }
    798
    799            // get the axis label size and update the space object...
    800            Rectangle2D labelEnclosure = getLabelEnclosure(g2, edge);
    801            double labelHeight = 0.0;
    802            double labelWidth = 0.0;
    803            if (RectangleEdge.isTopOrBottom(edge)) {
    804                labelHeight = labelEnclosure.getHeight();
    805                space.add(labelHeight + tickLabelHeight, edge);
    806            }
    807            else if (RectangleEdge.isLeftOrRight(edge)) {
    808                labelWidth = labelEnclosure.getWidth();
    809                space.add(labelWidth + tickLabelWidth, edge);
    810            }
    811
    812            return space;
    813
    814        }
    815
    816        /**
    817         * A utility method for determining the height of the tallest tick label.
    818         *
    819         * @param ticks  the ticks.
    820         * @param g2  the graphics device.
    821         * @param drawArea  the area within which the plot and axes should be drawn.
    822         * @param vertical  a flag that indicates whether or not the tick labels
    823         *                  are 'vertical'.
    824         *
    825         * @return The height of the tallest tick label.
    826         */
    827        protected double findMaximumTickLabelHeight(List ticks,
    828                                                    Graphics2D g2,
    829                                                    Rectangle2D drawArea,
    830                                                    boolean vertical) {
    831
    832            RectangleInsets insets = getTickLabelInsets();
    833            Font font = getTickLabelFont();
    834            double maxHeight = 0.0;
    835            if (vertical) {
    836                FontMetrics fm = g2.getFontMetrics(font);
    837                Iterator iterator = ticks.iterator();
    838                while (iterator.hasNext()) {
    839                    Tick tick = (Tick) iterator.next();
    840                    Rectangle2D labelBounds = TextUtilities.getTextBounds(
    841                            tick.getText(), g2, fm);
    842                    if (labelBounds.getWidth() + insets.getTop()
    843                            + insets.getBottom() > maxHeight) {
    844                        maxHeight = labelBounds.getWidth()
    845                                    + insets.getTop() + insets.getBottom();
    846                    }
    847                }
    848            }
    849            else {
    850                LineMetrics metrics = font.getLineMetrics("ABCxyz",
    851                        g2.getFontRenderContext());
    852                maxHeight = metrics.getHeight()
    853                            + insets.getTop() + insets.getBottom();
    854            }
    855            return maxHeight;
    856
    857        }
    858
    859        /**
    860         * A utility method for determining the width of the widest tick label.
    861         *
    862         * @param ticks  the ticks.
    863         * @param g2  the graphics device.
    864         * @param drawArea  the area within which the plot and axes should be drawn.
    865         * @param vertical  a flag that indicates whether or not the tick labels
    866         *                  are 'vertical'.
    867         *
    868         * @return The width of the tallest tick label.
    869         */
    870        protected double findMaximumTickLabelWidth(List ticks,
    871                                                   Graphics2D g2,
    872                                                   Rectangle2D drawArea,
    873                                                   boolean vertical) {
    874
    875            RectangleInsets insets = getTickLabelInsets();
    876            Font font = getTickLabelFont();
    877            double maxWidth = 0.0;
    878            if (!vertical) {
    879                FontMetrics fm = g2.getFontMetrics(font);
    880                Iterator iterator = ticks.iterator();
    881                while (iterator.hasNext()) {
    882                    Tick tick = (Tick) iterator.next();
    883                    Rectangle2D labelBounds = TextUtilities.getTextBounds(
    884                            tick.getText(), g2, fm);
    885                    if (labelBounds.getWidth() + insets.getLeft()
    886                            + insets.getRight() > maxWidth) {
    887                        maxWidth = labelBounds.getWidth()
    888                                   + insets.getLeft() + insets.getRight();
    889                    }
    890                }
    891            }
    892            else {
    893                LineMetrics metrics = font.getLineMetrics("ABCxyz",
    894                        g2.getFontRenderContext());
    895                maxWidth = metrics.getHeight()
    896                           + insets.getTop() + insets.getBottom();
    897            }
    898            return maxWidth;
    899
    900        }
    901
    902        /**
    903         * Returns a flag that controls the direction of values on the axis.
    904         * <P>
    905         * For a regular axis, values increase from left to right (for a horizontal
    906         * axis) and bottom to top (for a vertical axis).  When the axis is
    907         * 'inverted', the values increase in the opposite direction.
    908         *
    909         * @return The flag.
    910         *
    911         * @see #setInverted(boolean)
    912         */
    913        public boolean isInverted() {
    914            return this.inverted;
    915        }
    916
    917        /**
    918         * Sets a flag that controls the direction of values on the axis, and
    919         * notifies registered listeners that the axis has changed.
    920         *
    921         * @param flag  the flag.
    922         *
    923         * @see #isInverted()
    924         */
    925        public void setInverted(boolean flag) {
    926
    927            if (this.inverted != flag) {
    928                this.inverted = flag;
    929                notifyListeners(new AxisChangeEvent(this));
    930            }
    931
    932        }
    933
    934        /**
    935         * Returns the flag that controls whether or not the axis range is
    936         * automatically adjusted to fit the data values.
    937         *
    938         * @return The flag.
    939         *
    940         * @see #setAutoRange(boolean)
    941         */
    942        public boolean isAutoRange() {
    943            return this.autoRange;
    944        }
    945
    946        /**
    947         * Sets a flag that determines whether or not the axis range is
    948         * automatically adjusted to fit the data, and notifies registered
    949         * listeners that the axis has been modified.
    950         *
    951         * @param auto  the new value of the flag.
    952         *
    953         * @see #isAutoRange()
    954         */
    955        public void setAutoRange(boolean auto) {
    956            setAutoRange(auto, true);
    957        }
    958
    959        /**
    960         * Sets the auto range attribute.  If the <code>notify</code> flag is set,
    961         * an {@link AxisChangeEvent} is sent to registered listeners.
    962         *
    963         * @param auto  the flag.
    964         * @param notify  notify listeners?
    965         *
    966         * @see #isAutoRange()
    967         */
    968        protected void setAutoRange(boolean auto, boolean notify) {
    969            if (this.autoRange != auto) {
    970                this.autoRange = auto;
    971                if (this.autoRange) {
    972                    autoAdjustRange();
    973                }
    974                if (notify) {
    975                    notifyListeners(new AxisChangeEvent(this));
    976                }
    977            }
    978        }
    979
    980        /**
    981         * Returns the minimum size allowed for the axis range when it is
    982         * automatically calculated.
    983         *
    984         * @return The minimum range.
    985         *
    986         * @see #setAutoRangeMinimumSize(double)
    987         */
    988        public double getAutoRangeMinimumSize() {
    989            return this.autoRangeMinimumSize;
    990        }
    991
    992        /**
    993         * Sets the auto range minimum size and sends an {@link AxisChangeEvent}
    994         * to all registered listeners.
    995         *
    996         * @param size  the size.
    997         *
    998         * @see #getAutoRangeMinimumSize()
    999         */
    1000        public void setAutoRangeMinimumSize(double size) {
    1001            setAutoRangeMinimumSize(size, true);
    1002        }
    1003
    1004        /**
    1005         * Sets the minimum size allowed for the axis range when it is
    1006         * automatically calculated.
    1007         * <p>
    1008         * If requested, an {@link AxisChangeEvent} is forwarded to all registered
    1009         * listeners.
    1010         *
    1011         * @param size  the new minimum.
    1012         * @param notify  notify listeners?
    1013         */
    1014        public void setAutoRangeMinimumSize(double size, boolean notify) {
    1015            if (size <= 0.0) {
    1016                throw new IllegalArgumentException(
    1017                    "NumberAxis.setAutoRangeMinimumSize(double): must be > 0.0.");
    1018            }
    1019            if (this.autoRangeMinimumSize != size) {
    1020                this.autoRangeMinimumSize = size;
    1021                if (this.autoRange) {
    1022                    autoAdjustRange();
    1023                }
    1024                if (notify) {
    1025                    notifyListeners(new AxisChangeEvent(this));
    1026                }
    1027            }
    1028
    1029        }
    1030
    1031        /**
    1032         * Returns the default auto range.
    1033         *
    1034         * @return The default auto range (never <code>null</code>).
    1035         *
    1036         * @see #setDefaultAutoRange(Range)
    1037         * @since 1.0.5
    1038         */
    1039        public Range getDefaultAutoRange() {
    1040            return this.defaultAutoRange;
    1041        }
    1042
    1043        /**
    1044         * Sets the default auto range and sends an {@link AxisChangeEvent} to all
    1045         * registered listeners.
    1046         *
    1047         * @param range  the range (<code>null</code> not permitted).
    1048         *
    1049         * @see #getDefaultAutoRange()
    1050         *
    1051         * @since 1.0.5
    1052         */
    1053        public void setDefaultAutoRange(Range range) {
    1054            if (range == null) {
    1055                throw new IllegalArgumentException("Null 'range' argument.");
    1056            }
    1057            this.defaultAutoRange = range;
    1058            notifyListeners(new AxisChangeEvent(this));
    1059        }
    1060
    1061        /**
    1062         * Returns the lower margin for the axis, expressed as a percentage of the
    1063         * axis range.  This controls the space added to the lower end of the axis
    1064         * when the axis range is automatically calculated (it is ignored when the
    1065         * axis range is set explicitly). The default value is 0.05 (five percent).
    1066         *
    1067         * @return The lower margin.
    1068         *
    1069         * @see #setLowerMargin(double)
    1070         */
    1071        public double getLowerMargin() {
    1072            return this.lowerMargin;
    1073        }
    1074
    1075        /**
    1076         * Sets the lower margin for the axis (as a percentage of the axis range)
    1077         * and sends an {@link AxisChangeEvent} to all registered listeners.  This
    1078         * margin is added only when the axis range is auto-calculated - if you set
    1079         * the axis range manually, the margin is ignored.
    1080         *
    1081         * @param margin  the margin percentage (for example, 0.05 is five percent).
    1082         *
    1083         * @see #getLowerMargin()
    1084         * @see #setUpperMargin(double)
    1085         */
    1086        public void setLowerMargin(double margin) {
    1087            this.lowerMargin = margin;
    1088            if (isAutoRange()) {
    1089                autoAdjustRange();
    1090            }
    1091            notifyListeners(new AxisChangeEvent(this));
    1092        }
    1093
    1094        /**
    1095         * Returns the upper margin for the axis, expressed as a percentage of the
    1096         * axis range.  This controls the space added to the lower end of the axis
    1097         * when the axis range is automatically calculated (it is ignored when the
    1098         * axis range is set explicitly). The default value is 0.05 (five percent).
    1099         *
    1100         * @return The upper margin.
    1101         *
    1102         * @see #setUpperMargin(double)
    1103         */
    1104        public double getUpperMargin() {
    1105            return this.upperMargin;
    1106        }
    1107
    1108        /**
    1109         * Sets the upper margin for the axis (as a percentage of the axis range)
    1110         * and sends an {@link AxisChangeEvent} to all registered listeners.  This
    1111         * margin is added only when the axis range is auto-calculated - if you set
    1112         * the axis range manually, the margin is ignored.
    1113         *
    1114         * @param margin  the margin percentage (for example, 0.05 is five percent).
    1115         *
    1116         * @see #getLowerMargin()
    1117         * @see #setLowerMargin(double)
    1118         */
    1119        public void setUpperMargin(double margin) {
    1120            this.upperMargin = margin;
    1121            if (isAutoRange()) {
    1122                autoAdjustRange();
    1123            }
    1124            notifyListeners(new AxisChangeEvent(this));
    1125        }
    1126
    1127        /**
    1128         * Returns the fixed auto range.
    1129         *
    1130         * @return The length.
    1131         *
    1132         * @see #setFixedAutoRange(double)
    1133         */
    1134        public double getFixedAutoRange() {
    1135            return this.fixedAutoRange;
    1136        }
    1137
    1138        /**
    1139         * Sets the fixed auto range for the axis.
    1140         *
    1141         * @param length  the range length.
    1142         *
    1143         * @see #getFixedAutoRange()
    1144         */
    1145        public void setFixedAutoRange(double length) {
    1146            this.fixedAutoRange = length;
    1147            if (isAutoRange()) {
    1148                autoAdjustRange();
    1149            }
    1150            notifyListeners(new AxisChangeEvent(this));
    1151        }
    1152
    1153        /**
    1154         * Returns the lower bound of the axis range.
    1155         *
    1156         * @return The lower bound.
    1157         *
    1158         * @see #setLowerBound(double)
    1159         */
    1160        public double getLowerBound() {
    1161            return this.range.getLowerBound();
    1162        }
    1163
    1164        /**
    1165         * Sets the lower bound for the axis range.  An {@link AxisChangeEvent} is
    1166         * sent to all registered listeners.
    1167         *
    1168         * @param min  the new minimum.
    1169         *
    1170         * @see #getLowerBound()
    1171         */
    1172        public void setLowerBound(double min) {
    1173            if (this.range.getUpperBound() > min) {
    1174                setRange(new Range(min, this.range.getUpperBound()));
    1175            }
    1176            else {
    1177                setRange(new Range(min, min + 1.0));
    1178            }
    1179        }
    1180
    1181        /**
    1182         * Returns the upper bound for the axis range.
    1183         *
    1184         * @return The upper bound.
    1185         *
    1186         * @see #setUpperBound(double)
    1187         */
    1188        public double getUpperBound() {
    1189            return this.range.getUpperBound();
    1190        }
    1191
    1192        /**
    1193         * Sets the upper bound for the axis range, and sends an
    1194         * {@link AxisChangeEvent} to all registered listeners.
    1195         *
    1196         * @param max  the new maximum.
    1197         *
    1198         * @see #getUpperBound()
    1199         */
    1200        public void setUpperBound(double max) {
    1201            if (this.range.getLowerBound() < max) {
    1202                setRange(new Range(this.range.getLowerBound(), max));
    1203            }
    1204            else {
    1205                setRange(max - 1.0, max);
    1206            }
    1207        }
    1208
    1209        /**
    1210         * Returns the range for the axis.
    1211         *
    1212         * @return The axis range (never <code>null</code>).
    1213         *
    1214         * @see #setRange(Range)
    1215         */
    1216        public Range getRange() {
    1217            return this.range;
    1218        }
    1219
    1220        /**
    1221         * Sets the range attribute and sends an {@link AxisChangeEvent} to all
    1222         * registered listeners.  As a side-effect, the auto-range flag is set to
    1223         * <code>false</code>.
    1224         *
    1225         * @param range  the range (<code>null</code> not permitted).
    1226         *
    1227         * @see #getRange()
    1228         */
    1229        public void setRange(Range range) {
    1230            // defer argument checking
    1231            setRange(range, true, true);
    1232        }
    1233
    1234        /**
    1235         * Sets the range for the axis, if requested, sends an
    1236         * {@link AxisChangeEvent} to all registered listeners.  As a side-effect,
    1237         * the auto-range flag is set to <code>false</code> (optional).
    1238         *
    1239         * @param range  the range (<code>null</code> not permitted).
    1240         * @param turnOffAutoRange  a flag that controls whether or not the auto
    1241         *                          range is turned off.
    1242         * @param notify  a flag that controls whether or not listeners are
    1243         *                notified.
    1244         *
    1245         * @see #getRange()
    1246         */
    1247        public void setRange(Range range, boolean turnOffAutoRange,
    1248                             boolean notify) {
    1249            if (range == null) {
    1250                throw new IllegalArgumentException("Null 'range' argument.");
    1251            }
    1252            if (turnOffAutoRange) {
    1253                this.autoRange = false;
    1254            }
    1255            this.range = range;
    1256            if (notify) {
    1257                notifyListeners(new AxisChangeEvent(this));
    1258            }
    1259        }
    1260
    1261        /**
    1262         * Sets the axis range and sends an {@link AxisChangeEvent} to all
    1263         * registered listeners.  As a side-effect, the auto-range flag is set to
    1264         * <code>false</code>.
    1265         *
    1266         * @param lower  the lower axis limit.
    1267         * @param upper  the upper axis limit.
    1268         *
    1269         * @see #getRange()
    1270         * @see #setRange(Range)
    1271         */
    1272        public void setRange(double lower, double upper) {
    1273            setRange(new Range(lower, upper));
    1274        }
    1275
    1276        /**
    1277         * Sets the range for the axis (after first adding the current margins to
    1278         * the specified range) and sends an {@link AxisChangeEvent} to all
    1279         * registered listeners.
    1280         *
    1281         * @param range  the range (<code>null</code> not permitted).
    1282         */
    1283        public void setRangeWithMargins(Range range) {
    1284            setRangeWithMargins(range, true, true);
    1285        }
    1286
    1287        /**
    1288         * Sets the range for the axis after first adding the current margins to
    1289         * the range and, if requested, sends an {@link AxisChangeEvent} to all
    1290         * registered listeners.  As a side-effect, the auto-range flag is set to
    1291         * <code>false</code> (optional).
    1292         *
    1293         * @param range  the range (excluding margins, <code>null</code> not
    1294         *               permitted).
    1295         * @param turnOffAutoRange  a flag that controls whether or not the auto
    1296         *                          range is turned off.
    1297         * @param notify  a flag that controls whether or not listeners are
    1298         *                notified.
    1299         */
    1300        public void setRangeWithMargins(Range range, boolean turnOffAutoRange,
    1301                                        boolean notify) {
    1302            if (range == null) {
    1303                throw new IllegalArgumentException("Null 'range' argument.");
    1304            }
    1305            setRange(Range.expand(range, getLowerMargin(), getUpperMargin()),
    1306                    turnOffAutoRange, notify);
    1307        }
    1308
    1309        /**
    1310         * Sets the axis range (after first adding the current margins to the
    1311         * range) and sends an {@link AxisChangeEvent} to all registered listeners.
    1312         * As a side-effect, the auto-range flag is set to <code>false</code>.
    1313         *
    1314         * @param lower  the lower axis limit.
    1315         * @param upper  the upper axis limit.
    1316         */
    1317        public void setRangeWithMargins(double lower, double upper) {
    1318            setRangeWithMargins(new Range(lower, upper));
    1319        }
    1320
    1321        /**
    1322         * Sets the axis range, where the new range is 'size' in length, and
    1323         * centered on 'value'.
    1324         *
    1325         * @param value  the central value.
    1326         * @param length  the range length.
    1327         */
    1328        public void setRangeAboutValue(double value, double length) {
    1329            setRange(new Range(value - length / 2, value + length / 2));
    1330        }
    1331
    1332        /**
    1333         * Returns a flag indicating whether or not the tick unit is automatically
    1334         * selected from a range of standard tick units.
    1335         *
    1336         * @return A flag indicating whether or not the tick unit is automatically
    1337         *         selected.
    1338         *
    1339         * @see #setAutoTickUnitSelection(boolean)
    1340         */
    1341        public boolean isAutoTickUnitSelection() {
    1342            return this.autoTickUnitSelection;
    1343        }
    1344
    1345        /**
    1346         * Sets a flag indicating whether or not the tick unit is automatically
    1347         * selected from a range of standard tick units.  If the flag is changed,
    1348         * registered listeners are notified that the chart has changed.
    1349         *
    1350         * @param flag  the new value of the flag.
    1351         *
    1352         * @see #isAutoTickUnitSelection()
    1353         */
    1354        public void setAutoTickUnitSelection(boolean flag) {
    1355            setAutoTickUnitSelection(flag, true);
    1356        }
    1357
    1358        /**
    1359         * Sets a flag indicating whether or not the tick unit is automatically
    1360         * selected from a range of standard tick units.
    1361         *
    1362         * @param flag  the new value of the flag.
    1363         * @param notify  notify listeners?
    1364         *
    1365         * @see #isAutoTickUnitSelection()
    1366         */
    1367        public void setAutoTickUnitSelection(boolean flag, boolean notify) {
    1368
    1369            if (this.autoTickUnitSelection != flag) {
    1370                this.autoTickUnitSelection = flag;
    1371                if (notify) {
    1372                    notifyListeners(new AxisChangeEvent(this));
    1373                }
    1374            }
    1375        }
    1376
    1377        /**
    1378         * Returns the source for obtaining standard tick units for the axis.
    1379         *
    1380         * @return The source (possibly <code>null</code>).
    1381         *
    1382         * @see #setStandardTickUnits(TickUnitSource)
    1383         */
    1384        public TickUnitSource getStandardTickUnits() {
    1385            return this.standardTickUnits;
    1386        }
    1387
    1388        /**
    1389         * Sets the source for obtaining standard tick units for the axis and sends
    1390         * an {@link AxisChangeEvent} to all registered listeners.  The axis will
    1391         * try to select the smallest tick unit from the source that does not cause
    1392         * the tick labels to overlap (see also the
    1393         * {@link #setAutoTickUnitSelection(boolean)} method.
    1394         *
    1395         * @param source  the source for standard tick units (<code>null</code>
    1396         *                permitted).
    1397         *
    1398         * @see #getStandardTickUnits()
    1399         */
    1400        public void setStandardTickUnits(TickUnitSource source) {
    1401            this.standardTickUnits = source;
    1402            notifyListeners(new AxisChangeEvent(this));
    1403        }
    1404
    1405        /**
    1406         * Converts a data value to a coordinate in Java2D space, assuming that the
    1407         * axis runs along one edge of the specified dataArea.
    1408         * <p>
    1409         * Note that it is possible for the coordinate to fall outside the area.
    1410         *
    1411         * @param value  the data value.
    1412         * @param area  the area for plotting the data.
    1413         * @param edge  the edge along which the axis lies.
    1414         *
    1415         * @return The Java2D coordinate.
    1416         *
    1417         * @see #java2DToValue(double, Rectangle2D, RectangleEdge)
    1418         */
    1419        public abstract double valueToJava2D(double value, Rectangle2D area,
    1420                                             RectangleEdge edge);
    1421
    1422        /**
    1423         * Converts a length in data coordinates into the corresponding length in
    1424         * Java2D coordinates.
    1425         *
    1426         * @param length  the length.
    1427         * @param area  the plot area.
    1428         * @param edge  the edge along which the axis lies.
    1429         *
    1430         * @return The length in Java2D coordinates.
    1431         */
    1432        public double lengthToJava2D(double length, Rectangle2D area,
    1433                                     RectangleEdge edge) {
    1434            double zero = valueToJava2D(0.0, area, edge);
    1435            double l = valueToJava2D(length, area, edge);
    1436            return Math.abs(l - zero);
    1437        }
    1438
    1439        /**
    1440         * Converts a coordinate in Java2D space to the corresponding data value,
    1441         * assuming that the axis runs along one edge of the specified dataArea.
    1442         *
    1443         * @param java2DValue  the coordinate in Java2D space.
    1444         * @param area  the area in which the data is plotted.
    1445         * @param edge  the edge along which the axis lies.
    1446         *
    1447         * @return The data value.
    1448         *
    1449         * @see #valueToJava2D(double, Rectangle2D, RectangleEdge)
    1450         */
    1451        public abstract double java2DToValue(double java2DValue,
    1452                                             Rectangle2D area,
    1453                                             RectangleEdge edge);
    1454
    1455        /**
    1456         * Automatically sets the axis range to fit the range of values in the
    1457         * dataset.  Sometimes this can depend on the renderer used as well (for
    1458         * example, the renderer may "stack" values, requiring an axis range
    1459         * greater than otherwise necessary).
    1460         */
    1461        protected abstract void autoAdjustRange();
    1462
    1463        /**
    1464         * Centers the axis range about the specified value and sends an
    1465         * {@link AxisChangeEvent} to all registered listeners.
    1466         *
    1467         * @param value  the center value.
    1468         */
    1469        public void centerRange(double value) {
    1470
    1471            double central = this.range.getCentralValue();
    1472            Range adjusted = new Range(this.range.getLowerBound() + value - central,
    1473                    this.range.getUpperBound() + value - central);
    1474            setRange(adjusted);
    1475
    1476        }
    1477
    1478        /**
    1479         * Increases or decreases the axis range by the specified percentage about
    1480         * the central value and sends an {@link AxisChangeEvent} to all registered
    1481         * listeners.
    1482         * <P>
    1483         * To double the length of the axis range, use 200% (2.0).
    1484         * To halve the length of the axis range, use 50% (0.5).
    1485         *
    1486         * @param percent  the resize factor.
    1487         */
    1488        public void resizeRange(double percent) {
    1489            resizeRange(percent, this.range.getCentralValue());
    1490        }
    1491
    1492        /**
    1493         * Increases or decreases the axis range by the specified percentage about
    1494         * the specified anchor value and sends an {@link AxisChangeEvent} to all
    1495         * registered listeners.
    1496         * <P>
    1497         * To double the length of the axis range, use 200% (2.0).
    1498         * To halve the length of the axis range, use 50% (0.5).
    1499         *
    1500         * @param percent  the resize factor.
    1501         * @param anchorValue  the new central value after the resize.
    1502         */
    1503        public void resizeRange(double percent, double anchorValue) {
    1504            if (percent > 0.0) {
    1505                double halfLength = this.range.getLength() * percent / 2;
    1506                Range adjusted = new Range(anchorValue - halfLength,
    1507                        anchorValue + halfLength);
    1508                setRange(adjusted);
    1509            }
    1510            else {
    1511                setAutoRange(true);
    1512            }
    1513        }
    1514
    1515        /**
    1516         * Zooms in on the current range.
    1517         *
    1518         * @param lowerPercent  the new lower bound.
    1519         * @param upperPercent  the new upper bound.
    1520         */
    1521        public void zoomRange(double lowerPercent, double upperPercent) {
    1522            double start = this.range.getLowerBound();
    1523            double length = this.range.getLength();
    1524            Range adjusted = null;
    1525            if (isInverted()) {
    1526                adjusted = new Range(start + (length * (1 - upperPercent)),
    1527                                     start + (length * (1 - lowerPercent)));
    1528            }
    1529            else {
    1530                adjusted = new Range(start + length * lowerPercent,
    1531                        start + length * upperPercent);
    1532            }
    1533            setRange(adjusted);
    1534        }
    1535
    1536        /**
    1537         * Returns the auto tick index.
    1538         *
    1539         * @return The auto tick index.
    1540         *
    1541         * @see #setAutoTickIndex(int)
    1542         */
    1543        protected int getAutoTickIndex() {
    1544            return this.autoTickIndex;
    1545        }
    1546
    1547        /**
    1548         * Sets the auto tick index.
    1549         *
    1550         * @param index  the new value.
    1551         *
    1552         * @see #getAutoTickIndex()
    1553         */
    1554        protected void setAutoTickIndex(int index) {
    1555            this.autoTickIndex = index;
    1556        }
    1557
    1558        /**
    1559         * Tests the axis for equality with an arbitrary object.
    1560         *
    1561         * @param obj  the object (<code>null</code> permitted).
    1562         *
    1563         * @return <code>true</code> or <code>false</code>.
    1564         */
    1565        public boolean equals(Object obj) {
    1566
    1567            if (obj == this) {
    1568                return true;
    1569            }
    1570            if (!(obj instanceof ValueAxis)) {
    1571                return false;
    1572            }
    1573
    1574            ValueAxis that = (ValueAxis) obj;
    1575
    1576            if (this.positiveArrowVisible != that.positiveArrowVisible) {
    1577                return false;
    1578            }
    1579            if (this.negativeArrowVisible != that.negativeArrowVisible) {
    1580                return false;
    1581            }
    1582            if (this.inverted != that.inverted) {
    1583                return false;
    1584            }
    1585            if (!ObjectUtilities.equal(this.range, that.range)) {
    1586                return false;
    1587            }
    1588            if (this.autoRange != that.autoRange) {
    1589                return false;
    1590            }
    1591            if (this.autoRangeMinimumSize != that.autoRangeMinimumSize) {
    1592                return false;
    1593            }
    1594            if (!this.defaultAutoRange.equals(that.defaultAutoRange)) {
    1595                return false;
    1596            }
    1597            if (this.upperMargin != that.upperMargin) {
    1598                return false;
    1599            }
    1600            if (this.lowerMargin != that.lowerMargin) {
    1601                return false;
    1602            }
    1603            if (this.fixedAutoRange != that.fixedAutoRange) {
    1604                return false;
    1605            }
    1606            if (this.autoTickUnitSelection != that.autoTickUnitSelection) {
    1607                return false;
    1608            }
    1609            if (!ObjectUtilities.equal(this.standardTickUnits,
    1610                    that.standardTickUnits)) {
    1611                return false;
    1612            }
    1613            if (this.verticalTickLabels != that.verticalTickLabels) {
    1614                return false;
    1615            }
    1616
    1617            return super.equals(obj);
    1618
    1619        }
    1620
    1621        /**
    1622         * Returns a clone of the object.
    1623         *
    1624         * @return A clone.
    1625         *
    1626         * @throws CloneNotSupportedException if some component of the axis does
    1627         *         not support cloning.
    1628         */
    1629        public Object clone() throws CloneNotSupportedException {
    1630            ValueAxis clone = (ValueAxis) super.clone();
    1631            return clone;
    1632        }
    1633
    1634        /**
    1635         * Provides serialization support.
    1636         *
    1637         * @param stream  the output stream.
    1638         *
    1639         * @throws IOException  if there is an I/O error.
    1640         */
    1641        private void writeObject(ObjectOutputStream stream) throws IOException {
    1642            stream.defaultWriteObject();
    1643            SerialUtilities.writeShape(this.upArrow, stream);
    1644            SerialUtilities.writeShape(this.downArrow, stream);
    1645            SerialUtilities.writeShape(this.leftArrow, stream);
    1646            SerialUtilities.writeShape(this.rightArrow, stream);
    1647        }
    1648
    1649        /**
    1650         * Provides serialization support.
    1651         *
    1652         * @param stream  the input stream.
    1653         *
    1654         * @throws IOException  if there is an I/O error.
    1655         * @throws ClassNotFoundException  if there is a classpath problem.
    1656         */
    1657        private void readObject(ObjectInputStream stream)
    1658                throws IOException, ClassNotFoundException {
    1659
    1660            stream.defaultReadObject();
    1661            this.upArrow = SerialUtilities.readShape(stream);
    1662            this.downArrow = SerialUtilities.readShape(stream);
    1663            this.leftArrow = SerialUtilities.readShape(stream);
    1664            this.rightArrow = SerialUtilities.readShape(stream);
    1665
    1666        }
    1667
    1668    }