001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors. 006 * 007 * Project Info: http://www.jfree.org/jfreechart/index.html 008 * 009 * This library is free software; you can redistribute it and/or modify it 010 * under the terms of the GNU Lesser General Public License as published by 011 * the Free Software Foundation; either version 2.1 of the License, or 012 * (at your option) any later version. 013 * 014 * This library is distributed in the hope that it will be useful, but 015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 017 * License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this library; if not, write to the Free Software 021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 022 * USA. 023 * 024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 025 * in the United States and other countries.] 026 * 027 * -------------- 028 * ValueAxis.java 029 * -------------- 030 * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): Jonathan Nash; 034 * Nicolas Brodu (for Astrium and EADS Corporate Research 035 * Center); 036 * 037 * $Id: ValueAxis.java,v 1.10.2.5 2007/03/22 12:13:27 mungady Exp $ 038 * 039 * Changes (from 18-Sep-2001) 040 * -------------------------- 041 * 18-Sep-2001 : Added standard header and fixed DOS encoding problem (DG); 042 * 23-Nov-2001 : Overhauled standard tick unit code (DG); 043 * 04-Dec-2001 : Changed constructors to protected, and tidied up default 044 * values (DG); 045 * 12-Dec-2001 : Fixed vertical gridlines bug (DG); 046 * 16-Jan-2002 : Added an optional crosshair, based on the implementation by 047 * Jonathan Nash (DG); 048 * 23-Jan-2002 : Moved the minimum and maximum values to here from NumberAxis, 049 * and changed the type from Number to double (DG); 050 * 25-Feb-2002 : Added default value for autoRange. Changed autoAdjustRange 051 * from public to protected. Updated import statements (DG); 052 * 23-Apr-2002 : Added setRange() method (DG); 053 * 29-Apr-2002 : Added range adjustment methods (DG); 054 * 13-Jun-2002 : Modified setCrosshairValue() to notify listeners only when the 055 * crosshairs are visible, to avoid unnecessary repaints, as 056 * suggested by Kees Kuip (DG); 057 * 25-Jul-2002 : Moved lower and upper margin attributes from the NumberAxis 058 * class (DG); 059 * 05-Sep-2002 : Updated constructor for changes in Axis class (DG); 060 * 01-Oct-2002 : Fixed errors reported by Checkstyle (DG); 061 * 04-Oct-2002 : Moved standardTickUnits from NumberAxis --> ValueAxis (DG); 062 * 08-Nov-2002 : Moved to new package com.jrefinery.chart.axis (DG); 063 * 19-Nov-2002 : Removed grid settings (now controlled by the plot) (DG); 064 * 27-Nov-2002 : Moved the 'inverted' attributed from NumberAxis to 065 * ValueAxis (DG); 066 * 03-Jan-2003 : Small fix to ensure auto-range minimum is observed 067 * immediately (DG); 068 * 14-Jan-2003 : Changed autoRangeMinimumSize from Number --> double (DG); 069 * 20-Jan-2003 : Replaced monolithic constructor (DG); 070 * 26-Mar-2003 : Implemented Serializable (DG); 071 * 09-May-2003 : Added AxisLocation parameter to translation methods (DG); 072 * 13-Aug-2003 : Implemented Cloneable (DG); 073 * 01-Sep-2003 : Fixed bug 793167 (setMaximumAxisValue exception) (DG); 074 * 02-Sep-2003 : Fixed bug 795366 (zooming on inverted axes) (DG); 075 * 08-Sep-2003 : Completed Serialization support (NB); 076 * 08-Sep-2003 : Renamed get/setMinimumValue --> get/setLowerBound, 077 * and get/setMaximumValue --> get/setUpperBound (DG); 078 * 27-Oct-2003 : Changed DEFAULT_AUTO_RANGE_MINIMUM_SIZE value - see bug ID 079 * 829606 (DG); 080 * 07-Nov-2003 : Changes to tick mechanism (DG); 081 * 06-Jan-2004 : Moved axis line attributes to Axis class (DG); 082 * 21-Jan-2004 : Removed redundant axisLineVisible attribute. Renamed 083 * translateJava2DToValue --> java2DToValue, and 084 * translateValueToJava2D --> valueToJava2D (DG); 085 * 23-Jan-2004 : Fixed setAxisLinePaint() and setAxisLineStroke() which had no 086 * effect (andreas.gawecki@coremedia.com); 087 * 07-Apr-2004 : Changed text bounds calculation (DG); 088 * 26-Apr-2004 : Added getter/setter methods for arrow shapes (DG); 089 * 18-May-2004 : Added methods to set axis range *including* current 090 * margins (DG); 091 * 02-Jun-2004 : Fixed bug in setRangeWithMargins() method (DG); 092 * 30-Sep-2004 : Moved drawRotatedString() from RefineryUtilities 093 * --> TextUtilities (DG); 094 * 11-Jan-2005 : Removed deprecated methods in preparation for 1.0.0 095 * release (DG); 096 * 21-Apr-2005 : Replaced Insets with RectangleInsets (DG); 097 * ------------- JFREECHART 1.0.x --------------------------------------------- 098 * 10-Oct-2006 : Source reformatting (DG); 099 * 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 }