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    }