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