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