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