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