001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2011, by Object Refinery Limited and Contributors. 006 * 007 * Project Info: http://www.jfree.org/jfreechart/index.html 008 * 009 * This library is free software; you can redistribute it and/or modify it 010 * under the terms of the GNU Lesser General Public License as published by 011 * the Free Software Foundation; either version 2.1 of the License, or 012 * (at your option) any later version. 013 * 014 * This library is distributed in the hope that it will be useful, but 015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 017 * License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this library; if not, write to the Free Software 021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 022 * USA. 023 * 024 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 025 * Other names may be trademarks of their respective owners.] 026 * 027 * -------------- 028 * ValueAxis.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): Jonathan Nash; 034 * Nicolas Brodu (for Astrium and EADS Corporate Research 035 * Center); 036 * Peter Kolb (patch 1934255); 037 * Andrew Mickish (patch 1870189); 038 * 039 * Changes 040 * ------- 041 * 18-Sep-2001 : Added standard header and fixed DOS encoding problem (DG); 042 * 23-Nov-2001 : Overhauled standard tick unit code (DG); 043 * 04-Dec-2001 : Changed constructors to protected, and tidied up default 044 * values (DG); 045 * 12-Dec-2001 : Fixed vertical gridlines bug (DG); 046 * 16-Jan-2002 : Added an optional crosshair, based on the implementation by 047 * Jonathan Nash (DG); 048 * 23-Jan-2002 : Moved the minimum and maximum values to here from NumberAxis, 049 * and changed the type from Number to double (DG); 050 * 25-Feb-2002 : Added default value for autoRange. Changed autoAdjustRange 051 * from public to protected. Updated import statements (DG); 052 * 23-Apr-2002 : Added setRange() method (DG); 053 * 29-Apr-2002 : Added range adjustment methods (DG); 054 * 13-Jun-2002 : Modified setCrosshairValue() to notify listeners only when the 055 * crosshairs are visible, to avoid unnecessary repaints, as 056 * suggested by Kees Kuip (DG); 057 * 25-Jul-2002 : Moved lower and upper margin attributes from the NumberAxis 058 * class (DG); 059 * 05-Sep-2002 : Updated constructor for changes in Axis class (DG); 060 * 01-Oct-2002 : Fixed errors reported by Checkstyle (DG); 061 * 04-Oct-2002 : Moved standardTickUnits from NumberAxis --> ValueAxis (DG); 062 * 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 * 27-Nov-2002 : Moved the 'inverted' attributed from NumberAxis to 065 * ValueAxis (DG); 066 * 03-Jan-2003 : Small fix to ensure auto-range minimum is observed 067 * immediately (DG); 068 * 14-Jan-2003 : Changed autoRangeMinimumSize from Number --> double (DG); 069 * 20-Jan-2003 : Replaced monolithic constructor (DG); 070 * 26-Mar-2003 : Implemented Serializable (DG); 071 * 09-May-2003 : Added AxisLocation parameter to translation methods (DG); 072 * 13-Aug-2003 : Implemented Cloneable (DG); 073 * 01-Sep-2003 : Fixed bug 793167 (setMaximumAxisValue exception) (DG); 074 * 02-Sep-2003 : Fixed bug 795366 (zooming on inverted axes) (DG); 075 * 08-Sep-2003 : Completed Serialization support (NB); 076 * 08-Sep-2003 : Renamed get/setMinimumValue --> get/setLowerBound, 077 * and get/setMaximumValue --> get/setUpperBound (DG); 078 * 27-Oct-2003 : Changed DEFAULT_AUTO_RANGE_MINIMUM_SIZE value - see bug ID 079 * 829606 (DG); 080 * 07-Nov-2003 : Changes to tick mechanism (DG); 081 * 06-Jan-2004 : Moved axis line attributes to Axis class (DG); 082 * 21-Jan-2004 : Removed redundant axisLineVisible attribute. Renamed 083 * translateJava2DToValue --> java2DToValue, and 084 * translateValueToJava2D --> valueToJava2D (DG); 085 * 23-Jan-2004 : Fixed setAxisLinePaint() and setAxisLineStroke() which had no 086 * effect (andreas.gawecki@coremedia.com); 087 * 07-Apr-2004 : Changed text bounds calculation (DG); 088 * 26-Apr-2004 : Added getter/setter methods for arrow shapes (DG); 089 * 18-May-2004 : Added methods to set axis range *including* current 090 * margins (DG); 091 * 02-Jun-2004 : Fixed bug in setRangeWithMargins() method (DG); 092 * 30-Sep-2004 : Moved drawRotatedString() from RefineryUtilities 093 * --> TextUtilities (DG); 094 * 11-Jan-2005 : Removed deprecated methods in preparation for 1.0.0 095 * release (DG); 096 * 21-Apr-2005 : Replaced Insets with RectangleInsets (DG); 097 * ------------- JFREECHART 1.0.x --------------------------------------------- 098 * 10-Oct-2006 : Source reformatting (DG); 099 * 22-Mar-2007 : Added new defaultAutoRange attribute (DG); 100 * 02-Aug-2007 : Check for major tick when drawing label (DG); 101 * 25-Sep-2008 : Added minor tick support, see patch 1934255 by Peter Kolb (DG); 102 * 21-Jan-2009 : Updated default behaviour of minor ticks (DG); 103 * 18-Mar-2008 : Added resizeRange2() method which provides more natural 104 * anchored zooming for mouse wheel support (DG); 105 * 26-Mar-2009 : In equals(), only check current range if autoRange is 106 * false (DG); 107 * 30-Mar-2009 : Added pan(double) method (DG); 108 * 109 */ 110 111 package org.jfree.chart.axis; 112 113 import java.awt.Font; 114 import java.awt.FontMetrics; 115 import java.awt.Graphics2D; 116 import java.awt.Polygon; 117 import java.awt.Shape; 118 import java.awt.font.LineMetrics; 119 import java.awt.geom.AffineTransform; 120 import java.awt.geom.Line2D; 121 import java.awt.geom.Rectangle2D; 122 import java.io.IOException; 123 import java.io.ObjectInputStream; 124 import java.io.ObjectOutputStream; 125 import java.io.Serializable; 126 import java.util.Iterator; 127 import java.util.List; 128 129 import org.jfree.chart.event.AxisChangeEvent; 130 import org.jfree.chart.plot.Plot; 131 import org.jfree.data.Range; 132 import org.jfree.io.SerialUtilities; 133 import org.jfree.text.TextUtilities; 134 import org.jfree.ui.RectangleEdge; 135 import org.jfree.ui.RectangleInsets; 136 import org.jfree.util.ObjectUtilities; 137 import org.jfree.util.PublicCloneable; 138 139 /** 140 * The base class for axes that display value data, where values are measured 141 * using the <code>double</code> primitive. The two key subclasses are 142 * {@link DateAxis} and {@link NumberAxis}. 143 */ 144 public abstract class ValueAxis extends Axis 145 implements Cloneable, PublicCloneable, Serializable { 146 147 /** For serialization. */ 148 private static final long serialVersionUID = 3698345477322391456L; 149 150 /** The default axis range. */ 151 public static final Range DEFAULT_RANGE = new Range(0.0, 1.0); 152 153 /** The default auto-range value. */ 154 public static final boolean DEFAULT_AUTO_RANGE = true; 155 156 /** The default inverted flag setting. */ 157 public static final boolean DEFAULT_INVERTED = false; 158 159 /** The default minimum auto range. */ 160 public static final double DEFAULT_AUTO_RANGE_MINIMUM_SIZE = 0.00000001; 161 162 /** The default value for the lower margin (0.05 = 5%). */ 163 public static final double DEFAULT_LOWER_MARGIN = 0.05; 164 165 /** The default value for the upper margin (0.05 = 5%). */ 166 public static final double DEFAULT_UPPER_MARGIN = 0.05; 167 168 /** 169 * The default lower bound for the axis. 170 * 171 * @deprecated From 1.0.5 onwards, the axis defines a defaultRange 172 * attribute (see {@link #getDefaultAutoRange()}). 173 */ 174 public static final double DEFAULT_LOWER_BOUND = 0.0; 175 176 /** 177 * The default upper bound for the axis. 178 * 179 * @deprecated From 1.0.5 onwards, the axis defines a defaultRange 180 * attribute (see {@link #getDefaultAutoRange()}). 181 */ 182 public static final double DEFAULT_UPPER_BOUND = 1.0; 183 184 /** The default auto-tick-unit-selection value. */ 185 public static final boolean DEFAULT_AUTO_TICK_UNIT_SELECTION = true; 186 187 /** The maximum tick count. */ 188 public static final int MAXIMUM_TICK_COUNT = 500; 189 190 /** 191 * A flag that controls whether an arrow is drawn at the positive end of 192 * the axis line. 193 */ 194 private boolean positiveArrowVisible; 195 196 /** 197 * A flag that controls whether an arrow is drawn at the negative end of 198 * the axis line. 199 */ 200 private boolean negativeArrowVisible; 201 202 /** The shape used for an up arrow. */ 203 private transient Shape upArrow; 204 205 /** The shape used for a down arrow. */ 206 private transient Shape downArrow; 207 208 /** The shape used for a left arrow. */ 209 private transient Shape leftArrow; 210 211 /** The shape used for a right arrow. */ 212 private transient Shape rightArrow; 213 214 /** A flag that affects the orientation of the values on the axis. */ 215 private boolean inverted; 216 217 /** The axis range. */ 218 private Range range; 219 220 /** 221 * Flag that indicates whether the axis automatically scales to fit the 222 * chart data. 223 */ 224 private boolean autoRange; 225 226 /** The minimum size for the 'auto' axis range (excluding margins). */ 227 private double autoRangeMinimumSize; 228 229 /** 230 * The default range is used when the dataset is empty and the axis needs 231 * to determine the auto range. 232 * 233 * @since 1.0.5 234 */ 235 private Range defaultAutoRange; 236 237 /** 238 * The upper margin percentage. This indicates the amount by which the 239 * maximum axis value exceeds the maximum data value (as a percentage of 240 * the range on the axis) when the axis range is determined automatically. 241 */ 242 private double upperMargin; 243 244 /** 245 * The lower margin. This is a percentage that indicates the amount by 246 * which the minimum axis value is "less than" the minimum data value when 247 * the axis range is determined automatically. 248 */ 249 private double lowerMargin; 250 251 /** 252 * If this value is positive, the amount is subtracted from the maximum 253 * data value to determine the lower axis range. This can be used to 254 * provide a fixed "window" on dynamic data. 255 */ 256 private double fixedAutoRange; 257 258 /** 259 * Flag that indicates whether or not the tick unit is selected 260 * automatically. 261 */ 262 private boolean autoTickUnitSelection; 263 264 /** The standard tick units for the axis. */ 265 private TickUnitSource standardTickUnits; 266 267 /** An index into an array of standard tick values. */ 268 private int autoTickIndex; 269 270 /** 271 * The number of minor ticks per major tick unit. This is an override 272 * field, if the value is > 0 it is used, otherwise the axis refers to the 273 * minorTickCount in the current tickUnit. 274 */ 275 private int minorTickCount; 276 277 /** A flag indicating whether or not tick labels are rotated to vertical. */ 278 private boolean verticalTickLabels; 279 280 /** 281 * Constructs a value axis. 282 * 283 * @param label the axis label (<code>null</code> permitted). 284 * @param standardTickUnits the source for standard tick units 285 * (<code>null</code> permitted). 286 */ 287 protected ValueAxis(String label, TickUnitSource standardTickUnits) { 288 289 super(label); 290 291 this.positiveArrowVisible = false; 292 this.negativeArrowVisible = false; 293 294 this.range = DEFAULT_RANGE; 295 this.autoRange = DEFAULT_AUTO_RANGE; 296 this.defaultAutoRange = DEFAULT_RANGE; 297 298 this.inverted = DEFAULT_INVERTED; 299 this.autoRangeMinimumSize = DEFAULT_AUTO_RANGE_MINIMUM_SIZE; 300 301 this.lowerMargin = DEFAULT_LOWER_MARGIN; 302 this.upperMargin = DEFAULT_UPPER_MARGIN; 303 304 this.fixedAutoRange = 0.0; 305 306 this.autoTickUnitSelection = DEFAULT_AUTO_TICK_UNIT_SELECTION; 307 this.standardTickUnits = standardTickUnits; 308 309 Polygon p1 = new Polygon(); 310 p1.addPoint(0, 0); 311 p1.addPoint(-2, 2); 312 p1.addPoint(2, 2); 313 314 this.upArrow = p1; 315 316 Polygon p2 = new Polygon(); 317 p2.addPoint(0, 0); 318 p2.addPoint(-2, -2); 319 p2.addPoint(2, -2); 320 321 this.downArrow = p2; 322 323 Polygon p3 = new Polygon(); 324 p3.addPoint(0, 0); 325 p3.addPoint(-2, -2); 326 p3.addPoint(-2, 2); 327 328 this.rightArrow = p3; 329 330 Polygon p4 = new Polygon(); 331 p4.addPoint(0, 0); 332 p4.addPoint(2, -2); 333 p4.addPoint(2, 2); 334 335 this.leftArrow = p4; 336 337 this.verticalTickLabels = false; 338 this.minorTickCount = 0; 339 340 } 341 342 /** 343 * Returns <code>true</code> if the tick labels should be rotated (to 344 * vertical), and <code>false</code> otherwise. 345 * 346 * @return <code>true</code> or <code>false</code>. 347 * 348 * @see #setVerticalTickLabels(boolean) 349 */ 350 public boolean isVerticalTickLabels() { 351 return this.verticalTickLabels; 352 } 353 354 /** 355 * Sets the flag that controls whether the tick labels are displayed 356 * vertically (that is, rotated 90 degrees from horizontal). If the flag 357 * is changed, an {@link AxisChangeEvent} is sent to all registered 358 * listeners. 359 * 360 * @param flag the flag. 361 * 362 * @see #isVerticalTickLabels() 363 */ 364 public void setVerticalTickLabels(boolean flag) { 365 if (this.verticalTickLabels != flag) { 366 this.verticalTickLabels = flag; 367 notifyListeners(new AxisChangeEvent(this)); 368 } 369 } 370 371 /** 372 * Returns a flag that controls whether or not the axis line has an arrow 373 * drawn that points in the positive direction for the axis. 374 * 375 * @return A boolean. 376 * 377 * @see #setPositiveArrowVisible(boolean) 378 */ 379 public boolean isPositiveArrowVisible() { 380 return this.positiveArrowVisible; 381 } 382 383 /** 384 * Sets a flag that controls whether or not the axis lines has an arrow 385 * drawn that points in the positive direction for the axis, and sends an 386 * {@link AxisChangeEvent} to all registered listeners. 387 * 388 * @param visible the flag. 389 * 390 * @see #isPositiveArrowVisible() 391 */ 392 public void setPositiveArrowVisible(boolean visible) { 393 this.positiveArrowVisible = visible; 394 notifyListeners(new AxisChangeEvent(this)); 395 } 396 397 /** 398 * Returns a flag that controls whether or not the axis line has an arrow 399 * drawn that points in the negative direction for the axis. 400 * 401 * @return A boolean. 402 * 403 * @see #setNegativeArrowVisible(boolean) 404 */ 405 public boolean isNegativeArrowVisible() { 406 return this.negativeArrowVisible; 407 } 408 409 /** 410 * Sets a flag that controls whether or not the axis lines has an arrow 411 * drawn that points in the negative direction for the axis, and sends an 412 * {@link AxisChangeEvent} to all registered listeners. 413 * 414 * @param visible the flag. 415 * 416 * @see #setNegativeArrowVisible(boolean) 417 */ 418 public void setNegativeArrowVisible(boolean visible) { 419 this.negativeArrowVisible = visible; 420 notifyListeners(new AxisChangeEvent(this)); 421 } 422 423 /** 424 * Returns a shape that can be displayed as an arrow pointing upwards at 425 * the end of an axis line. 426 * 427 * @return A shape (never <code>null</code>). 428 * 429 * @see #setUpArrow(Shape) 430 */ 431 public Shape getUpArrow() { 432 return this.upArrow; 433 } 434 435 /** 436 * Sets the shape that can be displayed as an arrow pointing upwards at 437 * the end of an axis line and sends an {@link AxisChangeEvent} to all 438 * registered listeners. 439 * 440 * @param arrow the arrow shape (<code>null</code> not permitted). 441 * 442 * @see #getUpArrow() 443 */ 444 public void setUpArrow(Shape arrow) { 445 if (arrow == null) { 446 throw new IllegalArgumentException("Null 'arrow' argument."); 447 } 448 this.upArrow = arrow; 449 notifyListeners(new AxisChangeEvent(this)); 450 } 451 452 /** 453 * Returns a shape that can be displayed as an arrow pointing downwards at 454 * the end of an axis line. 455 * 456 * @return A shape (never <code>null</code>). 457 * 458 * @see #setDownArrow(Shape) 459 */ 460 public Shape getDownArrow() { 461 return this.downArrow; 462 } 463 464 /** 465 * Sets the shape that can be displayed as an arrow pointing downwards at 466 * the end of an axis line and sends an {@link AxisChangeEvent} to all 467 * registered listeners. 468 * 469 * @param arrow the arrow shape (<code>null</code> not permitted). 470 * 471 * @see #getDownArrow() 472 */ 473 public void setDownArrow(Shape arrow) { 474 if (arrow == null) { 475 throw new IllegalArgumentException("Null 'arrow' argument."); 476 } 477 this.downArrow = arrow; 478 notifyListeners(new AxisChangeEvent(this)); 479 } 480 481 /** 482 * Returns a shape that can be displayed as an arrow pointing left at the 483 * end of an axis line. 484 * 485 * @return A shape (never <code>null</code>). 486 * 487 * @see #setLeftArrow(Shape) 488 */ 489 public Shape getLeftArrow() { 490 return this.leftArrow; 491 } 492 493 /** 494 * Sets the shape that can be displayed as an arrow pointing left at the 495 * end of an axis line and sends an {@link AxisChangeEvent} to all 496 * registered listeners. 497 * 498 * @param arrow the arrow shape (<code>null</code> not permitted). 499 * 500 * @see #getLeftArrow() 501 */ 502 public void setLeftArrow(Shape arrow) { 503 if (arrow == null) { 504 throw new IllegalArgumentException("Null 'arrow' argument."); 505 } 506 this.leftArrow = arrow; 507 notifyListeners(new AxisChangeEvent(this)); 508 } 509 510 /** 511 * Returns a shape that can be displayed as an arrow pointing right at the 512 * end of an axis line. 513 * 514 * @return A shape (never <code>null</code>). 515 * 516 * @see #setRightArrow(Shape) 517 */ 518 public Shape getRightArrow() { 519 return this.rightArrow; 520 } 521 522 /** 523 * Sets the shape that can be displayed as an arrow pointing rightwards at 524 * the end of an axis line and sends an {@link AxisChangeEvent} to all 525 * registered listeners. 526 * 527 * @param arrow the arrow shape (<code>null</code> not permitted). 528 * 529 * @see #getRightArrow() 530 */ 531 public void setRightArrow(Shape arrow) { 532 if (arrow == null) { 533 throw new IllegalArgumentException("Null 'arrow' argument."); 534 } 535 this.rightArrow = arrow; 536 notifyListeners(new AxisChangeEvent(this)); 537 } 538 539 /** 540 * Draws an axis line at the current cursor position and edge. 541 * 542 * @param g2 the graphics device. 543 * @param cursor the cursor position. 544 * @param dataArea the data area. 545 * @param edge the edge. 546 */ 547 protected void drawAxisLine(Graphics2D g2, double cursor, 548 Rectangle2D dataArea, RectangleEdge edge) { 549 Line2D axisLine = null; 550 if (edge == RectangleEdge.TOP) { 551 axisLine = new Line2D.Double(dataArea.getX(), cursor, 552 dataArea.getMaxX(), cursor); 553 } 554 else if (edge == RectangleEdge.BOTTOM) { 555 axisLine = new Line2D.Double(dataArea.getX(), cursor, 556 dataArea.getMaxX(), cursor); 557 } 558 else if (edge == RectangleEdge.LEFT) { 559 axisLine = new Line2D.Double(cursor, dataArea.getY(), cursor, 560 dataArea.getMaxY()); 561 } 562 else if (edge == RectangleEdge.RIGHT) { 563 axisLine = new Line2D.Double(cursor, dataArea.getY(), cursor, 564 dataArea.getMaxY()); 565 } 566 g2.setPaint(getAxisLinePaint()); 567 g2.setStroke(getAxisLineStroke()); 568 g2.draw(axisLine); 569 570 boolean drawUpOrRight = false; 571 boolean drawDownOrLeft = false; 572 if (this.positiveArrowVisible) { 573 if (this.inverted) { 574 drawDownOrLeft = true; 575 } 576 else { 577 drawUpOrRight = true; 578 } 579 } 580 if (this.negativeArrowVisible) { 581 if (this.inverted) { 582 drawUpOrRight = true; 583 } 584 else { 585 drawDownOrLeft = true; 586 } 587 } 588 if (drawUpOrRight) { 589 double x = 0.0; 590 double y = 0.0; 591 Shape arrow = null; 592 if (edge == RectangleEdge.TOP || edge == RectangleEdge.BOTTOM) { 593 x = dataArea.getMaxX(); 594 y = cursor; 595 arrow = this.rightArrow; 596 } 597 else if (edge == RectangleEdge.LEFT 598 || edge == RectangleEdge.RIGHT) { 599 x = cursor; 600 y = dataArea.getMinY(); 601 arrow = this.upArrow; 602 } 603 604 // draw the arrow... 605 AffineTransform transformer = new AffineTransform(); 606 transformer.setToTranslation(x, y); 607 Shape shape = transformer.createTransformedShape(arrow); 608 g2.fill(shape); 609 g2.draw(shape); 610 } 611 612 if (drawDownOrLeft) { 613 double x = 0.0; 614 double y = 0.0; 615 Shape arrow = null; 616 if (edge == RectangleEdge.TOP || edge == RectangleEdge.BOTTOM) { 617 x = dataArea.getMinX(); 618 y = cursor; 619 arrow = this.leftArrow; 620 } 621 else if (edge == RectangleEdge.LEFT 622 || edge == RectangleEdge.RIGHT) { 623 x = cursor; 624 y = dataArea.getMaxY(); 625 arrow = this.downArrow; 626 } 627 628 // draw the arrow... 629 AffineTransform transformer = new AffineTransform(); 630 transformer.setToTranslation(x, y); 631 Shape shape = transformer.createTransformedShape(arrow); 632 g2.fill(shape); 633 g2.draw(shape); 634 } 635 636 } 637 638 /** 639 * Calculates the anchor point for a tick label. 640 * 641 * @param tick the tick. 642 * @param cursor the cursor. 643 * @param dataArea the data area. 644 * @param edge the edge on which the axis is drawn. 645 * 646 * @return The x and y coordinates of the anchor point. 647 */ 648 protected float[] calculateAnchorPoint(ValueTick tick, 649 double cursor, 650 Rectangle2D dataArea, 651 RectangleEdge edge) { 652 653 RectangleInsets insets = getTickLabelInsets(); 654 float[] result = new float[2]; 655 if (edge == RectangleEdge.TOP) { 656 result[0] = (float) valueToJava2D(tick.getValue(), dataArea, edge); 657 result[1] = (float) (cursor - insets.getBottom() - 2.0); 658 } 659 else if (edge == RectangleEdge.BOTTOM) { 660 result[0] = (float) valueToJava2D(tick.getValue(), dataArea, edge); 661 result[1] = (float) (cursor + insets.getTop() + 2.0); 662 } 663 else if (edge == RectangleEdge.LEFT) { 664 result[0] = (float) (cursor - insets.getLeft() - 2.0); 665 result[1] = (float) valueToJava2D(tick.getValue(), dataArea, edge); 666 } 667 else if (edge == RectangleEdge.RIGHT) { 668 result[0] = (float) (cursor + insets.getRight() + 2.0); 669 result[1] = (float) valueToJava2D(tick.getValue(), dataArea, edge); 670 } 671 return result; 672 } 673 674 /** 675 * Draws the axis line, tick marks and tick mark labels. 676 * 677 * @param g2 the graphics device. 678 * @param cursor the cursor. 679 * @param plotArea the plot area. 680 * @param dataArea the data area. 681 * @param edge the edge that the axis is aligned with. 682 * 683 * @return The width or height used to draw the axis. 684 */ 685 protected AxisState drawTickMarksAndLabels(Graphics2D g2, 686 double cursor, Rectangle2D plotArea, Rectangle2D dataArea, 687 RectangleEdge edge) { 688 689 AxisState state = new AxisState(cursor); 690 691 if (isAxisLineVisible()) { 692 drawAxisLine(g2, cursor, dataArea, edge); 693 } 694 695 List ticks = refreshTicks(g2, state, dataArea, edge); 696 state.setTicks(ticks); 697 g2.setFont(getTickLabelFont()); 698 Iterator iterator = ticks.iterator(); 699 while (iterator.hasNext()) { 700 ValueTick tick = (ValueTick) iterator.next(); 701 if (isTickLabelsVisible()) { 702 g2.setPaint(getTickLabelPaint()); 703 float[] anchorPoint = calculateAnchorPoint(tick, cursor, 704 dataArea, edge); 705 TextUtilities.drawRotatedString(tick.getText(), g2, 706 anchorPoint[0], anchorPoint[1], tick.getTextAnchor(), 707 tick.getAngle(), tick.getRotationAnchor()); 708 } 709 710 if ((isTickMarksVisible() && tick.getTickType().equals( 711 TickType.MAJOR)) || (isMinorTickMarksVisible() 712 && tick.getTickType().equals(TickType.MINOR))) { 713 714 double ol = (tick.getTickType().equals(TickType.MINOR)) 715 ? getMinorTickMarkOutsideLength() 716 : getTickMarkOutsideLength(); 717 718 double il = (tick.getTickType().equals(TickType.MINOR)) 719 ? getMinorTickMarkInsideLength() 720 : getTickMarkInsideLength(); 721 722 float xx = (float) valueToJava2D(tick.getValue(), dataArea, 723 edge); 724 Line2D mark = null; 725 g2.setStroke(getTickMarkStroke()); 726 g2.setPaint(getTickMarkPaint()); 727 if (edge == RectangleEdge.LEFT) { 728 mark = new Line2D.Double(cursor - ol, xx, cursor + il, xx); 729 } 730 else if (edge == RectangleEdge.RIGHT) { 731 mark = new Line2D.Double(cursor + ol, xx, cursor - il, xx); 732 } 733 else if (edge == RectangleEdge.TOP) { 734 mark = new Line2D.Double(xx, cursor - ol, xx, cursor + il); 735 } 736 else if (edge == RectangleEdge.BOTTOM) { 737 mark = new Line2D.Double(xx, cursor + ol, xx, cursor - il); 738 } 739 g2.draw(mark); 740 } 741 } 742 743 // need to work out the space used by the tick labels... 744 // so we can update the cursor... 745 double used = 0.0; 746 if (isTickLabelsVisible()) { 747 if (edge == RectangleEdge.LEFT) { 748 used += findMaximumTickLabelWidth(ticks, g2, plotArea, 749 isVerticalTickLabels()); 750 state.cursorLeft(used); 751 } 752 else if (edge == RectangleEdge.RIGHT) { 753 used = findMaximumTickLabelWidth(ticks, g2, plotArea, 754 isVerticalTickLabels()); 755 state.cursorRight(used); 756 } 757 else if (edge == RectangleEdge.TOP) { 758 used = findMaximumTickLabelHeight(ticks, g2, plotArea, 759 isVerticalTickLabels()); 760 state.cursorUp(used); 761 } 762 else if (edge == RectangleEdge.BOTTOM) { 763 used = findMaximumTickLabelHeight(ticks, g2, plotArea, 764 isVerticalTickLabels()); 765 state.cursorDown(used); 766 } 767 } 768 769 return state; 770 } 771 772 /** 773 * Returns the space required to draw the axis. 774 * 775 * @param g2 the graphics device. 776 * @param plot the plot that the axis belongs to. 777 * @param plotArea the area within which the plot should be drawn. 778 * @param edge the axis location. 779 * @param space the space already reserved (for other axes). 780 * 781 * @return The space required to draw the axis (including pre-reserved 782 * space). 783 */ 784 public AxisSpace reserveSpace(Graphics2D g2, Plot plot, 785 Rectangle2D plotArea, 786 RectangleEdge edge, AxisSpace space) { 787 788 // create a new space object if one wasn't supplied... 789 if (space == null) { 790 space = new AxisSpace(); 791 } 792 793 // if the axis is not visible, no additional space is required... 794 if (!isVisible()) { 795 return space; 796 } 797 798 // if the axis has a fixed dimension, return it... 799 double dimension = getFixedDimension(); 800 if (dimension > 0.0) { 801 space.ensureAtLeast(dimension, edge); 802 } 803 804 // calculate the max size of the tick labels (if visible)... 805 double tickLabelHeight = 0.0; 806 double tickLabelWidth = 0.0; 807 if (isTickLabelsVisible()) { 808 g2.setFont(getTickLabelFont()); 809 List ticks = refreshTicks(g2, new AxisState(), plotArea, edge); 810 if (RectangleEdge.isTopOrBottom(edge)) { 811 tickLabelHeight = findMaximumTickLabelHeight(ticks, g2, 812 plotArea, isVerticalTickLabels()); 813 } 814 else if (RectangleEdge.isLeftOrRight(edge)) { 815 tickLabelWidth = findMaximumTickLabelWidth(ticks, g2, plotArea, 816 isVerticalTickLabels()); 817 } 818 } 819 820 // get the axis label size and update the space object... 821 Rectangle2D labelEnclosure = getLabelEnclosure(g2, edge); 822 double labelHeight = 0.0; 823 double labelWidth = 0.0; 824 if (RectangleEdge.isTopOrBottom(edge)) { 825 labelHeight = labelEnclosure.getHeight(); 826 space.add(labelHeight + tickLabelHeight, edge); 827 } 828 else if (RectangleEdge.isLeftOrRight(edge)) { 829 labelWidth = labelEnclosure.getWidth(); 830 space.add(labelWidth + tickLabelWidth, edge); 831 } 832 833 return space; 834 835 } 836 837 /** 838 * A utility method for determining the height of the tallest tick label. 839 * 840 * @param ticks the ticks. 841 * @param g2 the graphics device. 842 * @param drawArea the area within which the plot and axes should be drawn. 843 * @param vertical a flag that indicates whether or not the tick labels 844 * are 'vertical'. 845 * 846 * @return The height of the tallest tick label. 847 */ 848 protected double findMaximumTickLabelHeight(List ticks, 849 Graphics2D g2, 850 Rectangle2D drawArea, 851 boolean vertical) { 852 853 RectangleInsets insets = getTickLabelInsets(); 854 Font font = getTickLabelFont(); 855 double maxHeight = 0.0; 856 if (vertical) { 857 FontMetrics fm = g2.getFontMetrics(font); 858 Iterator iterator = ticks.iterator(); 859 while (iterator.hasNext()) { 860 Tick tick = (Tick) iterator.next(); 861 Rectangle2D labelBounds = TextUtilities.getTextBounds( 862 tick.getText(), g2, fm); 863 if (labelBounds.getWidth() + insets.getTop() 864 + insets.getBottom() > maxHeight) { 865 maxHeight = labelBounds.getWidth() 866 + insets.getTop() + insets.getBottom(); 867 } 868 } 869 } 870 else { 871 LineMetrics metrics = font.getLineMetrics("ABCxyz", 872 g2.getFontRenderContext()); 873 maxHeight = metrics.getHeight() 874 + insets.getTop() + insets.getBottom(); 875 } 876 return maxHeight; 877 878 } 879 880 /** 881 * A utility method for determining the width of the widest tick label. 882 * 883 * @param ticks the ticks. 884 * @param g2 the graphics device. 885 * @param drawArea the area within which the plot and axes should be drawn. 886 * @param vertical a flag that indicates whether or not the tick labels 887 * are 'vertical'. 888 * 889 * @return The width of the tallest tick label. 890 */ 891 protected double findMaximumTickLabelWidth(List ticks, 892 Graphics2D g2, 893 Rectangle2D drawArea, 894 boolean vertical) { 895 896 RectangleInsets insets = getTickLabelInsets(); 897 Font font = getTickLabelFont(); 898 double maxWidth = 0.0; 899 if (!vertical) { 900 FontMetrics fm = g2.getFontMetrics(font); 901 Iterator iterator = ticks.iterator(); 902 while (iterator.hasNext()) { 903 Tick tick = (Tick) iterator.next(); 904 Rectangle2D labelBounds = TextUtilities.getTextBounds( 905 tick.getText(), g2, fm); 906 if (labelBounds.getWidth() + insets.getLeft() 907 + insets.getRight() > maxWidth) { 908 maxWidth = labelBounds.getWidth() 909 + insets.getLeft() + insets.getRight(); 910 } 911 } 912 } 913 else { 914 LineMetrics metrics = font.getLineMetrics("ABCxyz", 915 g2.getFontRenderContext()); 916 maxWidth = metrics.getHeight() 917 + insets.getTop() + insets.getBottom(); 918 } 919 return maxWidth; 920 921 } 922 923 /** 924 * Returns a flag that controls the direction of values on the axis. 925 * <P> 926 * For a regular axis, values increase from left to right (for a horizontal 927 * axis) and bottom to top (for a vertical axis). When the axis is 928 * 'inverted', the values increase in the opposite direction. 929 * 930 * @return The flag. 931 * 932 * @see #setInverted(boolean) 933 */ 934 public boolean isInverted() { 935 return this.inverted; 936 } 937 938 /** 939 * Sets a flag that controls the direction of values on the axis, and 940 * notifies registered listeners that the axis has changed. 941 * 942 * @param flag the flag. 943 * 944 * @see #isInverted() 945 */ 946 public void setInverted(boolean flag) { 947 948 if (this.inverted != flag) { 949 this.inverted = flag; 950 notifyListeners(new AxisChangeEvent(this)); 951 } 952 953 } 954 955 /** 956 * Returns the flag that controls whether or not the axis range is 957 * automatically adjusted to fit the data values. 958 * 959 * @return The flag. 960 * 961 * @see #setAutoRange(boolean) 962 */ 963 public boolean isAutoRange() { 964 return this.autoRange; 965 } 966 967 /** 968 * Sets a flag that determines whether or not the axis range is 969 * automatically adjusted to fit the data, and notifies registered 970 * listeners that the axis has been modified. 971 * 972 * @param auto the new value of the flag. 973 * 974 * @see #isAutoRange() 975 */ 976 public void setAutoRange(boolean auto) { 977 setAutoRange(auto, true); 978 } 979 980 /** 981 * Sets the auto range attribute. If the <code>notify</code> flag is set, 982 * an {@link AxisChangeEvent} is sent to registered listeners. 983 * 984 * @param auto the flag. 985 * @param notify notify listeners? 986 * 987 * @see #isAutoRange() 988 */ 989 protected void setAutoRange(boolean auto, boolean notify) { 990 if (this.autoRange != auto) { 991 this.autoRange = auto; 992 if (this.autoRange) { 993 autoAdjustRange(); 994 } 995 if (notify) { 996 notifyListeners(new AxisChangeEvent(this)); 997 } 998 } 999 } 1000 1001 /** 1002 * Returns the minimum size allowed for the axis range when it is 1003 * automatically calculated. 1004 * 1005 * @return The minimum range. 1006 * 1007 * @see #setAutoRangeMinimumSize(double) 1008 */ 1009 public double getAutoRangeMinimumSize() { 1010 return this.autoRangeMinimumSize; 1011 } 1012 1013 /** 1014 * Sets the auto range minimum size and sends an {@link AxisChangeEvent} 1015 * to all registered listeners. 1016 * 1017 * @param size the size. 1018 * 1019 * @see #getAutoRangeMinimumSize() 1020 */ 1021 public void setAutoRangeMinimumSize(double size) { 1022 setAutoRangeMinimumSize(size, true); 1023 } 1024 1025 /** 1026 * Sets the minimum size allowed for the axis range when it is 1027 * automatically calculated. 1028 * <p> 1029 * If requested, an {@link AxisChangeEvent} is forwarded to all registered 1030 * listeners. 1031 * 1032 * @param size the new minimum. 1033 * @param notify notify listeners? 1034 */ 1035 public void setAutoRangeMinimumSize(double size, boolean notify) { 1036 if (size <= 0.0) { 1037 throw new IllegalArgumentException( 1038 "NumberAxis.setAutoRangeMinimumSize(double): must be > 0.0."); 1039 } 1040 if (this.autoRangeMinimumSize != size) { 1041 this.autoRangeMinimumSize = size; 1042 if (this.autoRange) { 1043 autoAdjustRange(); 1044 } 1045 if (notify) { 1046 notifyListeners(new AxisChangeEvent(this)); 1047 } 1048 } 1049 1050 } 1051 1052 /** 1053 * Returns the default auto range. 1054 * 1055 * @return The default auto range (never <code>null</code>). 1056 * 1057 * @see #setDefaultAutoRange(Range) 1058 * 1059 * @since 1.0.5 1060 */ 1061 public Range getDefaultAutoRange() { 1062 return this.defaultAutoRange; 1063 } 1064 1065 /** 1066 * Sets the default auto range and sends an {@link AxisChangeEvent} to all 1067 * registered listeners. 1068 * 1069 * @param range the range (<code>null</code> not permitted). 1070 * 1071 * @see #getDefaultAutoRange() 1072 * 1073 * @since 1.0.5 1074 */ 1075 public void setDefaultAutoRange(Range range) { 1076 if (range == null) { 1077 throw new IllegalArgumentException("Null 'range' argument."); 1078 } 1079 this.defaultAutoRange = range; 1080 notifyListeners(new AxisChangeEvent(this)); 1081 } 1082 1083 /** 1084 * Returns the lower margin for the axis, expressed as a percentage of the 1085 * axis range. This controls the space added to the lower end of the axis 1086 * when the axis range is automatically calculated (it is ignored when the 1087 * axis range is set explicitly). The default value is 0.05 (five percent). 1088 * 1089 * @return The lower margin. 1090 * 1091 * @see #setLowerMargin(double) 1092 */ 1093 public double getLowerMargin() { 1094 return this.lowerMargin; 1095 } 1096 1097 /** 1098 * Sets the lower margin for the axis (as a percentage of the axis range) 1099 * and sends an {@link AxisChangeEvent} to all registered listeners. This 1100 * margin is added only when the axis range is auto-calculated - if you set 1101 * the axis range manually, the margin is ignored. 1102 * 1103 * @param margin the margin percentage (for example, 0.05 is five percent). 1104 * 1105 * @see #getLowerMargin() 1106 * @see #setUpperMargin(double) 1107 */ 1108 public void setLowerMargin(double margin) { 1109 this.lowerMargin = margin; 1110 if (isAutoRange()) { 1111 autoAdjustRange(); 1112 } 1113 notifyListeners(new AxisChangeEvent(this)); 1114 } 1115 1116 /** 1117 * Returns the upper margin for the axis, expressed as a percentage of the 1118 * axis range. This controls the space added to the lower end of the axis 1119 * when the axis range is automatically calculated (it is ignored when the 1120 * axis range is set explicitly). The default value is 0.05 (five percent). 1121 * 1122 * @return The upper margin. 1123 * 1124 * @see #setUpperMargin(double) 1125 */ 1126 public double getUpperMargin() { 1127 return this.upperMargin; 1128 } 1129 1130 /** 1131 * Sets the upper margin for the axis (as a percentage of the axis range) 1132 * and sends an {@link AxisChangeEvent} to all registered listeners. This 1133 * margin is added only when the axis range is auto-calculated - if you set 1134 * the axis range manually, the margin is ignored. 1135 * 1136 * @param margin the margin percentage (for example, 0.05 is five percent). 1137 * 1138 * @see #getLowerMargin() 1139 * @see #setLowerMargin(double) 1140 */ 1141 public void setUpperMargin(double margin) { 1142 this.upperMargin = margin; 1143 if (isAutoRange()) { 1144 autoAdjustRange(); 1145 } 1146 notifyListeners(new AxisChangeEvent(this)); 1147 } 1148 1149 /** 1150 * Returns the fixed auto range. 1151 * 1152 * @return The length. 1153 * 1154 * @see #setFixedAutoRange(double) 1155 */ 1156 public double getFixedAutoRange() { 1157 return this.fixedAutoRange; 1158 } 1159 1160 /** 1161 * Sets the fixed auto range for the axis. 1162 * 1163 * @param length the range length. 1164 * 1165 * @see #getFixedAutoRange() 1166 */ 1167 public void setFixedAutoRange(double length) { 1168 this.fixedAutoRange = length; 1169 if (isAutoRange()) { 1170 autoAdjustRange(); 1171 } 1172 notifyListeners(new AxisChangeEvent(this)); 1173 } 1174 1175 /** 1176 * Returns the lower bound of the axis range. 1177 * 1178 * @return The lower bound. 1179 * 1180 * @see #setLowerBound(double) 1181 */ 1182 public double getLowerBound() { 1183 return this.range.getLowerBound(); 1184 } 1185 1186 /** 1187 * Sets the lower bound for the axis range. An {@link AxisChangeEvent} is 1188 * sent to all registered listeners. 1189 * 1190 * @param min the new minimum. 1191 * 1192 * @see #getLowerBound() 1193 */ 1194 public void setLowerBound(double min) { 1195 if (this.range.getUpperBound() > min) { 1196 setRange(new Range(min, this.range.getUpperBound())); 1197 } 1198 else { 1199 setRange(new Range(min, min + 1.0)); 1200 } 1201 } 1202 1203 /** 1204 * Returns the upper bound for the axis range. 1205 * 1206 * @return The upper bound. 1207 * 1208 * @see #setUpperBound(double) 1209 */ 1210 public double getUpperBound() { 1211 return this.range.getUpperBound(); 1212 } 1213 1214 /** 1215 * Sets the upper bound for the axis range, and sends an 1216 * {@link AxisChangeEvent} to all registered listeners. 1217 * 1218 * @param max the new maximum. 1219 * 1220 * @see #getUpperBound() 1221 */ 1222 public void setUpperBound(double max) { 1223 if (this.range.getLowerBound() < max) { 1224 setRange(new Range(this.range.getLowerBound(), max)); 1225 } 1226 else { 1227 setRange(max - 1.0, max); 1228 } 1229 } 1230 1231 /** 1232 * Returns the range for the axis. 1233 * 1234 * @return The axis range (never <code>null</code>). 1235 * 1236 * @see #setRange(Range) 1237 */ 1238 public Range getRange() { 1239 return this.range; 1240 } 1241 1242 /** 1243 * Sets the range attribute and sends an {@link AxisChangeEvent} to all 1244 * registered listeners. As a side-effect, the auto-range flag is set to 1245 * <code>false</code>. 1246 * 1247 * @param range the range (<code>null</code> not permitted). 1248 * 1249 * @see #getRange() 1250 */ 1251 public void setRange(Range range) { 1252 // defer argument checking 1253 setRange(range, true, true); 1254 } 1255 1256 /** 1257 * Sets the range for the axis, if requested, sends an 1258 * {@link AxisChangeEvent} to all registered listeners. As a side-effect, 1259 * the auto-range flag is set to <code>false</code> (optional). 1260 * 1261 * @param range the range (<code>null</code> not permitted). 1262 * @param turnOffAutoRange a flag that controls whether or not the auto 1263 * range is turned off. 1264 * @param notify a flag that controls whether or not listeners are 1265 * notified. 1266 * 1267 * @see #getRange() 1268 */ 1269 public void setRange(Range range, boolean turnOffAutoRange, 1270 boolean notify) { 1271 if (range == null) { 1272 throw new IllegalArgumentException("Null 'range' argument."); 1273 } 1274 if (turnOffAutoRange) { 1275 this.autoRange = false; 1276 } 1277 this.range = range; 1278 if (notify) { 1279 notifyListeners(new AxisChangeEvent(this)); 1280 } 1281 } 1282 1283 /** 1284 * Sets the axis range and sends an {@link AxisChangeEvent} to all 1285 * registered listeners. As a side-effect, the auto-range flag is set to 1286 * <code>false</code>. 1287 * 1288 * @param lower the lower axis limit. 1289 * @param upper the upper axis limit. 1290 * 1291 * @see #getRange() 1292 * @see #setRange(Range) 1293 */ 1294 public void setRange(double lower, double upper) { 1295 setRange(new Range(lower, upper)); 1296 } 1297 1298 /** 1299 * Sets the range for the axis (after first adding the current margins to 1300 * the specified range) and sends an {@link AxisChangeEvent} to all 1301 * registered listeners. 1302 * 1303 * @param range the range (<code>null</code> not permitted). 1304 */ 1305 public void setRangeWithMargins(Range range) { 1306 setRangeWithMargins(range, true, true); 1307 } 1308 1309 /** 1310 * Sets the range for the axis after first adding the current margins to 1311 * the range and, if requested, sends an {@link AxisChangeEvent} to all 1312 * registered listeners. As a side-effect, the auto-range flag is set to 1313 * <code>false</code> (optional). 1314 * 1315 * @param range the range (excluding margins, <code>null</code> not 1316 * permitted). 1317 * @param turnOffAutoRange a flag that controls whether or not the auto 1318 * range is turned off. 1319 * @param notify a flag that controls whether or not listeners are 1320 * notified. 1321 */ 1322 public void setRangeWithMargins(Range range, boolean turnOffAutoRange, 1323 boolean notify) { 1324 if (range == null) { 1325 throw new IllegalArgumentException("Null 'range' argument."); 1326 } 1327 setRange(Range.expand(range, getLowerMargin(), getUpperMargin()), 1328 turnOffAutoRange, notify); 1329 } 1330 1331 /** 1332 * Sets the axis range (after first adding the current margins to the 1333 * range) and sends an {@link AxisChangeEvent} to all registered listeners. 1334 * As a side-effect, the auto-range flag is set to <code>false</code>. 1335 * 1336 * @param lower the lower axis limit. 1337 * @param upper the upper axis limit. 1338 */ 1339 public void setRangeWithMargins(double lower, double upper) { 1340 setRangeWithMargins(new Range(lower, upper)); 1341 } 1342 1343 /** 1344 * Sets the axis range, where the new range is 'size' in length, and 1345 * centered on 'value'. 1346 * 1347 * @param value the central value. 1348 * @param length the range length. 1349 */ 1350 public void setRangeAboutValue(double value, double length) { 1351 setRange(new Range(value - length / 2, value + length / 2)); 1352 } 1353 1354 /** 1355 * Returns a flag indicating whether or not the tick unit is automatically 1356 * selected from a range of standard tick units. 1357 * 1358 * @return A flag indicating whether or not the tick unit is automatically 1359 * selected. 1360 * 1361 * @see #setAutoTickUnitSelection(boolean) 1362 */ 1363 public boolean isAutoTickUnitSelection() { 1364 return this.autoTickUnitSelection; 1365 } 1366 1367 /** 1368 * Sets a flag indicating whether or not the tick unit is automatically 1369 * selected from a range of standard tick units. If the flag is changed, 1370 * registered listeners are notified that the chart has changed. 1371 * 1372 * @param flag the new value of the flag. 1373 * 1374 * @see #isAutoTickUnitSelection() 1375 */ 1376 public void setAutoTickUnitSelection(boolean flag) { 1377 setAutoTickUnitSelection(flag, true); 1378 } 1379 1380 /** 1381 * Sets a flag indicating whether or not the tick unit is automatically 1382 * selected from a range of standard tick units. 1383 * 1384 * @param flag the new value of the flag. 1385 * @param notify notify listeners? 1386 * 1387 * @see #isAutoTickUnitSelection() 1388 */ 1389 public void setAutoTickUnitSelection(boolean flag, boolean notify) { 1390 1391 if (this.autoTickUnitSelection != flag) { 1392 this.autoTickUnitSelection = flag; 1393 if (notify) { 1394 notifyListeners(new AxisChangeEvent(this)); 1395 } 1396 } 1397 } 1398 1399 /** 1400 * Returns the source for obtaining standard tick units for the axis. 1401 * 1402 * @return The source (possibly <code>null</code>). 1403 * 1404 * @see #setStandardTickUnits(TickUnitSource) 1405 */ 1406 public TickUnitSource getStandardTickUnits() { 1407 return this.standardTickUnits; 1408 } 1409 1410 /** 1411 * Sets the source for obtaining standard tick units for the axis and sends 1412 * an {@link AxisChangeEvent} to all registered listeners. The axis will 1413 * try to select the smallest tick unit from the source that does not cause 1414 * the tick labels to overlap (see also the 1415 * {@link #setAutoTickUnitSelection(boolean)} method. 1416 * 1417 * @param source the source for standard tick units (<code>null</code> 1418 * permitted). 1419 * 1420 * @see #getStandardTickUnits() 1421 */ 1422 public void setStandardTickUnits(TickUnitSource source) { 1423 this.standardTickUnits = source; 1424 notifyListeners(new AxisChangeEvent(this)); 1425 } 1426 1427 /** 1428 * Returns the number of minor tick marks to display. 1429 * 1430 * @return The number of minor tick marks to display. 1431 * 1432 * @see #setMinorTickCount(int) 1433 * 1434 * @since 1.0.12 1435 */ 1436 public int getMinorTickCount() { 1437 return this.minorTickCount; 1438 } 1439 1440 /** 1441 * Sets the number of minor tick marks to display, and sends an 1442 * {@link AxisChangeEvent} to all registered listeners. 1443 * 1444 * @param count the count. 1445 * 1446 * @see #getMinorTickCount() 1447 * 1448 * @since 1.0.12 1449 */ 1450 public void setMinorTickCount(int count) { 1451 this.minorTickCount = count; 1452 notifyListeners(new AxisChangeEvent(this)); 1453 } 1454 1455 /** 1456 * Converts a data value to a coordinate in Java2D space, assuming that the 1457 * axis runs along one edge of the specified dataArea. 1458 * <p> 1459 * Note that it is possible for the coordinate to fall outside the area. 1460 * 1461 * @param value the data value. 1462 * @param area the area for plotting the data. 1463 * @param edge the edge along which the axis lies. 1464 * 1465 * @return The Java2D coordinate. 1466 * 1467 * @see #java2DToValue(double, Rectangle2D, RectangleEdge) 1468 */ 1469 public abstract double valueToJava2D(double value, Rectangle2D area, 1470 RectangleEdge edge); 1471 1472 /** 1473 * Converts a length in data coordinates into the corresponding length in 1474 * Java2D coordinates. 1475 * 1476 * @param length the length. 1477 * @param area the plot area. 1478 * @param edge the edge along which the axis lies. 1479 * 1480 * @return The length in Java2D coordinates. 1481 */ 1482 public double lengthToJava2D(double length, Rectangle2D area, 1483 RectangleEdge edge) { 1484 double zero = valueToJava2D(0.0, area, edge); 1485 double l = valueToJava2D(length, area, edge); 1486 return Math.abs(l - zero); 1487 } 1488 1489 /** 1490 * Converts a coordinate in Java2D space to the corresponding data value, 1491 * assuming that the axis runs along one edge of the specified dataArea. 1492 * 1493 * @param java2DValue the coordinate in Java2D space. 1494 * @param area the area in which the data is plotted. 1495 * @param edge the edge along which the axis lies. 1496 * 1497 * @return The data value. 1498 * 1499 * @see #valueToJava2D(double, Rectangle2D, RectangleEdge) 1500 */ 1501 public abstract double java2DToValue(double java2DValue, 1502 Rectangle2D area, 1503 RectangleEdge edge); 1504 1505 /** 1506 * Automatically sets the axis range to fit the range of values in the 1507 * dataset. Sometimes this can depend on the renderer used as well (for 1508 * example, the renderer may "stack" values, requiring an axis range 1509 * greater than otherwise necessary). 1510 */ 1511 protected abstract void autoAdjustRange(); 1512 1513 /** 1514 * Centers the axis range about the specified value and sends an 1515 * {@link AxisChangeEvent} to all registered listeners. 1516 * 1517 * @param value the center value. 1518 */ 1519 public void centerRange(double value) { 1520 1521 double central = this.range.getCentralValue(); 1522 Range adjusted = new Range(this.range.getLowerBound() + value - central, 1523 this.range.getUpperBound() + value - central); 1524 setRange(adjusted); 1525 1526 } 1527 1528 /** 1529 * Increases or decreases the axis range by the specified percentage about 1530 * the central value and sends an {@link AxisChangeEvent} to all registered 1531 * listeners. 1532 * <P> 1533 * To double the length of the axis range, use 200% (2.0). 1534 * To halve the length of the axis range, use 50% (0.5). 1535 * 1536 * @param percent the resize factor. 1537 * 1538 * @see #resizeRange(double, double) 1539 */ 1540 public void resizeRange(double percent) { 1541 resizeRange(percent, this.range.getCentralValue()); 1542 } 1543 1544 /** 1545 * Increases or decreases the axis range by the specified percentage about 1546 * the specified anchor value and sends an {@link AxisChangeEvent} to all 1547 * registered listeners. 1548 * <P> 1549 * To double the length of the axis range, use 200% (2.0). 1550 * To halve the length of the axis range, use 50% (0.5). 1551 * 1552 * @param percent the resize factor. 1553 * @param anchorValue the new central value after the resize. 1554 * 1555 * @see #resizeRange(double) 1556 */ 1557 public void resizeRange(double percent, double anchorValue) { 1558 if (percent > 0.0) { 1559 double halfLength = this.range.getLength() * percent / 2; 1560 Range adjusted = new Range(anchorValue - halfLength, 1561 anchorValue + halfLength); 1562 setRange(adjusted); 1563 } 1564 else { 1565 setAutoRange(true); 1566 } 1567 } 1568 1569 /** 1570 * Increases or decreases the axis range by the specified percentage about 1571 * the specified anchor value and sends an {@link AxisChangeEvent} to all 1572 * registered listeners. 1573 * <P> 1574 * To double the length of the axis range, use 200% (2.0). 1575 * To halve the length of the axis range, use 50% (0.5). 1576 * 1577 * @param percent the resize factor. 1578 * @param anchorValue the new central value after the resize. 1579 * 1580 * @see #resizeRange(double) 1581 * 1582 * @since 1.0.13 1583 */ 1584 public void resizeRange2(double percent, double anchorValue) { 1585 if (percent > 0.0) { 1586 double left = anchorValue - getLowerBound(); 1587 double right = getUpperBound() - anchorValue; 1588 Range adjusted = new Range(anchorValue - left * percent, 1589 anchorValue + right * percent); 1590 setRange(adjusted); 1591 } 1592 else { 1593 setAutoRange(true); 1594 } 1595 } 1596 1597 /** 1598 * Zooms in on the current range. 1599 * 1600 * @param lowerPercent the new lower bound. 1601 * @param upperPercent the new upper bound. 1602 */ 1603 public void zoomRange(double lowerPercent, double upperPercent) { 1604 double start = this.range.getLowerBound(); 1605 double length = this.range.getLength(); 1606 Range adjusted = null; 1607 if (isInverted()) { 1608 adjusted = new Range(start + (length * (1 - upperPercent)), 1609 start + (length * (1 - lowerPercent))); 1610 } 1611 else { 1612 adjusted = new Range(start + length * lowerPercent, 1613 start + length * upperPercent); 1614 } 1615 setRange(adjusted); 1616 } 1617 1618 /** 1619 * Slides the axis range by the specified percentage. 1620 * 1621 * @param percent the percentage. 1622 * 1623 * @since 1.0.13 1624 */ 1625 public void pan(double percent) { 1626 Range range = getRange(); 1627 double length = range.getLength(); 1628 double adj = length * percent; 1629 double lower = range.getLowerBound() + adj; 1630 double upper = range.getUpperBound() + adj; 1631 setRange(lower, upper); 1632 } 1633 1634 /** 1635 * Returns the auto tick index. 1636 * 1637 * @return The auto tick index. 1638 * 1639 * @see #setAutoTickIndex(int) 1640 */ 1641 protected int getAutoTickIndex() { 1642 return this.autoTickIndex; 1643 } 1644 1645 /** 1646 * Sets the auto tick index. 1647 * 1648 * @param index the new value. 1649 * 1650 * @see #getAutoTickIndex() 1651 */ 1652 protected void setAutoTickIndex(int index) { 1653 this.autoTickIndex = index; 1654 } 1655 1656 /** 1657 * Tests the axis for equality with an arbitrary object. 1658 * 1659 * @param obj the object (<code>null</code> permitted). 1660 * 1661 * @return <code>true</code> or <code>false</code>. 1662 */ 1663 public boolean equals(Object obj) { 1664 if (obj == this) { 1665 return true; 1666 } 1667 if (!(obj instanceof ValueAxis)) { 1668 return false; 1669 } 1670 ValueAxis that = (ValueAxis) obj; 1671 if (this.positiveArrowVisible != that.positiveArrowVisible) { 1672 return false; 1673 } 1674 if (this.negativeArrowVisible != that.negativeArrowVisible) { 1675 return false; 1676 } 1677 if (this.inverted != that.inverted) { 1678 return false; 1679 } 1680 // if autoRange is true, then the current range is irrelevant 1681 if (!this.autoRange && !ObjectUtilities.equal(this.range, that.range)) { 1682 return false; 1683 } 1684 if (this.autoRange != that.autoRange) { 1685 return false; 1686 } 1687 if (this.autoRangeMinimumSize != that.autoRangeMinimumSize) { 1688 return false; 1689 } 1690 if (!this.defaultAutoRange.equals(that.defaultAutoRange)) { 1691 return false; 1692 } 1693 if (this.upperMargin != that.upperMargin) { 1694 return false; 1695 } 1696 if (this.lowerMargin != that.lowerMargin) { 1697 return false; 1698 } 1699 if (this.fixedAutoRange != that.fixedAutoRange) { 1700 return false; 1701 } 1702 if (this.autoTickUnitSelection != that.autoTickUnitSelection) { 1703 return false; 1704 } 1705 if (!ObjectUtilities.equal(this.standardTickUnits, 1706 that.standardTickUnits)) { 1707 return false; 1708 } 1709 if (this.verticalTickLabels != that.verticalTickLabels) { 1710 return false; 1711 } 1712 if (this.minorTickCount != that.minorTickCount) { 1713 return false; 1714 } 1715 return super.equals(obj); 1716 } 1717 1718 /** 1719 * Returns a clone of the object. 1720 * 1721 * @return A clone. 1722 * 1723 * @throws CloneNotSupportedException if some component of the axis does 1724 * not support cloning. 1725 */ 1726 public Object clone() throws CloneNotSupportedException { 1727 ValueAxis clone = (ValueAxis) super.clone(); 1728 return clone; 1729 } 1730 1731 /** 1732 * Provides serialization support. 1733 * 1734 * @param stream the output stream. 1735 * 1736 * @throws IOException if there is an I/O error. 1737 */ 1738 private void writeObject(ObjectOutputStream stream) throws IOException { 1739 stream.defaultWriteObject(); 1740 SerialUtilities.writeShape(this.upArrow, stream); 1741 SerialUtilities.writeShape(this.downArrow, stream); 1742 SerialUtilities.writeShape(this.leftArrow, stream); 1743 SerialUtilities.writeShape(this.rightArrow, stream); 1744 } 1745 1746 /** 1747 * Provides serialization support. 1748 * 1749 * @param stream the input stream. 1750 * 1751 * @throws IOException if there is an I/O error. 1752 * @throws ClassNotFoundException if there is a classpath problem. 1753 */ 1754 private void readObject(ObjectInputStream stream) 1755 throws IOException, ClassNotFoundException { 1756 1757 stream.defaultReadObject(); 1758 this.upArrow = SerialUtilities.readShape(stream); 1759 this.downArrow = SerialUtilities.readShape(stream); 1760 this.leftArrow = SerialUtilities.readShape(stream); 1761 this.rightArrow = SerialUtilities.readShape(stream); 1762 } 1763 1764 }