    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
    111package org.jfree.chart.axis;
    112
    113import java.awt.Font;
    114import java.awt.FontMetrics;
    115import java.awt.Graphics2D;
    116import java.awt.Polygon;
    117import java.awt.Shape;
    118import java.awt.font.LineMetrics;
    119import java.awt.geom.AffineTransform;
    120import java.awt.geom.Line2D;
    121import java.awt.geom.Rectangle2D;
    122import java.io.IOException;
    123import java.io.ObjectInputStream;
    124import java.io.ObjectOutputStream;
    125import java.io.Serializable;
    126import java.util.Iterator;
    127import java.util.List;
    128
    129import org.jfree.chart.event.AxisChangeEvent;
    130import org.jfree.chart.plot.Plot;
    131import org.jfree.data.Range;
    132import org.jfree.io.SerialUtilities;
    133import org.jfree.text.TextUtilities;
    134import org.jfree.ui.RectangleEdge;
    135import org.jfree.ui.RectangleInsets;
    136import org.jfree.util.ObjectUtilities;
    137import 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 */
    144public 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}