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