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