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