Frames | No Frames |
1: /* =========================================================== 2: * JFreeChart : a free chart library for the Java(tm) platform 3: * =========================================================== 4: * 5: * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors. 6: * 7: * Project Info: http://www.jfree.org/jfreechart/index.html 8: * 9: * This library is free software; you can redistribute it and/or modify it 10: * under the terms of the GNU Lesser General Public License as published by 11: * the Free Software Foundation; either version 2.1 of the License, or 12: * (at your option) any later version. 13: * 14: * This library is distributed in the hope that it will be useful, but 15: * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 16: * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 17: * License for more details. 18: * 19: * You should have received a copy of the GNU Lesser General Public 20: * License along with this library; if not, write to the Free Software 21: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 22: * USA. 23: * 24: * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 25: * in the United States and other countries.] 26: * 27: * -------------- 28: * ValueAxis.java 29: * -------------- 30: * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors. 31: * 32: * Original Author: David Gilbert (for Object Refinery Limited); 33: * Contributor(s): Jonathan Nash; 34: * Nicolas Brodu (for Astrium and EADS Corporate Research 35: * Center); 36: * 37: * $Id: ValueAxis.java,v 1.10.2.1 2005/10/25 20:37:34 mungady Exp $ 38: * 39: * Changes (from 18-Sep-2001) 40: * -------------------------- 41: * 18-Sep-2001 : Added standard header and fixed DOS encoding problem (DG); 42: * 23-Nov-2001 : Overhauled standard tick unit code (DG); 43: * 04-Dec-2001 : Changed constructors to protected, and tidied up default 44: * values (DG); 45: * 12-Dec-2001 : Fixed vertical gridlines bug (DG); 46: * 16-Jan-2002 : Added an optional crosshair, based on the implementation by 47: * Jonathan Nash (DG); 48: * 23-Jan-2002 : Moved the minimum and maximum values to here from NumberAxis, 49: * and changed the type from Number to double (DG); 50: * 25-Feb-2002 : Added default value for autoRange. Changed autoAdjustRange 51: * from public to protected. Updated import statements (DG); 52: * 23-Apr-2002 : Added setRange() method (DG); 53: * 29-Apr-2002 : Added range adjustment methods (DG); 54: * 13-Jun-2002 : Modified setCrosshairValue() to notify listeners only when the 55: * crosshairs are visible, to avoid unnecessary repaints, as 56: * suggested by Kees Kuip (DG); 57: * 25-Jul-2002 : Moved lower and upper margin attributes from the NumberAxis 58: * class (DG); 59: * 05-Sep-2002 : Updated constructor for changes in Axis class (DG); 60: * 01-Oct-2002 : Fixed errors reported by Checkstyle (DG); 61: * 04-Oct-2002 : Moved standardTickUnits from NumberAxis --> ValueAxis (DG); 62: * 08-Nov-2002 : Moved to new package com.jrefinery.chart.axis (DG); 63: * 19-Nov-2002 : Removed grid settings (now controlled by the plot) (DG); 64: * 27-Nov-2002 : Moved the 'inverted' attributed from NumberAxis to 65: * ValueAxis (DG); 66: * 03-Jan-2003 : Small fix to ensure auto-range minimum is observed 67: * immediately (DG); 68: * 14-Jan-2003 : Changed autoRangeMinimumSize from Number --> double (DG); 69: * 20-Jan-2003 : Replaced monolithic constructor (DG); 70: * 26-Mar-2003 : Implemented Serializable (DG); 71: * 09-May-2003 : Added AxisLocation parameter to translation methods (DG); 72: * 13-Aug-2003 : Implemented Cloneable (DG); 73: * 01-Sep-2003 : Fixed bug 793167 (setMaximumAxisValue exception) (DG); 74: * 02-Sep-2003 : Fixed bug 795366 (zooming on inverted axes) (DG); 75: * 08-Sep-2003 : Completed Serialization support (NB); 76: * 08-Sep-2003 : Renamed get/setMinimumValue --> get/setLowerBound, 77: * and get/setMaximumValue --> get/setUpperBound (DG); 78: * 27-Oct-2003 : Changed DEFAULT_AUTO_RANGE_MINIMUM_SIZE value - see bug ID 79: * 829606 (DG); 80: * 07-Nov-2003 : Changes to tick mechanism (DG); 81: * 06-Jan-2004 : Moved axis line attributes to Axis class (DG); 82: * 21-Jan-2004 : Removed redundant axisLineVisible attribute. Renamed 83: * translateJava2DToValue --> java2DToValue, and 84: * translateValueToJava2D --> valueToJava2D (DG); 85: * 23-Jan-2004 : Fixed setAxisLinePaint() and setAxisLineStroke() which had no 86: * effect (andreas.gawecki@coremedia.com); 87: * 07-Apr-2004 : Changed text bounds calculation (DG); 88: * 26-Apr-2004 : Added getter/setter methods for arrow shapes (DG); 89: * 18-May-2004 : Added methods to set axis range *including* current 90: * margins (DG); 91: * 02-Jun-2004 : Fixed bug in setRangeWithMargins() method (DG); 92: * 30-Sep-2004 : Moved drawRotatedString() from RefineryUtilities 93: * --> TextUtilities (DG); 94: * 11-Jan-2005 : Removed deprecated methods in preparation for 1.0.0 95: * release (DG); 96: * 21-Apr-2005 : Replaced Insets with RectangleInsets (DG); 97: * 98: */ 99: 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: }