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 published by 11: * the Free Software Foundation; either version 2.1 of the License, or 12: * (at your option) any later version. 13: * 14: * This library is distributed in the hope that it will be useful, but 15: * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 16: * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 17: * License for more details. 18: * 19: * You should have received a copy of the GNU Lesser General Public 20: * License along with this library; if not, write to the Free Software 21: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 22: * USA. 23: * 24: * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 25: * in the United States and other countries.] 26: * 27: * -------------- 28: * ValueAxis.java 29: * -------------- 30: * (C) Copyright 2000-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: * $Id: ValueAxis.java,v 1.10.2.5 2007/03/22 12:13:27 mungady Exp $ 38: * 39: * Changes (from 18-Sep-2001) 40: * -------------------------- 41: * 18-Sep-2001 : Added standard header and fixed DOS encoding problem (DG); 42: * 23-Nov-2001 : Overhauled standard tick unit code (DG); 43: * 04-Dec-2001 : Changed constructors to protected, and tidied up default 44: * values (DG); 45: * 12-Dec-2001 : Fixed vertical gridlines bug (DG); 46: * 16-Jan-2002 : Added an optional crosshair, based on the implementation by 47: * Jonathan Nash (DG); 48: * 23-Jan-2002 : Moved the minimum and maximum values to here from NumberAxis, 49: * and changed the type from Number to double (DG); 50: * 25-Feb-2002 : Added default value for autoRange. Changed autoAdjustRange 51: * from public to protected. Updated import statements (DG); 52: * 23-Apr-2002 : Added setRange() method (DG); 53: * 29-Apr-2002 : Added range adjustment methods (DG); 54: * 13-Jun-2002 : Modified setCrosshairValue() to notify listeners only when the 55: * crosshairs are visible, to avoid unnecessary repaints, as 56: * suggested by Kees Kuip (DG); 57: * 25-Jul-2002 : Moved lower and upper margin attributes from the NumberAxis 58: * class (DG); 59: * 05-Sep-2002 : Updated constructor for changes in Axis class (DG); 60: * 01-Oct-2002 : Fixed errors reported by Checkstyle (DG); 61: * 04-Oct-2002 : Moved standardTickUnits from NumberAxis --> ValueAxis (DG); 62: * 08-Nov-2002 : Moved to new package com.jrefinery.chart.axis (DG); 63: * 19-Nov-2002 : Removed grid settings (now controlled by the plot) (DG); 64: * 27-Nov-2002 : Moved the 'inverted' attributed from NumberAxis to 65: * ValueAxis (DG); 66: * 03-Jan-2003 : Small fix to ensure auto-range minimum is observed 67: * immediately (DG); 68: * 14-Jan-2003 : Changed autoRangeMinimumSize from Number --> double (DG); 69: * 20-Jan-2003 : Replaced monolithic constructor (DG); 70: * 26-Mar-2003 : Implemented Serializable (DG); 71: * 09-May-2003 : Added AxisLocation parameter to translation methods (DG); 72: * 13-Aug-2003 : Implemented Cloneable (DG); 73: * 01-Sep-2003 : Fixed bug 793167 (setMaximumAxisValue exception) (DG); 74: * 02-Sep-2003 : Fixed bug 795366 (zooming on inverted axes) (DG); 75: * 08-Sep-2003 : Completed Serialization support (NB); 76: * 08-Sep-2003 : Renamed get/setMinimumValue --> get/setLowerBound, 77: * and get/setMaximumValue --> get/setUpperBound (DG); 78: * 27-Oct-2003 : Changed DEFAULT_AUTO_RANGE_MINIMUM_SIZE value - see bug ID 79: * 829606 (DG); 80: * 07-Nov-2003 : Changes to tick mechanism (DG); 81: * 06-Jan-2004 : Moved axis line attributes to Axis class (DG); 82: * 21-Jan-2004 : Removed redundant axisLineVisible attribute. Renamed 83: * translateJava2DToValue --> java2DToValue, and 84: * translateValueToJava2D --> valueToJava2D (DG); 85: * 23-Jan-2004 : Fixed setAxisLinePaint() and setAxisLineStroke() which had no 86: * effect (andreas.gawecki@coremedia.com); 87: * 07-Apr-2004 : Changed text bounds calculation (DG); 88: * 26-Apr-2004 : Added getter/setter methods for arrow shapes (DG); 89: * 18-May-2004 : Added methods to set axis range *including* current 90: * margins (DG); 91: * 02-Jun-2004 : Fixed bug in setRangeWithMargins() method (DG); 92: * 30-Sep-2004 : Moved drawRotatedString() from RefineryUtilities 93: * --> TextUtilities (DG); 94: * 11-Jan-2005 : Removed deprecated methods in preparation for 1.0.0 95: * release (DG); 96: * 21-Apr-2005 : Replaced Insets with RectangleInsets (DG); 97: * ------------- JFREECHART 1.0.x --------------------------------------------- 98: * 10-Oct-2006 : Source reformatting (DG); 99: * 22-Mar-2007 : Added new defaultAutoRange attribute (DG); 100: * 101: */ 102: 103: package org.jfree.chart.axis; 104: 105: import java.awt.Font; 106: import java.awt.FontMetrics; 107: import java.awt.Graphics2D; 108: import java.awt.Polygon; 109: import java.awt.Shape; 110: import java.awt.font.LineMetrics; 111: import java.awt.geom.AffineTransform; 112: import java.awt.geom.Line2D; 113: import java.awt.geom.Rectangle2D; 114: import java.io.IOException; 115: import java.io.ObjectInputStream; 116: import java.io.ObjectOutputStream; 117: import java.io.Serializable; 118: import java.util.Iterator; 119: import java.util.List; 120: 121: import org.jfree.chart.event.AxisChangeEvent; 122: import org.jfree.chart.plot.Plot; 123: import org.jfree.data.Range; 124: import org.jfree.io.SerialUtilities; 125: import org.jfree.text.TextUtilities; 126: import org.jfree.ui.RectangleEdge; 127: import org.jfree.ui.RectangleInsets; 128: import org.jfree.util.ObjectUtilities; 129: import org.jfree.util.PublicCloneable; 130: 131: /** 132: * The base class for axes that display value data, where values are measured 133: * using the <code>double</code> primitive. The two key subclasses are 134: * {@link DateAxis} and {@link NumberAxis}. 135: */ 136: public abstract class ValueAxis extends Axis 137: implements Cloneable, PublicCloneable, 138: Serializable { 139: 140: /** For serialization. */ 141: private static final long serialVersionUID = 3698345477322391456L; 142: 143: /** The default axis range. */ 144: public static final Range DEFAULT_RANGE = new Range(0.0, 1.0); 145: 146: /** The default auto-range value. */ 147: public static final boolean DEFAULT_AUTO_RANGE = true; 148: 149: /** The default inverted flag setting. */ 150: public static final boolean DEFAULT_INVERTED = false; 151: 152: /** The default minimum auto range. */ 153: public static final double DEFAULT_AUTO_RANGE_MINIMUM_SIZE = 0.00000001; 154: 155: /** The default value for the lower margin (0.05 = 5%). */ 156: public static final double DEFAULT_LOWER_MARGIN = 0.05; 157: 158: /** The default value for the upper margin (0.05 = 5%). */ 159: public static final double DEFAULT_UPPER_MARGIN = 0.05; 160: 161: /** 162: * The default lower bound for the axis. 163: * 164: * @deprecated From 1.0.5 onwards, the axis defines a defaultRange 165: * attribute (see {@link #getDefaultAutoRange()}). 166: */ 167: public static final double DEFAULT_LOWER_BOUND = 0.0; 168: 169: /** 170: * The default upper bound for the axis. 171: * 172: * @deprecated From 1.0.5 onwards, the axis defines a defaultRange 173: * attribute (see {@link #getDefaultAutoRange()}). 174: */ 175: public static final double DEFAULT_UPPER_BOUND = 1.0; 176: 177: /** The default auto-tick-unit-selection value. */ 178: public static final boolean DEFAULT_AUTO_TICK_UNIT_SELECTION = true; 179: 180: /** The maximum tick count. */ 181: public static final int MAXIMUM_TICK_COUNT = 500; 182: 183: /** 184: * A flag that controls whether an arrow is drawn at the positive end of 185: * the axis line. 186: */ 187: private boolean positiveArrowVisible; 188: 189: /** 190: * A flag that controls whether an arrow is drawn at the negative end of 191: * the axis line. 192: */ 193: private boolean negativeArrowVisible; 194: 195: /** The shape used for an up arrow. */ 196: private transient Shape upArrow; 197: 198: /** The shape used for a down arrow. */ 199: private transient Shape downArrow; 200: 201: /** The shape used for a left arrow. */ 202: private transient Shape leftArrow; 203: 204: /** The shape used for a right arrow. */ 205: private transient Shape rightArrow; 206: 207: /** A flag that affects the orientation of the values on the axis. */ 208: private boolean inverted; 209: 210: /** The axis range. */ 211: private Range range; 212: 213: /** 214: * Flag that indicates whether the axis automatically scales to fit the 215: * chart data. 216: */ 217: private boolean autoRange; 218: 219: /** The minimum size for the 'auto' axis range (excluding margins). */ 220: private double autoRangeMinimumSize; 221: 222: /** 223: * The default range is used when the dataset is empty and the axis needs 224: * to determine the auto range. 225: * 226: * @since 1.0.5 227: */ 228: private Range defaultAutoRange; 229: 230: /** 231: * The upper margin percentage. This indicates the amount by which the 232: * maximum axis value exceeds the maximum data value (as a percentage of 233: * the range on the axis) when the axis range is determined automatically. 234: */ 235: private double upperMargin; 236: 237: /** 238: * The lower margin. This is a percentage that indicates the amount by 239: * which the minimum axis value is "less than" the minimum data value when 240: * the axis range is determined automatically. 241: */ 242: private double lowerMargin; 243: 244: /** 245: * If this value is positive, the amount is subtracted from the maximum 246: * data value to determine the lower axis range. This can be used to 247: * provide a fixed "window" on dynamic data. 248: */ 249: private double fixedAutoRange; 250: 251: /** 252: * Flag that indicates whether or not the tick unit is selected 253: * automatically. 254: */ 255: private boolean autoTickUnitSelection; 256: 257: /** The standard tick units for the axis. */ 258: private TickUnitSource standardTickUnits; 259: 260: /** An index into an array of standard tick values. */ 261: private int autoTickIndex; 262: 263: /** A flag indicating whether or not tick labels are rotated to vertical. */ 264: private boolean verticalTickLabels; 265: 266: /** 267: * Constructs a value axis. 268: * 269: * @param label the axis label (<code>null</code> permitted). 270: * @param standardTickUnits the source for standard tick units 271: * (<code>null</code> permitted). 272: */ 273: protected ValueAxis(String label, TickUnitSource standardTickUnits) { 274: 275: super(label); 276: 277: this.positiveArrowVisible = false; 278: this.negativeArrowVisible = false; 279: 280: this.range = DEFAULT_RANGE; 281: this.autoRange = DEFAULT_AUTO_RANGE; 282: this.defaultAutoRange = DEFAULT_RANGE; 283: 284: this.inverted = DEFAULT_INVERTED; 285: this.autoRangeMinimumSize = DEFAULT_AUTO_RANGE_MINIMUM_SIZE; 286: 287: this.lowerMargin = DEFAULT_LOWER_MARGIN; 288: this.upperMargin = DEFAULT_UPPER_MARGIN; 289: 290: this.fixedAutoRange = 0.0; 291: 292: this.autoTickUnitSelection = DEFAULT_AUTO_TICK_UNIT_SELECTION; 293: this.standardTickUnits = standardTickUnits; 294: 295: Polygon p1 = new Polygon(); 296: p1.addPoint(0, 0); 297: p1.addPoint(-2, 2); 298: p1.addPoint(2, 2); 299: 300: this.upArrow = p1; 301: 302: Polygon p2 = new Polygon(); 303: p2.addPoint(0, 0); 304: p2.addPoint(-2, -2); 305: p2.addPoint(2, -2); 306: 307: this.downArrow = p2; 308: 309: Polygon p3 = new Polygon(); 310: p3.addPoint(0, 0); 311: p3.addPoint(-2, -2); 312: p3.addPoint(-2, 2); 313: 314: this.rightArrow = p3; 315: 316: Polygon p4 = new Polygon(); 317: p4.addPoint(0, 0); 318: p4.addPoint(2, -2); 319: p4.addPoint(2, 2); 320: 321: this.leftArrow = p4; 322: 323: this.verticalTickLabels = false; 324: 325: } 326: 327: /** 328: * Returns <code>true</code> if the tick labels should be rotated (to 329: * vertical), and <code>false</code> otherwise. 330: * 331: * @return <code>true</code> or <code>false</code>. 332: * 333: * @see #setVerticalTickLabels(boolean) 334: */ 335: public boolean isVerticalTickLabels() { 336: return this.verticalTickLabels; 337: } 338: 339: /** 340: * Sets the flag that controls whether the tick labels are displayed 341: * vertically (that is, rotated 90 degrees from horizontal). If the flag 342: * is changed, an {@link AxisChangeEvent} is sent to all registered 343: * listeners. 344: * 345: * @param flag the flag. 346: * 347: * @see #isVerticalTickLabels() 348: */ 349: public void setVerticalTickLabels(boolean flag) { 350: if (this.verticalTickLabels != flag) { 351: this.verticalTickLabels = flag; 352: notifyListeners(new AxisChangeEvent(this)); 353: } 354: } 355: 356: /** 357: * Returns a flag that controls whether or not the axis line has an arrow 358: * drawn that points in the positive direction for the axis. 359: * 360: * @return A boolean. 361: * 362: * @see #setPositiveArrowVisible(boolean) 363: */ 364: public boolean isPositiveArrowVisible() { 365: return this.positiveArrowVisible; 366: } 367: 368: /** 369: * Sets a flag that controls whether or not the axis lines has an arrow 370: * drawn that points in the positive direction for the axis, and sends an 371: * {@link AxisChangeEvent} to all registered listeners. 372: * 373: * @param visible the flag. 374: * 375: * @see #isPositiveArrowVisible() 376: */ 377: public void setPositiveArrowVisible(boolean visible) { 378: this.positiveArrowVisible = visible; 379: notifyListeners(new AxisChangeEvent(this)); 380: } 381: 382: /** 383: * Returns a flag that controls whether or not the axis line has an arrow 384: * drawn that points in the negative direction for the axis. 385: * 386: * @return A boolean. 387: * 388: * @see #setNegativeArrowVisible(boolean) 389: */ 390: public boolean isNegativeArrowVisible() { 391: return this.negativeArrowVisible; 392: } 393: 394: /** 395: * Sets a flag that controls whether or not the axis lines has an arrow 396: * drawn that points in the negative direction for the axis, and sends an 397: * {@link AxisChangeEvent} to all registered listeners. 398: * 399: * @param visible the flag. 400: * 401: * @see #setNegativeArrowVisible(boolean) 402: */ 403: public void setNegativeArrowVisible(boolean visible) { 404: this.negativeArrowVisible = visible; 405: notifyListeners(new AxisChangeEvent(this)); 406: } 407: 408: /** 409: * Returns a shape that can be displayed as an arrow pointing upwards at 410: * the end of an axis line. 411: * 412: * @return A shape (never <code>null</code>). 413: * 414: * @see #setUpArrow(Shape) 415: */ 416: public Shape getUpArrow() { 417: return this.upArrow; 418: } 419: 420: /** 421: * Sets the shape that can be displayed as an arrow pointing upwards at 422: * the end of an axis line and sends an {@link AxisChangeEvent} to all 423: * registered listeners. 424: * 425: * @param arrow the arrow shape (<code>null</code> not permitted). 426: * 427: * @see #getUpArrow() 428: */ 429: public void setUpArrow(Shape arrow) { 430: if (arrow == null) { 431: throw new IllegalArgumentException("Null 'arrow' argument."); 432: } 433: this.upArrow = arrow; 434: notifyListeners(new AxisChangeEvent(this)); 435: } 436: 437: /** 438: * Returns a shape that can be displayed as an arrow pointing downwards at 439: * the end of an axis line. 440: * 441: * @return A shape (never <code>null</code>). 442: * 443: * @see #setDownArrow(Shape) 444: */ 445: public Shape getDownArrow() { 446: return this.downArrow; 447: } 448: 449: /** 450: * Sets the shape that can be displayed as an arrow pointing downwards at 451: * the end of an axis line and sends an {@link AxisChangeEvent} to all 452: * registered listeners. 453: * 454: * @param arrow the arrow shape (<code>null</code> not permitted). 455: * 456: * @see #getDownArrow() 457: */ 458: public void setDownArrow(Shape arrow) { 459: if (arrow == null) { 460: throw new IllegalArgumentException("Null 'arrow' argument."); 461: } 462: this.downArrow = arrow; 463: notifyListeners(new AxisChangeEvent(this)); 464: } 465: 466: /** 467: * Returns a shape that can be displayed as an arrow pointing left at the 468: * end of an axis line. 469: * 470: * @return A shape (never <code>null</code>). 471: * 472: * @see #setLeftArrow(Shape) 473: */ 474: public Shape getLeftArrow() { 475: return this.leftArrow; 476: } 477: 478: /** 479: * Sets the shape that can be displayed as an arrow pointing left at the 480: * end of an axis line and sends an {@link AxisChangeEvent} to all 481: * registered listeners. 482: * 483: * @param arrow the arrow shape (<code>null</code> not permitted). 484: * 485: * @see #getLeftArrow() 486: */ 487: public void setLeftArrow(Shape arrow) { 488: if (arrow == null) { 489: throw new IllegalArgumentException("Null 'arrow' argument."); 490: } 491: this.leftArrow = arrow; 492: notifyListeners(new AxisChangeEvent(this)); 493: } 494: 495: /** 496: * Returns a shape that can be displayed as an arrow pointing right at the 497: * end of an axis line. 498: * 499: * @return A shape (never <code>null</code>). 500: * 501: * @see #setRightArrow(Shape) 502: */ 503: public Shape getRightArrow() { 504: return this.rightArrow; 505: } 506: 507: /** 508: * Sets the shape that can be displayed as an arrow pointing rightwards at 509: * the end of an axis line and sends an {@link AxisChangeEvent} to all 510: * registered listeners. 511: * 512: * @param arrow the arrow shape (<code>null</code> not permitted). 513: * 514: * @see #getRightArrow() 515: */ 516: public void setRightArrow(Shape arrow) { 517: if (arrow == null) { 518: throw new IllegalArgumentException("Null 'arrow' argument."); 519: } 520: this.rightArrow = arrow; 521: notifyListeners(new AxisChangeEvent(this)); 522: } 523: 524: /** 525: * Draws an axis line at the current cursor position and edge. 526: * 527: * @param g2 the graphics device. 528: * @param cursor the cursor position. 529: * @param dataArea the data area. 530: * @param edge the edge. 531: */ 532: protected void drawAxisLine(Graphics2D g2, double cursor, 533: Rectangle2D dataArea, RectangleEdge edge) { 534: Line2D axisLine = null; 535: if (edge == RectangleEdge.TOP) { 536: axisLine = new Line2D.Double(dataArea.getX(), cursor, 537: dataArea.getMaxX(), cursor); 538: } 539: else if (edge == RectangleEdge.BOTTOM) { 540: axisLine = new Line2D.Double(dataArea.getX(), cursor, 541: dataArea.getMaxX(), cursor); 542: } 543: else if (edge == RectangleEdge.LEFT) { 544: axisLine = new Line2D.Double(cursor, dataArea.getY(), cursor, 545: dataArea.getMaxY()); 546: } 547: else if (edge == RectangleEdge.RIGHT) { 548: axisLine = new Line2D.Double(cursor, dataArea.getY(), cursor, 549: dataArea.getMaxY()); 550: } 551: g2.setPaint(getAxisLinePaint()); 552: g2.setStroke(getAxisLineStroke()); 553: g2.draw(axisLine); 554: 555: boolean drawUpOrRight = false; 556: boolean drawDownOrLeft = false; 557: if (this.positiveArrowVisible) { 558: if (this.inverted) { 559: drawDownOrLeft = true; 560: } 561: else { 562: drawUpOrRight = true; 563: } 564: } 565: if (this.negativeArrowVisible) { 566: if (this.inverted) { 567: drawUpOrRight = true; 568: } 569: else { 570: drawDownOrLeft = true; 571: } 572: } 573: if (drawUpOrRight) { 574: double x = 0.0; 575: double y = 0.0; 576: Shape arrow = null; 577: if (edge == RectangleEdge.TOP || edge == RectangleEdge.BOTTOM) { 578: x = dataArea.getMaxX(); 579: y = cursor; 580: arrow = this.rightArrow; 581: } 582: else if (edge == RectangleEdge.LEFT 583: || edge == RectangleEdge.RIGHT) { 584: x = cursor; 585: y = dataArea.getMinY(); 586: arrow = this.upArrow; 587: } 588: 589: // draw the arrow... 590: AffineTransform transformer = new AffineTransform(); 591: transformer.setToTranslation(x, y); 592: Shape shape = transformer.createTransformedShape(arrow); 593: g2.fill(shape); 594: g2.draw(shape); 595: } 596: 597: if (drawDownOrLeft) { 598: double x = 0.0; 599: double y = 0.0; 600: Shape arrow = null; 601: if (edge == RectangleEdge.TOP || edge == RectangleEdge.BOTTOM) { 602: x = dataArea.getMinX(); 603: y = cursor; 604: arrow = this.leftArrow; 605: } 606: else if (edge == RectangleEdge.LEFT 607: || edge == RectangleEdge.RIGHT) { 608: x = cursor; 609: y = dataArea.getMaxY(); 610: arrow = this.downArrow; 611: } 612: 613: // draw the arrow... 614: AffineTransform transformer = new AffineTransform(); 615: transformer.setToTranslation(x, y); 616: Shape shape = transformer.createTransformedShape(arrow); 617: g2.fill(shape); 618: g2.draw(shape); 619: } 620: 621: } 622: 623: /** 624: * Calculates the anchor point for a tick label. 625: * 626: * @param tick the tick. 627: * @param cursor the cursor. 628: * @param dataArea the data area. 629: * @param edge the edge on which the axis is drawn. 630: * 631: * @return The x and y coordinates of the anchor point. 632: */ 633: protected float[] calculateAnchorPoint(ValueTick tick, 634: double cursor, 635: Rectangle2D dataArea, 636: RectangleEdge edge) { 637: 638: RectangleInsets insets = getTickLabelInsets(); 639: float[] result = new float[2]; 640: if (edge == RectangleEdge.TOP) { 641: result[0] = (float) valueToJava2D(tick.getValue(), dataArea, edge); 642: result[1] = (float) (cursor - insets.getBottom() - 2.0); 643: } 644: else if (edge == RectangleEdge.BOTTOM) { 645: result[0] = (float) valueToJava2D(tick.getValue(), dataArea, edge); 646: result[1] = (float) (cursor + insets.getTop() + 2.0); 647: } 648: else if (edge == RectangleEdge.LEFT) { 649: result[0] = (float) (cursor - insets.getLeft() - 2.0); 650: result[1] = (float) valueToJava2D(tick.getValue(), dataArea, edge); 651: } 652: else if (edge == RectangleEdge.RIGHT) { 653: result[0] = (float) (cursor + insets.getRight() + 2.0); 654: result[1] = (float) valueToJava2D(tick.getValue(), dataArea, edge); 655: } 656: return result; 657: } 658: 659: /** 660: * Draws the axis line, tick marks and tick mark labels. 661: * 662: * @param g2 the graphics device. 663: * @param cursor the cursor. 664: * @param plotArea the plot area. 665: * @param dataArea the data area. 666: * @param edge the edge that the axis is aligned with. 667: * 668: * @return The width or height used to draw the axis. 669: */ 670: protected AxisState drawTickMarksAndLabels(Graphics2D g2, 671: double cursor, 672: Rectangle2D plotArea, 673: Rectangle2D dataArea, 674: RectangleEdge edge) { 675: 676: AxisState state = new AxisState(cursor); 677: 678: if (isAxisLineVisible()) { 679: drawAxisLine(g2, cursor, dataArea, edge); 680: } 681: 682: double ol = getTickMarkOutsideLength(); 683: double il = getTickMarkInsideLength(); 684: 685: List ticks = refreshTicks(g2, state, dataArea, edge); 686: state.setTicks(ticks); 687: g2.setFont(getTickLabelFont()); 688: Iterator iterator = ticks.iterator(); 689: while (iterator.hasNext()) { 690: ValueTick tick = (ValueTick) iterator.next(); 691: if (isTickLabelsVisible()) { 692: g2.setPaint(getTickLabelPaint()); 693: float[] anchorPoint = calculateAnchorPoint(tick, cursor, 694: dataArea, edge); 695: TextUtilities.drawRotatedString(tick.getText(), g2, 696: anchorPoint[0], anchorPoint[1], tick.getTextAnchor(), 697: tick.getAngle(), tick.getRotationAnchor()); 698: } 699: 700: if (isTickMarksVisible()) { 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: * @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: public void resizeRange(double percent) { 1489: resizeRange(percent, this.range.getCentralValue()); 1490: } 1491: 1492: /** 1493: * Increases or decreases the axis range by the specified percentage about 1494: * the specified anchor value and sends an {@link AxisChangeEvent} to all 1495: * registered listeners. 1496: * <P> 1497: * To double the length of the axis range, use 200% (2.0). 1498: * To halve the length of the axis range, use 50% (0.5). 1499: * 1500: * @param percent the resize factor. 1501: * @param anchorValue the new central value after the resize. 1502: */ 1503: public void resizeRange(double percent, double anchorValue) { 1504: if (percent > 0.0) { 1505: double halfLength = this.range.getLength() * percent / 2; 1506: Range adjusted = new Range(anchorValue - halfLength, 1507: anchorValue + halfLength); 1508: setRange(adjusted); 1509: } 1510: else { 1511: setAutoRange(true); 1512: } 1513: } 1514: 1515: /** 1516: * Zooms in on the current range. 1517: * 1518: * @param lowerPercent the new lower bound. 1519: * @param upperPercent the new upper bound. 1520: */ 1521: public void zoomRange(double lowerPercent, double upperPercent) { 1522: double start = this.range.getLowerBound(); 1523: double length = this.range.getLength(); 1524: Range adjusted = null; 1525: if (isInverted()) { 1526: adjusted = new Range(start + (length * (1 - upperPercent)), 1527: start + (length * (1 - lowerPercent))); 1528: } 1529: else { 1530: adjusted = new Range(start + length * lowerPercent, 1531: start + length * upperPercent); 1532: } 1533: setRange(adjusted); 1534: } 1535: 1536: /** 1537: * Returns the auto tick index. 1538: * 1539: * @return The auto tick index. 1540: * 1541: * @see #setAutoTickIndex(int) 1542: */ 1543: protected int getAutoTickIndex() { 1544: return this.autoTickIndex; 1545: } 1546: 1547: /** 1548: * Sets the auto tick index. 1549: * 1550: * @param index the new value. 1551: * 1552: * @see #getAutoTickIndex() 1553: */ 1554: protected void setAutoTickIndex(int index) { 1555: this.autoTickIndex = index; 1556: } 1557: 1558: /** 1559: * Tests the axis for equality with an arbitrary object. 1560: * 1561: * @param obj the object (<code>null</code> permitted). 1562: * 1563: * @return <code>true</code> or <code>false</code>. 1564: */ 1565: public boolean equals(Object obj) { 1566: 1567: if (obj == this) { 1568: return true; 1569: } 1570: if (!(obj instanceof ValueAxis)) { 1571: return false; 1572: } 1573: 1574: ValueAxis that = (ValueAxis) obj; 1575: 1576: if (this.positiveArrowVisible != that.positiveArrowVisible) { 1577: return false; 1578: } 1579: if (this.negativeArrowVisible != that.negativeArrowVisible) { 1580: return false; 1581: } 1582: if (this.inverted != that.inverted) { 1583: return false; 1584: } 1585: if (!ObjectUtilities.equal(this.range, that.range)) { 1586: return false; 1587: } 1588: if (this.autoRange != that.autoRange) { 1589: return false; 1590: } 1591: if (this.autoRangeMinimumSize != that.autoRangeMinimumSize) { 1592: return false; 1593: } 1594: if (!this.defaultAutoRange.equals(that.defaultAutoRange)) { 1595: return false; 1596: } 1597: if (this.upperMargin != that.upperMargin) { 1598: return false; 1599: } 1600: if (this.lowerMargin != that.lowerMargin) { 1601: return false; 1602: } 1603: if (this.fixedAutoRange != that.fixedAutoRange) { 1604: return false; 1605: } 1606: if (this.autoTickUnitSelection != that.autoTickUnitSelection) { 1607: return false; 1608: } 1609: if (!ObjectUtilities.equal(this.standardTickUnits, 1610: that.standardTickUnits)) { 1611: return false; 1612: } 1613: if (this.verticalTickLabels != that.verticalTickLabels) { 1614: return false; 1615: } 1616: 1617: return super.equals(obj); 1618: 1619: } 1620: 1621: /** 1622: * Returns a clone of the object. 1623: * 1624: * @return A clone. 1625: * 1626: * @throws CloneNotSupportedException if some component of the axis does 1627: * not support cloning. 1628: */ 1629: public Object clone() throws CloneNotSupportedException { 1630: ValueAxis clone = (ValueAxis) super.clone(); 1631: return clone; 1632: } 1633: 1634: /** 1635: * Provides serialization support. 1636: * 1637: * @param stream the output stream. 1638: * 1639: * @throws IOException if there is an I/O error. 1640: */ 1641: private void writeObject(ObjectOutputStream stream) throws IOException { 1642: stream.defaultWriteObject(); 1643: SerialUtilities.writeShape(this.upArrow, stream); 1644: SerialUtilities.writeShape(this.downArrow, stream); 1645: SerialUtilities.writeShape(this.leftArrow, stream); 1646: SerialUtilities.writeShape(this.rightArrow, stream); 1647: } 1648: 1649: /** 1650: * Provides serialization support. 1651: * 1652: * @param stream the input stream. 1653: * 1654: * @throws IOException if there is an I/O error. 1655: * @throws ClassNotFoundException if there is a classpath problem. 1656: */ 1657: private void readObject(ObjectInputStream stream) 1658: throws IOException, ClassNotFoundException { 1659: 1660: stream.defaultReadObject(); 1661: this.upArrow = SerialUtilities.readShape(stream); 1662: this.downArrow = SerialUtilities.readShape(stream); 1663: this.leftArrow = SerialUtilities.readShape(stream); 1664: this.rightArrow = SerialUtilities.readShape(stream); 1665: 1666: } 1667: 1668: }