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