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