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