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