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