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