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