001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2009, by Object Refinery Limited and Contributors. 006 * 007 * Project Info: http://www.jfree.org/jfreechart/index.html 008 * 009 * This library is free software; you can redistribute it and/or modify it 010 * under the terms of the GNU Lesser General Public License as published by 011 * the Free Software Foundation; either version 2.1 of the License, or 012 * (at your option) any later version. 013 * 014 * This library is distributed in the hope that it will be useful, but 015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 017 * License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this library; if not, write to the Free Software 021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 022 * USA. 023 * 024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 025 * in the United States and other countries.] 026 * 027 * -------------- 028 * 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() : getTickMarkOutsideLength(); 716 717 double il = (tick.getTickType().equals(TickType.MINOR)) ? 718 getMinorTickMarkInsideLength() : getTickMarkInsideLength(); 719 720 float xx = (float) valueToJava2D(tick.getValue(), dataArea, 721 edge); 722 Line2D mark = null; 723 g2.setStroke(getTickMarkStroke()); 724 g2.setPaint(getTickMarkPaint()); 725 if (edge == RectangleEdge.LEFT) { 726 mark = new Line2D.Double(cursor - ol, xx, cursor + il, xx); 727 } 728 else if (edge == RectangleEdge.RIGHT) { 729 mark = new Line2D.Double(cursor + ol, xx, cursor - il, xx); 730 } 731 else if (edge == RectangleEdge.TOP) { 732 mark = new Line2D.Double(xx, cursor - ol, xx, cursor + il); 733 } 734 else if (edge == RectangleEdge.BOTTOM) { 735 mark = new Line2D.Double(xx, cursor + ol, xx, cursor - il); 736 } 737 g2.draw(mark); 738 } 739 } 740 741 // need to work out the space used by the tick labels... 742 // so we can update the cursor... 743 double used = 0.0; 744 if (isTickLabelsVisible()) { 745 if (edge == RectangleEdge.LEFT) { 746 used += findMaximumTickLabelWidth(ticks, g2, plotArea, 747 isVerticalTickLabels()); 748 state.cursorLeft(used); 749 } 750 else if (edge == RectangleEdge.RIGHT) { 751 used = findMaximumTickLabelWidth(ticks, g2, plotArea, 752 isVerticalTickLabels()); 753 state.cursorRight(used); 754 } 755 else if (edge == RectangleEdge.TOP) { 756 used = findMaximumTickLabelHeight(ticks, g2, plotArea, 757 isVerticalTickLabels()); 758 state.cursorUp(used); 759 } 760 else if (edge == RectangleEdge.BOTTOM) { 761 used = findMaximumTickLabelHeight(ticks, g2, plotArea, 762 isVerticalTickLabels()); 763 state.cursorDown(used); 764 } 765 } 766 767 return state; 768 } 769 770 /** 771 * Returns the space required to draw the axis. 772 * 773 * @param g2 the graphics device. 774 * @param plot the plot that the axis belongs to. 775 * @param plotArea the area within which the plot should be drawn. 776 * @param edge the axis location. 777 * @param space the space already reserved (for other axes). 778 * 779 * @return The space required to draw the axis (including pre-reserved 780 * space). 781 */ 782 public AxisSpace reserveSpace(Graphics2D g2, Plot plot, 783 Rectangle2D plotArea, 784 RectangleEdge edge, AxisSpace space) { 785 786 // create a new space object if one wasn't supplied... 787 if (space == null) { 788 space = new AxisSpace(); 789 } 790 791 // if the axis is not visible, no additional space is required... 792 if (!isVisible()) { 793 return space; 794 } 795 796 // if the axis has a fixed dimension, return it... 797 double dimension = getFixedDimension(); 798 if (dimension > 0.0) { 799 space.ensureAtLeast(dimension, edge); 800 } 801 802 // calculate the max size of the tick labels (if visible)... 803 double tickLabelHeight = 0.0; 804 double tickLabelWidth = 0.0; 805 if (isTickLabelsVisible()) { 806 g2.setFont(getTickLabelFont()); 807 List ticks = refreshTicks(g2, new AxisState(), plotArea, edge); 808 if (RectangleEdge.isTopOrBottom(edge)) { 809 tickLabelHeight = findMaximumTickLabelHeight(ticks, g2, 810 plotArea, isVerticalTickLabels()); 811 } 812 else if (RectangleEdge.isLeftOrRight(edge)) { 813 tickLabelWidth = findMaximumTickLabelWidth(ticks, g2, plotArea, 814 isVerticalTickLabels()); 815 } 816 } 817 818 // get the axis label size and update the space object... 819 Rectangle2D labelEnclosure = getLabelEnclosure(g2, edge); 820 double labelHeight = 0.0; 821 double labelWidth = 0.0; 822 if (RectangleEdge.isTopOrBottom(edge)) { 823 labelHeight = labelEnclosure.getHeight(); 824 space.add(labelHeight + tickLabelHeight, edge); 825 } 826 else if (RectangleEdge.isLeftOrRight(edge)) { 827 labelWidth = labelEnclosure.getWidth(); 828 space.add(labelWidth + tickLabelWidth, edge); 829 } 830 831 return space; 832 833 } 834 835 /** 836 * A utility method for determining the height of the tallest tick label. 837 * 838 * @param ticks the ticks. 839 * @param g2 the graphics device. 840 * @param drawArea the area within which the plot and axes should be drawn. 841 * @param vertical a flag that indicates whether or not the tick labels 842 * are 'vertical'. 843 * 844 * @return The height of the tallest tick label. 845 */ 846 protected double findMaximumTickLabelHeight(List ticks, 847 Graphics2D g2, 848 Rectangle2D drawArea, 849 boolean vertical) { 850 851 RectangleInsets insets = getTickLabelInsets(); 852 Font font = getTickLabelFont(); 853 double maxHeight = 0.0; 854 if (vertical) { 855 FontMetrics fm = g2.getFontMetrics(font); 856 Iterator iterator = ticks.iterator(); 857 while (iterator.hasNext()) { 858 Tick tick = (Tick) iterator.next(); 859 Rectangle2D labelBounds = TextUtilities.getTextBounds( 860 tick.getText(), g2, fm); 861 if (labelBounds.getWidth() + insets.getTop() 862 + insets.getBottom() > maxHeight) { 863 maxHeight = labelBounds.getWidth() 864 + insets.getTop() + insets.getBottom(); 865 } 866 } 867 } 868 else { 869 LineMetrics metrics = font.getLineMetrics("ABCxyz", 870 g2.getFontRenderContext()); 871 maxHeight = metrics.getHeight() 872 + insets.getTop() + insets.getBottom(); 873 } 874 return maxHeight; 875 876 } 877 878 /** 879 * A utility method for determining the width of the widest tick label. 880 * 881 * @param ticks the ticks. 882 * @param g2 the graphics device. 883 * @param drawArea the area within which the plot and axes should be drawn. 884 * @param vertical a flag that indicates whether or not the tick labels 885 * are 'vertical'. 886 * 887 * @return The width of the tallest tick label. 888 */ 889 protected double findMaximumTickLabelWidth(List ticks, 890 Graphics2D g2, 891 Rectangle2D drawArea, 892 boolean vertical) { 893 894 RectangleInsets insets = getTickLabelInsets(); 895 Font font = getTickLabelFont(); 896 double maxWidth = 0.0; 897 if (!vertical) { 898 FontMetrics fm = g2.getFontMetrics(font); 899 Iterator iterator = ticks.iterator(); 900 while (iterator.hasNext()) { 901 Tick tick = (Tick) iterator.next(); 902 Rectangle2D labelBounds = TextUtilities.getTextBounds( 903 tick.getText(), g2, fm); 904 if (labelBounds.getWidth() + insets.getLeft() 905 + insets.getRight() > maxWidth) { 906 maxWidth = labelBounds.getWidth() 907 + insets.getLeft() + insets.getRight(); 908 } 909 } 910 } 911 else { 912 LineMetrics metrics = font.getLineMetrics("ABCxyz", 913 g2.getFontRenderContext()); 914 maxWidth = metrics.getHeight() 915 + insets.getTop() + insets.getBottom(); 916 } 917 return maxWidth; 918 919 } 920 921 /** 922 * Returns a flag that controls the direction of values on the axis. 923 * <P> 924 * For a regular axis, values increase from left to right (for a horizontal 925 * axis) and bottom to top (for a vertical axis). When the axis is 926 * 'inverted', the values increase in the opposite direction. 927 * 928 * @return The flag. 929 * 930 * @see #setInverted(boolean) 931 */ 932 public boolean isInverted() { 933 return this.inverted; 934 } 935 936 /** 937 * Sets a flag that controls the direction of values on the axis, and 938 * notifies registered listeners that the axis has changed. 939 * 940 * @param flag the flag. 941 * 942 * @see #isInverted() 943 */ 944 public void setInverted(boolean flag) { 945 946 if (this.inverted != flag) { 947 this.inverted = flag; 948 notifyListeners(new AxisChangeEvent(this)); 949 } 950 951 } 952 953 /** 954 * Returns the flag that controls whether or not the axis range is 955 * automatically adjusted to fit the data values. 956 * 957 * @return The flag. 958 * 959 * @see #setAutoRange(boolean) 960 */ 961 public boolean isAutoRange() { 962 return this.autoRange; 963 } 964 965 /** 966 * Sets a flag that determines whether or not the axis range is 967 * automatically adjusted to fit the data, and notifies registered 968 * listeners that the axis has been modified. 969 * 970 * @param auto the new value of the flag. 971 * 972 * @see #isAutoRange() 973 */ 974 public void setAutoRange(boolean auto) { 975 setAutoRange(auto, true); 976 } 977 978 /** 979 * Sets the auto range attribute. If the <code>notify</code> flag is set, 980 * an {@link AxisChangeEvent} is sent to registered listeners. 981 * 982 * @param auto the flag. 983 * @param notify notify listeners? 984 * 985 * @see #isAutoRange() 986 */ 987 protected void setAutoRange(boolean auto, boolean notify) { 988 if (this.autoRange != auto) { 989 this.autoRange = auto; 990 if (this.autoRange) { 991 autoAdjustRange(); 992 } 993 if (notify) { 994 notifyListeners(new AxisChangeEvent(this)); 995 } 996 } 997 } 998 999 /** 1000 * Returns the minimum size allowed for the axis range when it is 1001 * automatically calculated. 1002 * 1003 * @return The minimum range. 1004 * 1005 * @see #setAutoRangeMinimumSize(double) 1006 */ 1007 public double getAutoRangeMinimumSize() { 1008 return this.autoRangeMinimumSize; 1009 } 1010 1011 /** 1012 * Sets the auto range minimum size and sends an {@link AxisChangeEvent} 1013 * to all registered listeners. 1014 * 1015 * @param size the size. 1016 * 1017 * @see #getAutoRangeMinimumSize() 1018 */ 1019 public void setAutoRangeMinimumSize(double size) { 1020 setAutoRangeMinimumSize(size, true); 1021 } 1022 1023 /** 1024 * Sets the minimum size allowed for the axis range when it is 1025 * automatically calculated. 1026 * <p> 1027 * If requested, an {@link AxisChangeEvent} is forwarded to all registered 1028 * listeners. 1029 * 1030 * @param size the new minimum. 1031 * @param notify notify listeners? 1032 */ 1033 public void setAutoRangeMinimumSize(double size, boolean notify) { 1034 if (size <= 0.0) { 1035 throw new IllegalArgumentException( 1036 "NumberAxis.setAutoRangeMinimumSize(double): must be > 0.0."); 1037 } 1038 if (this.autoRangeMinimumSize != size) { 1039 this.autoRangeMinimumSize = size; 1040 if (this.autoRange) { 1041 autoAdjustRange(); 1042 } 1043 if (notify) { 1044 notifyListeners(new AxisChangeEvent(this)); 1045 } 1046 } 1047 1048 } 1049 1050 /** 1051 * Returns the default auto range. 1052 * 1053 * @return The default auto range (never <code>null</code>). 1054 * 1055 * @see #setDefaultAutoRange(Range) 1056 * 1057 * @since 1.0.5 1058 */ 1059 public Range getDefaultAutoRange() { 1060 return this.defaultAutoRange; 1061 } 1062 1063 /** 1064 * Sets the default auto range and sends an {@link AxisChangeEvent} to all 1065 * registered listeners. 1066 * 1067 * @param range the range (<code>null</code> not permitted). 1068 * 1069 * @see #getDefaultAutoRange() 1070 * 1071 * @since 1.0.5 1072 */ 1073 public void setDefaultAutoRange(Range range) { 1074 if (range == null) { 1075 throw new IllegalArgumentException("Null 'range' argument."); 1076 } 1077 this.defaultAutoRange = range; 1078 notifyListeners(new AxisChangeEvent(this)); 1079 } 1080 1081 /** 1082 * Returns the lower margin for the axis, expressed as a percentage of the 1083 * axis range. This controls the space added to the lower end of the axis 1084 * when the axis range is automatically calculated (it is ignored when the 1085 * axis range is set explicitly). The default value is 0.05 (five percent). 1086 * 1087 * @return The lower margin. 1088 * 1089 * @see #setLowerMargin(double) 1090 */ 1091 public double getLowerMargin() { 1092 return this.lowerMargin; 1093 } 1094 1095 /** 1096 * Sets the lower margin for the axis (as a percentage of the axis range) 1097 * and sends an {@link AxisChangeEvent} to all registered listeners. This 1098 * margin is added only when the axis range is auto-calculated - if you set 1099 * the axis range manually, the margin is ignored. 1100 * 1101 * @param margin the margin percentage (for example, 0.05 is five percent). 1102 * 1103 * @see #getLowerMargin() 1104 * @see #setUpperMargin(double) 1105 */ 1106 public void setLowerMargin(double margin) { 1107 this.lowerMargin = margin; 1108 if (isAutoRange()) { 1109 autoAdjustRange(); 1110 } 1111 notifyListeners(new AxisChangeEvent(this)); 1112 } 1113 1114 /** 1115 * Returns the upper margin for the axis, expressed as a percentage of the 1116 * axis range. This controls the space added to the lower end of the axis 1117 * when the axis range is automatically calculated (it is ignored when the 1118 * axis range is set explicitly). The default value is 0.05 (five percent). 1119 * 1120 * @return The upper margin. 1121 * 1122 * @see #setUpperMargin(double) 1123 */ 1124 public double getUpperMargin() { 1125 return this.upperMargin; 1126 } 1127 1128 /** 1129 * Sets the upper margin for the axis (as a percentage of the axis range) 1130 * and sends an {@link AxisChangeEvent} to all registered listeners. This 1131 * margin is added only when the axis range is auto-calculated - if you set 1132 * the axis range manually, the margin is ignored. 1133 * 1134 * @param margin the margin percentage (for example, 0.05 is five percent). 1135 * 1136 * @see #getLowerMargin() 1137 * @see #setLowerMargin(double) 1138 */ 1139 public void setUpperMargin(double margin) { 1140 this.upperMargin = margin; 1141 if (isAutoRange()) { 1142 autoAdjustRange(); 1143 } 1144 notifyListeners(new AxisChangeEvent(this)); 1145 } 1146 1147 /** 1148 * Returns the fixed auto range. 1149 * 1150 * @return The length. 1151 * 1152 * @see #setFixedAutoRange(double) 1153 */ 1154 public double getFixedAutoRange() { 1155 return this.fixedAutoRange; 1156 } 1157 1158 /** 1159 * Sets the fixed auto range for the axis. 1160 * 1161 * @param length the range length. 1162 * 1163 * @see #getFixedAutoRange() 1164 */ 1165 public void setFixedAutoRange(double length) { 1166 this.fixedAutoRange = length; 1167 if (isAutoRange()) { 1168 autoAdjustRange(); 1169 } 1170 notifyListeners(new AxisChangeEvent(this)); 1171 } 1172 1173 /** 1174 * Returns the lower bound of the axis range. 1175 * 1176 * @return The lower bound. 1177 * 1178 * @see #setLowerBound(double) 1179 */ 1180 public double getLowerBound() { 1181 return this.range.getLowerBound(); 1182 } 1183 1184 /** 1185 * Sets the lower bound for the axis range. An {@link AxisChangeEvent} is 1186 * sent to all registered listeners. 1187 * 1188 * @param min the new minimum. 1189 * 1190 * @see #getLowerBound() 1191 */ 1192 public void setLowerBound(double min) { 1193 if (this.range.getUpperBound() > min) { 1194 setRange(new Range(min, this.range.getUpperBound())); 1195 } 1196 else { 1197 setRange(new Range(min, min + 1.0)); 1198 } 1199 } 1200 1201 /** 1202 * Returns the upper bound for the axis range. 1203 * 1204 * @return The upper bound. 1205 * 1206 * @see #setUpperBound(double) 1207 */ 1208 public double getUpperBound() { 1209 return this.range.getUpperBound(); 1210 } 1211 1212 /** 1213 * Sets the upper bound for the axis range, and sends an 1214 * {@link AxisChangeEvent} to all registered listeners. 1215 * 1216 * @param max the new maximum. 1217 * 1218 * @see #getUpperBound() 1219 */ 1220 public void setUpperBound(double max) { 1221 if (this.range.getLowerBound() < max) { 1222 setRange(new Range(this.range.getLowerBound(), max)); 1223 } 1224 else { 1225 setRange(max - 1.0, max); 1226 } 1227 } 1228 1229 /** 1230 * Returns the range for the axis. 1231 * 1232 * @return The axis range (never <code>null</code>). 1233 * 1234 * @see #setRange(Range) 1235 */ 1236 public Range getRange() { 1237 return this.range; 1238 } 1239 1240 /** 1241 * Sets the range attribute and sends an {@link AxisChangeEvent} to all 1242 * registered listeners. As a side-effect, the auto-range flag is set to 1243 * <code>false</code>. 1244 * 1245 * @param range the range (<code>null</code> not permitted). 1246 * 1247 * @see #getRange() 1248 */ 1249 public void setRange(Range range) { 1250 // defer argument checking 1251 setRange(range, true, true); 1252 } 1253 1254 /** 1255 * Sets the range for the axis, if requested, sends an 1256 * {@link AxisChangeEvent} to all registered listeners. As a side-effect, 1257 * the auto-range flag is set to <code>false</code> (optional). 1258 * 1259 * @param range the range (<code>null</code> not permitted). 1260 * @param turnOffAutoRange a flag that controls whether or not the auto 1261 * range is turned off. 1262 * @param notify a flag that controls whether or not listeners are 1263 * notified. 1264 * 1265 * @see #getRange() 1266 */ 1267 public void setRange(Range range, boolean turnOffAutoRange, 1268 boolean notify) { 1269 if (range == null) { 1270 throw new IllegalArgumentException("Null 'range' argument."); 1271 } 1272 if (turnOffAutoRange) { 1273 this.autoRange = false; 1274 } 1275 this.range = range; 1276 if (notify) { 1277 notifyListeners(new AxisChangeEvent(this)); 1278 } 1279 } 1280 1281 /** 1282 * Sets the axis range and sends an {@link AxisChangeEvent} to all 1283 * registered listeners. As a side-effect, the auto-range flag is set to 1284 * <code>false</code>. 1285 * 1286 * @param lower the lower axis limit. 1287 * @param upper the upper axis limit. 1288 * 1289 * @see #getRange() 1290 * @see #setRange(Range) 1291 */ 1292 public void setRange(double lower, double upper) { 1293 setRange(new Range(lower, upper)); 1294 } 1295 1296 /** 1297 * Sets the range for the axis (after first adding the current margins to 1298 * the specified range) and sends an {@link AxisChangeEvent} to all 1299 * registered listeners. 1300 * 1301 * @param range the range (<code>null</code> not permitted). 1302 */ 1303 public void setRangeWithMargins(Range range) { 1304 setRangeWithMargins(range, true, true); 1305 } 1306 1307 /** 1308 * Sets the range for the axis after first adding the current margins to 1309 * the range and, if requested, sends an {@link AxisChangeEvent} to all 1310 * registered listeners. As a side-effect, the auto-range flag is set to 1311 * <code>false</code> (optional). 1312 * 1313 * @param range the range (excluding margins, <code>null</code> not 1314 * permitted). 1315 * @param turnOffAutoRange a flag that controls whether or not the auto 1316 * range is turned off. 1317 * @param notify a flag that controls whether or not listeners are 1318 * notified. 1319 */ 1320 public void setRangeWithMargins(Range range, boolean turnOffAutoRange, 1321 boolean notify) { 1322 if (range == null) { 1323 throw new IllegalArgumentException("Null 'range' argument."); 1324 } 1325 setRange(Range.expand(range, getLowerMargin(), getUpperMargin()), 1326 turnOffAutoRange, notify); 1327 } 1328 1329 /** 1330 * Sets the axis range (after first adding the current margins to the 1331 * range) and sends an {@link AxisChangeEvent} to all registered listeners. 1332 * As a side-effect, the auto-range flag is set to <code>false</code>. 1333 * 1334 * @param lower the lower axis limit. 1335 * @param upper the upper axis limit. 1336 */ 1337 public void setRangeWithMargins(double lower, double upper) { 1338 setRangeWithMargins(new Range(lower, upper)); 1339 } 1340 1341 /** 1342 * Sets the axis range, where the new range is 'size' in length, and 1343 * centered on 'value'. 1344 * 1345 * @param value the central value. 1346 * @param length the range length. 1347 */ 1348 public void setRangeAboutValue(double value, double length) { 1349 setRange(new Range(value - length / 2, value + length / 2)); 1350 } 1351 1352 /** 1353 * Returns a flag indicating whether or not the tick unit is automatically 1354 * selected from a range of standard tick units. 1355 * 1356 * @return A flag indicating whether or not the tick unit is automatically 1357 * selected. 1358 * 1359 * @see #setAutoTickUnitSelection(boolean) 1360 */ 1361 public boolean isAutoTickUnitSelection() { 1362 return this.autoTickUnitSelection; 1363 } 1364 1365 /** 1366 * Sets a flag indicating whether or not the tick unit is automatically 1367 * selected from a range of standard tick units. If the flag is changed, 1368 * registered listeners are notified that the chart has changed. 1369 * 1370 * @param flag the new value of the flag. 1371 * 1372 * @see #isAutoTickUnitSelection() 1373 */ 1374 public void setAutoTickUnitSelection(boolean flag) { 1375 setAutoTickUnitSelection(flag, true); 1376 } 1377 1378 /** 1379 * Sets a flag indicating whether or not the tick unit is automatically 1380 * selected from a range of standard tick units. 1381 * 1382 * @param flag the new value of the flag. 1383 * @param notify notify listeners? 1384 * 1385 * @see #isAutoTickUnitSelection() 1386 */ 1387 public void setAutoTickUnitSelection(boolean flag, boolean notify) { 1388 1389 if (this.autoTickUnitSelection != flag) { 1390 this.autoTickUnitSelection = flag; 1391 if (notify) { 1392 notifyListeners(new AxisChangeEvent(this)); 1393 } 1394 } 1395 } 1396 1397 /** 1398 * Returns the source for obtaining standard tick units for the axis. 1399 * 1400 * @return The source (possibly <code>null</code>). 1401 * 1402 * @see #setStandardTickUnits(TickUnitSource) 1403 */ 1404 public TickUnitSource getStandardTickUnits() { 1405 return this.standardTickUnits; 1406 } 1407 1408 /** 1409 * Sets the source for obtaining standard tick units for the axis and sends 1410 * an {@link AxisChangeEvent} to all registered listeners. The axis will 1411 * try to select the smallest tick unit from the source that does not cause 1412 * the tick labels to overlap (see also the 1413 * {@link #setAutoTickUnitSelection(boolean)} method. 1414 * 1415 * @param source the source for standard tick units (<code>null</code> 1416 * permitted). 1417 * 1418 * @see #getStandardTickUnits() 1419 */ 1420 public void setStandardTickUnits(TickUnitSource source) { 1421 this.standardTickUnits = source; 1422 notifyListeners(new AxisChangeEvent(this)); 1423 } 1424 1425 /** 1426 * Returns the number of minor tick marks to display. 1427 * 1428 * @return The number of minor tick marks to display. 1429 * 1430 * @see #setMinorTickCount(int) 1431 * 1432 * @since 1.0.12 1433 */ 1434 public int getMinorTickCount() { 1435 return this.minorTickCount; 1436 } 1437 1438 /** 1439 * Sets the number of minor tick marks to display, and sends an 1440 * {@link AxisChangeEvent} to all registered listeners. 1441 * 1442 * @param count the count. 1443 * 1444 * @see #getMinorTickCount() 1445 * 1446 * @since 1.0.12 1447 */ 1448 public void setMinorTickCount(int count) { 1449 this.minorTickCount = count; 1450 notifyListeners(new AxisChangeEvent(this)); 1451 } 1452 1453 /** 1454 * Converts a data value to a coordinate in Java2D space, assuming that the 1455 * axis runs along one edge of the specified dataArea. 1456 * <p> 1457 * Note that it is possible for the coordinate to fall outside the area. 1458 * 1459 * @param value the data value. 1460 * @param area the area for plotting the data. 1461 * @param edge the edge along which the axis lies. 1462 * 1463 * @return The Java2D coordinate. 1464 * 1465 * @see #java2DToValue(double, Rectangle2D, RectangleEdge) 1466 */ 1467 public abstract double valueToJava2D(double value, Rectangle2D area, 1468 RectangleEdge edge); 1469 1470 /** 1471 * Converts a length in data coordinates into the corresponding length in 1472 * Java2D coordinates. 1473 * 1474 * @param length the length. 1475 * @param area the plot area. 1476 * @param edge the edge along which the axis lies. 1477 * 1478 * @return The length in Java2D coordinates. 1479 */ 1480 public double lengthToJava2D(double length, Rectangle2D area, 1481 RectangleEdge edge) { 1482 double zero = valueToJava2D(0.0, area, edge); 1483 double l = valueToJava2D(length, area, edge); 1484 return Math.abs(l - zero); 1485 } 1486 1487 /** 1488 * Converts a coordinate in Java2D space to the corresponding data value, 1489 * assuming that the axis runs along one edge of the specified dataArea. 1490 * 1491 * @param java2DValue the coordinate in Java2D space. 1492 * @param area the area in which the data is plotted. 1493 * @param edge the edge along which the axis lies. 1494 * 1495 * @return The data value. 1496 * 1497 * @see #valueToJava2D(double, Rectangle2D, RectangleEdge) 1498 */ 1499 public abstract double java2DToValue(double java2DValue, 1500 Rectangle2D area, 1501 RectangleEdge edge); 1502 1503 /** 1504 * Automatically sets the axis range to fit the range of values in the 1505 * dataset. Sometimes this can depend on the renderer used as well (for 1506 * example, the renderer may "stack" values, requiring an axis range 1507 * greater than otherwise necessary). 1508 */ 1509 protected abstract void autoAdjustRange(); 1510 1511 /** 1512 * Centers the axis range about the specified value and sends an 1513 * {@link AxisChangeEvent} to all registered listeners. 1514 * 1515 * @param value the center value. 1516 */ 1517 public void centerRange(double value) { 1518 1519 double central = this.range.getCentralValue(); 1520 Range adjusted = new Range(this.range.getLowerBound() + value - central, 1521 this.range.getUpperBound() + value - central); 1522 setRange(adjusted); 1523 1524 } 1525 1526 /** 1527 * Increases or decreases the axis range by the specified percentage about 1528 * the central value and sends an {@link AxisChangeEvent} to all registered 1529 * listeners. 1530 * <P> 1531 * To double the length of the axis range, use 200% (2.0). 1532 * To halve the length of the axis range, use 50% (0.5). 1533 * 1534 * @param percent the resize factor. 1535 * 1536 * @see #resizeRange(double, double) 1537 */ 1538 public void resizeRange(double percent) { 1539 resizeRange(percent, this.range.getCentralValue()); 1540 } 1541 1542 /** 1543 * Increases or decreases the axis range by the specified percentage about 1544 * the specified anchor value and sends an {@link AxisChangeEvent} to all 1545 * registered listeners. 1546 * <P> 1547 * To double the length of the axis range, use 200% (2.0). 1548 * To halve the length of the axis range, use 50% (0.5). 1549 * 1550 * @param percent the resize factor. 1551 * @param anchorValue the new central value after the resize. 1552 * 1553 * @see #resizeRange(double) 1554 */ 1555 public void resizeRange(double percent, double anchorValue) { 1556 if (percent > 0.0) { 1557 double halfLength = this.range.getLength() * percent / 2; 1558 Range adjusted = new Range(anchorValue - halfLength, 1559 anchorValue + halfLength); 1560 setRange(adjusted); 1561 } 1562 else { 1563 setAutoRange(true); 1564 } 1565 } 1566 1567 /** 1568 * Increases or decreases the axis range by the specified percentage about 1569 * the specified anchor value and sends an {@link AxisChangeEvent} to all 1570 * registered listeners. 1571 * <P> 1572 * To double the length of the axis range, use 200% (2.0). 1573 * To halve the length of the axis range, use 50% (0.5). 1574 * 1575 * @param percent the resize factor. 1576 * @param anchorValue the new central value after the resize. 1577 * 1578 * @see #resizeRange(double) 1579 * 1580 * @since 1.0.13 1581 */ 1582 public void resizeRange2(double percent, double anchorValue) { 1583 if (percent > 0.0) { 1584 double left = anchorValue - getLowerBound(); 1585 double right = getUpperBound() - anchorValue; 1586 Range adjusted = new Range(anchorValue - left * percent, 1587 anchorValue + right * percent); 1588 setRange(adjusted); 1589 } 1590 else { 1591 setAutoRange(true); 1592 } 1593 } 1594 1595 /** 1596 * Zooms in on the current range. 1597 * 1598 * @param lowerPercent the new lower bound. 1599 * @param upperPercent the new upper bound. 1600 */ 1601 public void zoomRange(double lowerPercent, double upperPercent) { 1602 double start = this.range.getLowerBound(); 1603 double length = this.range.getLength(); 1604 Range adjusted = null; 1605 if (isInverted()) { 1606 adjusted = new Range(start + (length * (1 - upperPercent)), 1607 start + (length * (1 - lowerPercent))); 1608 } 1609 else { 1610 adjusted = new Range(start + length * lowerPercent, 1611 start + length * upperPercent); 1612 } 1613 setRange(adjusted); 1614 } 1615 1616 /** 1617 * Slides the axis range by the specified percentage. 1618 * 1619 * @param percent the percentage. 1620 * 1621 * @since 1.0.13 1622 */ 1623 public void pan(double percent) { 1624 Range range = getRange(); 1625 double length = range.getLength(); 1626 double adj = length * percent; 1627 double lower = range.getLowerBound() + adj; 1628 double upper = range.getUpperBound() + adj; 1629 setRange(lower, upper); 1630 } 1631 1632 /** 1633 * Returns the auto tick index. 1634 * 1635 * @return The auto tick index. 1636 * 1637 * @see #setAutoTickIndex(int) 1638 */ 1639 protected int getAutoTickIndex() { 1640 return this.autoTickIndex; 1641 } 1642 1643 /** 1644 * Sets the auto tick index. 1645 * 1646 * @param index the new value. 1647 * 1648 * @see #getAutoTickIndex() 1649 */ 1650 protected void setAutoTickIndex(int index) { 1651 this.autoTickIndex = index; 1652 } 1653 1654 /** 1655 * Tests the axis for equality with an arbitrary object. 1656 * 1657 * @param obj the object (<code>null</code> permitted). 1658 * 1659 * @return <code>true</code> or <code>false</code>. 1660 */ 1661 public boolean equals(Object obj) { 1662 if (obj == this) { 1663 return true; 1664 } 1665 if (!(obj instanceof ValueAxis)) { 1666 return false; 1667 } 1668 ValueAxis that = (ValueAxis) obj; 1669 if (this.positiveArrowVisible != that.positiveArrowVisible) { 1670 return false; 1671 } 1672 if (this.negativeArrowVisible != that.negativeArrowVisible) { 1673 return false; 1674 } 1675 if (this.inverted != that.inverted) { 1676 return false; 1677 } 1678 // if autoRange is true, then the current range is irrelevant 1679 if (!this.autoRange && !ObjectUtilities.equal(this.range, that.range)) { 1680 return false; 1681 } 1682 if (this.autoRange != that.autoRange) { 1683 return false; 1684 } 1685 if (this.autoRangeMinimumSize != that.autoRangeMinimumSize) { 1686 return false; 1687 } 1688 if (!this.defaultAutoRange.equals(that.defaultAutoRange)) { 1689 return false; 1690 } 1691 if (this.upperMargin != that.upperMargin) { 1692 return false; 1693 } 1694 if (this.lowerMargin != that.lowerMargin) { 1695 return false; 1696 } 1697 if (this.fixedAutoRange != that.fixedAutoRange) { 1698 return false; 1699 } 1700 if (this.autoTickUnitSelection != that.autoTickUnitSelection) { 1701 return false; 1702 } 1703 if (!ObjectUtilities.equal(this.standardTickUnits, 1704 that.standardTickUnits)) { 1705 return false; 1706 } 1707 if (this.verticalTickLabels != that.verticalTickLabels) { 1708 return false; 1709 } 1710 if (this.minorTickCount != that.minorTickCount) { 1711 return false; 1712 } 1713 return super.equals(obj); 1714 } 1715 1716 /** 1717 * Returns a clone of the object. 1718 * 1719 * @return A clone. 1720 * 1721 * @throws CloneNotSupportedException if some component of the axis does 1722 * not support cloning. 1723 */ 1724 public Object clone() throws CloneNotSupportedException { 1725 ValueAxis clone = (ValueAxis) super.clone(); 1726 return clone; 1727 } 1728 1729 /** 1730 * Provides serialization support. 1731 * 1732 * @param stream the output stream. 1733 * 1734 * @throws IOException if there is an I/O error. 1735 */ 1736 private void writeObject(ObjectOutputStream stream) throws IOException { 1737 stream.defaultWriteObject(); 1738 SerialUtilities.writeShape(this.upArrow, stream); 1739 SerialUtilities.writeShape(this.downArrow, stream); 1740 SerialUtilities.writeShape(this.leftArrow, stream); 1741 SerialUtilities.writeShape(this.rightArrow, stream); 1742 } 1743 1744 /** 1745 * Provides serialization support. 1746 * 1747 * @param stream the input stream. 1748 * 1749 * @throws IOException if there is an I/O error. 1750 * @throws ClassNotFoundException if there is a classpath problem. 1751 */ 1752 private void readObject(ObjectInputStream stream) 1753 throws IOException, ClassNotFoundException { 1754 1755 stream.defaultReadObject(); 1756 this.upArrow = SerialUtilities.readShape(stream); 1757 this.downArrow = SerialUtilities.readShape(stream); 1758 this.leftArrow = SerialUtilities.readShape(stream); 1759 this.rightArrow = SerialUtilities.readShape(stream); 1760 } 1761 1762 }