    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
    006     *
    007     * Project Info:  http://www.jfree.org/jfreechart/index.html
    008     *
    009     * This library is free software; you can redistribute it and/or modify it
    010     * under the terms of the GNU Lesser General Public License as published by
    011     * the Free Software Foundation; either version 2.1 of the License, or
    012     * (at your option) any later version.
    013     *
    014     * This library is distributed in the hope that it will be useful, but
    015     * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
    016     * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
    017     * License for more details.
    018     *
    019     * You should have received a copy of the GNU Lesser General Public
    020     * License along with this library; if not, write to the Free Software
    021     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
    022     * USA.
    023     *
    024     * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
    025     * in the United States and other countries.]
    026     *
    027     * ---------------
    028     * NumberAxis.java
    029     * ---------------
    030     * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
    031     *
    032     * Original Author:  David Gilbert (for Object Refinery Limited);
    033     * Contributor(s):   Laurence Vanhelsuwe;
    034     *
    035     * $Id: NumberAxis.java,v 1.16.2.1 2005/10/25 20:37:34 mungady Exp $
    036     *
    037     * Changes (from 18-Sep-2001)
    038     * --------------------------
    039     * 18-Sep-2001 : Added standard header and fixed DOS encoding problem (DG);
    040     * 22-Sep-2001 : Changed setMinimumAxisValue() and setMaximumAxisValue() so
    041     *               that they clear the autoRange flag (DG);
    042     * 27-Nov-2001 : Removed old, redundant code (DG);
    043     * 30-Nov-2001 : Added accessor methods for the standard tick units (DG);
    044     * 08-Jan-2002 : Added setAxisRange() method (since renamed setRange()) (DG);
    045     * 16-Jan-2002 : Added setTickUnit() method.  Extended ValueAxis to support an
    046     *               optional cross-hair (DG);
    047     * 08-Feb-2002 : Fixes bug to ensure the autorange is recalculated if the
    048     *               setAutoRangeIncludesZero flag is changed (DG);
    049     * 25-Feb-2002 : Added a new flag autoRangeStickyZero to provide further
    050     *               control over margins in the auto-range mechanism.  Updated
    051     *               constructors.  Updated import statements.  Moved the
    052     *               createStandardTickUnits() method to the TickUnits class (DG);
    053     * 19-Apr-2002 : Updated Javadoc comments (DG);
    054     * 01-May-2002 : Updated for changes to TickUnit class, removed valueToString()
    055     *               method (DG);
    056     * 25-Jul-2002 : Moved the lower and upper margin attributes, and the
    057     *               auto-range minimum size, up one level to the ValueAxis
    058     *               class (DG);
    059     * 05-Sep-2002 : Updated constructor to match 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     * 24-Oct-2002 : Added a number format override (DG);
    063     * 08-Nov-2002 : Moved to new package com.jrefinery.chart.axis (DG);
    064     * 19-Nov-2002 : Removed grid settings (now controlled by the plot) (DG);
    065     * 14-Jan-2003 : Changed autoRangeMinimumSize from Number --> double, and moved
    066     *               crosshair settings to the plot classes (DG);
    067     * 20-Jan-2003 : Removed the monolithic constructor (DG);
    068     * 26-Mar-2003 : Implemented Serializable (DG);
    069     * 16-Jul-2003 : Reworked to allow for multiple secondary axes (DG);
    070     * 13-Aug-2003 : Implemented Cloneable (DG);
    071     * 07-Oct-2003 : Fixed bug (815028) in the auto range calculation (DG);
    072     * 29-Oct-2003 : Added workaround for font alignment in PDF output (DG);
    073     * 07-Nov-2003 : Modified to use NumberTick class (DG);
    074     * 21-Jan-2004 : Renamed translateJava2DToValue --> java2DToValue, and
    075     *               translateValueToJava2D --> valueToJava2D (DG);
    076     * 03-Mar-2004 : Added plotState to draw() method (DG);
    077     * 07-Apr-2004 : Changed string width calculation (DG);
    078     * 11-Jan-2005 : Removed deprecated methods in preparation for 1.0.0
    079     *               release (DG);
    080     * 28-Mar-2005 : Renamed autoRangeIncludesZero() --> getAutoRangeIncludesZero()
    081     *               and autoRangeStickyZero() --> getAutoRangeStickyZero() (DG);
    082     * 21-Apr-2005 : Removed redundant argument from selectAutoTickUnit() (DG);
    083     * 22-Apr-2005 : Renamed refreshHorizontalTicks --> refreshTicksHorizontal
    084     *               (and likewise the vertical version) for consistency with
    085     *               other axis classes (DG);
    086     *
    087     */
    088
    089    package org.jfree.chart.axis;
    090
    091    import java.awt.Font;
    092    import java.awt.FontMetrics;
    093    import java.awt.Graphics2D;
    094    import java.awt.font.FontRenderContext;
    095    import java.awt.font.LineMetrics;
    096    import java.awt.geom.Rectangle2D;
    097    import java.io.Serializable;
    098    import java.text.DecimalFormat;
    099    import java.text.NumberFormat;
    100    import java.util.List;
    101    import java.util.Locale;
    102
    103    import org.jfree.chart.event.AxisChangeEvent;
    104    import org.jfree.chart.plot.Plot;
    105    import org.jfree.chart.plot.PlotRenderingInfo;
    106    import org.jfree.chart.plot.ValueAxisPlot;
    107    import org.jfree.data.Range;
    108    import org.jfree.data.RangeType;
    109    import org.jfree.ui.RectangleEdge;
    110    import org.jfree.ui.RectangleInsets;
    111    import org.jfree.ui.TextAnchor;
    112    import org.jfree.util.ObjectUtilities;
    113
    114    /**
    115     * An axis for displaying numerical data.
    116     * <P>
    117     * If the axis is set up to automatically determine its range to fit the data,
    118     * you can ensure that the range includes zero (statisticians usually prefer
    119     * this) by setting the <code>autoRangeIncludesZero</code> flag to
    120     * <code>true</code>.
    121     * <P>
    122     * The <code>NumberAxis</code> class has a mechanism for automatically
    123     * selecting a tick unit that is appropriate for the current axis range.  This
    124     * mechanism is an adaptation of code suggested by Laurence Vanhelsuwe.
    125     */
    126    public class NumberAxis extends ValueAxis implements Cloneable, Serializable {
    127
    128        /** For serialization. */
    129        private static final long serialVersionUID = 2805933088476185789L;
    130
    131        /** The default value for the autoRangeIncludesZero flag. */
    132        public static final boolean DEFAULT_AUTO_RANGE_INCLUDES_ZERO = true;
    133
    134        /** The default value for the autoRangeStickyZero flag. */
    135        public static final boolean DEFAULT_AUTO_RANGE_STICKY_ZERO = true;
    136
    137        /** The default tick unit. */
    138        public static final NumberTickUnit
    139            DEFAULT_TICK_UNIT = new NumberTickUnit(1.0, new DecimalFormat("0"));
    140
    141        /** The default setting for the vertical tick labels flag. */
    142        public static final boolean DEFAULT_VERTICAL_TICK_LABELS = false;
    143
    144        /**
    145         * The range type (can be used to force the axis to display only positive
    146         * values or only negative values.
    147         */
    148        private RangeType rangeType;
    149
    150        /**
    151         * A flag that affects the axis range when the range is determined
    152         * automatically.  If the auto range does NOT include zero and this flag
    153         * is TRUE, then the range is changed to include zero.
    154         */
    155        private boolean autoRangeIncludesZero;
    156
    157        /**
    158         * A flag that affects the size of the margins added to the axis range when
    159         * the range is determined automatically.  If the value 0 falls within the
    160         * margin and this flag is TRUE, then the margin is truncated at zero.
    161         */
    162        private boolean autoRangeStickyZero;
    163
    164        /** The tick unit for the axis. */
    165        private NumberTickUnit tickUnit;
    166
    167        /** The override number format. */
    168        private NumberFormat numberFormatOverride;
    169
    170        /** An optional band for marking regions on the axis. */
    171        private MarkerAxisBand markerBand;
    172
    173        /**
    174         * Default constructor.
    175         */
    176        public NumberAxis() {
    177            this(null);
    178        }
    179
    180        /**
    181         * Constructs a number axis, using default values where necessary.
    182         *
    183         * @param label  the axis label (<code>null</code> permitted).
    184         */
    185        public NumberAxis(String label) {
    186            super(label, NumberAxis.createStandardTickUnits());
    187            this.rangeType = RangeType.FULL;
    188            this.autoRangeIncludesZero = DEFAULT_AUTO_RANGE_INCLUDES_ZERO;
    189            this.autoRangeStickyZero = DEFAULT_AUTO_RANGE_STICKY_ZERO;
    190            this.tickUnit = DEFAULT_TICK_UNIT;
    191            this.numberFormatOverride = null;
    192            this.markerBand = null;
    193        }
    194
    195        /**
    196         * Returns the axis range type.
    197         *
    198         * @return The axis range type (never <code>null</code>).
    199         */
    200        public RangeType getRangeType() {
    201            return this.rangeType;
    202        }
    203
    204        /**
    205         * Sets the axis range type.
    206         *
    207         * @param rangeType  the range type (<code>null</code> not permitted).
    208         */
    209        public void setRangeType(RangeType rangeType) {
    210            if (rangeType == null) {
    211                throw new IllegalArgumentException("Null 'rangeType' argument.");
    212            }
    213            this.rangeType = rangeType;
    214            notifyListeners(new AxisChangeEvent(this));
    215        }
    216
    217        /**
    218         * Returns the flag that indicates whether or not the automatic axis range
    219         * (if indeed it is determined automatically) is forced to include zero.
    220         *
    221         * @return The flag.
    222         */
    223        public boolean getAutoRangeIncludesZero() {
    224            return this.autoRangeIncludesZero;
    225        }
    226
    227        /**
    228         * Sets the flag that indicates whether or not the axis range, if
    229         * automatically calculated, is forced to include zero.
    230         * <p>
    231         * If the flag is changed to <code>true</code>, the axis range is
    232         * recalculated.
    233         * <p>
    234         * Any change to the flag will trigger an {@link AxisChangeEvent}.
    235         *
    236         * @param flag  the new value of the flag.
    237         */
    238        public void setAutoRangeIncludesZero(boolean flag) {
    239            if (this.autoRangeIncludesZero != flag) {
    240                this.autoRangeIncludesZero = flag;
    241                if (isAutoRange()) {
    242                    autoAdjustRange();
    243                }
    244                notifyListeners(new AxisChangeEvent(this));
    245            }
    246        }
    247
    248        /**
    249         * Returns a flag that affects the auto-range when zero falls outside the
    250         * data range but inside the margins defined for the axis.
    251         *
    252         * @return The flag.
    253         */
    254        public boolean getAutoRangeStickyZero() {
    255            return this.autoRangeStickyZero;
    256        }
    257
    258        /**
    259         * Sets a flag that affects the auto-range when zero falls outside the data
    260         * range but inside the margins defined for the axis.
    261         *
    262         * @param flag  the new flag.
    263         */
    264        public void setAutoRangeStickyZero(boolean flag) {
    265            if (this.autoRangeStickyZero != flag) {
    266                this.autoRangeStickyZero = flag;
    267                if (isAutoRange()) {
    268                    autoAdjustRange();
    269                }
    270                notifyListeners(new AxisChangeEvent(this));
    271            }
    272        }
    273
    274        /**
    275         * Returns the tick unit for the axis.
    276         *
    277         * @return The tick unit for the axis.
    278         */
    279        public NumberTickUnit getTickUnit() {
    280            return this.tickUnit;
    281        }
    282
    283        /**
    284         * Sets the tick unit for the axis and sends an {@link AxisChangeEvent} to
    285         * all registered listeners.  A side effect of calling this method is that
    286         * the "auto-select" feature for tick units is switched off (you can
    287         * restore it using the {@link ValueAxis#setAutoTickUnitSelection(boolean)}
    288         * method).
    289         *
    290         * @param unit  the new tick unit (<code>null</code> not permitted).
    291         */
    292        public void setTickUnit(NumberTickUnit unit) {
    293            // defer argument checking...
    294            setTickUnit(unit, true, true);
    295        }
    296
    297        /**
    298         * Sets the tick unit for the axis and, if requested, sends an
    299         * {@link AxisChangeEvent} to all registered listeners.  In addition, an
    300         * option is provided to turn off the "auto-select" feature for tick units
    301         * (you can restore it using the
    302         * {@link ValueAxis#setAutoTickUnitSelection(boolean)} method).
    303         *
    304         * @param unit  the new tick unit (<code>null</code> not permitted).
    305         * @param notify  notify listeners?
    306         * @param turnOffAutoSelect  turn off the auto-tick selection?
    307         */
    308        public void setTickUnit(NumberTickUnit unit, boolean notify,
    309                                boolean turnOffAutoSelect) {
    310
    311            if (unit == null) {
    312                throw new IllegalArgumentException("Null 'unit' argument.");
    313            }
    314            this.tickUnit = unit;
    315            if (turnOffAutoSelect) {
    316                setAutoTickUnitSelection(false, false);
    317            }
    318            if (notify) {
    319                notifyListeners(new AxisChangeEvent(this));
    320            }
    321
    322        }
    323
    324        /**
    325         * Returns the number format override.  If this is non-null, then it will
    326         * be used to format the numbers on the axis.
    327         *
    328         * @return The number formatter (possibly <code>null</code>).
    329         */
    330        public NumberFormat getNumberFormatOverride() {
    331            return this.numberFormatOverride;
    332        }
    333
    334        /**
    335         * Sets the number format override.  If this is non-null, then it will be
    336         * used to format the numbers on the axis.
    337         *
    338         * @param formatter  the number formatter (<code>null</code> permitted).
    339         */
    340        public void setNumberFormatOverride(NumberFormat formatter) {
    341            this.numberFormatOverride = formatter;
    342            notifyListeners(new AxisChangeEvent(this));
    343        }
    344
    345        /**
    346         * Returns the (optional) marker band for the axis.
    347         *
    348         * @return The marker band (possibly <code>null</code>).
    349         */
    350        public MarkerAxisBand getMarkerBand() {
    351            return this.markerBand;
    352        }
    353
    354        /**
    355         * Sets the marker band for the axis.
    356         * <P>
    357         * The marker band is optional, leave it set to <code>null</code> if you
    358         * don't require it.
    359         *
    360         * @param band the new band (<code>null<code> permitted).
    361         */
    362        public void setMarkerBand(MarkerAxisBand band) {
    363            this.markerBand = band;
    364            notifyListeners(new AxisChangeEvent(this));
    365        }
    366
    367        /**
    368         * Configures the axis to work with the specified plot.  If the axis has
    369         * auto-scaling, then sets the maximum and minimum values.
    370         */
    371        public void configure() {
    372            if (isAutoRange()) {
    373                autoAdjustRange();
    374            }
    375        }
    376
    377        /**
    378         * Rescales the axis to ensure that all data is visible.
    379         */
    380        protected void autoAdjustRange() {
    381
    382            Plot plot = getPlot();
    383            if (plot == null) {
    384                return;  // no plot, no data
    385            }
    386
    387            if (plot instanceof ValueAxisPlot) {
    388                ValueAxisPlot vap = (ValueAxisPlot) plot;
    389
    390                Range r = vap.getDataRange(this);
    391                if (r == null) {
    392                    r = new Range(DEFAULT_LOWER_BOUND, DEFAULT_UPPER_BOUND);
    393                }
    394
    395                double upper = r.getUpperBound();
    396                double lower = r.getLowerBound();
    397                if (this.rangeType == RangeType.POSITIVE) {
    398                    lower = Math.max(0.0, lower);
    399                    upper = Math.max(0.0, upper);
    400                }
    401                else if (this.rangeType == RangeType.NEGATIVE) {
    402                    lower = Math.min(0.0, lower);
    403                    upper = Math.min(0.0, upper);
    404                }
    405
    406                if (getAutoRangeIncludesZero()) {
    407                    lower = Math.min(lower, 0.0);
    408                    upper = Math.max(upper, 0.0);
    409                }
    410                double range = upper - lower;
    411
    412                // if fixed auto range, then derive lower bound...
    413                double fixedAutoRange = getFixedAutoRange();
    414                if (fixedAutoRange > 0.0) {
    415                    lower = upper - fixedAutoRange;
    416                }
    417                else {
    418                    // ensure the autorange is at least <minRange> in size...
    419                    double minRange = getAutoRangeMinimumSize();
    420                    if (range < minRange) {
    421                        double expand = (minRange - range) / 2;
    422                        upper = upper + expand;
    423                        lower = lower - expand;
    424                        if (this.rangeType == RangeType.POSITIVE) {
    425                            if (lower < 0.0) {
    426                                upper = upper - lower;
    427                                lower = 0.0;
    428                            }
    429                        }
    430                        else if (this.rangeType == RangeType.NEGATIVE) {
    431                            if (upper > 0.0) {
    432                                lower = lower - upper;
    433                                upper = 0.0;
    434                            }
    435                        }
    436                    }
    437
    438                    if (getAutoRangeStickyZero()) {
    439                        if (upper <= 0.0) {
    440                            upper = Math.min(0.0, upper + getUpperMargin() * range);
    441                        }
    442                        else {
    443                            upper = upper + getUpperMargin() * range;
    444                        }
    445                        if (lower >= 0.0) {
    446                            lower = Math.max(0.0, lower - getLowerMargin() * range);
    447                        }
    448                        else {
    449                            lower = lower - getLowerMargin() * range;
    450                        }
    451                    }
    452                    else {
    453                        upper = upper + getUpperMargin() * range;
    454                        lower = lower - getLowerMargin() * range;
    455                    }
    456                }
    457
    458                setRange(new Range(lower, upper), false, false);
    459            }
    460
    461        }
    462
    463        /**
    464         * Converts a data value to a coordinate in Java2D space, assuming that the
    465         * axis runs along one edge of the specified dataArea.
    466         * <p>
    467         * Note that it is possible for the coordinate to fall outside the plotArea.
    468         *
    469         * @param value  the data value.
    470         * @param area  the area for plotting the data.
    471         * @param edge  the axis location.
    472         *
    473         * @return The Java2D coordinate.
    474         */
    475        public double valueToJava2D(double value, Rectangle2D area,
    476                                    RectangleEdge edge) {
    477
    478            Range range = getRange();
    479            double axisMin = range.getLowerBound();
    480            double axisMax = range.getUpperBound();
    481
    482            double min = 0.0;
    483            double max = 0.0;
    484            if (RectangleEdge.isTopOrBottom(edge)) {
    485                min = area.getX();
    486                max = area.getMaxX();
    487            }
    488            else if (RectangleEdge.isLeftOrRight(edge)) {
    489                max = area.getMinY();
    490                min = area.getMaxY();
    491            }
    492            if (isInverted()) {
    493                return max
    494                       - ((value - axisMin) / (axisMax - axisMin)) * (max - min);
    495            }
    496            else {
    497                return min
    498                       + ((value - axisMin) / (axisMax - axisMin)) * (max - min);
    499            }
    500
    501        }
    502
    503        /**
    504         * Converts a coordinate in Java2D space to the corresponding data value,
    505         * assuming that the axis runs along one edge of the specified dataArea.
    506         *
    507         * @param java2DValue  the coordinate in Java2D space.
    508         * @param area  the area in which the data is plotted.
    509         * @param edge  the location.
    510         *
    511         * @return The data value.
    512         */
    513        public double java2DToValue(double java2DValue, Rectangle2D area,
    514                                    RectangleEdge edge) {
    515
    516            Range range = getRange();
    517            double axisMin = range.getLowerBound();
    518            double axisMax = range.getUpperBound();
    519
    520            double min = 0.0;
    521            double max = 0.0;
    522            if (RectangleEdge.isTopOrBottom(edge)) {
    523                min = area.getX();
    524                max = area.getMaxX();
    525            }
    526            else if (RectangleEdge.isLeftOrRight(edge)) {
    527                min = area.getMaxY();
    528                max = area.getY();
    529            }
    530            if (isInverted()) {
    531                return axisMax
    532                       - (java2DValue - min) / (max - min) * (axisMax - axisMin);
    533            }
    534            else {
    535                return axisMin
    536                       + (java2DValue - min) / (max - min) * (axisMax - axisMin);
    537            }
    538
    539        }
    540
    541        /**
    542         * Calculates the value of the lowest visible tick on the axis.
    543         *
    544         * @return The value of the lowest visible tick on the axis.
    545         */
    546        protected double calculateLowestVisibleTickValue() {
    547
    548            double unit = getTickUnit().getSize();
    549            double index = Math.ceil(getRange().getLowerBound() / unit);
    550            return index * unit;
    551
    552        }
    553
    554        /**
    555         * Calculates the value of the highest visible tick on the axis.
    556         *
    557         * @return The value of the highest visible tick on the axis.
    558         */
    559        protected double calculateHighestVisibleTickValue() {
    560
    561            double unit = getTickUnit().getSize();
    562            double index = Math.floor(getRange().getUpperBound() / unit);
    563            return index * unit;
    564
    565        }
    566
    567        /**
    568         * Calculates the number of visible ticks.
    569         *
    570         * @return The number of visible ticks on the axis.
    571         */
    572        protected int calculateVisibleTickCount() {
    573
    574            double unit = getTickUnit().getSize();
    575            Range range = getRange();
    576            return (int) (Math.floor(range.getUpperBound() / unit)
    577                          - Math.ceil(range.getLowerBound() / unit) + 1);
    578
    579        }
    580
    581        /**
    582         * Draws the axis on a Java 2D graphics device (such as the screen or a
    583         * printer).
    584         *
    585         * @param g2  the graphics device (<code>null</code> not permitted).
    586         * @param cursor  the cursor location.
    587         * @param plotArea  the area within which the axes and data should be drawn
    588         *                  (<code>null</code> not permitted).
    589         * @param dataArea  the area within which the data should be drawn
    590         *                  (<code>null</code> not permitted).
    591         * @param edge  the location of the axis (<code>null</code> not permitted).
    592         * @param plotState  collects information about the plot
    593         *                   (<code>null</code> permitted).
    594         *
    595         * @return The axis state (never <code>null</code>).
    596         */
    597        public AxisState draw(Graphics2D g2,
    598                              double cursor,
    599                              Rectangle2D plotArea,
    600                              Rectangle2D dataArea,
    601                              RectangleEdge edge,
    602                              PlotRenderingInfo plotState) {
    603
    604            AxisState state = null;
    605            // if the axis is not visible, don't draw it...
    606            if (!isVisible()) {
    607                state = new AxisState(cursor);
    608                // even though the axis is not visible, we need ticks for the
    609                // gridlines...
    610                List ticks = refreshTicks(g2, state, dataArea, edge);
    611                state.setTicks(ticks);
    612                return state;
    613            }
    614
    615            // draw the tick marks and labels...
    616            state = drawTickMarksAndLabels(g2, cursor, plotArea, dataArea, edge);
    617
    618    //        // draw the marker band (if there is one)...
    619    //        if (getMarkerBand() != null) {
    620    //            if (edge == RectangleEdge.BOTTOM) {
    621    //                cursor = cursor - getMarkerBand().getHeight(g2);
    622    //            }
    623    //            getMarkerBand().draw(g2, plotArea, dataArea, 0, cursor);
    624    //        }
    625
    626            // draw the axis label...
    627            state = drawLabel(getLabel(), g2, plotArea, dataArea, edge, state);
    628
    629            return state;
    630
    631        }
    632
    633        /**
    634         * Creates the standard tick units.
    635         * <P>
    636         * If you don't like these defaults, create your own instance of TickUnits
    637         * and then pass it to the setStandardTickUnits() method in the
    638         * NumberAxis class.
    639         *
    640         * @return The standard tick units.
    641         */
    642        public static TickUnitSource createStandardTickUnits() {
    643
    644            TickUnits units = new TickUnits();
    645            DecimalFormat df0 = new DecimalFormat("0.00000000");
    646            DecimalFormat df1 = new DecimalFormat("0.0000000");
    647            DecimalFormat df2 = new DecimalFormat("0.000000");
    648            DecimalFormat df3 = new DecimalFormat("0.00000");
    649            DecimalFormat df4 = new DecimalFormat("0.0000");
    650            DecimalFormat df5 = new DecimalFormat("0.000");
    651            DecimalFormat df6 = new DecimalFormat("0.00");
    652            DecimalFormat df7 = new DecimalFormat("0.0");
    653            DecimalFormat df8 = new DecimalFormat("#,##0");
    654            DecimalFormat df9 = new DecimalFormat("#,###,##0");
    655            DecimalFormat df10 = new DecimalFormat("#,###,###,##0");
    656
    657            // we can add the units in any order, the TickUnits collection will
    658            // sort them...
    659            units.add(new NumberTickUnit(0.0000001, df1));
    660            units.add(new NumberTickUnit(0.000001, df2));
    661            units.add(new NumberTickUnit(0.00001, df3));
    662            units.add(new NumberTickUnit(0.0001, df4));
    663            units.add(new NumberTickUnit(0.001, df5));
    664            units.add(new NumberTickUnit(0.01, df6));
    665            units.add(new NumberTickUnit(0.1, df7));
    666            units.add(new NumberTickUnit(1, df8));
    667            units.add(new NumberTickUnit(10, df8));
    668            units.add(new NumberTickUnit(100, df8));
    669            units.add(new NumberTickUnit(1000, df8));
    670            units.add(new NumberTickUnit(10000, df8));
    671            units.add(new NumberTickUnit(100000, df8));
    672            units.add(new NumberTickUnit(1000000, df9));
    673            units.add(new NumberTickUnit(10000000, df9));
    674            units.add(new NumberTickUnit(100000000, df9));
    675            units.add(new NumberTickUnit(1000000000, df10));
    676            units.add(new NumberTickUnit(10000000000.0, df10));
    677            units.add(new NumberTickUnit(100000000000.0, df10));
    678
    679            units.add(new NumberTickUnit(0.00000025, df0));
    680            units.add(new NumberTickUnit(0.0000025, df1));
    681            units.add(new NumberTickUnit(0.000025, df2));
    682            units.add(new NumberTickUnit(0.00025, df3));
    683            units.add(new NumberTickUnit(0.0025, df4));
    684            units.add(new NumberTickUnit(0.025, df5));
    685            units.add(new NumberTickUnit(0.25, df6));
    686            units.add(new NumberTickUnit(2.5, df7));
    687            units.add(new NumberTickUnit(25, df8));
    688            units.add(new NumberTickUnit(250, df8));
    689            units.add(new NumberTickUnit(2500, df8));
    690            units.add(new NumberTickUnit(25000, df8));
    691            units.add(new NumberTickUnit(250000, df8));
    692            units.add(new NumberTickUnit(2500000, df9));
    693            units.add(new NumberTickUnit(25000000, df9));
    694            units.add(new NumberTickUnit(250000000, df9));
    695            units.add(new NumberTickUnit(2500000000.0, df10));
    696            units.add(new NumberTickUnit(25000000000.0, df10));
    697            units.add(new NumberTickUnit(250000000000.0, df10));
    698
    699            units.add(new NumberTickUnit(0.0000005, df1));
    700            units.add(new NumberTickUnit(0.000005, df2));
    701            units.add(new NumberTickUnit(0.00005, df3));
    702            units.add(new NumberTickUnit(0.0005, df4));
    703            units.add(new NumberTickUnit(0.005, df5));
    704            units.add(new NumberTickUnit(0.05, df6));
    705            units.add(new NumberTickUnit(0.5, df7));
    706            units.add(new NumberTickUnit(5L, df8));
    707            units.add(new NumberTickUnit(50L, df8));
    708            units.add(new NumberTickUnit(500L, df8));
    709            units.add(new NumberTickUnit(5000L, df8));
    710            units.add(new NumberTickUnit(50000L, df8));
    711            units.add(new NumberTickUnit(500000L, df8));
    712            units.add(new NumberTickUnit(5000000L, df9));
    713            units.add(new NumberTickUnit(50000000L, df9));
    714            units.add(new NumberTickUnit(500000000L, df9));
    715            units.add(new NumberTickUnit(5000000000L, df10));
    716            units.add(new NumberTickUnit(50000000000L, df10));
    717            units.add(new NumberTickUnit(500000000000L, df10));
    718
    719            return units;
    720
    721        }
    722
    723        /**
    724         * Returns a collection of tick units for integer values.
    725         *
    726         * @return A collection of tick units for integer values.
    727         */
    728        public static TickUnitSource createIntegerTickUnits() {
    729
    730            TickUnits units = new TickUnits();
    731            DecimalFormat df0 = new DecimalFormat("0");
    732            DecimalFormat df1 = new DecimalFormat("#,##0");
    733            units.add(new NumberTickUnit(1, df0));
    734            units.add(new NumberTickUnit(2, df0));
    735            units.add(new NumberTickUnit(5, df0));
    736            units.add(new NumberTickUnit(10, df0));
    737            units.add(new NumberTickUnit(20, df0));
    738            units.add(new NumberTickUnit(50, df0));
    739            units.add(new NumberTickUnit(100, df0));
    740            units.add(new NumberTickUnit(200, df0));
    741            units.add(new NumberTickUnit(500, df0));
    742            units.add(new NumberTickUnit(1000, df1));
    743            units.add(new NumberTickUnit(2000, df1));
    744            units.add(new NumberTickUnit(5000, df1));
    745            units.add(new NumberTickUnit(10000, df1));
    746            units.add(new NumberTickUnit(20000, df1));
    747            units.add(new NumberTickUnit(50000, df1));
    748            units.add(new NumberTickUnit(100000, df1));
    749            units.add(new NumberTickUnit(200000, df1));
    750            units.add(new NumberTickUnit(500000, df1));
    751            units.add(new NumberTickUnit(1000000, df1));
    752            units.add(new NumberTickUnit(2000000, df1));
    753            units.add(new NumberTickUnit(5000000, df1));
    754            units.add(new NumberTickUnit(10000000, df1));
    755            units.add(new NumberTickUnit(20000000, df1));
    756            units.add(new NumberTickUnit(50000000, df1));
    757            units.add(new NumberTickUnit(100000000, df1));
    758            units.add(new NumberTickUnit(200000000, df1));
    759            units.add(new NumberTickUnit(500000000, df1));
    760            units.add(new NumberTickUnit(1000000000, df1));
    761            units.add(new NumberTickUnit(2000000000, df1));
    762            units.add(new NumberTickUnit(5000000000.0, df1));
    763            units.add(new NumberTickUnit(10000000000.0, df1));
    764
    765            return units;
    766
    767        }
    768
    769        /**
    770         * Creates a collection of standard tick units.  The supplied locale is
    771         * used to create the number formatter (a localised instance of
    772         * <code>NumberFormat</code>).
    773         * <P>
    774         * If you don't like these defaults, create your own instance of
    775         * {@link TickUnits} and then pass it to the
    776         * <code>setStandardTickUnits()</code> method.
    777         *
    778         * @param locale  the locale.
    779         *
    780         * @return A tick unit collection.
    781         */
    782        public static TickUnitSource createStandardTickUnits(Locale locale) {
    783
    784            TickUnits units = new TickUnits();
    785
    786            NumberFormat numberFormat = NumberFormat.getNumberInstance(locale);
    787
    788            // we can add the units in any order, the TickUnits collection will
    789            // sort them...
    790            units.add(new NumberTickUnit(0.0000001,    numberFormat));
    791            units.add(new NumberTickUnit(0.000001,     numberFormat));
    792            units.add(new NumberTickUnit(0.00001,      numberFormat));
    793            units.add(new NumberTickUnit(0.0001,       numberFormat));
    794            units.add(new NumberTickUnit(0.001,        numberFormat));
    795            units.add(new NumberTickUnit(0.01,         numberFormat));
    796            units.add(new NumberTickUnit(0.1,          numberFormat));
    797            units.add(new NumberTickUnit(1,            numberFormat));
    798            units.add(new NumberTickUnit(10,           numberFormat));
    799            units.add(new NumberTickUnit(100,          numberFormat));
    800            units.add(new NumberTickUnit(1000,         numberFormat));
    801            units.add(new NumberTickUnit(10000,        numberFormat));
    802            units.add(new NumberTickUnit(100000,       numberFormat));
    803            units.add(new NumberTickUnit(1000000,      numberFormat));
    804            units.add(new NumberTickUnit(10000000,     numberFormat));
    805            units.add(new NumberTickUnit(100000000,    numberFormat));
    806            units.add(new NumberTickUnit(1000000000,   numberFormat));
    807            units.add(new NumberTickUnit(10000000000.0,   numberFormat));
    808
    809            units.add(new NumberTickUnit(0.00000025,   numberFormat));
    810            units.add(new NumberTickUnit(0.0000025,    numberFormat));
    811            units.add(new NumberTickUnit(0.000025,     numberFormat));
    812            units.add(new NumberTickUnit(0.00025,      numberFormat));
    813            units.add(new NumberTickUnit(0.0025,       numberFormat));
    814            units.add(new NumberTickUnit(0.025,        numberFormat));
    815            units.add(new NumberTickUnit(0.25,         numberFormat));
    816            units.add(new NumberTickUnit(2.5,          numberFormat));
    817            units.add(new NumberTickUnit(25,           numberFormat));
    818            units.add(new NumberTickUnit(250,          numberFormat));
    819            units.add(new NumberTickUnit(2500,         numberFormat));
    820            units.add(new NumberTickUnit(25000,        numberFormat));
    821            units.add(new NumberTickUnit(250000,       numberFormat));
    822            units.add(new NumberTickUnit(2500000,      numberFormat));
    823            units.add(new NumberTickUnit(25000000,     numberFormat));
    824            units.add(new NumberTickUnit(250000000,    numberFormat));
    825            units.add(new NumberTickUnit(2500000000.0,   numberFormat));
    826            units.add(new NumberTickUnit(25000000000.0,   numberFormat));
    827
    828            units.add(new NumberTickUnit(0.0000005,    numberFormat));
    829            units.add(new NumberTickUnit(0.000005,     numberFormat));
    830            units.add(new NumberTickUnit(0.00005,      numberFormat));
    831            units.add(new NumberTickUnit(0.0005,       numberFormat));
    832            units.add(new NumberTickUnit(0.005,        numberFormat));
    833            units.add(new NumberTickUnit(0.05,         numberFormat));
    834            units.add(new NumberTickUnit(0.5,          numberFormat));
    835            units.add(new NumberTickUnit(5L,           numberFormat));
    836            units.add(new NumberTickUnit(50L,          numberFormat));
    837            units.add(new NumberTickUnit(500L,         numberFormat));
    838            units.add(new NumberTickUnit(5000L,        numberFormat));
    839            units.add(new NumberTickUnit(50000L,       numberFormat));
    840            units.add(new NumberTickUnit(500000L,      numberFormat));
    841            units.add(new NumberTickUnit(5000000L,     numberFormat));
    842            units.add(new NumberTickUnit(50000000L,    numberFormat));
    843            units.add(new NumberTickUnit(500000000L,   numberFormat));
    844            units.add(new NumberTickUnit(5000000000L,  numberFormat));
    845            units.add(new NumberTickUnit(50000000000L,  numberFormat));
    846
    847            return units;
    848
    849        }
    850
    851        /**
    852         * Returns a collection of tick units for integer values.
    853         * Uses a given Locale to create the DecimalFormats.
    854         *
    855         * @param locale the locale to use to represent Numbers.
    856         *
    857         * @return A collection of tick units for integer values.
    858         */
    859        public static TickUnitSource createIntegerTickUnits(Locale locale) {
    860
    861            TickUnits units = new TickUnits();
    862
    863            NumberFormat numberFormat = NumberFormat.getNumberInstance(locale);
    864
    865            units.add(new NumberTickUnit(1,              numberFormat));
    866            units.add(new NumberTickUnit(2,              numberFormat));
    867            units.add(new NumberTickUnit(5,              numberFormat));
    868            units.add(new NumberTickUnit(10,             numberFormat));
    869            units.add(new NumberTickUnit(20,             numberFormat));
    870            units.add(new NumberTickUnit(50,             numberFormat));
    871            units.add(new NumberTickUnit(100,            numberFormat));
    872            units.add(new NumberTickUnit(200,            numberFormat));
    873            units.add(new NumberTickUnit(500,            numberFormat));
    874            units.add(new NumberTickUnit(1000,           numberFormat));
    875            units.add(new NumberTickUnit(2000,           numberFormat));
    876            units.add(new NumberTickUnit(5000,           numberFormat));
    877            units.add(new NumberTickUnit(10000,          numberFormat));
    878            units.add(new NumberTickUnit(20000,          numberFormat));
    879            units.add(new NumberTickUnit(50000,          numberFormat));
    880            units.add(new NumberTickUnit(100000,         numberFormat));
    881            units.add(new NumberTickUnit(200000,         numberFormat));
    882            units.add(new NumberTickUnit(500000,         numberFormat));
    883            units.add(new NumberTickUnit(1000000,        numberFormat));
    884            units.add(new NumberTickUnit(2000000,        numberFormat));
    885            units.add(new NumberTickUnit(5000000,        numberFormat));
    886            units.add(new NumberTickUnit(10000000,       numberFormat));
    887            units.add(new NumberTickUnit(20000000,       numberFormat));
    888            units.add(new NumberTickUnit(50000000,       numberFormat));
    889            units.add(new NumberTickUnit(100000000,      numberFormat));
    890            units.add(new NumberTickUnit(200000000,      numberFormat));
    891            units.add(new NumberTickUnit(500000000,      numberFormat));
    892            units.add(new NumberTickUnit(1000000000,     numberFormat));
    893            units.add(new NumberTickUnit(2000000000,     numberFormat));
    894            units.add(new NumberTickUnit(5000000000.0,   numberFormat));
    895            units.add(new NumberTickUnit(10000000000.0,  numberFormat));
    896
    897            return units;
    898
    899        }
    900
    901        /**
    902         * Estimates the maximum tick label height.
    903         *
    904         * @param g2  the graphics device.
    905         *
    906         * @return The maximum height.
    907         */
    908        protected double estimateMaximumTickLabelHeight(Graphics2D g2) {
    909
    910            RectangleInsets tickLabelInsets = getTickLabelInsets();
    911            double result = tickLabelInsets.getTop() + tickLabelInsets.getBottom();
    912
    913            Font tickLabelFont = getTickLabelFont();
    914            FontRenderContext frc = g2.getFontRenderContext();
    915            result += tickLabelFont.getLineMetrics("123", frc).getHeight();
    916            return result;
    917
    918        }
    919
    920        /**
    921         * Estimates the maximum width of the tick labels, assuming the specified
    922         * tick unit is used.
    923         * <P>
    924         * Rather than computing the string bounds of every tick on the axis, we
    925         * just look at two values: the lower bound and the upper bound for the
    926         * axis.  These two values will usually be representative.
    927         *
    928         * @param g2  the graphics device.
    929         * @param unit  the tick unit to use for calculation.
    930         *
    931         * @return The estimated maximum width of the tick labels.
    932         */
    933        protected double estimateMaximumTickLabelWidth(Graphics2D g2,
    934                                                       TickUnit unit) {
    935
    936            RectangleInsets tickLabelInsets = getTickLabelInsets();
    937            double result = tickLabelInsets.getLeft() + tickLabelInsets.getRight();
    938
    939            if (isVerticalTickLabels()) {
    940                // all tick labels have the same width (equal to the height of the
    941                // font)...
    942                FontRenderContext frc = g2.getFontRenderContext();
    943                LineMetrics lm = getTickLabelFont().getLineMetrics("0", frc);
    944                result += lm.getHeight();
    945            }
    946            else {
    947                // look at lower and upper bounds...
    948                FontMetrics fm = g2.getFontMetrics(getTickLabelFont());
    949                Range range = getRange();
    950                double lower = range.getLowerBound();
    951                double upper = range.getUpperBound();
    952                String lowerStr = unit.valueToString(lower);
    953                String upperStr = unit.valueToString(upper);
    954                double w1 = fm.stringWidth(lowerStr);
    955                double w2 = fm.stringWidth(upperStr);
    956                result += Math.max(w1, w2);
    957            }
    958
    959            return result;
    960
    961        }
    962
    963        /**
    964         * Selects an appropriate tick value for the axis.  The strategy is to
    965         * display as many ticks as possible (selected from an array of 'standard'
    966         * tick units) without the labels overlapping.
    967         *
    968         * @param g2  the graphics device.
    969         * @param dataArea  the area defined by the axes.
    970         * @param edge  the axis location.
    971         */
    972        protected void selectAutoTickUnit(Graphics2D g2,
    973                                          Rectangle2D dataArea,
    974                                          RectangleEdge edge) {
    975
    976            if (RectangleEdge.isTopOrBottom(edge)) {
    977                selectHorizontalAutoTickUnit(g2, dataArea, edge);
    978            }
    979            else if (RectangleEdge.isLeftOrRight(edge)) {
    980                selectVerticalAutoTickUnit(g2, dataArea, edge);
    981            }
    982
    983        }
    984
    985        /**
    986         * Selects an appropriate tick value for the axis.  The strategy is to
    987         * display as many ticks as possible (selected from an array of 'standard'
    988         * tick units) without the labels overlapping.
    989         *
    990         * @param g2  the graphics device.
    991         * @param dataArea  the area defined by the axes.
    992         * @param edge  the axis location.
    993         */
    994       protected void selectHorizontalAutoTickUnit(Graphics2D g2,
    995                                                   Rectangle2D dataArea,
    996                                                   RectangleEdge edge) {
    997
    998            double tickLabelWidth = estimateMaximumTickLabelWidth(
    999                g2, getTickUnit()
    1000            );
    1001
    1002            // start with the current tick unit...
    1003            TickUnitSource tickUnits = getStandardTickUnits();
    1004            TickUnit unit1 = tickUnits.getCeilingTickUnit(getTickUnit());
    1005            double unit1Width = lengthToJava2D(unit1.getSize(), dataArea, edge);
    1006
    1007            // then extrapolate...
    1008            double guess = (tickLabelWidth / unit1Width) * unit1.getSize();
    1009
    1010            NumberTickUnit unit2
    1011                = (NumberTickUnit) tickUnits.getCeilingTickUnit(guess);
    1012            double unit2Width = lengthToJava2D(unit2.getSize(), dataArea, edge);
    1013
    1014            tickLabelWidth = estimateMaximumTickLabelWidth(g2, unit2);
    1015            if (tickLabelWidth > unit2Width) {
    1016                unit2 = (NumberTickUnit) tickUnits.getLargerTickUnit(unit2);
    1017            }
    1018
    1019            setTickUnit(unit2, false, false);
    1020
    1021        }
    1022
    1023        /**
    1024         * Selects an appropriate tick value for the axis.  The strategy is to
    1025         * display as many ticks as possible (selected from an array of 'standard'
    1026         * tick units) without the labels overlapping.
    1027         *
    1028         * @param g2  the graphics device.
    1029         * @param dataArea  the area in which the plot should be drawn.
    1030         * @param edge  the axis location.
    1031         */
    1032        protected void selectVerticalAutoTickUnit(Graphics2D g2,
    1033                                                  Rectangle2D dataArea,
    1034                                                  RectangleEdge edge) {
    1035
    1036            double tickLabelHeight = estimateMaximumTickLabelHeight(g2);
    1037
    1038            // start with the current tick unit...
    1039            TickUnitSource tickUnits = getStandardTickUnits();
    1040            TickUnit unit1 = tickUnits.getCeilingTickUnit(getTickUnit());
    1041            double unitHeight = lengthToJava2D(unit1.getSize(), dataArea, edge);
    1042
    1043            // then extrapolate...
    1044            double guess = (tickLabelHeight / unitHeight) * unit1.getSize();
    1045
    1046            NumberTickUnit unit2
    1047                = (NumberTickUnit) tickUnits.getCeilingTickUnit(guess);
    1048            double unit2Height = lengthToJava2D(unit2.getSize(), dataArea, edge);
    1049
    1050            tickLabelHeight = estimateMaximumTickLabelHeight(g2);
    1051            if (tickLabelHeight > unit2Height) {
    1052                unit2 = (NumberTickUnit) tickUnits.getLargerTickUnit(unit2);
    1053            }
    1054
    1055            setTickUnit(unit2, false, false);
    1056
    1057        }
    1058
    1059        /**
    1060         * Calculates the positions of the tick labels for the axis, storing the
    1061         * results in the tick label list (ready for drawing).
    1062         *
    1063         * @param g2  the graphics device.
    1064         * @param state  the axis state.
    1065         * @param dataArea  the area in which the plot should be drawn.
    1066         * @param edge  the location of the axis.
    1067         *
    1068         * @return A list of ticks.
    1069         *
    1070         */
    1071        public List refreshTicks(Graphics2D g2,
    1072                                 AxisState state,
    1073                                 Rectangle2D dataArea,
    1074                                 RectangleEdge edge) {
    1075
    1076            List result = new java.util.ArrayList();
    1077            if (RectangleEdge.isTopOrBottom(edge)) {
    1078                result = refreshTicksHorizontal(g2, dataArea, edge);
    1079            }
    1080            else if (RectangleEdge.isLeftOrRight(edge)) {
    1081                result = refreshTicksVertical(g2, dataArea, edge);
    1082            }
    1083            return result;
    1084
    1085        }
    1086
    1087        /**
    1088         * Calculates the positions of the tick labels for the axis, storing the
    1089         * results in the tick label list (ready for drawing).
    1090         *
    1091         * @param g2  the graphics device.
    1092         * @param dataArea  the area in which the data should be drawn.
    1093         * @param edge  the location of the axis.
    1094         *
    1095         * @return A list of ticks.
    1096         */
    1097        protected List refreshTicksHorizontal(Graphics2D g2,
    1098                                              Rectangle2D dataArea,
    1099                                              RectangleEdge edge) {
    1100
    1101            List result = new java.util.ArrayList();
    1102
    1103            Font tickLabelFont = getTickLabelFont();
    1104            g2.setFont(tickLabelFont);
    1105
    1106            if (isAutoTickUnitSelection()) {
    1107                selectAutoTickUnit(g2, dataArea, edge);
    1108            }
    1109
    1110            double size = getTickUnit().getSize();
    1111            int count = calculateVisibleTickCount();
    1112            double lowestTickValue = calculateLowestVisibleTickValue();
    1113
    1114            if (count <= ValueAxis.MAXIMUM_TICK_COUNT) {
    1115                for (int i = 0; i < count; i++) {
    1116                    double currentTickValue = lowestTickValue + (i * size);
    1117                    String tickLabel;
    1118                    NumberFormat formatter = getNumberFormatOverride();
    1119                    if (formatter != null) {
    1120                        tickLabel = formatter.format(currentTickValue);
    1121                    }
    1122                    else {
    1123                        tickLabel = getTickUnit().valueToString(currentTickValue);
    1124                    }
    1125                    TextAnchor anchor = null;
    1126                    TextAnchor rotationAnchor = null;
    1127                    double angle = 0.0;
    1128                    if (isVerticalTickLabels()) {
    1129                        anchor = TextAnchor.CENTER_RIGHT;
    1130                        rotationAnchor = TextAnchor.CENTER_RIGHT;
    1131                        if (edge == RectangleEdge.TOP) {
    1132                            angle = Math.PI / 2.0;
    1133                        }
    1134                        else {
    1135                            angle = -Math.PI / 2.0;
    1136                        }
    1137                    }
    1138                    else {
    1139                        if (edge == RectangleEdge.TOP) {
    1140                            anchor = TextAnchor.BOTTOM_CENTER;
    1141                            rotationAnchor = TextAnchor.BOTTOM_CENTER;
    1142                        }
    1143                        else {
    1144                            anchor = TextAnchor.TOP_CENTER;
    1145                            rotationAnchor = TextAnchor.TOP_CENTER;
    1146                        }
    1147                    }
    1148
    1149                    Tick tick = new NumberTick(
    1150                        new Double(currentTickValue), tickLabel, anchor,
    1151                        rotationAnchor, angle
    1152                    );
    1153                    result.add(tick);
    1154                }
    1155            }
    1156            return result;
    1157
    1158        }
    1159
    1160        /**
    1161         * Calculates the positions of the tick labels for the axis, storing the
    1162         * results in the tick label list (ready for drawing).
    1163         *
    1164         * @param g2  the graphics device.
    1165         * @param dataArea  the area in which the plot should be drawn.
    1166         * @param edge  the location of the axis.
    1167         *
    1168         * @return A list of ticks.
    1169         *
    1170         */
    1171        protected List refreshTicksVertical(Graphics2D g2,
    1172                                            Rectangle2D dataArea,
    1173                                            RectangleEdge edge) {
    1174
    1175            List result = new java.util.ArrayList();
    1176            result.clear();
    1177
    1178            Font tickLabelFont = getTickLabelFont();
    1179            g2.setFont(tickLabelFont);
    1180            if (isAutoTickUnitSelection()) {
    1181                selectAutoTickUnit(g2, dataArea, edge);
    1182            }
    1183
    1184            double size = getTickUnit().getSize();
    1185            int count = calculateVisibleTickCount();
    1186            double lowestTickValue = calculateLowestVisibleTickValue();
    1187
    1188            if (count <= ValueAxis.MAXIMUM_TICK_COUNT) {
    1189                for (int i = 0; i < count; i++) {
    1190                    double currentTickValue = lowestTickValue + (i * size);
    1191                    String tickLabel;
    1192                    NumberFormat formatter = getNumberFormatOverride();
    1193                    if (formatter != null) {
    1194                        tickLabel = formatter.format(currentTickValue);
    1195                    }
    1196                    else {
    1197                        tickLabel = getTickUnit().valueToString(currentTickValue);
    1198                    }
    1199
    1200                    TextAnchor anchor = null;
    1201                    TextAnchor rotationAnchor = null;
    1202                    double angle = 0.0;
    1203                    if (isVerticalTickLabels()) {
    1204                        if (edge == RectangleEdge.LEFT) {
    1205                            anchor = TextAnchor.BOTTOM_CENTER;
    1206                            rotationAnchor = TextAnchor.BOTTOM_CENTER;
    1207                            angle = -Math.PI / 2.0;
    1208                        }
    1209                        else {
    1210                            anchor = TextAnchor.BOTTOM_CENTER;
    1211                            rotationAnchor = TextAnchor.BOTTOM_CENTER;
    1212                            angle = Math.PI / 2.0;
    1213                        }
    1214                    }
    1215                    else {
    1216                        if (edge == RectangleEdge.LEFT) {
    1217                            anchor = TextAnchor.CENTER_RIGHT;
    1218                            rotationAnchor = TextAnchor.CENTER_RIGHT;
    1219                        }
    1220                        else {
    1221                            anchor = TextAnchor.CENTER_LEFT;
    1222                            rotationAnchor = TextAnchor.CENTER_LEFT;
    1223                        }
    1224                    }
    1225
    1226                    Tick tick = new NumberTick(
    1227                        new Double(currentTickValue), tickLabel, anchor,
    1228                        rotationAnchor, angle
    1229                    );
    1230                    result.add(tick);
    1231                }
    1232            }
    1233            return result;
    1234
    1235        }
    1236
    1237        /**
    1238         * Returns a clone of the axis.
    1239         *
    1240         * @return A clone
    1241         *
    1242         * @throws CloneNotSupportedException if some component of the axis does
    1243         *         not support cloning.
    1244         */
    1245        public Object clone() throws CloneNotSupportedException {
    1246            NumberAxis clone = (NumberAxis) super.clone();
    1247            if (this.numberFormatOverride != null) {
    1248                clone.numberFormatOverride
    1249                    = (NumberFormat) this.numberFormatOverride.clone();
    1250            }
    1251            return clone;
    1252        }
    1253
    1254        /**
    1255         * Tests an object for equality with this instance.
    1256         *
    1257         * @param obj  the object.
    1258         *
    1259         * @return A boolean.
    1260         */
    1261        public boolean equals(Object obj) {
    1262            if (obj == this) {
    1263                return true;
    1264            }
    1265            if (!(obj instanceof NumberAxis)) {
    1266                return false;
    1267            }
    1268            if (!super.equals(obj)) {
    1269                return false;
    1270            }
    1271            NumberAxis that = (NumberAxis) obj;
    1272            if (this.autoRangeIncludesZero != that.autoRangeIncludesZero) {
    1273                return false;
    1274            }
    1275            if (this.autoRangeStickyZero != that.autoRangeStickyZero) {
    1276                return false;
    1277            }
    1278            if (!ObjectUtilities.equal(this.tickUnit, that.tickUnit)) {
    1279                return false;
    1280            }
    1281            if (!ObjectUtilities.equal(this.numberFormatOverride,
    1282                    that.numberFormatOverride)) {
    1283                return false;
    1284            }
    1285            return true;
    1286        }
    1287
    1288        /**
    1289         * Returns a hash code for this object.
    1290         *
    1291         * @return A hash code.
    1292         */
    1293        public int hashCode() {
    1294            if (getLabel() != null) {
    1295                return getLabel().hashCode();
    1296            }
    1297            else {
    1298                return 0;
    1299            }
    1300        }
    1301
    1302    }