001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2009, 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-2009, 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 df0 = new DecimalFormat("0.00000000");
700            DecimalFormat df1 = new DecimalFormat("0.0000000");
701            DecimalFormat df2 = new DecimalFormat("0.000000");
702            DecimalFormat df3 = new DecimalFormat("0.00000");
703            DecimalFormat df4 = new DecimalFormat("0.0000");
704            DecimalFormat df5 = new DecimalFormat("0.000");
705            DecimalFormat df6 = new DecimalFormat("0.00");
706            DecimalFormat df7 = new DecimalFormat("0.0");
707            DecimalFormat df8 = new DecimalFormat("#,##0");
708            DecimalFormat df9 = new DecimalFormat("#,###,##0");
709            DecimalFormat df10 = new DecimalFormat("#,###,###,##0");
710    
711            // we can add the units in any order, the TickUnits collection will
712            // sort them...
713            units.add(new NumberTickUnit(0.0000001, df1, 2));
714            units.add(new NumberTickUnit(0.000001, df2, 2));
715            units.add(new NumberTickUnit(0.00001, df3, 2));
716            units.add(new NumberTickUnit(0.0001, df4, 2));
717            units.add(new NumberTickUnit(0.001, df5, 2));
718            units.add(new NumberTickUnit(0.01, df6, 2));
719            units.add(new NumberTickUnit(0.1, df7, 2));
720            units.add(new NumberTickUnit(1, df8, 2));
721            units.add(new NumberTickUnit(10, df8, 2));
722            units.add(new NumberTickUnit(100, df8, 2));
723            units.add(new NumberTickUnit(1000, df8, 2));
724            units.add(new NumberTickUnit(10000, df8, 2));
725            units.add(new NumberTickUnit(100000, df8, 2));
726            units.add(new NumberTickUnit(1000000, df9, 2));
727            units.add(new NumberTickUnit(10000000, df9, 2));
728            units.add(new NumberTickUnit(100000000, df9, 2));
729            units.add(new NumberTickUnit(1000000000, df10, 2));
730            units.add(new NumberTickUnit(10000000000.0, df10, 2));
731            units.add(new NumberTickUnit(100000000000.0, df10, 2));
732    
733            units.add(new NumberTickUnit(0.00000025, df0, 5));
734            units.add(new NumberTickUnit(0.0000025, df1, 5));
735            units.add(new NumberTickUnit(0.000025, df2, 5));
736            units.add(new NumberTickUnit(0.00025, df3, 5));
737            units.add(new NumberTickUnit(0.0025, df4, 5));
738            units.add(new NumberTickUnit(0.025, df5, 5));
739            units.add(new NumberTickUnit(0.25, df6, 5));
740            units.add(new NumberTickUnit(2.5, df7, 5));
741            units.add(new NumberTickUnit(25, df8, 5));
742            units.add(new NumberTickUnit(250, df8, 5));
743            units.add(new NumberTickUnit(2500, df8, 5));
744            units.add(new NumberTickUnit(25000, df8, 5));
745            units.add(new NumberTickUnit(250000, df8, 5));
746            units.add(new NumberTickUnit(2500000, df9, 5));
747            units.add(new NumberTickUnit(25000000, df9, 5));
748            units.add(new NumberTickUnit(250000000, df9, 5));
749            units.add(new NumberTickUnit(2500000000.0, df10, 5));
750            units.add(new NumberTickUnit(25000000000.0, df10, 5));
751            units.add(new NumberTickUnit(250000000000.0, df10, 5));
752    
753            units.add(new NumberTickUnit(0.0000005, df1, 5));
754            units.add(new NumberTickUnit(0.000005, df2, 5));
755            units.add(new NumberTickUnit(0.00005, df3, 5));
756            units.add(new NumberTickUnit(0.0005, df4, 5));
757            units.add(new NumberTickUnit(0.005, df5, 5));
758            units.add(new NumberTickUnit(0.05, df6, 5));
759            units.add(new NumberTickUnit(0.5, df7, 5));
760            units.add(new NumberTickUnit(5L, df8, 5));
761            units.add(new NumberTickUnit(50L, df8, 5));
762            units.add(new NumberTickUnit(500L, df8, 5));
763            units.add(new NumberTickUnit(5000L, df8, 5));
764            units.add(new NumberTickUnit(50000L, df8, 5));
765            units.add(new NumberTickUnit(500000L, df8, 5));
766            units.add(new NumberTickUnit(5000000L, df9, 5));
767            units.add(new NumberTickUnit(50000000L, df9, 5));
768            units.add(new NumberTickUnit(500000000L, df9, 5));
769            units.add(new NumberTickUnit(5000000000L, df10, 5));
770            units.add(new NumberTickUnit(50000000000L, df10, 5));
771            units.add(new NumberTickUnit(500000000000L, df10, 5));
772    
773            return units;
774    
775        }
776    
777        /**
778         * Returns a collection of tick units for integer values.
779         *
780         * @return A collection of tick units for integer values.
781         *
782         * @see #setStandardTickUnits(TickUnitSource)
783         * @see #createStandardTickUnits()
784         */
785        public static TickUnitSource createIntegerTickUnits() {
786            TickUnits units = new TickUnits();
787            DecimalFormat df0 = new DecimalFormat("0");
788            DecimalFormat df1 = new DecimalFormat("#,##0");
789            units.add(new NumberTickUnit(1, df0, 2));
790            units.add(new NumberTickUnit(2, df0, 2));
791            units.add(new NumberTickUnit(5, df0, 5));
792            units.add(new NumberTickUnit(10, df0, 2));
793            units.add(new NumberTickUnit(20, df0, 2));
794            units.add(new NumberTickUnit(50, df0, 5));
795            units.add(new NumberTickUnit(100, df0, 2));
796            units.add(new NumberTickUnit(200, df0, 2));
797            units.add(new NumberTickUnit(500, df0, 5));
798            units.add(new NumberTickUnit(1000, df1, 2));
799            units.add(new NumberTickUnit(2000, df1, 2));
800            units.add(new NumberTickUnit(5000, df1, 5));
801            units.add(new NumberTickUnit(10000, df1, 2));
802            units.add(new NumberTickUnit(20000, df1, 2));
803            units.add(new NumberTickUnit(50000, df1, 5));
804            units.add(new NumberTickUnit(100000, df1, 2));
805            units.add(new NumberTickUnit(200000, df1, 2));
806            units.add(new NumberTickUnit(500000, df1, 5));
807            units.add(new NumberTickUnit(1000000, df1, 2));
808            units.add(new NumberTickUnit(2000000, df1, 2));
809            units.add(new NumberTickUnit(5000000, df1, 5));
810            units.add(new NumberTickUnit(10000000, df1, 2));
811            units.add(new NumberTickUnit(20000000, df1, 2));
812            units.add(new NumberTickUnit(50000000, df1, 5));
813            units.add(new NumberTickUnit(100000000, df1, 2));
814            units.add(new NumberTickUnit(200000000, df1, 2));
815            units.add(new NumberTickUnit(500000000, df1, 5));
816            units.add(new NumberTickUnit(1000000000, df1, 2));
817            units.add(new NumberTickUnit(2000000000, df1, 2));
818            units.add(new NumberTickUnit(5000000000.0, df1, 5));
819            units.add(new NumberTickUnit(10000000000.0, df1, 2));
820            return units;
821        }
822    
823        /**
824         * Creates a collection of standard tick units.  The supplied locale is
825         * used to create the number formatter (a localised instance of
826         * <code>NumberFormat</code>).
827         * <P>
828         * If you don't like these defaults, create your own instance of
829         * {@link TickUnits} and then pass it to the
830         * <code>setStandardTickUnits()</code> method.
831         *
832         * @param locale  the locale.
833         *
834         * @return A tick unit collection.
835         *
836         * @see #setStandardTickUnits(TickUnitSource)
837         */
838        public static TickUnitSource createStandardTickUnits(Locale locale) {
839    
840            TickUnits units = new TickUnits();
841            NumberFormat numberFormat = NumberFormat.getNumberInstance(locale);
842            // we can add the units in any order, the TickUnits collection will
843            // sort them...
844            units.add(new NumberTickUnit(0.0000001, numberFormat, 2));
845            units.add(new NumberTickUnit(0.000001, numberFormat, 2));
846            units.add(new NumberTickUnit(0.00001, numberFormat, 2));
847            units.add(new NumberTickUnit(0.0001, numberFormat, 2));
848            units.add(new NumberTickUnit(0.001, numberFormat, 2));
849            units.add(new NumberTickUnit(0.01, numberFormat, 2));
850            units.add(new NumberTickUnit(0.1, numberFormat, 2));
851            units.add(new NumberTickUnit(1, numberFormat, 2));
852            units.add(new NumberTickUnit(10, numberFormat, 2));
853            units.add(new NumberTickUnit(100, numberFormat, 2));
854            units.add(new NumberTickUnit(1000, numberFormat, 2));
855            units.add(new NumberTickUnit(10000, numberFormat, 2));
856            units.add(new NumberTickUnit(100000, numberFormat, 2));
857            units.add(new NumberTickUnit(1000000, numberFormat, 2));
858            units.add(new NumberTickUnit(10000000, numberFormat, 2));
859            units.add(new NumberTickUnit(100000000, numberFormat, 2));
860            units.add(new NumberTickUnit(1000000000, numberFormat, 2));
861            units.add(new NumberTickUnit(10000000000.0, numberFormat, 2));
862    
863            units.add(new NumberTickUnit(0.00000025, numberFormat, 5));
864            units.add(new NumberTickUnit(0.0000025, numberFormat, 5));
865            units.add(new NumberTickUnit(0.000025, numberFormat, 5));
866            units.add(new NumberTickUnit(0.00025, numberFormat, 5));
867            units.add(new NumberTickUnit(0.0025, numberFormat, 5));
868            units.add(new NumberTickUnit(0.025, numberFormat, 5));
869            units.add(new NumberTickUnit(0.25, numberFormat, 5));
870            units.add(new NumberTickUnit(2.5, numberFormat, 5));
871            units.add(new NumberTickUnit(25, numberFormat, 5));
872            units.add(new NumberTickUnit(250, numberFormat, 5));
873            units.add(new NumberTickUnit(2500, numberFormat, 5));
874            units.add(new NumberTickUnit(25000, numberFormat, 5));
875            units.add(new NumberTickUnit(250000, numberFormat, 5));
876            units.add(new NumberTickUnit(2500000, numberFormat, 5));
877            units.add(new NumberTickUnit(25000000, numberFormat, 5));
878            units.add(new NumberTickUnit(250000000, numberFormat, 5));
879            units.add(new NumberTickUnit(2500000000.0, numberFormat, 5));
880            units.add(new NumberTickUnit(25000000000.0, numberFormat, 5));
881    
882            units.add(new NumberTickUnit(0.0000005, numberFormat, 5));
883            units.add(new NumberTickUnit(0.000005, numberFormat, 5));
884            units.add(new NumberTickUnit(0.00005, numberFormat, 5));
885            units.add(new NumberTickUnit(0.0005, numberFormat, 5));
886            units.add(new NumberTickUnit(0.005, numberFormat, 5));
887            units.add(new NumberTickUnit(0.05, numberFormat, 5));
888            units.add(new NumberTickUnit(0.5, numberFormat, 5));
889            units.add(new NumberTickUnit(5L, numberFormat, 5));
890            units.add(new NumberTickUnit(50L, numberFormat, 5));
891            units.add(new NumberTickUnit(500L, numberFormat, 5));
892            units.add(new NumberTickUnit(5000L, numberFormat, 5));
893            units.add(new NumberTickUnit(50000L, numberFormat, 5));
894            units.add(new NumberTickUnit(500000L, numberFormat, 5));
895            units.add(new NumberTickUnit(5000000L, numberFormat, 5));
896            units.add(new NumberTickUnit(50000000L, numberFormat, 5));
897            units.add(new NumberTickUnit(500000000L, numberFormat, 5));
898            units.add(new NumberTickUnit(5000000000L, numberFormat, 5));
899            units.add(new NumberTickUnit(50000000000L, numberFormat, 5));
900    
901            return units;
902    
903        }
904    
905        /**
906         * Returns a collection of tick units for integer values.
907         * Uses a given Locale to create the DecimalFormats.
908         *
909         * @param locale the locale to use to represent Numbers.
910         *
911         * @return A collection of tick units for integer values.
912         *
913         * @see #setStandardTickUnits(TickUnitSource)
914         */
915        public static TickUnitSource createIntegerTickUnits(Locale locale) {
916            TickUnits units = new TickUnits();
917            NumberFormat numberFormat = NumberFormat.getNumberInstance(locale);
918            units.add(new NumberTickUnit(1, numberFormat, 2));
919            units.add(new NumberTickUnit(2, numberFormat, 2));
920            units.add(new NumberTickUnit(5, numberFormat, 5));
921            units.add(new NumberTickUnit(10, numberFormat, 2));
922            units.add(new NumberTickUnit(20, numberFormat, 2));
923            units.add(new NumberTickUnit(50, numberFormat, 5));
924            units.add(new NumberTickUnit(100, numberFormat, 2));
925            units.add(new NumberTickUnit(200, numberFormat, 2));
926            units.add(new NumberTickUnit(500, numberFormat, 5));
927            units.add(new NumberTickUnit(1000, numberFormat, 2));
928            units.add(new NumberTickUnit(2000, numberFormat, 2));
929            units.add(new NumberTickUnit(5000, numberFormat, 5));
930            units.add(new NumberTickUnit(10000, numberFormat, 2));
931            units.add(new NumberTickUnit(20000, numberFormat, 2));
932            units.add(new NumberTickUnit(50000, numberFormat, 5));
933            units.add(new NumberTickUnit(100000, numberFormat, 2));
934            units.add(new NumberTickUnit(200000, numberFormat, 2));
935            units.add(new NumberTickUnit(500000, numberFormat, 5));
936            units.add(new NumberTickUnit(1000000, numberFormat, 2));
937            units.add(new NumberTickUnit(2000000, numberFormat, 2));
938            units.add(new NumberTickUnit(5000000, numberFormat, 5));
939            units.add(new NumberTickUnit(10000000, numberFormat, 2));
940            units.add(new NumberTickUnit(20000000, numberFormat, 2));
941            units.add(new NumberTickUnit(50000000, numberFormat, 5));
942            units.add(new NumberTickUnit(100000000, numberFormat, 2));
943            units.add(new NumberTickUnit(200000000, numberFormat, 2));
944            units.add(new NumberTickUnit(500000000, numberFormat, 5));
945            units.add(new NumberTickUnit(1000000000, numberFormat, 2));
946            units.add(new NumberTickUnit(2000000000, numberFormat, 2));
947            units.add(new NumberTickUnit(5000000000.0, numberFormat, 5));
948            units.add(new NumberTickUnit(10000000000.0, numberFormat, 2));
949            return units;
950        }
951    
952        /**
953         * Estimates the maximum tick label height.
954         *
955         * @param g2  the graphics device.
956         *
957         * @return The maximum height.
958         */
959        protected double estimateMaximumTickLabelHeight(Graphics2D g2) {
960    
961            RectangleInsets tickLabelInsets = getTickLabelInsets();
962            double result = tickLabelInsets.getTop() + tickLabelInsets.getBottom();
963    
964            Font tickLabelFont = getTickLabelFont();
965            FontRenderContext frc = g2.getFontRenderContext();
966            result += tickLabelFont.getLineMetrics("123", frc).getHeight();
967            return result;
968    
969        }
970    
971        /**
972         * Estimates the maximum width of the tick labels, assuming the specified
973         * tick unit is used.
974         * <P>
975         * Rather than computing the string bounds of every tick on the axis, we
976         * just look at two values: the lower bound and the upper bound for the
977         * axis.  These two values will usually be representative.
978         *
979         * @param g2  the graphics device.
980         * @param unit  the tick unit to use for calculation.
981         *
982         * @return The estimated maximum width of the tick labels.
983         */
984        protected double estimateMaximumTickLabelWidth(Graphics2D g2,
985                                                       TickUnit unit) {
986    
987            RectangleInsets tickLabelInsets = getTickLabelInsets();
988            double result = tickLabelInsets.getLeft() + tickLabelInsets.getRight();
989    
990            if (isVerticalTickLabels()) {
991                // all tick labels have the same width (equal to the height of the
992                // font)...
993                FontRenderContext frc = g2.getFontRenderContext();
994                LineMetrics lm = getTickLabelFont().getLineMetrics("0", frc);
995                result += lm.getHeight();
996            }
997            else {
998                // look at lower and upper bounds...
999                FontMetrics fm = g2.getFontMetrics(getTickLabelFont());
1000                Range range = getRange();
1001                double lower = range.getLowerBound();
1002                double upper = range.getUpperBound();
1003                String lowerStr = "";
1004                String upperStr = "";
1005                NumberFormat formatter = getNumberFormatOverride();
1006                if (formatter != null) {
1007                    lowerStr = formatter.format(lower);
1008                    upperStr = formatter.format(upper);
1009                }
1010                else {
1011                    lowerStr = unit.valueToString(lower);
1012                    upperStr = unit.valueToString(upper);
1013                }
1014                double w1 = fm.stringWidth(lowerStr);
1015                double w2 = fm.stringWidth(upperStr);
1016                result += Math.max(w1, w2);
1017            }
1018    
1019            return result;
1020    
1021        }
1022    
1023        /**
1024         * Selects an appropriate tick value for the axis.  The strategy is to
1025         * display as many ticks as possible (selected from an array of 'standard'
1026         * tick units) without the labels overlapping.
1027         *
1028         * @param g2  the graphics device.
1029         * @param dataArea  the area defined by the axes.
1030         * @param edge  the axis location.
1031         */
1032        protected void selectAutoTickUnit(Graphics2D g2,
1033                                          Rectangle2D dataArea,
1034                                          RectangleEdge edge) {
1035    
1036            if (RectangleEdge.isTopOrBottom(edge)) {
1037                selectHorizontalAutoTickUnit(g2, dataArea, edge);
1038            }
1039            else if (RectangleEdge.isLeftOrRight(edge)) {
1040                selectVerticalAutoTickUnit(g2, dataArea, edge);
1041            }
1042    
1043        }
1044    
1045        /**
1046         * Selects an appropriate tick value for the axis.  The strategy is to
1047         * display as many ticks as possible (selected from an array of 'standard'
1048         * tick units) without the labels overlapping.
1049         *
1050         * @param g2  the graphics device.
1051         * @param dataArea  the area defined by the axes.
1052         * @param edge  the axis location.
1053         */
1054       protected void selectHorizontalAutoTickUnit(Graphics2D g2,
1055                                                   Rectangle2D dataArea,
1056                                                   RectangleEdge edge) {
1057    
1058            double tickLabelWidth = estimateMaximumTickLabelWidth(g2,
1059                    getTickUnit());
1060    
1061            // start with the current tick unit...
1062            TickUnitSource tickUnits = getStandardTickUnits();
1063            TickUnit unit1 = tickUnits.getCeilingTickUnit(getTickUnit());
1064            double unit1Width = lengthToJava2D(unit1.getSize(), dataArea, edge);
1065    
1066            // then extrapolate...
1067            double guess = (tickLabelWidth / unit1Width) * unit1.getSize();
1068    
1069            NumberTickUnit unit2 = (NumberTickUnit) tickUnits.getCeilingTickUnit(
1070                    guess);
1071            double unit2Width = lengthToJava2D(unit2.getSize(), dataArea, edge);
1072    
1073            tickLabelWidth = estimateMaximumTickLabelWidth(g2, unit2);
1074            if (tickLabelWidth > unit2Width) {
1075                unit2 = (NumberTickUnit) tickUnits.getLargerTickUnit(unit2);
1076            }
1077    
1078            setTickUnit(unit2, false, false);
1079    
1080        }
1081    
1082        /**
1083         * Selects an appropriate tick value for the axis.  The strategy is to
1084         * display as many ticks as possible (selected from an array of 'standard'
1085         * tick units) without the labels overlapping.
1086         *
1087         * @param g2  the graphics device.
1088         * @param dataArea  the area in which the plot should be drawn.
1089         * @param edge  the axis location.
1090         */
1091        protected void selectVerticalAutoTickUnit(Graphics2D g2,
1092                                                  Rectangle2D dataArea,
1093                                                  RectangleEdge edge) {
1094    
1095            double tickLabelHeight = estimateMaximumTickLabelHeight(g2);
1096    
1097            // start with the current tick unit...
1098            TickUnitSource tickUnits = getStandardTickUnits();
1099            TickUnit unit1 = tickUnits.getCeilingTickUnit(getTickUnit());
1100            double unitHeight = lengthToJava2D(unit1.getSize(), dataArea, edge);
1101    
1102            // then extrapolate...
1103            double guess = (tickLabelHeight / unitHeight) * unit1.getSize();
1104    
1105            NumberTickUnit unit2
1106                = (NumberTickUnit) tickUnits.getCeilingTickUnit(guess);
1107            double unit2Height = lengthToJava2D(unit2.getSize(), dataArea, edge);
1108    
1109            tickLabelHeight = estimateMaximumTickLabelHeight(g2);
1110            if (tickLabelHeight > unit2Height) {
1111                unit2 = (NumberTickUnit) tickUnits.getLargerTickUnit(unit2);
1112            }
1113    
1114            setTickUnit(unit2, false, false);
1115    
1116        }
1117    
1118        /**
1119         * Calculates the positions of the tick labels for the axis, storing the
1120         * results in the tick label list (ready for drawing).
1121         *
1122         * @param g2  the graphics device.
1123         * @param state  the axis state.
1124         * @param dataArea  the area in which the plot should be drawn.
1125         * @param edge  the location of the axis.
1126         *
1127         * @return A list of ticks.
1128         *
1129         */
1130        public List refreshTicks(Graphics2D g2,
1131                                 AxisState state,
1132                                 Rectangle2D dataArea,
1133                                 RectangleEdge edge) {
1134    
1135            List result = new java.util.ArrayList();
1136            if (RectangleEdge.isTopOrBottom(edge)) {
1137                result = refreshTicksHorizontal(g2, dataArea, edge);
1138            }
1139            else if (RectangleEdge.isLeftOrRight(edge)) {
1140                result = refreshTicksVertical(g2, dataArea, edge);
1141            }
1142            return result;
1143    
1144        }
1145    
1146        /**
1147         * Calculates the positions of the tick labels for the axis, storing the
1148         * results in the tick label list (ready for drawing).
1149         *
1150         * @param g2  the graphics device.
1151         * @param dataArea  the area in which the data should be drawn.
1152         * @param edge  the location of the axis.
1153         *
1154         * @return A list of ticks.
1155         */
1156        protected List refreshTicksHorizontal(Graphics2D g2,
1157                Rectangle2D dataArea, RectangleEdge edge) {
1158    
1159            List result = new java.util.ArrayList();
1160    
1161            Font tickLabelFont = getTickLabelFont();
1162            g2.setFont(tickLabelFont);
1163    
1164            if (isAutoTickUnitSelection()) {
1165                selectAutoTickUnit(g2, dataArea, edge);
1166            }
1167    
1168            TickUnit tu = getTickUnit();
1169            double size = tu.getSize();
1170            int count = calculateVisibleTickCount();
1171            double lowestTickValue = calculateLowestVisibleTickValue();
1172    
1173            if (count <= ValueAxis.MAXIMUM_TICK_COUNT) {
1174                int minorTickSpaces = getMinorTickCount();
1175                if (minorTickSpaces <= 0) {
1176                    minorTickSpaces = tu.getMinorTickCount();
1177                }
1178                for (int minorTick = 1; minorTick < minorTickSpaces; minorTick++) {
1179                    double minorTickValue = lowestTickValue 
1180                            - size * minorTick / minorTickSpaces;
1181                    if (getRange().contains(minorTickValue)){
1182                        result.add(new NumberTick(TickType.MINOR, minorTickValue,
1183                                "", TextAnchor.TOP_CENTER, TextAnchor.CENTER,
1184                                0.0));
1185                    }
1186                }
1187                for (int i = 0; i < count; i++) {
1188                    double currentTickValue = lowestTickValue + (i * size);
1189                    String tickLabel;
1190                    NumberFormat formatter = getNumberFormatOverride();
1191                    if (formatter != null) {
1192                        tickLabel = formatter.format(currentTickValue);
1193                    }
1194                    else {
1195                        tickLabel = getTickUnit().valueToString(currentTickValue);
1196                    }
1197                    TextAnchor anchor = null;
1198                    TextAnchor rotationAnchor = null;
1199                    double angle = 0.0;
1200                    if (isVerticalTickLabels()) {
1201                        anchor = TextAnchor.CENTER_RIGHT;
1202                        rotationAnchor = TextAnchor.CENTER_RIGHT;
1203                        if (edge == RectangleEdge.TOP) {
1204                            angle = Math.PI / 2.0;
1205                        }
1206                        else {
1207                            angle = -Math.PI / 2.0;
1208                        }
1209                    }
1210                    else {
1211                        if (edge == RectangleEdge.TOP) {
1212                            anchor = TextAnchor.BOTTOM_CENTER;
1213                            rotationAnchor = TextAnchor.BOTTOM_CENTER;
1214                        }
1215                        else {
1216                            anchor = TextAnchor.TOP_CENTER;
1217                            rotationAnchor = TextAnchor.TOP_CENTER;
1218                        }
1219                    }
1220    
1221                    Tick tick = new NumberTick(new Double(currentTickValue),
1222                            tickLabel, anchor, rotationAnchor, angle);
1223                    result.add(tick);
1224                    double nextTickValue = lowestTickValue + ((i + 1)* size);
1225                    for (int minorTick = 1; minorTick < minorTickSpaces;
1226                            minorTick++) {
1227                        double minorTickValue = currentTickValue
1228                                + (nextTickValue - currentTickValue)
1229                                * minorTick / minorTickSpaces;
1230                        if (getRange().contains(minorTickValue)){
1231                            result.add(new NumberTick(TickType.MINOR,
1232                                    minorTickValue, "", TextAnchor.TOP_CENTER,
1233                                    TextAnchor.CENTER, 0.0));
1234                        }
1235                    }
1236                }
1237            }
1238            return result;
1239    
1240        }
1241    
1242        /**
1243         * Calculates the positions of the tick labels for the axis, storing the
1244         * results in the tick label list (ready for drawing).
1245         *
1246         * @param g2  the graphics device.
1247         * @param dataArea  the area in which the plot should be drawn.
1248         * @param edge  the location of the axis.
1249         *
1250         * @return A list of ticks.
1251         */
1252        protected List refreshTicksVertical(Graphics2D g2,
1253                Rectangle2D dataArea, RectangleEdge edge) {
1254    
1255            List result = new java.util.ArrayList();
1256            result.clear();
1257    
1258            Font tickLabelFont = getTickLabelFont();
1259            g2.setFont(tickLabelFont);
1260            if (isAutoTickUnitSelection()) {
1261                selectAutoTickUnit(g2, dataArea, edge);
1262            }
1263    
1264            TickUnit tu = getTickUnit();
1265            double size = tu.getSize();
1266            int count = calculateVisibleTickCount();
1267            double lowestTickValue = calculateLowestVisibleTickValue();
1268    
1269            if (count <= ValueAxis.MAXIMUM_TICK_COUNT) {
1270                int minorTickSpaces = getMinorTickCount();
1271                if (minorTickSpaces <= 0) {
1272                    minorTickSpaces = tu.getMinorTickCount();
1273                }
1274                for (int minorTick = 1; minorTick < minorTickSpaces; minorTick++){
1275                    double minorTickValue = lowestTickValue
1276                            - size * minorTick / minorTickSpaces;
1277                    if (getRange().contains(minorTickValue)){
1278                        result.add(new NumberTick(TickType.MINOR, minorTickValue,
1279                                "", TextAnchor.TOP_CENTER, TextAnchor.CENTER,
1280                                0.0));
1281                    }
1282                }
1283    
1284                for (int i = 0; i < count; i++) {
1285                    double currentTickValue = lowestTickValue + (i * size);
1286                    String tickLabel;
1287                    NumberFormat formatter = getNumberFormatOverride();
1288                    if (formatter != null) {
1289                        tickLabel = formatter.format(currentTickValue);
1290                    }
1291                    else {
1292                        tickLabel = getTickUnit().valueToString(currentTickValue);
1293                    }
1294    
1295                    TextAnchor anchor = null;
1296                    TextAnchor rotationAnchor = null;
1297                    double angle = 0.0;
1298                    if (isVerticalTickLabels()) {
1299                        if (edge == RectangleEdge.LEFT) {
1300                            anchor = TextAnchor.BOTTOM_CENTER;
1301                            rotationAnchor = TextAnchor.BOTTOM_CENTER;
1302                            angle = -Math.PI / 2.0;
1303                        }
1304                        else {
1305                            anchor = TextAnchor.BOTTOM_CENTER;
1306                            rotationAnchor = TextAnchor.BOTTOM_CENTER;
1307                            angle = Math.PI / 2.0;
1308                        }
1309                    }
1310                    else {
1311                        if (edge == RectangleEdge.LEFT) {
1312                            anchor = TextAnchor.CENTER_RIGHT;
1313                            rotationAnchor = TextAnchor.CENTER_RIGHT;
1314                        }
1315                        else {
1316                            anchor = TextAnchor.CENTER_LEFT;
1317                            rotationAnchor = TextAnchor.CENTER_LEFT;
1318                        }
1319                    }
1320    
1321                    Tick tick = new NumberTick(new Double(currentTickValue),
1322                            tickLabel, anchor, rotationAnchor, angle);
1323                    result.add(tick);
1324    
1325                    double nextTickValue = lowestTickValue + ((i + 1)* size);
1326                    for (int minorTick = 1; minorTick < minorTickSpaces;
1327                            minorTick++){
1328                        double minorTickValue = currentTickValue
1329                                + (nextTickValue - currentTickValue)
1330                                * minorTick / minorTickSpaces;
1331                        if (getRange().contains(minorTickValue)){
1332                            result.add(new NumberTick(TickType.MINOR,
1333                                    minorTickValue, "", TextAnchor.TOP_CENTER,
1334                                    TextAnchor.CENTER, 0.0));
1335                        }
1336                    }
1337                }
1338            }
1339            return result;
1340    
1341        }
1342    
1343        /**
1344         * Returns a clone of the axis.
1345         *
1346         * @return A clone
1347         *
1348         * @throws CloneNotSupportedException if some component of the axis does
1349         *         not support cloning.
1350         */
1351        public Object clone() throws CloneNotSupportedException {
1352            NumberAxis clone = (NumberAxis) super.clone();
1353            if (this.numberFormatOverride != null) {
1354                clone.numberFormatOverride
1355                    = (NumberFormat) this.numberFormatOverride.clone();
1356            }
1357            return clone;
1358        }
1359    
1360        /**
1361         * Tests the axis for equality with an arbitrary object.
1362         *
1363         * @param obj  the object (<code>null</code> permitted).
1364         *
1365         * @return A boolean.
1366         */
1367        public boolean equals(Object obj) {
1368            if (obj == this) {
1369                return true;
1370            }
1371            if (!(obj instanceof NumberAxis)) {
1372                return false;
1373            }
1374            NumberAxis that = (NumberAxis) obj;
1375            if (this.autoRangeIncludesZero != that.autoRangeIncludesZero) {
1376                return false;
1377            }
1378            if (this.autoRangeStickyZero != that.autoRangeStickyZero) {
1379                return false;
1380            }
1381            if (!ObjectUtilities.equal(this.tickUnit, that.tickUnit)) {
1382                return false;
1383            }
1384            if (!ObjectUtilities.equal(this.numberFormatOverride,
1385                    that.numberFormatOverride)) {
1386                return false;
1387            }
1388            if (!this.rangeType.equals(that.rangeType)) {
1389                return false;
1390            }
1391            return super.equals(obj);
1392        }
1393    
1394        /**
1395         * Returns a hash code for this object.
1396         *
1397         * @return A hash code.
1398         */
1399        public int hashCode() {
1400            if (getLabel() != null) {
1401                return getLabel().hashCode();
1402            }
1403            else {
1404                return 0;
1405            }
1406        }
1407    
1408    }