001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2009, 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-2009, 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 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, 2)); 714 units.add(new NumberTickUnit(0.000001, df2, 2)); 715 units.add(new NumberTickUnit(0.00001, df3, 2)); 716 units.add(new NumberTickUnit(0.0001, df4, 2)); 717 units.add(new NumberTickUnit(0.001, df5, 2)); 718 units.add(new NumberTickUnit(0.01, df6, 2)); 719 units.add(new NumberTickUnit(0.1, df7, 2)); 720 units.add(new NumberTickUnit(1, df8, 2)); 721 units.add(new NumberTickUnit(10, df8, 2)); 722 units.add(new NumberTickUnit(100, df8, 2)); 723 units.add(new NumberTickUnit(1000, df8, 2)); 724 units.add(new NumberTickUnit(10000, df8, 2)); 725 units.add(new NumberTickUnit(100000, df8, 2)); 726 units.add(new NumberTickUnit(1000000, df9, 2)); 727 units.add(new NumberTickUnit(10000000, df9, 2)); 728 units.add(new NumberTickUnit(100000000, df9, 2)); 729 units.add(new NumberTickUnit(1000000000, df10, 2)); 730 units.add(new NumberTickUnit(10000000000.0, df10, 2)); 731 units.add(new NumberTickUnit(100000000000.0, df10, 2)); 732 733 units.add(new NumberTickUnit(0.00000025, df0, 5)); 734 units.add(new NumberTickUnit(0.0000025, df1, 5)); 735 units.add(new NumberTickUnit(0.000025, df2, 5)); 736 units.add(new NumberTickUnit(0.00025, df3, 5)); 737 units.add(new NumberTickUnit(0.0025, df4, 5)); 738 units.add(new NumberTickUnit(0.025, df5, 5)); 739 units.add(new NumberTickUnit(0.25, df6, 5)); 740 units.add(new NumberTickUnit(2.5, df7, 5)); 741 units.add(new NumberTickUnit(25, df8, 5)); 742 units.add(new NumberTickUnit(250, df8, 5)); 743 units.add(new NumberTickUnit(2500, df8, 5)); 744 units.add(new NumberTickUnit(25000, df8, 5)); 745 units.add(new NumberTickUnit(250000, df8, 5)); 746 units.add(new NumberTickUnit(2500000, df9, 5)); 747 units.add(new NumberTickUnit(25000000, df9, 5)); 748 units.add(new NumberTickUnit(250000000, df9, 5)); 749 units.add(new NumberTickUnit(2500000000.0, df10, 5)); 750 units.add(new NumberTickUnit(25000000000.0, df10, 5)); 751 units.add(new NumberTickUnit(250000000000.0, df10, 5)); 752 753 units.add(new NumberTickUnit(0.0000005, df1, 5)); 754 units.add(new NumberTickUnit(0.000005, df2, 5)); 755 units.add(new NumberTickUnit(0.00005, df3, 5)); 756 units.add(new NumberTickUnit(0.0005, df4, 5)); 757 units.add(new NumberTickUnit(0.005, df5, 5)); 758 units.add(new NumberTickUnit(0.05, df6, 5)); 759 units.add(new NumberTickUnit(0.5, df7, 5)); 760 units.add(new NumberTickUnit(5L, df8, 5)); 761 units.add(new NumberTickUnit(50L, df8, 5)); 762 units.add(new NumberTickUnit(500L, df8, 5)); 763 units.add(new NumberTickUnit(5000L, df8, 5)); 764 units.add(new NumberTickUnit(50000L, df8, 5)); 765 units.add(new NumberTickUnit(500000L, df8, 5)); 766 units.add(new NumberTickUnit(5000000L, df9, 5)); 767 units.add(new NumberTickUnit(50000000L, df9, 5)); 768 units.add(new NumberTickUnit(500000000L, df9, 5)); 769 units.add(new NumberTickUnit(5000000000L, df10, 5)); 770 units.add(new NumberTickUnit(50000000000L, df10, 5)); 771 units.add(new NumberTickUnit(500000000000L, df10, 5)); 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 TickUnits units = new TickUnits(); 787 DecimalFormat df0 = new DecimalFormat("0"); 788 DecimalFormat df1 = new DecimalFormat("#,##0"); 789 units.add(new NumberTickUnit(1, df0, 2)); 790 units.add(new NumberTickUnit(2, df0, 2)); 791 units.add(new NumberTickUnit(5, df0, 5)); 792 units.add(new NumberTickUnit(10, df0, 2)); 793 units.add(new NumberTickUnit(20, df0, 2)); 794 units.add(new NumberTickUnit(50, df0, 5)); 795 units.add(new NumberTickUnit(100, df0, 2)); 796 units.add(new NumberTickUnit(200, df0, 2)); 797 units.add(new NumberTickUnit(500, df0, 5)); 798 units.add(new NumberTickUnit(1000, df1, 2)); 799 units.add(new NumberTickUnit(2000, df1, 2)); 800 units.add(new NumberTickUnit(5000, df1, 5)); 801 units.add(new NumberTickUnit(10000, df1, 2)); 802 units.add(new NumberTickUnit(20000, df1, 2)); 803 units.add(new NumberTickUnit(50000, df1, 5)); 804 units.add(new NumberTickUnit(100000, df1, 2)); 805 units.add(new NumberTickUnit(200000, df1, 2)); 806 units.add(new NumberTickUnit(500000, df1, 5)); 807 units.add(new NumberTickUnit(1000000, df1, 2)); 808 units.add(new NumberTickUnit(2000000, df1, 2)); 809 units.add(new NumberTickUnit(5000000, df1, 5)); 810 units.add(new NumberTickUnit(10000000, df1, 2)); 811 units.add(new NumberTickUnit(20000000, df1, 2)); 812 units.add(new NumberTickUnit(50000000, df1, 5)); 813 units.add(new NumberTickUnit(100000000, df1, 2)); 814 units.add(new NumberTickUnit(200000000, df1, 2)); 815 units.add(new NumberTickUnit(500000000, df1, 5)); 816 units.add(new NumberTickUnit(1000000000, df1, 2)); 817 units.add(new NumberTickUnit(2000000000, df1, 2)); 818 units.add(new NumberTickUnit(5000000000.0, df1, 5)); 819 units.add(new NumberTickUnit(10000000000.0, df1, 2)); 820 return units; 821 } 822 823 /** 824 * Creates a collection of standard tick units. The supplied locale is 825 * used to create the number formatter (a localised instance of 826 * <code>NumberFormat</code>). 827 * <P> 828 * If you don't like these defaults, create your own instance of 829 * {@link TickUnits} and then pass it to the 830 * <code>setStandardTickUnits()</code> method. 831 * 832 * @param locale the locale. 833 * 834 * @return A tick unit collection. 835 * 836 * @see #setStandardTickUnits(TickUnitSource) 837 */ 838 public static TickUnitSource createStandardTickUnits(Locale locale) { 839 840 TickUnits units = new TickUnits(); 841 NumberFormat numberFormat = NumberFormat.getNumberInstance(locale); 842 // we can add the units in any order, the TickUnits collection will 843 // sort them... 844 units.add(new NumberTickUnit(0.0000001, numberFormat, 2)); 845 units.add(new NumberTickUnit(0.000001, numberFormat, 2)); 846 units.add(new NumberTickUnit(0.00001, numberFormat, 2)); 847 units.add(new NumberTickUnit(0.0001, numberFormat, 2)); 848 units.add(new NumberTickUnit(0.001, numberFormat, 2)); 849 units.add(new NumberTickUnit(0.01, numberFormat, 2)); 850 units.add(new NumberTickUnit(0.1, numberFormat, 2)); 851 units.add(new NumberTickUnit(1, numberFormat, 2)); 852 units.add(new NumberTickUnit(10, numberFormat, 2)); 853 units.add(new NumberTickUnit(100, numberFormat, 2)); 854 units.add(new NumberTickUnit(1000, numberFormat, 2)); 855 units.add(new NumberTickUnit(10000, numberFormat, 2)); 856 units.add(new NumberTickUnit(100000, numberFormat, 2)); 857 units.add(new NumberTickUnit(1000000, numberFormat, 2)); 858 units.add(new NumberTickUnit(10000000, numberFormat, 2)); 859 units.add(new NumberTickUnit(100000000, numberFormat, 2)); 860 units.add(new NumberTickUnit(1000000000, numberFormat, 2)); 861 units.add(new NumberTickUnit(10000000000.0, numberFormat, 2)); 862 863 units.add(new NumberTickUnit(0.00000025, numberFormat, 5)); 864 units.add(new NumberTickUnit(0.0000025, numberFormat, 5)); 865 units.add(new NumberTickUnit(0.000025, numberFormat, 5)); 866 units.add(new NumberTickUnit(0.00025, numberFormat, 5)); 867 units.add(new NumberTickUnit(0.0025, numberFormat, 5)); 868 units.add(new NumberTickUnit(0.025, numberFormat, 5)); 869 units.add(new NumberTickUnit(0.25, numberFormat, 5)); 870 units.add(new NumberTickUnit(2.5, numberFormat, 5)); 871 units.add(new NumberTickUnit(25, numberFormat, 5)); 872 units.add(new NumberTickUnit(250, numberFormat, 5)); 873 units.add(new NumberTickUnit(2500, numberFormat, 5)); 874 units.add(new NumberTickUnit(25000, numberFormat, 5)); 875 units.add(new NumberTickUnit(250000, numberFormat, 5)); 876 units.add(new NumberTickUnit(2500000, numberFormat, 5)); 877 units.add(new NumberTickUnit(25000000, numberFormat, 5)); 878 units.add(new NumberTickUnit(250000000, numberFormat, 5)); 879 units.add(new NumberTickUnit(2500000000.0, numberFormat, 5)); 880 units.add(new NumberTickUnit(25000000000.0, numberFormat, 5)); 881 882 units.add(new NumberTickUnit(0.0000005, numberFormat, 5)); 883 units.add(new NumberTickUnit(0.000005, numberFormat, 5)); 884 units.add(new NumberTickUnit(0.00005, numberFormat, 5)); 885 units.add(new NumberTickUnit(0.0005, numberFormat, 5)); 886 units.add(new NumberTickUnit(0.005, numberFormat, 5)); 887 units.add(new NumberTickUnit(0.05, numberFormat, 5)); 888 units.add(new NumberTickUnit(0.5, numberFormat, 5)); 889 units.add(new NumberTickUnit(5L, numberFormat, 5)); 890 units.add(new NumberTickUnit(50L, numberFormat, 5)); 891 units.add(new NumberTickUnit(500L, numberFormat, 5)); 892 units.add(new NumberTickUnit(5000L, numberFormat, 5)); 893 units.add(new NumberTickUnit(50000L, numberFormat, 5)); 894 units.add(new NumberTickUnit(500000L, numberFormat, 5)); 895 units.add(new NumberTickUnit(5000000L, numberFormat, 5)); 896 units.add(new NumberTickUnit(50000000L, numberFormat, 5)); 897 units.add(new NumberTickUnit(500000000L, numberFormat, 5)); 898 units.add(new NumberTickUnit(5000000000L, numberFormat, 5)); 899 units.add(new NumberTickUnit(50000000000L, numberFormat, 5)); 900 901 return units; 902 903 } 904 905 /** 906 * Returns a collection of tick units for integer values. 907 * Uses a given Locale to create the DecimalFormats. 908 * 909 * @param locale the locale to use to represent Numbers. 910 * 911 * @return A collection of tick units for integer values. 912 * 913 * @see #setStandardTickUnits(TickUnitSource) 914 */ 915 public static TickUnitSource createIntegerTickUnits(Locale locale) { 916 TickUnits units = new TickUnits(); 917 NumberFormat numberFormat = NumberFormat.getNumberInstance(locale); 918 units.add(new NumberTickUnit(1, numberFormat, 2)); 919 units.add(new NumberTickUnit(2, numberFormat, 2)); 920 units.add(new NumberTickUnit(5, numberFormat, 5)); 921 units.add(new NumberTickUnit(10, numberFormat, 2)); 922 units.add(new NumberTickUnit(20, numberFormat, 2)); 923 units.add(new NumberTickUnit(50, numberFormat, 5)); 924 units.add(new NumberTickUnit(100, numberFormat, 2)); 925 units.add(new NumberTickUnit(200, numberFormat, 2)); 926 units.add(new NumberTickUnit(500, numberFormat, 5)); 927 units.add(new NumberTickUnit(1000, numberFormat, 2)); 928 units.add(new NumberTickUnit(2000, numberFormat, 2)); 929 units.add(new NumberTickUnit(5000, numberFormat, 5)); 930 units.add(new NumberTickUnit(10000, numberFormat, 2)); 931 units.add(new NumberTickUnit(20000, numberFormat, 2)); 932 units.add(new NumberTickUnit(50000, numberFormat, 5)); 933 units.add(new NumberTickUnit(100000, numberFormat, 2)); 934 units.add(new NumberTickUnit(200000, numberFormat, 2)); 935 units.add(new NumberTickUnit(500000, numberFormat, 5)); 936 units.add(new NumberTickUnit(1000000, numberFormat, 2)); 937 units.add(new NumberTickUnit(2000000, numberFormat, 2)); 938 units.add(new NumberTickUnit(5000000, numberFormat, 5)); 939 units.add(new NumberTickUnit(10000000, numberFormat, 2)); 940 units.add(new NumberTickUnit(20000000, numberFormat, 2)); 941 units.add(new NumberTickUnit(50000000, numberFormat, 5)); 942 units.add(new NumberTickUnit(100000000, numberFormat, 2)); 943 units.add(new NumberTickUnit(200000000, numberFormat, 2)); 944 units.add(new NumberTickUnit(500000000, numberFormat, 5)); 945 units.add(new NumberTickUnit(1000000000, numberFormat, 2)); 946 units.add(new NumberTickUnit(2000000000, numberFormat, 2)); 947 units.add(new NumberTickUnit(5000000000.0, numberFormat, 5)); 948 units.add(new NumberTickUnit(10000000000.0, numberFormat, 2)); 949 return units; 950 } 951 952 /** 953 * Estimates the maximum tick label height. 954 * 955 * @param g2 the graphics device. 956 * 957 * @return The maximum height. 958 */ 959 protected double estimateMaximumTickLabelHeight(Graphics2D g2) { 960 961 RectangleInsets tickLabelInsets = getTickLabelInsets(); 962 double result = tickLabelInsets.getTop() + tickLabelInsets.getBottom(); 963 964 Font tickLabelFont = getTickLabelFont(); 965 FontRenderContext frc = g2.getFontRenderContext(); 966 result += tickLabelFont.getLineMetrics("123", frc).getHeight(); 967 return result; 968 969 } 970 971 /** 972 * Estimates the maximum width of the tick labels, assuming the specified 973 * tick unit is used. 974 * <P> 975 * Rather than computing the string bounds of every tick on the axis, we 976 * just look at two values: the lower bound and the upper bound for the 977 * axis. These two values will usually be representative. 978 * 979 * @param g2 the graphics device. 980 * @param unit the tick unit to use for calculation. 981 * 982 * @return The estimated maximum width of the tick labels. 983 */ 984 protected double estimateMaximumTickLabelWidth(Graphics2D g2, 985 TickUnit unit) { 986 987 RectangleInsets tickLabelInsets = getTickLabelInsets(); 988 double result = tickLabelInsets.getLeft() + tickLabelInsets.getRight(); 989 990 if (isVerticalTickLabels()) { 991 // all tick labels have the same width (equal to the height of the 992 // font)... 993 FontRenderContext frc = g2.getFontRenderContext(); 994 LineMetrics lm = getTickLabelFont().getLineMetrics("0", frc); 995 result += lm.getHeight(); 996 } 997 else { 998 // look at lower and upper bounds... 999 FontMetrics fm = g2.getFontMetrics(getTickLabelFont()); 1000 Range range = getRange(); 1001 double lower = range.getLowerBound(); 1002 double upper = range.getUpperBound(); 1003 String lowerStr = ""; 1004 String upperStr = ""; 1005 NumberFormat formatter = getNumberFormatOverride(); 1006 if (formatter != null) { 1007 lowerStr = formatter.format(lower); 1008 upperStr = formatter.format(upper); 1009 } 1010 else { 1011 lowerStr = unit.valueToString(lower); 1012 upperStr = unit.valueToString(upper); 1013 } 1014 double w1 = fm.stringWidth(lowerStr); 1015 double w2 = fm.stringWidth(upperStr); 1016 result += Math.max(w1, w2); 1017 } 1018 1019 return result; 1020 1021 } 1022 1023 /** 1024 * Selects an appropriate tick value for the axis. The strategy is to 1025 * display as many ticks as possible (selected from an array of 'standard' 1026 * tick units) without the labels overlapping. 1027 * 1028 * @param g2 the graphics device. 1029 * @param dataArea the area defined by the axes. 1030 * @param edge the axis location. 1031 */ 1032 protected void selectAutoTickUnit(Graphics2D g2, 1033 Rectangle2D dataArea, 1034 RectangleEdge edge) { 1035 1036 if (RectangleEdge.isTopOrBottom(edge)) { 1037 selectHorizontalAutoTickUnit(g2, dataArea, edge); 1038 } 1039 else if (RectangleEdge.isLeftOrRight(edge)) { 1040 selectVerticalAutoTickUnit(g2, dataArea, edge); 1041 } 1042 1043 } 1044 1045 /** 1046 * Selects an appropriate tick value for the axis. The strategy is to 1047 * display as many ticks as possible (selected from an array of 'standard' 1048 * tick units) without the labels overlapping. 1049 * 1050 * @param g2 the graphics device. 1051 * @param dataArea the area defined by the axes. 1052 * @param edge the axis location. 1053 */ 1054 protected void selectHorizontalAutoTickUnit(Graphics2D g2, 1055 Rectangle2D dataArea, 1056 RectangleEdge edge) { 1057 1058 double tickLabelWidth = estimateMaximumTickLabelWidth(g2, 1059 getTickUnit()); 1060 1061 // start with the current tick unit... 1062 TickUnitSource tickUnits = getStandardTickUnits(); 1063 TickUnit unit1 = tickUnits.getCeilingTickUnit(getTickUnit()); 1064 double unit1Width = lengthToJava2D(unit1.getSize(), dataArea, edge); 1065 1066 // then extrapolate... 1067 double guess = (tickLabelWidth / unit1Width) * unit1.getSize(); 1068 1069 NumberTickUnit unit2 = (NumberTickUnit) tickUnits.getCeilingTickUnit( 1070 guess); 1071 double unit2Width = lengthToJava2D(unit2.getSize(), dataArea, edge); 1072 1073 tickLabelWidth = estimateMaximumTickLabelWidth(g2, unit2); 1074 if (tickLabelWidth > unit2Width) { 1075 unit2 = (NumberTickUnit) tickUnits.getLargerTickUnit(unit2); 1076 } 1077 1078 setTickUnit(unit2, false, false); 1079 1080 } 1081 1082 /** 1083 * Selects an appropriate tick value for the axis. The strategy is to 1084 * display as many ticks as possible (selected from an array of 'standard' 1085 * tick units) without the labels overlapping. 1086 * 1087 * @param g2 the graphics device. 1088 * @param dataArea the area in which the plot should be drawn. 1089 * @param edge the axis location. 1090 */ 1091 protected void selectVerticalAutoTickUnit(Graphics2D g2, 1092 Rectangle2D dataArea, 1093 RectangleEdge edge) { 1094 1095 double tickLabelHeight = estimateMaximumTickLabelHeight(g2); 1096 1097 // start with the current tick unit... 1098 TickUnitSource tickUnits = getStandardTickUnits(); 1099 TickUnit unit1 = tickUnits.getCeilingTickUnit(getTickUnit()); 1100 double unitHeight = lengthToJava2D(unit1.getSize(), dataArea, edge); 1101 1102 // then extrapolate... 1103 double guess = (tickLabelHeight / unitHeight) * unit1.getSize(); 1104 1105 NumberTickUnit unit2 1106 = (NumberTickUnit) tickUnits.getCeilingTickUnit(guess); 1107 double unit2Height = lengthToJava2D(unit2.getSize(), dataArea, edge); 1108 1109 tickLabelHeight = estimateMaximumTickLabelHeight(g2); 1110 if (tickLabelHeight > unit2Height) { 1111 unit2 = (NumberTickUnit) tickUnits.getLargerTickUnit(unit2); 1112 } 1113 1114 setTickUnit(unit2, false, false); 1115 1116 } 1117 1118 /** 1119 * Calculates the positions of the tick labels for the axis, storing the 1120 * results in the tick label list (ready for drawing). 1121 * 1122 * @param g2 the graphics device. 1123 * @param state the axis state. 1124 * @param dataArea the area in which the plot should be drawn. 1125 * @param edge the location of the axis. 1126 * 1127 * @return A list of ticks. 1128 * 1129 */ 1130 public List refreshTicks(Graphics2D g2, 1131 AxisState state, 1132 Rectangle2D dataArea, 1133 RectangleEdge edge) { 1134 1135 List result = new java.util.ArrayList(); 1136 if (RectangleEdge.isTopOrBottom(edge)) { 1137 result = refreshTicksHorizontal(g2, dataArea, edge); 1138 } 1139 else if (RectangleEdge.isLeftOrRight(edge)) { 1140 result = refreshTicksVertical(g2, dataArea, edge); 1141 } 1142 return result; 1143 1144 } 1145 1146 /** 1147 * Calculates the positions of the tick labels for the axis, storing the 1148 * results in the tick label list (ready for drawing). 1149 * 1150 * @param g2 the graphics device. 1151 * @param dataArea the area in which the data should be drawn. 1152 * @param edge the location of the axis. 1153 * 1154 * @return A list of ticks. 1155 */ 1156 protected List refreshTicksHorizontal(Graphics2D g2, 1157 Rectangle2D dataArea, RectangleEdge edge) { 1158 1159 List result = new java.util.ArrayList(); 1160 1161 Font tickLabelFont = getTickLabelFont(); 1162 g2.setFont(tickLabelFont); 1163 1164 if (isAutoTickUnitSelection()) { 1165 selectAutoTickUnit(g2, dataArea, edge); 1166 } 1167 1168 TickUnit tu = getTickUnit(); 1169 double size = tu.getSize(); 1170 int count = calculateVisibleTickCount(); 1171 double lowestTickValue = calculateLowestVisibleTickValue(); 1172 1173 if (count <= ValueAxis.MAXIMUM_TICK_COUNT) { 1174 int minorTickSpaces = getMinorTickCount(); 1175 if (minorTickSpaces <= 0) { 1176 minorTickSpaces = tu.getMinorTickCount(); 1177 } 1178 for (int minorTick = 1; minorTick < minorTickSpaces; minorTick++) { 1179 double minorTickValue = lowestTickValue 1180 - size * minorTick / minorTickSpaces; 1181 if (getRange().contains(minorTickValue)){ 1182 result.add(new NumberTick(TickType.MINOR, minorTickValue, 1183 "", TextAnchor.TOP_CENTER, TextAnchor.CENTER, 1184 0.0)); 1185 } 1186 } 1187 for (int i = 0; i < count; i++) { 1188 double currentTickValue = lowestTickValue + (i * size); 1189 String tickLabel; 1190 NumberFormat formatter = getNumberFormatOverride(); 1191 if (formatter != null) { 1192 tickLabel = formatter.format(currentTickValue); 1193 } 1194 else { 1195 tickLabel = getTickUnit().valueToString(currentTickValue); 1196 } 1197 TextAnchor anchor = null; 1198 TextAnchor rotationAnchor = null; 1199 double angle = 0.0; 1200 if (isVerticalTickLabels()) { 1201 anchor = TextAnchor.CENTER_RIGHT; 1202 rotationAnchor = TextAnchor.CENTER_RIGHT; 1203 if (edge == RectangleEdge.TOP) { 1204 angle = Math.PI / 2.0; 1205 } 1206 else { 1207 angle = -Math.PI / 2.0; 1208 } 1209 } 1210 else { 1211 if (edge == RectangleEdge.TOP) { 1212 anchor = TextAnchor.BOTTOM_CENTER; 1213 rotationAnchor = TextAnchor.BOTTOM_CENTER; 1214 } 1215 else { 1216 anchor = TextAnchor.TOP_CENTER; 1217 rotationAnchor = TextAnchor.TOP_CENTER; 1218 } 1219 } 1220 1221 Tick tick = new NumberTick(new Double(currentTickValue), 1222 tickLabel, anchor, rotationAnchor, angle); 1223 result.add(tick); 1224 double nextTickValue = lowestTickValue + ((i + 1)* size); 1225 for (int minorTick = 1; minorTick < minorTickSpaces; 1226 minorTick++) { 1227 double minorTickValue = currentTickValue 1228 + (nextTickValue - currentTickValue) 1229 * minorTick / minorTickSpaces; 1230 if (getRange().contains(minorTickValue)){ 1231 result.add(new NumberTick(TickType.MINOR, 1232 minorTickValue, "", TextAnchor.TOP_CENTER, 1233 TextAnchor.CENTER, 0.0)); 1234 } 1235 } 1236 } 1237 } 1238 return result; 1239 1240 } 1241 1242 /** 1243 * Calculates the positions of the tick labels for the axis, storing the 1244 * results in the tick label list (ready for drawing). 1245 * 1246 * @param g2 the graphics device. 1247 * @param dataArea the area in which the plot should be drawn. 1248 * @param edge the location of the axis. 1249 * 1250 * @return A list of ticks. 1251 */ 1252 protected List refreshTicksVertical(Graphics2D g2, 1253 Rectangle2D dataArea, RectangleEdge edge) { 1254 1255 List result = new java.util.ArrayList(); 1256 result.clear(); 1257 1258 Font tickLabelFont = getTickLabelFont(); 1259 g2.setFont(tickLabelFont); 1260 if (isAutoTickUnitSelection()) { 1261 selectAutoTickUnit(g2, dataArea, edge); 1262 } 1263 1264 TickUnit tu = getTickUnit(); 1265 double size = tu.getSize(); 1266 int count = calculateVisibleTickCount(); 1267 double lowestTickValue = calculateLowestVisibleTickValue(); 1268 1269 if (count <= ValueAxis.MAXIMUM_TICK_COUNT) { 1270 int minorTickSpaces = getMinorTickCount(); 1271 if (minorTickSpaces <= 0) { 1272 minorTickSpaces = tu.getMinorTickCount(); 1273 } 1274 for (int minorTick = 1; minorTick < minorTickSpaces; minorTick++){ 1275 double minorTickValue = lowestTickValue 1276 - size * minorTick / minorTickSpaces; 1277 if (getRange().contains(minorTickValue)){ 1278 result.add(new NumberTick(TickType.MINOR, minorTickValue, 1279 "", TextAnchor.TOP_CENTER, TextAnchor.CENTER, 1280 0.0)); 1281 } 1282 } 1283 1284 for (int i = 0; i < count; i++) { 1285 double currentTickValue = lowestTickValue + (i * size); 1286 String tickLabel; 1287 NumberFormat formatter = getNumberFormatOverride(); 1288 if (formatter != null) { 1289 tickLabel = formatter.format(currentTickValue); 1290 } 1291 else { 1292 tickLabel = getTickUnit().valueToString(currentTickValue); 1293 } 1294 1295 TextAnchor anchor = null; 1296 TextAnchor rotationAnchor = null; 1297 double angle = 0.0; 1298 if (isVerticalTickLabels()) { 1299 if (edge == RectangleEdge.LEFT) { 1300 anchor = TextAnchor.BOTTOM_CENTER; 1301 rotationAnchor = TextAnchor.BOTTOM_CENTER; 1302 angle = -Math.PI / 2.0; 1303 } 1304 else { 1305 anchor = TextAnchor.BOTTOM_CENTER; 1306 rotationAnchor = TextAnchor.BOTTOM_CENTER; 1307 angle = Math.PI / 2.0; 1308 } 1309 } 1310 else { 1311 if (edge == RectangleEdge.LEFT) { 1312 anchor = TextAnchor.CENTER_RIGHT; 1313 rotationAnchor = TextAnchor.CENTER_RIGHT; 1314 } 1315 else { 1316 anchor = TextAnchor.CENTER_LEFT; 1317 rotationAnchor = TextAnchor.CENTER_LEFT; 1318 } 1319 } 1320 1321 Tick tick = new NumberTick(new Double(currentTickValue), 1322 tickLabel, anchor, rotationAnchor, angle); 1323 result.add(tick); 1324 1325 double nextTickValue = lowestTickValue + ((i + 1)* size); 1326 for (int minorTick = 1; minorTick < minorTickSpaces; 1327 minorTick++){ 1328 double minorTickValue = currentTickValue 1329 + (nextTickValue - currentTickValue) 1330 * minorTick / minorTickSpaces; 1331 if (getRange().contains(minorTickValue)){ 1332 result.add(new NumberTick(TickType.MINOR, 1333 minorTickValue, "", TextAnchor.TOP_CENTER, 1334 TextAnchor.CENTER, 0.0)); 1335 } 1336 } 1337 } 1338 } 1339 return result; 1340 1341 } 1342 1343 /** 1344 * Returns a clone of the axis. 1345 * 1346 * @return A clone 1347 * 1348 * @throws CloneNotSupportedException if some component of the axis does 1349 * not support cloning. 1350 */ 1351 public Object clone() throws CloneNotSupportedException { 1352 NumberAxis clone = (NumberAxis) super.clone(); 1353 if (this.numberFormatOverride != null) { 1354 clone.numberFormatOverride 1355 = (NumberFormat) this.numberFormatOverride.clone(); 1356 } 1357 return clone; 1358 } 1359 1360 /** 1361 * Tests the axis for equality with an arbitrary object. 1362 * 1363 * @param obj the object (<code>null</code> permitted). 1364 * 1365 * @return A boolean. 1366 */ 1367 public boolean equals(Object obj) { 1368 if (obj == this) { 1369 return true; 1370 } 1371 if (!(obj instanceof NumberAxis)) { 1372 return false; 1373 } 1374 NumberAxis that = (NumberAxis) obj; 1375 if (this.autoRangeIncludesZero != that.autoRangeIncludesZero) { 1376 return false; 1377 } 1378 if (this.autoRangeStickyZero != that.autoRangeStickyZero) { 1379 return false; 1380 } 1381 if (!ObjectUtilities.equal(this.tickUnit, that.tickUnit)) { 1382 return false; 1383 } 1384 if (!ObjectUtilities.equal(this.numberFormatOverride, 1385 that.numberFormatOverride)) { 1386 return false; 1387 } 1388 if (!this.rangeType.equals(that.rangeType)) { 1389 return false; 1390 } 1391 return super.equals(obj); 1392 } 1393 1394 /** 1395 * Returns a hash code for this object. 1396 * 1397 * @return A hash code. 1398 */ 1399 public int hashCode() { 1400 if (getLabel() != null) { 1401 return getLabel().hashCode(); 1402 } 1403 else { 1404 return 0; 1405 } 1406 } 1407 1408 }