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