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