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