    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2008, 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 publihed 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-2008, 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     * Changes
    038     * -------
    039     * 18-Sep-2001 : Added standard header and fixed DOS encoding problem (DG);
    040     * 23-Nov-2001 : Overhauled standard tick unit code (DG);
    041     * 04-Dec-2001 : Changed constructors to protected, and tidied up default
    042     *               values (DG);
    043     * 12-Dec-2001 : Fixed vertical gridlines bug (DG);
    044     * 16-Jan-2002 : Added an optional crosshair, based on the implementation by
    045     *               Jonathan Nash (DG);
    046     * 23-Jan-2002 : Moved the minimum and maximum values to here from NumberAxis,
    047     *               and changed the type from Number to double (DG);
    048     * 25-Feb-2002 : Added default value for autoRange. Changed autoAdjustRange
    049     *               from public to protected. Updated import statements (DG);
    050     * 23-Apr-2002 : Added setRange() method (DG);
    051     * 29-Apr-2002 : Added range adjustment methods (DG);
    052     * 13-Jun-2002 : Modified setCrosshairValue() to notify listeners only when the
    053     *               crosshairs are visible, to avoid unnecessary repaints, as
    054     *               suggested by Kees Kuip (DG);
    055     * 25-Jul-2002 : Moved lower and upper margin attributes from the NumberAxis
    056     *               class (DG);
    057     * 05-Sep-2002 : Updated constructor for changes in Axis class (DG);
    058     * 01-Oct-2002 : Fixed errors reported by Checkstyle (DG);
    059     * 04-Oct-2002 : Moved standardTickUnits from NumberAxis --> ValueAxis (DG);
    060     * 08-Nov-2002 : Moved to new package com.jrefinery.chart.axis (DG);
    061     * 19-Nov-2002 : Removed grid settings (now controlled by the plot) (DG);
    062     * 27-Nov-2002 : Moved the 'inverted' attributed from NumberAxis to
    063     *               ValueAxis (DG);
    064     * 03-Jan-2003 : Small fix to ensure auto-range minimum is observed
    065     *               immediately (DG);
    066     * 14-Jan-2003 : Changed autoRangeMinimumSize from Number --> double (DG);
    067     * 20-Jan-2003 : Replaced monolithic constructor (DG);
    068     * 26-Mar-2003 : Implemented Serializable (DG);
    069     * 09-May-2003 : Added AxisLocation parameter to translation methods (DG);
    070     * 13-Aug-2003 : Implemented Cloneable (DG);
    071     * 01-Sep-2003 : Fixed bug 793167 (setMaximumAxisValue exception) (DG);
    072     * 02-Sep-2003 : Fixed bug 795366 (zooming on inverted axes) (DG);
    073     * 08-Sep-2003 : Completed Serialization support (NB);
    074     * 08-Sep-2003 : Renamed get/setMinimumValue --> get/setLowerBound,
    075     *               and get/setMaximumValue --> get/setUpperBound (DG);
    076     * 27-Oct-2003 : Changed DEFAULT_AUTO_RANGE_MINIMUM_SIZE value - see bug ID
    077     *               829606 (DG);
    078     * 07-Nov-2003 : Changes to tick mechanism (DG);
    079     * 06-Jan-2004 : Moved axis line attributes to Axis class (DG);
    080     * 21-Jan-2004 : Removed redundant axisLineVisible attribute.  Renamed
    081     *               translateJava2DToValue --> java2DToValue, and
    082     *               translateValueToJava2D --> valueToJava2D (DG);
    083     * 23-Jan-2004 : Fixed setAxisLinePaint() and setAxisLineStroke() which had no
    084     *               effect (andreas.gawecki@coremedia.com);
    085     * 07-Apr-2004 : Changed text bounds calculation (DG);
    086     * 26-Apr-2004 : Added getter/setter methods for arrow shapes (DG);
    087     * 18-May-2004 : Added methods to set axis range *including* current
    088     *               margins (DG);
    089     * 02-Jun-2004 : Fixed bug in setRangeWithMargins() method (DG);
    090     * 30-Sep-2004 : Moved drawRotatedString() from RefineryUtilities
    091     *               --> TextUtilities (DG);
    092     * 11-Jan-2005 : Removed deprecated methods in preparation for 1.0.0
    093     *               release (DG);
    094     * 21-Apr-2005 : Replaced Insets with RectangleInsets (DG);
    095     * ------------- JFREECHART 1.0.x ---------------------------------------------
    096     * 10-Oct-2006 : Source reformatting (DG);
    097     * 22-Mar-2007 : Added new defaultAutoRange attribute (DG);
    098     * 02-Aug-2007 : Check for major tick when drawing label (DG);
    099     *
    100     */
    101
    102    package org.jfree.chart.axis;
    103
    104    import java.awt.Font;
    105    import java.awt.FontMetrics;
    106    import java.awt.Graphics2D;
    107    import java.awt.Polygon;
    108    import java.awt.Shape;
    109    import java.awt.font.LineMetrics;
    110    import java.awt.geom.AffineTransform;
    111    import java.awt.geom.Line2D;
    112    import java.awt.geom.Rectangle2D;
    113    import java.io.IOException;
    114    import java.io.ObjectInputStream;
    115    import java.io.ObjectOutputStream;
    116    import java.io.Serializable;
    117    import java.util.Iterator;
    118    import java.util.List;
    119
    120    import org.jfree.chart.event.AxisChangeEvent;
    121    import org.jfree.chart.plot.Plot;
    122    import org.jfree.data.Range;
    123    import org.jfree.io.SerialUtilities;
    124    import org.jfree.text.TextUtilities;
    125    import org.jfree.ui.RectangleEdge;
    126    import org.jfree.ui.RectangleInsets;
    127    import org.jfree.util.ObjectUtilities;
    128    import org.jfree.util.PublicCloneable;
    129
    130    /**
    131     * The base class for axes that display value data, where values are measured
    132     * using the <code>double</code> primitive.  The two key subclasses are
    133     * {@link DateAxis} and {@link NumberAxis}.
    134     */
    135    public abstract class ValueAxis extends Axis
    136            implements Cloneable, PublicCloneable, Serializable {
    137
    138        /** For serialization. */
    139        private static final long serialVersionUID = 3698345477322391456L;
    140
    141        /** The default axis range. */
    142        public static final Range DEFAULT_RANGE = new Range(0.0, 1.0);
    143
    144        /** The default auto-range value. */
    145        public static final boolean DEFAULT_AUTO_RANGE = true;
    146
    147        /** The default inverted flag setting. */
    148        public static final boolean DEFAULT_INVERTED = false;
    149
    150        /** The default minimum auto range. */
    151        public static final double DEFAULT_AUTO_RANGE_MINIMUM_SIZE = 0.00000001;
    152
    153        /** The default value for the lower margin (0.05 = 5%). */
    154        public static final double DEFAULT_LOWER_MARGIN = 0.05;
    155
    156        /** The default value for the upper margin (0.05 = 5%). */
    157        public static final double DEFAULT_UPPER_MARGIN = 0.05;
    158
    159        /**
    160         * The default lower bound for the axis.
    161         *
    162         * @deprecated From 1.0.5 onwards, the axis defines a defaultRange
    163         *     attribute (see {@link #getDefaultAutoRange()}).
    164         */
    165        public static final double DEFAULT_LOWER_BOUND = 0.0;
    166
    167        /**
    168         * The default upper bound for the axis.
    169         *
    170         * @deprecated From 1.0.5 onwards, the axis defines a defaultRange
    171         *     attribute (see {@link #getDefaultAutoRange()}).
    172         */
    173        public static final double DEFAULT_UPPER_BOUND = 1.0;
    174
    175        /** The default auto-tick-unit-selection value. */
    176        public static final boolean DEFAULT_AUTO_TICK_UNIT_SELECTION = true;
    177
    178        /** The maximum tick count. */
    179        public static final int MAXIMUM_TICK_COUNT = 500;
    180
    181        /**
    182         * A flag that controls whether an arrow is drawn at the positive end of
    183         * the axis line.
    184         */
    185        private boolean positiveArrowVisible;
    186
    187        /**
    188         * A flag that controls whether an arrow is drawn at the negative end of
    189         * the axis line.
    190         */
    191        private boolean negativeArrowVisible;
    192
    193        /** The shape used for an up arrow. */
    194        private transient Shape upArrow;
    195
    196        /** The shape used for a down arrow. */
    197        private transient Shape downArrow;
    198
    199        /** The shape used for a left arrow. */
    200        private transient Shape leftArrow;
    201
    202        /** The shape used for a right arrow. */
    203        private transient Shape rightArrow;
    204
    205        /** A flag that affects the orientation of the values on the axis. */
    206        private boolean inverted;
    207
    208        /** The axis range. */
    209        private Range range;
    210
    211        /**
    212         * Flag that indicates whether the axis automatically scales to fit the
    213         * chart data.
    214         */
    215        private boolean autoRange;
    216
    217        /** The minimum size for the 'auto' axis range (excluding margins). */
    218        private double autoRangeMinimumSize;
    219
    220        /**
    221         * The default range is used when the dataset is empty and the axis needs
    222         * to determine the auto range.
    223         *
    224         * @since 1.0.5
    225         */
    226        private Range defaultAutoRange;
    227
    228        /**
    229         * The upper margin percentage.  This indicates the amount by which the
    230         * maximum axis value exceeds the maximum data value (as a percentage of
    231         * the range on the axis) when the axis range is determined automatically.
    232         */
    233        private double upperMargin;
    234
    235        /**
    236         * The lower margin.  This is a percentage that indicates the amount by
    237         * which the minimum axis value is "less than" the minimum data value when
    238         * the axis range is determined automatically.
    239         */
    240        private double lowerMargin;
    241
    242        /**
    243         * If this value is positive, the amount is subtracted from the maximum
    244         * data value to determine the lower axis range.  This can be used to
    245         * provide a fixed "window" on dynamic data.
    246         */
    247        private double fixedAutoRange;
    248
    249        /**
    250         * Flag that indicates whether or not the tick unit is selected
    251         * automatically.
    252         */
    253        private boolean autoTickUnitSelection;
    254
    255        /** The standard tick units for the axis. */
    256        private TickUnitSource standardTickUnits;
    257
    258        /** An index into an array of standard tick values. */
    259        private int autoTickIndex;
    260
    261        /** A flag indicating whether or not tick labels are rotated to vertical. */
    262        private boolean verticalTickLabels;
    263
    264        /**
    265         * Constructs a value axis.
    266         *
    267         * @param label  the axis label (<code>null</code> permitted).
    268         * @param standardTickUnits  the source for standard tick units
    269         *                           (<code>null</code> permitted).
    270         */
    271        protected ValueAxis(String label, TickUnitSource standardTickUnits) {
    272
    273            super(label);
    274
    275            this.positiveArrowVisible = false;
    276            this.negativeArrowVisible = false;
    277
    278            this.range = DEFAULT_RANGE;
    279            this.autoRange = DEFAULT_AUTO_RANGE;
    280            this.defaultAutoRange = DEFAULT_RANGE;
    281
    282            this.inverted = DEFAULT_INVERTED;
    283            this.autoRangeMinimumSize = DEFAULT_AUTO_RANGE_MINIMUM_SIZE;
    284
    285            this.lowerMargin = DEFAULT_LOWER_MARGIN;
    286            this.upperMargin = DEFAULT_UPPER_MARGIN;
    287
    288            this.fixedAutoRange = 0.0;
    289
    290            this.autoTickUnitSelection = DEFAULT_AUTO_TICK_UNIT_SELECTION;
    291            this.standardTickUnits = standardTickUnits;
    292
    293            Polygon p1 = new Polygon();
    294            p1.addPoint(0, 0);
    295            p1.addPoint(-2, 2);
    296            p1.addPoint(2, 2);
    297
    298            this.upArrow = p1;
    299
    300            Polygon p2 = new Polygon();
    301            p2.addPoint(0, 0);
    302            p2.addPoint(-2, -2);
    303            p2.addPoint(2, -2);
    304
    305            this.downArrow = p2;
    306
    307            Polygon p3 = new Polygon();
    308            p3.addPoint(0, 0);
    309            p3.addPoint(-2, -2);
    310            p3.addPoint(-2, 2);
    311
    312            this.rightArrow = p3;
    313
    314            Polygon p4 = new Polygon();
    315            p4.addPoint(0, 0);
    316            p4.addPoint(2, -2);
    317            p4.addPoint(2, 2);
    318
    319            this.leftArrow = p4;
    320
    321            this.verticalTickLabels = false;
    322
    323        }
    324
    325        /**
    326         * Returns <code>true</code> if the tick labels should be rotated (to
    327         * vertical), and <code>false</code> otherwise.
    328         *
    329         * @return <code>true</code> or <code>false</code>.
    330         *
    331         * @see #setVerticalTickLabels(boolean)
    332         */
    333        public boolean isVerticalTickLabels() {
    334            return this.verticalTickLabels;
    335        }
    336
    337        /**
    338         * Sets the flag that controls whether the tick labels are displayed
    339         * vertically (that is, rotated 90 degrees from horizontal).  If the flag
    340         * is changed, an {@link AxisChangeEvent} is sent to all registered
    341         * listeners.
    342         *
    343         * @param flag  the flag.
    344         *
    345         * @see #isVerticalTickLabels()
    346         */
    347        public void setVerticalTickLabels(boolean flag) {
    348            if (this.verticalTickLabels != flag) {
    349                this.verticalTickLabels = flag;
    350                notifyListeners(new AxisChangeEvent(this));
    351            }
    352        }
    353
    354        /**
    355         * Returns a flag that controls whether or not the axis line has an arrow
    356         * drawn that points in the positive direction for the axis.
    357         *
    358         * @return A boolean.
    359         *
    360         * @see #setPositiveArrowVisible(boolean)
    361         */
    362        public boolean isPositiveArrowVisible() {
    363            return this.positiveArrowVisible;
    364        }
    365
    366        /**
    367         * Sets a flag that controls whether or not the axis lines has an arrow
    368         * drawn that points in the positive direction for the axis, and sends an
    369         * {@link AxisChangeEvent} to all registered listeners.
    370         *
    371         * @param visible  the flag.
    372         *
    373         * @see #isPositiveArrowVisible()
    374         */
    375        public void setPositiveArrowVisible(boolean visible) {
    376            this.positiveArrowVisible = visible;
    377            notifyListeners(new AxisChangeEvent(this));
    378        }
    379
    380        /**
    381         * Returns a flag that controls whether or not the axis line has an arrow
    382         * drawn that points in the negative direction for the axis.
    383         *
    384         * @return A boolean.
    385         *
    386         * @see #setNegativeArrowVisible(boolean)
    387         */
    388        public boolean isNegativeArrowVisible() {
    389            return this.negativeArrowVisible;
    390        }
    391
    392        /**
    393         * Sets a flag that controls whether or not the axis lines has an arrow
    394         * drawn that points in the negative direction for the axis, and sends an
    395         * {@link AxisChangeEvent} to all registered listeners.
    396         *
    397         * @param visible  the flag.
    398         *
    399         * @see #setNegativeArrowVisible(boolean)
    400         */
    401        public void setNegativeArrowVisible(boolean visible) {
    402            this.negativeArrowVisible = visible;
    403            notifyListeners(new AxisChangeEvent(this));
    404        }
    405
    406        /**
    407         * Returns a shape that can be displayed as an arrow pointing upwards at
    408         * the end of an axis line.
    409         *
    410         * @return A shape (never <code>null</code>).
    411         *
    412         * @see #setUpArrow(Shape)
    413         */
    414        public Shape getUpArrow() {
    415            return this.upArrow;
    416        }
    417
    418        /**
    419         * Sets the shape that can be displayed as an arrow pointing upwards at
    420         * the end of an axis line and sends an {@link AxisChangeEvent} to all
    421         * registered listeners.
    422         *
    423         * @param arrow  the arrow shape (<code>null</code> not permitted).
    424         *
    425         * @see #getUpArrow()
    426         */
    427        public void setUpArrow(Shape arrow) {
    428            if (arrow == null) {
    429                throw new IllegalArgumentException("Null 'arrow' argument.");
    430            }
    431            this.upArrow = arrow;
    432            notifyListeners(new AxisChangeEvent(this));
    433        }
    434
    435        /**
    436         * Returns a shape that can be displayed as an arrow pointing downwards at
    437         * the end of an axis line.
    438         *
    439         * @return A shape (never <code>null</code>).
    440         *
    441         * @see #setDownArrow(Shape)
    442         */
    443        public Shape getDownArrow() {
    444            return this.downArrow;
    445        }
    446
    447        /**
    448         * Sets the shape that can be displayed as an arrow pointing downwards at
    449         * the end of an axis line and sends an {@link AxisChangeEvent} to all
    450         * registered listeners.
    451         *
    452         * @param arrow  the arrow shape (<code>null</code> not permitted).
    453         *
    454         * @see #getDownArrow()
    455         */
    456        public void setDownArrow(Shape arrow) {
    457            if (arrow == null) {
    458                throw new IllegalArgumentException("Null 'arrow' argument.");
    459            }
    460            this.downArrow = arrow;
    461            notifyListeners(new AxisChangeEvent(this));
    462        }
    463
    464        /**
    465         * Returns a shape that can be displayed as an arrow pointing left at the
    466         * end of an axis line.
    467         *
    468         * @return A shape (never <code>null</code>).
    469         *
    470         * @see #setLeftArrow(Shape)
    471         */
    472        public Shape getLeftArrow() {
    473            return this.leftArrow;
    474        }
    475
    476        /**
    477         * Sets the shape that can be displayed as an arrow pointing left at the
    478         * end of an axis line and sends an {@link AxisChangeEvent} to all
    479         * registered listeners.
    480         *
    481         * @param arrow  the arrow shape (<code>null</code> not permitted).
    482         *
    483         * @see #getLeftArrow()
    484         */
    485        public void setLeftArrow(Shape arrow) {
    486            if (arrow == null) {
    487                throw new IllegalArgumentException("Null 'arrow' argument.");
    488            }
    489            this.leftArrow = arrow;
    490            notifyListeners(new AxisChangeEvent(this));
    491        }
    492
    493        /**
    494         * Returns a shape that can be displayed as an arrow pointing right at the
    495         * end of an axis line.
    496         *
    497         * @return A shape (never <code>null</code>).
    498         *
    499         * @see #setRightArrow(Shape)
    500         */
    501        public Shape getRightArrow() {
    502            return this.rightArrow;
    503        }
    504
    505        /**
    506         * Sets the shape that can be displayed as an arrow pointing rightwards at
    507         * the end of an axis line and sends an {@link AxisChangeEvent} to all
    508         * registered listeners.
    509         *
    510         * @param arrow  the arrow shape (<code>null</code> not permitted).
    511         *
    512         * @see #getRightArrow()
    513         */
    514        public void setRightArrow(Shape arrow) {
    515            if (arrow == null) {
    516                throw new IllegalArgumentException("Null 'arrow' argument.");
    517            }
    518            this.rightArrow = arrow;
    519            notifyListeners(new AxisChangeEvent(this));
    520        }
    521
    522        /**
    523         * Draws an axis line at the current cursor position and edge.
    524         *
    525         * @param g2  the graphics device.
    526         * @param cursor  the cursor position.
    527         * @param dataArea  the data area.
    528         * @param edge  the edge.
    529         */
    530        protected void drawAxisLine(Graphics2D g2, double cursor,
    531                                    Rectangle2D dataArea, RectangleEdge edge) {
    532            Line2D axisLine = null;
    533            if (edge == RectangleEdge.TOP) {
    534                axisLine = new Line2D.Double(dataArea.getX(), cursor,
    535                        dataArea.getMaxX(), cursor);
    536            }
    537            else if (edge == RectangleEdge.BOTTOM) {
    538                axisLine = new Line2D.Double(dataArea.getX(), cursor,
    539                        dataArea.getMaxX(), cursor);
    540            }
    541            else if (edge == RectangleEdge.LEFT) {
    542                axisLine = new Line2D.Double(cursor, dataArea.getY(), cursor,
    543                        dataArea.getMaxY());
    544            }
    545            else if (edge == RectangleEdge.RIGHT) {
    546                axisLine = new Line2D.Double(cursor, dataArea.getY(), cursor,
    547                        dataArea.getMaxY());
    548            }
    549            g2.setPaint(getAxisLinePaint());
    550            g2.setStroke(getAxisLineStroke());
    551            g2.draw(axisLine);
    552
    553            boolean drawUpOrRight = false;
    554            boolean drawDownOrLeft = false;
    555            if (this.positiveArrowVisible) {
    556                if (this.inverted) {
    557                    drawDownOrLeft = true;
    558                }
    559                else {
    560                    drawUpOrRight = true;
    561                }
    562            }
    563            if (this.negativeArrowVisible) {
    564                if (this.inverted) {
    565                    drawUpOrRight = true;
    566                }
    567                else {
    568                    drawDownOrLeft = true;
    569                }
    570            }
    571            if (drawUpOrRight) {
    572                double x = 0.0;
    573                double y = 0.0;
    574                Shape arrow = null;
    575                if (edge == RectangleEdge.TOP || edge == RectangleEdge.BOTTOM) {
    576                    x = dataArea.getMaxX();
    577                    y = cursor;
    578                    arrow = this.rightArrow;
    579                }
    580                else if (edge == RectangleEdge.LEFT
    581                        || edge == RectangleEdge.RIGHT) {
    582                    x = cursor;
    583                    y = dataArea.getMinY();
    584                    arrow = this.upArrow;
    585                }
    586
    587                // draw the arrow...
    588                AffineTransform transformer = new AffineTransform();
    589                transformer.setToTranslation(x, y);
    590                Shape shape = transformer.createTransformedShape(arrow);
    591                g2.fill(shape);
    592                g2.draw(shape);
    593            }
    594
    595            if (drawDownOrLeft) {
    596                double x = 0.0;
    597                double y = 0.0;
    598                Shape arrow = null;
    599                if (edge == RectangleEdge.TOP || edge == RectangleEdge.BOTTOM) {
    600                    x = dataArea.getMinX();
    601                    y = cursor;
    602                    arrow = this.leftArrow;
    603                }
    604                else if (edge == RectangleEdge.LEFT
    605                        || edge == RectangleEdge.RIGHT) {
    606                    x = cursor;
    607                    y = dataArea.getMaxY();
    608                    arrow = this.downArrow;
    609                }
    610
    611                // draw the arrow...
    612                AffineTransform transformer = new AffineTransform();
    613                transformer.setToTranslation(x, y);
    614                Shape shape = transformer.createTransformedShape(arrow);
    615                g2.fill(shape);
    616                g2.draw(shape);
    617            }
    618
    619        }
    620
    621        /**
    622         * Calculates the anchor point for a tick label.
    623         *
    624         * @param tick  the tick.
    625         * @param cursor  the cursor.
    626         * @param dataArea  the data area.
    627         * @param edge  the edge on which the axis is drawn.
    628         *
    629         * @return The x and y coordinates of the anchor point.
    630         */
    631        protected float[] calculateAnchorPoint(ValueTick tick,
    632                                               double cursor,
    633                                               Rectangle2D dataArea,
    634                                               RectangleEdge edge) {
    635
    636            RectangleInsets insets = getTickLabelInsets();
    637            float[] result = new float[2];
    638            if (edge == RectangleEdge.TOP) {
    639                result[0] = (float) valueToJava2D(tick.getValue(), dataArea, edge);
    640                result[1] = (float) (cursor - insets.getBottom() - 2.0);
    641            }
    642            else if (edge == RectangleEdge.BOTTOM) {
    643                result[0] = (float) valueToJava2D(tick.getValue(), dataArea, edge);
    644                result[1] = (float) (cursor + insets.getTop() + 2.0);
    645            }
    646            else if (edge == RectangleEdge.LEFT) {
    647                result[0] = (float) (cursor - insets.getLeft() - 2.0);
    648                result[1] = (float) valueToJava2D(tick.getValue(), dataArea, edge);
    649            }
    650            else if (edge == RectangleEdge.RIGHT) {
    651                result[0] = (float) (cursor + insets.getRight() + 2.0);
    652                result[1] = (float) valueToJava2D(tick.getValue(), dataArea, edge);
    653            }
    654            return result;
    655        }
    656
    657        /**
    658         * Draws the axis line, tick marks and tick mark labels.
    659         *
    660         * @param g2  the graphics device.
    661         * @param cursor  the cursor.
    662         * @param plotArea  the plot area.
    663         * @param dataArea  the data area.
    664         * @param edge  the edge that the axis is aligned with.
    665         *
    666         * @return The width or height used to draw the axis.
    667         */
    668        protected AxisState drawTickMarksAndLabels(Graphics2D g2,
    669                                                   double cursor,
    670                                                   Rectangle2D plotArea,
    671                                                   Rectangle2D dataArea,
    672                                                   RectangleEdge edge) {
    673
    674            AxisState state = new AxisState(cursor);
    675
    676            if (isAxisLineVisible()) {
    677                drawAxisLine(g2, cursor, dataArea, edge);
    678            }
    679
    680            double ol = getTickMarkOutsideLength();
    681            double il = getTickMarkInsideLength();
    682
    683            List ticks = refreshTicks(g2, state, dataArea, edge);
    684            state.setTicks(ticks);
    685            g2.setFont(getTickLabelFont());
    686            Iterator iterator = ticks.iterator();
    687            while (iterator.hasNext()) {
    688                ValueTick tick = (ValueTick) iterator.next();
    689                if (isTickLabelsVisible()) {
    690                    g2.setPaint(getTickLabelPaint());
    691                    float[] anchorPoint = calculateAnchorPoint(tick, cursor,
    692                            dataArea, edge);
    693                    TextUtilities.drawRotatedString(tick.getText(), g2,
    694                            anchorPoint[0], anchorPoint[1], tick.getTextAnchor(),
    695                            tick.getAngle(), tick.getRotationAnchor());
    696                }
    697
    698                if (isTickMarksVisible() && tick.getTickType().equals(
    699                        TickType.MAJOR)) {
    700                    float xx = (float) valueToJava2D(tick.getValue(), dataArea,
    701                            edge);
    702                    Line2D mark = null;
    703                    g2.setStroke(getTickMarkStroke());
    704                    g2.setPaint(getTickMarkPaint());
    705                    if (edge == RectangleEdge.LEFT) {
    706                        mark = new Line2D.Double(cursor - ol, xx, cursor + il, xx);
    707                    }
    708                    else if (edge == RectangleEdge.RIGHT) {
    709                        mark = new Line2D.Double(cursor + ol, xx, cursor - il, xx);
    710                    }
    711                    else if (edge == RectangleEdge.TOP) {
    712                        mark = new Line2D.Double(xx, cursor - ol, xx, cursor + il);
    713                    }
    714                    else if (edge == RectangleEdge.BOTTOM) {
    715                        mark = new Line2D.Double(xx, cursor + ol, xx, cursor - il);
    716                    }
    717                    g2.draw(mark);
    718                }
    719            }
    720
    721            // need to work out the space used by the tick labels...
    722            // so we can update the cursor...
    723            double used = 0.0;
    724            if (isTickLabelsVisible()) {
    725                if (edge == RectangleEdge.LEFT) {
    726                    used += findMaximumTickLabelWidth(ticks, g2, plotArea,
    727                            isVerticalTickLabels());
    728                    state.cursorLeft(used);
    729                }
    730                else if (edge == RectangleEdge.RIGHT) {
    731                    used = findMaximumTickLabelWidth(ticks, g2, plotArea,
    732                            isVerticalTickLabels());
    733                    state.cursorRight(used);
    734                }
    735                else if (edge == RectangleEdge.TOP) {
    736                    used = findMaximumTickLabelHeight(ticks, g2, plotArea,
    737                            isVerticalTickLabels());
    738                    state.cursorUp(used);
    739                }
    740                else if (edge == RectangleEdge.BOTTOM) {
    741                    used = findMaximumTickLabelHeight(ticks, g2, plotArea,
    742                            isVerticalTickLabels());
    743                    state.cursorDown(used);
    744                }
    745            }
    746
    747            return state;
    748        }
    749
    750        /**
    751         * Returns the space required to draw the axis.
    752         *
    753         * @param g2  the graphics device.
    754         * @param plot  the plot that the axis belongs to.
    755         * @param plotArea  the area within which the plot should be drawn.
    756         * @param edge  the axis location.
    757         * @param space  the space already reserved (for other axes).
    758         *
    759         * @return The space required to draw the axis (including pre-reserved
    760         *         space).
    761         */
    762        public AxisSpace reserveSpace(Graphics2D g2, Plot plot,
    763                                      Rectangle2D plotArea,
    764                                      RectangleEdge edge, AxisSpace space) {
    765
    766            // create a new space object if one wasn't supplied...
    767            if (space == null) {
    768                space = new AxisSpace();
    769            }
    770
    771            // if the axis is not visible, no additional space is required...
    772            if (!isVisible()) {
    773                return space;
    774            }
    775
    776            // if the axis has a fixed dimension, return it...
    777            double dimension = getFixedDimension();
    778            if (dimension > 0.0) {
    779                space.ensureAtLeast(dimension, edge);
    780            }
    781
    782            // calculate the max size of the tick labels (if visible)...
    783            double tickLabelHeight = 0.0;
    784            double tickLabelWidth = 0.0;
    785            if (isTickLabelsVisible()) {
    786                g2.setFont(getTickLabelFont());
    787                List ticks = refreshTicks(g2, new AxisState(), plotArea, edge);
    788                if (RectangleEdge.isTopOrBottom(edge)) {
    789                    tickLabelHeight = findMaximumTickLabelHeight(ticks, g2,
    790                            plotArea, isVerticalTickLabels());
    791                }
    792                else if (RectangleEdge.isLeftOrRight(edge)) {
    793                    tickLabelWidth = findMaximumTickLabelWidth(ticks, g2, plotArea,
    794                            isVerticalTickLabels());
    795                }
    796            }
    797
    798            // get the axis label size and update the space object...
    799            Rectangle2D labelEnclosure = getLabelEnclosure(g2, edge);
    800            double labelHeight = 0.0;
    801            double labelWidth = 0.0;
    802            if (RectangleEdge.isTopOrBottom(edge)) {
    803                labelHeight = labelEnclosure.getHeight();
    804                space.add(labelHeight + tickLabelHeight, edge);
    805            }
    806            else if (RectangleEdge.isLeftOrRight(edge)) {
    807                labelWidth = labelEnclosure.getWidth();
    808                space.add(labelWidth + tickLabelWidth, edge);
    809            }
    810
    811            return space;
    812
    813        }
    814
    815        /**
    816         * A utility method for determining the height of the tallest tick label.
    817         *
    818         * @param ticks  the ticks.
    819         * @param g2  the graphics device.
    820         * @param drawArea  the area within which the plot and axes should be drawn.
    821         * @param vertical  a flag that indicates whether or not the tick labels
    822         *                  are 'vertical'.
    823         *
    824         * @return The height of the tallest tick label.
    825         */
    826        protected double findMaximumTickLabelHeight(List ticks,
    827                                                    Graphics2D g2,
    828                                                    Rectangle2D drawArea,
    829                                                    boolean vertical) {
    830
    831            RectangleInsets insets = getTickLabelInsets();
    832            Font font = getTickLabelFont();
    833            double maxHeight = 0.0;
    834            if (vertical) {
    835                FontMetrics fm = g2.getFontMetrics(font);
    836                Iterator iterator = ticks.iterator();
    837                while (iterator.hasNext()) {
    838                    Tick tick = (Tick) iterator.next();
    839                    Rectangle2D labelBounds = TextUtilities.getTextBounds(
    840                            tick.getText(), g2, fm);
    841                    if (labelBounds.getWidth() + insets.getTop()
    842                            + insets.getBottom() > maxHeight) {
    843                        maxHeight = labelBounds.getWidth()
    844                                    + insets.getTop() + insets.getBottom();
    845                    }
    846                }
    847            }
    848            else {
    849                LineMetrics metrics = font.getLineMetrics("ABCxyz",
    850                        g2.getFontRenderContext());
    851                maxHeight = metrics.getHeight()
    852                            + insets.getTop() + insets.getBottom();
    853            }
    854            return maxHeight;
    855
    856        }
    857
    858        /**
    859         * A utility method for determining the width of the widest tick label.
    860         *
    861         * @param ticks  the ticks.
    862         * @param g2  the graphics device.
    863         * @param drawArea  the area within which the plot and axes should be drawn.
    864         * @param vertical  a flag that indicates whether or not the tick labels
    865         *                  are 'vertical'.
    866         *
    867         * @return The width of the tallest tick label.
    868         */
    869        protected double findMaximumTickLabelWidth(List ticks,
    870                                                   Graphics2D g2,
    871                                                   Rectangle2D drawArea,
    872                                                   boolean vertical) {
    873
    874            RectangleInsets insets = getTickLabelInsets();
    875            Font font = getTickLabelFont();
    876            double maxWidth = 0.0;
    877            if (!vertical) {
    878                FontMetrics fm = g2.getFontMetrics(font);
    879                Iterator iterator = ticks.iterator();
    880                while (iterator.hasNext()) {
    881                    Tick tick = (Tick) iterator.next();
    882                    Rectangle2D labelBounds = TextUtilities.getTextBounds(
    883                            tick.getText(), g2, fm);
    884                    if (labelBounds.getWidth() + insets.getLeft()
    885                            + insets.getRight() > maxWidth) {
    886                        maxWidth = labelBounds.getWidth()
    887                                   + insets.getLeft() + insets.getRight();
    888                    }
    889                }
    890            }
    891            else {
    892                LineMetrics metrics = font.getLineMetrics("ABCxyz",
    893                        g2.getFontRenderContext());
    894                maxWidth = metrics.getHeight()
    895                           + insets.getTop() + insets.getBottom();
    896            }
    897            return maxWidth;
    898
    899        }
    900
    901        /**
    902         * Returns a flag that controls the direction of values on the axis.
    903         * <P>
    904         * For a regular axis, values increase from left to right (for a horizontal
    905         * axis) and bottom to top (for a vertical axis).  When the axis is
    906         * 'inverted', the values increase in the opposite direction.
    907         *
    908         * @return The flag.
    909         *
    910         * @see #setInverted(boolean)
    911         */
    912        public boolean isInverted() {
    913            return this.inverted;
    914        }
    915
    916        /**
    917         * Sets a flag that controls the direction of values on the axis, and
    918         * notifies registered listeners that the axis has changed.
    919         *
    920         * @param flag  the flag.
    921         *
    922         * @see #isInverted()
    923         */
    924        public void setInverted(boolean flag) {
    925
    926            if (this.inverted != flag) {
    927                this.inverted = flag;
    928                notifyListeners(new AxisChangeEvent(this));
    929            }
    930
    931        }
    932
    933        /**
    934         * Returns the flag that controls whether or not the axis range is
    935         * automatically adjusted to fit the data values.
    936         *
    937         * @return The flag.
    938         *
    939         * @see #setAutoRange(boolean)
    940         */
    941        public boolean isAutoRange() {
    942            return this.autoRange;
    943        }
    944
    945        /**
    946         * Sets a flag that determines whether or not the axis range is
    947         * automatically adjusted to fit the data, and notifies registered
    948         * listeners that the axis has been modified.
    949         *
    950         * @param auto  the new value of the flag.
    951         *
    952         * @see #isAutoRange()
    953         */
    954        public void setAutoRange(boolean auto) {
    955            setAutoRange(auto, true);
    956        }
    957
    958        /**
    959         * Sets the auto range attribute.  If the <code>notify</code> flag is set,
    960         * an {@link AxisChangeEvent} is sent to registered listeners.
    961         *
    962         * @param auto  the flag.
    963         * @param notify  notify listeners?
    964         *
    965         * @see #isAutoRange()
    966         */
    967        protected void setAutoRange(boolean auto, boolean notify) {
    968            if (this.autoRange != auto) {
    969                this.autoRange = auto;
    970                if (this.autoRange) {
    971                    autoAdjustRange();
    972                }
    973                if (notify) {
    974                    notifyListeners(new AxisChangeEvent(this));
    975                }
    976            }
    977        }
    978
    979        /**
    980         * Returns the minimum size allowed for the axis range when it is
    981         * automatically calculated.
    982         *
    983         * @return The minimum range.
    984         *
    985         * @see #setAutoRangeMinimumSize(double)
    986         */
    987        public double getAutoRangeMinimumSize() {
    988            return this.autoRangeMinimumSize;
    989        }
    990
    991        /**
    992         * Sets the auto range minimum size and sends an {@link AxisChangeEvent}
    993         * to all registered listeners.
    994         *
    995         * @param size  the size.
    996         *
    997         * @see #getAutoRangeMinimumSize()
    998         */
    999        public void setAutoRangeMinimumSize(double size) {
    1000            setAutoRangeMinimumSize(size, true);
    1001        }
    1002
    1003        /**
    1004         * Sets the minimum size allowed for the axis range when it is
    1005         * automatically calculated.
    1006         * <p>
    1007         * If requested, an {@link AxisChangeEvent} is forwarded to all registered
    1008         * listeners.
    1009         *
    1010         * @param size  the new minimum.
    1011         * @param notify  notify listeners?
    1012         */
    1013        public void setAutoRangeMinimumSize(double size, boolean notify) {
    1014            if (size <= 0.0) {
    1015                throw new IllegalArgumentException(
    1016                    "NumberAxis.setAutoRangeMinimumSize(double): must be > 0.0.");
    1017            }
    1018            if (this.autoRangeMinimumSize != size) {
    1019                this.autoRangeMinimumSize = size;
    1020                if (this.autoRange) {
    1021                    autoAdjustRange();
    1022                }
    1023                if (notify) {
    1024                    notifyListeners(new AxisChangeEvent(this));
    1025                }
    1026            }
    1027
    1028        }
    1029
    1030        /**
    1031         * Returns the default auto range.
    1032         *
    1033         * @return The default auto range (never <code>null</code>).
    1034         *
    1035         * @see #setDefaultAutoRange(Range)
    1036         *
    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         * @see #resizeRange(double, double)
    1489         */
    1490        public void resizeRange(double percent) {
    1491            resizeRange(percent, this.range.getCentralValue());
    1492        }
    1493
    1494        /**
    1495         * Increases or decreases the axis range by the specified percentage about
    1496         * the specified anchor value and sends an {@link AxisChangeEvent} to all
    1497         * registered listeners.
    1498         * <P>
    1499         * To double the length of the axis range, use 200% (2.0).
    1500         * To halve the length of the axis range, use 50% (0.5).
    1501         *
    1502         * @param percent  the resize factor.
    1503         * @param anchorValue  the new central value after the resize.
    1504         *
    1505         * @see #resizeRange(double)
    1506         */
    1507        public void resizeRange(double percent, double anchorValue) {
    1508            if (percent > 0.0) {
    1509                double halfLength = this.range.getLength() * percent / 2;
    1510                Range adjusted = new Range(anchorValue - halfLength,
    1511                        anchorValue + halfLength);
    1512                setRange(adjusted);
    1513            }
    1514            else {
    1515                setAutoRange(true);
    1516            }
    1517        }
    1518
    1519        /**
    1520         * Zooms in on the current range.
    1521         *
    1522         * @param lowerPercent  the new lower bound.
    1523         * @param upperPercent  the new upper bound.
    1524         */
    1525        public void zoomRange(double lowerPercent, double upperPercent) {
    1526            double start = this.range.getLowerBound();
    1527            double length = this.range.getLength();
    1528            Range adjusted = null;
    1529            if (isInverted()) {
    1530                adjusted = new Range(start + (length * (1 - upperPercent)),
    1531                                     start + (length * (1 - lowerPercent)));
    1532            }
    1533            else {
    1534                adjusted = new Range(start + length * lowerPercent,
    1535                        start + length * upperPercent);
    1536            }
    1537            setRange(adjusted);
    1538        }
    1539
    1540        /**
    1541         * Returns the auto tick index.
    1542         *
    1543         * @return The auto tick index.
    1544         *
    1545         * @see #setAutoTickIndex(int)
    1546         */
    1547        protected int getAutoTickIndex() {
    1548            return this.autoTickIndex;
    1549        }
    1550
    1551        /**
    1552         * Sets the auto tick index.
    1553         *
    1554         * @param index  the new value.
    1555         *
    1556         * @see #getAutoTickIndex()
    1557         */
    1558        protected void setAutoTickIndex(int index) {
    1559            this.autoTickIndex = index;
    1560        }
    1561
    1562        /**
    1563         * Tests the axis for equality with an arbitrary object.
    1564         *
    1565         * @param obj  the object (<code>null</code> permitted).
    1566         *
    1567         * @return <code>true</code> or <code>false</code>.
    1568         */
    1569        public boolean equals(Object obj) {
    1570
    1571            if (obj == this) {
    1572                return true;
    1573            }
    1574            if (!(obj instanceof ValueAxis)) {
    1575                return false;
    1576            }
    1577
    1578            ValueAxis that = (ValueAxis) obj;
    1579
    1580            if (this.positiveArrowVisible != that.positiveArrowVisible) {
    1581                return false;
    1582            }
    1583            if (this.negativeArrowVisible != that.negativeArrowVisible) {
    1584                return false;
    1585            }
    1586            if (this.inverted != that.inverted) {
    1587                return false;
    1588            }
    1589            if (!ObjectUtilities.equal(this.range, that.range)) {
    1590                return false;
    1591            }
    1592            if (this.autoRange != that.autoRange) {
    1593                return false;
    1594            }
    1595            if (this.autoRangeMinimumSize != that.autoRangeMinimumSize) {
    1596                return false;
    1597            }
    1598            if (!this.defaultAutoRange.equals(that.defaultAutoRange)) {
    1599                return false;
    1600            }
    1601            if (this.upperMargin != that.upperMargin) {
    1602                return false;
    1603            }
    1604            if (this.lowerMargin != that.lowerMargin) {
    1605                return false;
    1606            }
    1607            if (this.fixedAutoRange != that.fixedAutoRange) {
    1608                return false;
    1609            }
    1610            if (this.autoTickUnitSelection != that.autoTickUnitSelection) {
    1611                return false;
    1612            }
    1613            if (!ObjectUtilities.equal(this.standardTickUnits,
    1614                    that.standardTickUnits)) {
    1615                return false;
    1616            }
    1617            if (this.verticalTickLabels != that.verticalTickLabels) {
    1618                return false;
    1619            }
    1620
    1621            return super.equals(obj);
    1622
    1623        }
    1624
    1625        /**
    1626         * Returns a clone of the object.
    1627         *
    1628         * @return A clone.
    1629         *
    1630         * @throws CloneNotSupportedException if some component of the axis does
    1631         *         not support cloning.
    1632         */
    1633        public Object clone() throws CloneNotSupportedException {
    1634            ValueAxis clone = (ValueAxis) super.clone();
    1635            return clone;
    1636        }
    1637
    1638        /**
    1639         * Provides serialization support.
    1640         *
    1641         * @param stream  the output stream.
    1642         *
    1643         * @throws IOException  if there is an I/O error.
    1644         */
    1645        private void writeObject(ObjectOutputStream stream) throws IOException {
    1646            stream.defaultWriteObject();
    1647            SerialUtilities.writeShape(this.upArrow, stream);
    1648            SerialUtilities.writeShape(this.downArrow, stream);
    1649            SerialUtilities.writeShape(this.leftArrow, stream);
    1650            SerialUtilities.writeShape(this.rightArrow, stream);
    1651        }
    1652
    1653        /**
    1654         * Provides serialization support.
    1655         *
    1656         * @param stream  the input stream.
    1657         *
    1658         * @throws IOException  if there is an I/O error.
    1659         * @throws ClassNotFoundException  if there is a classpath problem.
    1660         */
    1661        private void readObject(ObjectInputStream stream)
    1662                throws IOException, ClassNotFoundException {
    1663
    1664            stream.defaultReadObject();
    1665            this.upArrow = SerialUtilities.readShape(stream);
    1666            this.downArrow = SerialUtilities.readShape(stream);
    1667            this.leftArrow = SerialUtilities.readShape(stream);
    1668            this.rightArrow = SerialUtilities.readShape(stream);
    1669
    1670        }
    1671
    1672    }