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