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