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