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