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