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