Source for org.jfree.chart.axis.NumberAxis

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