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