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