001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2008, 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 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 025 * in the United States and other countries.] 026 * 027 * --------- 028 * Axis.java 029 * --------- 030 * (C) Copyright 2000-2008, by Object Refinery Limited and Contributors. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): Bill Kelemen; 034 * Nicolas Brodu. 035 * 036 * Changes 037 * ------- 038 * 21-Aug-2001 : Added standard header, fixed DOS encoding problem (DG); 039 * 18-Sep-2001 : Updated header (DG); 040 * 07-Nov-2001 : Allow null axis labels (DG); 041 * : Added default font values (DG); 042 * 13-Nov-2001 : Modified the setPlot() method to check compatibility between 043 * the axis and the plot (DG); 044 * 30-Nov-2001 : Changed default font from "Arial" --> "SansSerif" (DG); 045 * 06-Dec-2001 : Allow null in setPlot() method (BK); 046 * 06-Mar-2002 : Added AxisConstants interface (DG); 047 * 23-Apr-2002 : Added a visible property. Moved drawVerticalString to 048 * RefineryUtilities. Added fixedDimension property for use in 049 * combined plots (DG); 050 * 25-Jun-2002 : Removed unnecessary imports (DG); 051 * 05-Sep-2002 : Added attribute for tick mark paint (DG); 052 * 18-Sep-2002 : Fixed errors reported by Checkstyle (DG); 053 * 07-Nov-2002 : Added attributes to control the inside and outside length of 054 * the tick marks (DG); 055 * 08-Nov-2002 : Moved to new package com.jrefinery.chart.axis (DG); 056 * 18-Nov-2002 : Added axis location to refreshTicks() parameters (DG); 057 * 15-Jan-2003 : Removed monolithic constructor (DG); 058 * 17-Jan-2003 : Moved plot classes to separate package (DG); 059 * 26-Mar-2003 : Implemented Serializable (DG); 060 * 03-Jul-2003 : Modified reserveSpace method (DG); 061 * 13-Aug-2003 : Implemented Cloneable (DG); 062 * 11-Sep-2003 : Took care of listeners while cloning (NB); 063 * 29-Oct-2003 : Added workaround for font alignment in PDF output (DG); 064 * 06-Nov-2003 : Modified refreshTicks() signature (DG); 065 * 06-Jan-2004 : Added axis line attributes (DG); 066 * 16-Mar-2004 : Added plot state to draw() method (DG); 067 * 07-Apr-2004 : Modified text bounds calculation (DG); 068 * 18-May-2004 : Eliminated AxisConstants.java (DG); 069 * 30-Sep-2004 : Moved drawRotatedString() from RefineryUtilities --> 070 * TextUtilities (DG); 071 * 04-Oct-2004 : Modified getLabelEnclosure() method to treat an empty String 072 * the same way as a null string - see bug 1026521 (DG); 073 * 21-Apr-2005 : Replaced Insets with RectangleInsets (DG); 074 * 26-Apr-2005 : Removed LOGGER (DG); 075 * 01-Jun-2005 : Added hasListener() method for unit testing (DG); 076 * 08-Jun-2005 : Fixed equals() method to handle GradientPaint (DG); 077 * ------------- JFREECHART 1.0.x --------------------------------------------- 078 * 22-Aug-2006 : API doc updates (DG); 079 * 06-Jun-2008 : Added setTickLabelInsets(RectangleInsets, boolean) (DG); 080 * 081 */ 082 083 package org.jfree.chart.axis; 084 085 import java.awt.BasicStroke; 086 import java.awt.Color; 087 import java.awt.Font; 088 import java.awt.FontMetrics; 089 import java.awt.Graphics2D; 090 import java.awt.Paint; 091 import java.awt.Shape; 092 import java.awt.Stroke; 093 import java.awt.geom.AffineTransform; 094 import java.awt.geom.Line2D; 095 import java.awt.geom.Rectangle2D; 096 import java.io.IOException; 097 import java.io.ObjectInputStream; 098 import java.io.ObjectOutputStream; 099 import java.io.Serializable; 100 import java.util.Arrays; 101 import java.util.EventListener; 102 import java.util.List; 103 104 import javax.swing.event.EventListenerList; 105 106 import org.jfree.chart.event.AxisChangeEvent; 107 import org.jfree.chart.event.AxisChangeListener; 108 import org.jfree.chart.plot.Plot; 109 import org.jfree.chart.plot.PlotRenderingInfo; 110 import org.jfree.io.SerialUtilities; 111 import org.jfree.text.TextUtilities; 112 import org.jfree.ui.RectangleEdge; 113 import org.jfree.ui.RectangleInsets; 114 import org.jfree.ui.TextAnchor; 115 import org.jfree.util.ObjectUtilities; 116 import org.jfree.util.PaintUtilities; 117 118 /** 119 * The base class for all axes in JFreeChart. Subclasses are divided into 120 * those that display values ({@link ValueAxis}) and those that display 121 * categories ({@link CategoryAxis}). 122 */ 123 public abstract class Axis implements Cloneable, Serializable { 124 125 /** For serialization. */ 126 private static final long serialVersionUID = 7719289504573298271L; 127 128 /** The default axis visibility. */ 129 public static final boolean DEFAULT_AXIS_VISIBLE = true; 130 131 /** The default axis label font. */ 132 public static final Font DEFAULT_AXIS_LABEL_FONT = new Font( 133 "SansSerif", Font.PLAIN, 12); 134 135 /** The default axis label paint. */ 136 public static final Paint DEFAULT_AXIS_LABEL_PAINT = Color.black; 137 138 /** The default axis label insets. */ 139 public static final RectangleInsets DEFAULT_AXIS_LABEL_INSETS 140 = new RectangleInsets(3.0, 3.0, 3.0, 3.0); 141 142 /** The default axis line paint. */ 143 public static final Paint DEFAULT_AXIS_LINE_PAINT = Color.gray; 144 145 /** The default axis line stroke. */ 146 public static final Stroke DEFAULT_AXIS_LINE_STROKE = new BasicStroke(1.0f); 147 148 /** The default tick labels visibility. */ 149 public static final boolean DEFAULT_TICK_LABELS_VISIBLE = true; 150 151 /** The default tick label font. */ 152 public static final Font DEFAULT_TICK_LABEL_FONT = new Font("SansSerif", 153 Font.PLAIN, 10); 154 155 /** The default tick label paint. */ 156 public static final Paint DEFAULT_TICK_LABEL_PAINT = Color.black; 157 158 /** The default tick label insets. */ 159 public static final RectangleInsets DEFAULT_TICK_LABEL_INSETS 160 = new RectangleInsets(2.0, 4.0, 2.0, 4.0); 161 162 /** The default tick marks visible. */ 163 public static final boolean DEFAULT_TICK_MARKS_VISIBLE = true; 164 165 /** The default tick stroke. */ 166 public static final Stroke DEFAULT_TICK_MARK_STROKE = new BasicStroke(1); 167 168 /** The default tick paint. */ 169 public static final Paint DEFAULT_TICK_MARK_PAINT = Color.gray; 170 171 /** The default tick mark inside length. */ 172 public static final float DEFAULT_TICK_MARK_INSIDE_LENGTH = 0.0f; 173 174 /** The default tick mark outside length. */ 175 public static final float DEFAULT_TICK_MARK_OUTSIDE_LENGTH = 2.0f; 176 177 /** A flag indicating whether or not the axis is visible. */ 178 private boolean visible; 179 180 /** The label for the axis. */ 181 private String label; 182 183 /** The font for displaying the axis label. */ 184 private Font labelFont; 185 186 /** The paint for drawing the axis label. */ 187 private transient Paint labelPaint; 188 189 /** The insets for the axis label. */ 190 private RectangleInsets labelInsets; 191 192 /** The label angle. */ 193 private double labelAngle; 194 195 /** A flag that controls whether or not the axis line is visible. */ 196 private boolean axisLineVisible; 197 198 /** The stroke used for the axis line. */ 199 private transient Stroke axisLineStroke; 200 201 /** The paint used for the axis line. */ 202 private transient Paint axisLinePaint; 203 204 /** 205 * A flag that indicates whether or not tick labels are visible for the 206 * axis. 207 */ 208 private boolean tickLabelsVisible; 209 210 /** The font used to display the tick labels. */ 211 private Font tickLabelFont; 212 213 /** The color used to display the tick labels. */ 214 private transient Paint tickLabelPaint; 215 216 /** The blank space around each tick label. */ 217 private RectangleInsets tickLabelInsets; 218 219 /** 220 * A flag that indicates whether or not tick marks are visible for the 221 * axis. 222 */ 223 private boolean tickMarksVisible; 224 225 /** The length of the tick mark inside the data area (zero permitted). */ 226 private float tickMarkInsideLength; 227 228 /** The length of the tick mark outside the data area (zero permitted). */ 229 private float tickMarkOutsideLength; 230 231 /** The stroke used to draw tick marks. */ 232 private transient Stroke tickMarkStroke; 233 234 /** The paint used to draw tick marks. */ 235 private transient Paint tickMarkPaint; 236 237 /** The fixed (horizontal or vertical) dimension for the axis. */ 238 private double fixedDimension; 239 240 /** 241 * A reference back to the plot that the axis is assigned to (can be 242 * <code>null</code>). 243 */ 244 private transient Plot plot; 245 246 /** Storage for registered listeners. */ 247 private transient EventListenerList listenerList; 248 249 /** 250 * Constructs an axis, using default values where necessary. 251 * 252 * @param label the axis label (<code>null</code> permitted). 253 */ 254 protected Axis(String label) { 255 256 this.label = label; 257 this.visible = DEFAULT_AXIS_VISIBLE; 258 this.labelFont = DEFAULT_AXIS_LABEL_FONT; 259 this.labelPaint = DEFAULT_AXIS_LABEL_PAINT; 260 this.labelInsets = DEFAULT_AXIS_LABEL_INSETS; 261 this.labelAngle = 0.0; 262 263 this.axisLineVisible = true; 264 this.axisLinePaint = DEFAULT_AXIS_LINE_PAINT; 265 this.axisLineStroke = DEFAULT_AXIS_LINE_STROKE; 266 267 this.tickLabelsVisible = DEFAULT_TICK_LABELS_VISIBLE; 268 this.tickLabelFont = DEFAULT_TICK_LABEL_FONT; 269 this.tickLabelPaint = DEFAULT_TICK_LABEL_PAINT; 270 this.tickLabelInsets = DEFAULT_TICK_LABEL_INSETS; 271 272 this.tickMarksVisible = DEFAULT_TICK_MARKS_VISIBLE; 273 this.tickMarkStroke = DEFAULT_TICK_MARK_STROKE; 274 this.tickMarkPaint = DEFAULT_TICK_MARK_PAINT; 275 this.tickMarkInsideLength = DEFAULT_TICK_MARK_INSIDE_LENGTH; 276 this.tickMarkOutsideLength = DEFAULT_TICK_MARK_OUTSIDE_LENGTH; 277 278 this.plot = null; 279 280 this.listenerList = new EventListenerList(); 281 282 } 283 284 /** 285 * Returns <code>true</code> if the axis is visible, and 286 * <code>false</code> otherwise. 287 * 288 * @return A boolean. 289 * 290 * @see #setVisible(boolean) 291 */ 292 public boolean isVisible() { 293 return this.visible; 294 } 295 296 /** 297 * Sets a flag that controls whether or not the axis is visible and sends 298 * an {@link AxisChangeEvent} to all registered listeners. 299 * 300 * @param flag the flag. 301 * 302 * @see #isVisible() 303 */ 304 public void setVisible(boolean flag) { 305 if (flag != this.visible) { 306 this.visible = flag; 307 notifyListeners(new AxisChangeEvent(this)); 308 } 309 } 310 311 /** 312 * Returns the label for the axis. 313 * 314 * @return The label for the axis (<code>null</code> possible). 315 * 316 * @see #getLabelFont() 317 * @see #getLabelPaint() 318 * @see #setLabel(String) 319 */ 320 public String getLabel() { 321 return this.label; 322 } 323 324 /** 325 * Sets the label for the axis and sends an {@link AxisChangeEvent} to all 326 * registered listeners. 327 * 328 * @param label the new label (<code>null</code> permitted). 329 * 330 * @see #getLabel() 331 * @see #setLabelFont(Font) 332 * @see #setLabelPaint(Paint) 333 */ 334 public void setLabel(String label) { 335 336 String existing = this.label; 337 if (existing != null) { 338 if (!existing.equals(label)) { 339 this.label = label; 340 notifyListeners(new AxisChangeEvent(this)); 341 } 342 } 343 else { 344 if (label != null) { 345 this.label = label; 346 notifyListeners(new AxisChangeEvent(this)); 347 } 348 } 349 350 } 351 352 /** 353 * Returns the font for the axis label. 354 * 355 * @return The font (never <code>null</code>). 356 * 357 * @see #setLabelFont(Font) 358 */ 359 public Font getLabelFont() { 360 return this.labelFont; 361 } 362 363 /** 364 * Sets the font for the axis label and sends an {@link AxisChangeEvent} 365 * to all registered listeners. 366 * 367 * @param font the font (<code>null</code> not permitted). 368 * 369 * @see #getLabelFont() 370 */ 371 public void setLabelFont(Font font) { 372 if (font == null) { 373 throw new IllegalArgumentException("Null 'font' argument."); 374 } 375 if (!this.labelFont.equals(font)) { 376 this.labelFont = font; 377 notifyListeners(new AxisChangeEvent(this)); 378 } 379 } 380 381 /** 382 * Returns the color/shade used to draw the axis label. 383 * 384 * @return The paint (never <code>null</code>). 385 * 386 * @see #setLabelPaint(Paint) 387 */ 388 public Paint getLabelPaint() { 389 return this.labelPaint; 390 } 391 392 /** 393 * Sets the paint used to draw the axis label and sends an 394 * {@link AxisChangeEvent} to all registered listeners. 395 * 396 * @param paint the paint (<code>null</code> not permitted). 397 * 398 * @see #getLabelPaint() 399 */ 400 public void setLabelPaint(Paint paint) { 401 if (paint == null) { 402 throw new IllegalArgumentException("Null 'paint' argument."); 403 } 404 this.labelPaint = paint; 405 notifyListeners(new AxisChangeEvent(this)); 406 } 407 408 /** 409 * Returns the insets for the label (that is, the amount of blank space 410 * that should be left around the label). 411 * 412 * @return The label insets (never <code>null</code>). 413 * 414 * @see #setLabelInsets(RectangleInsets) 415 */ 416 public RectangleInsets getLabelInsets() { 417 return this.labelInsets; 418 } 419 420 /** 421 * Sets the insets for the axis label, and sends an {@link AxisChangeEvent} 422 * to all registered listeners. 423 * 424 * @param insets the insets (<code>null</code> not permitted). 425 * 426 * @see #getLabelInsets() 427 */ 428 public void setLabelInsets(RectangleInsets insets) { 429 setLabelInsets(insets, true); 430 } 431 432 /** 433 * Sets the insets for the axis label, and sends an {@link AxisChangeEvent} 434 * to all registered listeners. 435 * 436 * @param insets the insets (<code>null</code> not permitted). 437 * @param notify notify listeners? 438 * 439 * @since 1.0.10 440 */ 441 public void setLabelInsets(RectangleInsets insets, boolean notify) { 442 if (insets == null) { 443 throw new IllegalArgumentException("Null 'insets' argument."); 444 } 445 if (!insets.equals(this.labelInsets)) { 446 this.labelInsets = insets; 447 if (notify) { 448 notifyListeners(new AxisChangeEvent(this)); 449 } 450 } 451 } 452 453 /** 454 * Returns the angle of the axis label. 455 * 456 * @return The angle (in radians). 457 * 458 * @see #setLabelAngle(double) 459 */ 460 public double getLabelAngle() { 461 return this.labelAngle; 462 } 463 464 /** 465 * Sets the angle for the label and sends an {@link AxisChangeEvent} to all 466 * registered listeners. 467 * 468 * @param angle the angle (in radians). 469 * 470 * @see #getLabelAngle() 471 */ 472 public void setLabelAngle(double angle) { 473 this.labelAngle = angle; 474 notifyListeners(new AxisChangeEvent(this)); 475 } 476 477 /** 478 * A flag that controls whether or not the axis line is drawn. 479 * 480 * @return A boolean. 481 * 482 * @see #getAxisLinePaint() 483 * @see #getAxisLineStroke() 484 * @see #setAxisLineVisible(boolean) 485 */ 486 public boolean isAxisLineVisible() { 487 return this.axisLineVisible; 488 } 489 490 /** 491 * Sets a flag that controls whether or not the axis line is visible and 492 * sends an {@link AxisChangeEvent} to all registered listeners. 493 * 494 * @param visible the flag. 495 * 496 * @see #isAxisLineVisible() 497 * @see #setAxisLinePaint(Paint) 498 * @see #setAxisLineStroke(Stroke) 499 */ 500 public void setAxisLineVisible(boolean visible) { 501 this.axisLineVisible = visible; 502 notifyListeners(new AxisChangeEvent(this)); 503 } 504 505 /** 506 * Returns the paint used to draw the axis line. 507 * 508 * @return The paint (never <code>null</code>). 509 * 510 * @see #setAxisLinePaint(Paint) 511 */ 512 public Paint getAxisLinePaint() { 513 return this.axisLinePaint; 514 } 515 516 /** 517 * Sets the paint used to draw the axis line and sends an 518 * {@link AxisChangeEvent} to all registered listeners. 519 * 520 * @param paint the paint (<code>null</code> not permitted). 521 * 522 * @see #getAxisLinePaint() 523 */ 524 public void setAxisLinePaint(Paint paint) { 525 if (paint == null) { 526 throw new IllegalArgumentException("Null 'paint' argument."); 527 } 528 this.axisLinePaint = paint; 529 notifyListeners(new AxisChangeEvent(this)); 530 } 531 532 /** 533 * Returns the stroke used to draw the axis line. 534 * 535 * @return The stroke (never <code>null</code>). 536 * 537 * @see #setAxisLineStroke(Stroke) 538 */ 539 public Stroke getAxisLineStroke() { 540 return this.axisLineStroke; 541 } 542 543 /** 544 * Sets the stroke used to draw the axis line and sends an 545 * {@link AxisChangeEvent} to all registered listeners. 546 * 547 * @param stroke the stroke (<code>null</code> not permitted). 548 * 549 * @see #getAxisLineStroke() 550 */ 551 public void setAxisLineStroke(Stroke stroke) { 552 if (stroke == null) { 553 throw new IllegalArgumentException("Null 'stroke' argument."); 554 } 555 this.axisLineStroke = stroke; 556 notifyListeners(new AxisChangeEvent(this)); 557 } 558 559 /** 560 * Returns a flag indicating whether or not the tick labels are visible. 561 * 562 * @return The flag. 563 * 564 * @see #getTickLabelFont() 565 * @see #getTickLabelPaint() 566 * @see #setTickLabelsVisible(boolean) 567 */ 568 public boolean isTickLabelsVisible() { 569 return this.tickLabelsVisible; 570 } 571 572 /** 573 * Sets the flag that determines whether or not the tick labels are 574 * visible and sends an {@link AxisChangeEvent} to all registered 575 * listeners. 576 * 577 * @param flag the flag. 578 * 579 * @see #isTickLabelsVisible() 580 * @see #setTickLabelFont(Font) 581 * @see #setTickLabelPaint(Paint) 582 */ 583 public void setTickLabelsVisible(boolean flag) { 584 585 if (flag != this.tickLabelsVisible) { 586 this.tickLabelsVisible = flag; 587 notifyListeners(new AxisChangeEvent(this)); 588 } 589 590 } 591 592 /** 593 * Returns the font used for the tick labels (if showing). 594 * 595 * @return The font (never <code>null</code>). 596 * 597 * @see #setTickLabelFont(Font) 598 */ 599 public Font getTickLabelFont() { 600 return this.tickLabelFont; 601 } 602 603 /** 604 * Sets the font for the tick labels and sends an {@link AxisChangeEvent} 605 * to all registered listeners. 606 * 607 * @param font the font (<code>null</code> not allowed). 608 * 609 * @see #getTickLabelFont() 610 */ 611 public void setTickLabelFont(Font font) { 612 613 if (font == null) { 614 throw new IllegalArgumentException("Null 'font' argument."); 615 } 616 617 if (!this.tickLabelFont.equals(font)) { 618 this.tickLabelFont = font; 619 notifyListeners(new AxisChangeEvent(this)); 620 } 621 622 } 623 624 /** 625 * Returns the color/shade used for the tick labels. 626 * 627 * @return The paint used for the tick labels. 628 * 629 * @see #setTickLabelPaint(Paint) 630 */ 631 public Paint getTickLabelPaint() { 632 return this.tickLabelPaint; 633 } 634 635 /** 636 * Sets the paint used to draw tick labels (if they are showing) and 637 * sends an {@link AxisChangeEvent} to all registered listeners. 638 * 639 * @param paint the paint (<code>null</code> not permitted). 640 * 641 * @see #getTickLabelPaint() 642 */ 643 public void setTickLabelPaint(Paint paint) { 644 if (paint == null) { 645 throw new IllegalArgumentException("Null 'paint' argument."); 646 } 647 this.tickLabelPaint = paint; 648 notifyListeners(new AxisChangeEvent(this)); 649 } 650 651 /** 652 * Returns the insets for the tick labels. 653 * 654 * @return The insets (never <code>null</code>). 655 * 656 * @see #setTickLabelInsets(RectangleInsets) 657 */ 658 public RectangleInsets getTickLabelInsets() { 659 return this.tickLabelInsets; 660 } 661 662 /** 663 * Sets the insets for the tick labels and sends an {@link AxisChangeEvent} 664 * to all registered listeners. 665 * 666 * @param insets the insets (<code>null</code> not permitted). 667 * 668 * @see #getTickLabelInsets() 669 */ 670 public void setTickLabelInsets(RectangleInsets insets) { 671 if (insets == null) { 672 throw new IllegalArgumentException("Null 'insets' argument."); 673 } 674 if (!this.tickLabelInsets.equals(insets)) { 675 this.tickLabelInsets = insets; 676 notifyListeners(new AxisChangeEvent(this)); 677 } 678 } 679 680 /** 681 * Returns the flag that indicates whether or not the tick marks are 682 * showing. 683 * 684 * @return The flag that indicates whether or not the tick marks are 685 * showing. 686 * 687 * @see #setTickMarksVisible(boolean) 688 */ 689 public boolean isTickMarksVisible() { 690 return this.tickMarksVisible; 691 } 692 693 /** 694 * Sets the flag that indicates whether or not the tick marks are showing 695 * and sends an {@link AxisChangeEvent} to all registered listeners. 696 * 697 * @param flag the flag. 698 * 699 * @see #isTickMarksVisible() 700 */ 701 public void setTickMarksVisible(boolean flag) { 702 if (flag != this.tickMarksVisible) { 703 this.tickMarksVisible = flag; 704 notifyListeners(new AxisChangeEvent(this)); 705 } 706 } 707 708 /** 709 * Returns the inside length of the tick marks. 710 * 711 * @return The length. 712 * 713 * @see #getTickMarkOutsideLength() 714 * @see #setTickMarkInsideLength(float) 715 */ 716 public float getTickMarkInsideLength() { 717 return this.tickMarkInsideLength; 718 } 719 720 /** 721 * Sets the inside length of the tick marks and sends 722 * an {@link AxisChangeEvent} to all registered listeners. 723 * 724 * @param length the new length. 725 * 726 * @see #getTickMarkInsideLength() 727 */ 728 public void setTickMarkInsideLength(float length) { 729 this.tickMarkInsideLength = length; 730 notifyListeners(new AxisChangeEvent(this)); 731 } 732 733 /** 734 * Returns the outside length of the tick marks. 735 * 736 * @return The length. 737 * 738 * @see #getTickMarkInsideLength() 739 * @see #setTickMarkOutsideLength(float) 740 */ 741 public float getTickMarkOutsideLength() { 742 return this.tickMarkOutsideLength; 743 } 744 745 /** 746 * Sets the outside length of the tick marks and sends 747 * an {@link AxisChangeEvent} to all registered listeners. 748 * 749 * @param length the new length. 750 * 751 * @see #getTickMarkInsideLength() 752 */ 753 public void setTickMarkOutsideLength(float length) { 754 this.tickMarkOutsideLength = length; 755 notifyListeners(new AxisChangeEvent(this)); 756 } 757 758 /** 759 * Returns the stroke used to draw tick marks. 760 * 761 * @return The stroke (never <code>null</code>). 762 * 763 * @see #setTickMarkStroke(Stroke) 764 */ 765 public Stroke getTickMarkStroke() { 766 return this.tickMarkStroke; 767 } 768 769 /** 770 * Sets the stroke used to draw tick marks and sends 771 * an {@link AxisChangeEvent} to all registered listeners. 772 * 773 * @param stroke the stroke (<code>null</code> not permitted). 774 * 775 * @see #getTickMarkStroke() 776 */ 777 public void setTickMarkStroke(Stroke stroke) { 778 if (stroke == null) { 779 throw new IllegalArgumentException("Null 'stroke' argument."); 780 } 781 if (!this.tickMarkStroke.equals(stroke)) { 782 this.tickMarkStroke = stroke; 783 notifyListeners(new AxisChangeEvent(this)); 784 } 785 } 786 787 /** 788 * Returns the paint used to draw tick marks (if they are showing). 789 * 790 * @return The paint (never <code>null</code>). 791 * 792 * @see #setTickMarkPaint(Paint) 793 */ 794 public Paint getTickMarkPaint() { 795 return this.tickMarkPaint; 796 } 797 798 /** 799 * Sets the paint used to draw tick marks and sends an 800 * {@link AxisChangeEvent} to all registered listeners. 801 * 802 * @param paint the paint (<code>null</code> not permitted). 803 * 804 * @see #getTickMarkPaint() 805 */ 806 public void setTickMarkPaint(Paint paint) { 807 if (paint == null) { 808 throw new IllegalArgumentException("Null 'paint' argument."); 809 } 810 this.tickMarkPaint = paint; 811 notifyListeners(new AxisChangeEvent(this)); 812 } 813 814 /** 815 * Returns the plot that the axis is assigned to. This method will return 816 * <code>null</code> if the axis is not currently assigned to a plot. 817 * 818 * @return The plot that the axis is assigned to (possibly 819 * <code>null</code>). 820 * 821 * @see #setPlot(Plot) 822 */ 823 public Plot getPlot() { 824 return this.plot; 825 } 826 827 /** 828 * Sets a reference to the plot that the axis is assigned to. 829 * <P> 830 * This method is used internally, you shouldn't need to call it yourself. 831 * 832 * @param plot the plot. 833 * 834 * @see #getPlot() 835 */ 836 public void setPlot(Plot plot) { 837 this.plot = plot; 838 configure(); 839 } 840 841 /** 842 * Returns the fixed dimension for the axis. 843 * 844 * @return The fixed dimension. 845 * 846 * @see #setFixedDimension(double) 847 */ 848 public double getFixedDimension() { 849 return this.fixedDimension; 850 } 851 852 /** 853 * Sets the fixed dimension for the axis. 854 * <P> 855 * This is used when combining more than one plot on a chart. In this case, 856 * there may be several axes that need to have the same height or width so 857 * that they are aligned. This method is used to fix a dimension for the 858 * axis (the context determines whether the dimension is horizontal or 859 * vertical). 860 * 861 * @param dimension the fixed dimension. 862 * 863 * @see #getFixedDimension() 864 */ 865 public void setFixedDimension(double dimension) { 866 this.fixedDimension = dimension; 867 } 868 869 /** 870 * Configures the axis to work with the current plot. Override this method 871 * to perform any special processing (such as auto-rescaling). 872 */ 873 public abstract void configure(); 874 875 /** 876 * Estimates the space (height or width) required to draw the axis. 877 * 878 * @param g2 the graphics device. 879 * @param plot the plot that the axis belongs to. 880 * @param plotArea the area within which the plot (including axes) should 881 * be drawn. 882 * @param edge the axis location. 883 * @param space space already reserved. 884 * 885 * @return The space required to draw the axis (including pre-reserved 886 * space). 887 */ 888 public abstract AxisSpace reserveSpace(Graphics2D g2, Plot plot, 889 Rectangle2D plotArea, 890 RectangleEdge edge, 891 AxisSpace space); 892 893 /** 894 * Draws the axis on a Java 2D graphics device (such as the screen or a 895 * printer). 896 * 897 * @param g2 the graphics device (<code>null</code> not permitted). 898 * @param cursor the cursor location (determines where to draw the axis). 899 * @param plotArea the area within which the axes and plot should be drawn. 900 * @param dataArea the area within which the data should be drawn. 901 * @param edge the axis location (<code>null</code> not permitted). 902 * @param plotState collects information about the plot 903 * (<code>null</code> permitted). 904 * 905 * @return The axis state (never <code>null</code>). 906 */ 907 public abstract AxisState draw(Graphics2D g2, 908 double cursor, 909 Rectangle2D plotArea, 910 Rectangle2D dataArea, 911 RectangleEdge edge, 912 PlotRenderingInfo plotState); 913 914 /** 915 * Calculates the positions of the ticks for the axis, storing the results 916 * in the tick list (ready for drawing). 917 * 918 * @param g2 the graphics device. 919 * @param state the axis state. 920 * @param dataArea the area inside the axes. 921 * @param edge the edge on which the axis is located. 922 * 923 * @return The list of ticks. 924 */ 925 public abstract List refreshTicks(Graphics2D g2, 926 AxisState state, 927 Rectangle2D dataArea, 928 RectangleEdge edge); 929 930 /** 931 * Registers an object for notification of changes to the axis. 932 * 933 * @param listener the object that is being registered. 934 * 935 * @see #removeChangeListener(AxisChangeListener) 936 */ 937 public void addChangeListener(AxisChangeListener listener) { 938 this.listenerList.add(AxisChangeListener.class, listener); 939 } 940 941 /** 942 * Deregisters an object for notification of changes to the axis. 943 * 944 * @param listener the object to deregister. 945 * 946 * @see #addChangeListener(AxisChangeListener) 947 */ 948 public void removeChangeListener(AxisChangeListener listener) { 949 this.listenerList.remove(AxisChangeListener.class, listener); 950 } 951 952 /** 953 * Returns <code>true</code> if the specified object is registered with 954 * the dataset as a listener. Most applications won't need to call this 955 * method, it exists mainly for use by unit testing code. 956 * 957 * @param listener the listener. 958 * 959 * @return A boolean. 960 */ 961 public boolean hasListener(EventListener listener) { 962 List list = Arrays.asList(this.listenerList.getListenerList()); 963 return list.contains(listener); 964 } 965 966 /** 967 * Notifies all registered listeners that the axis has changed. 968 * The AxisChangeEvent provides information about the change. 969 * 970 * @param event information about the change to the axis. 971 */ 972 protected void notifyListeners(AxisChangeEvent event) { 973 974 Object[] listeners = this.listenerList.getListenerList(); 975 for (int i = listeners.length - 2; i >= 0; i -= 2) { 976 if (listeners[i] == AxisChangeListener.class) { 977 ((AxisChangeListener) listeners[i + 1]).axisChanged(event); 978 } 979 } 980 981 } 982 983 /** 984 * Returns a rectangle that encloses the axis label. This is typically 985 * used for layout purposes (it gives the maximum dimensions of the label). 986 * 987 * @param g2 the graphics device. 988 * @param edge the edge of the plot area along which the axis is measuring. 989 * 990 * @return The enclosing rectangle. 991 */ 992 protected Rectangle2D getLabelEnclosure(Graphics2D g2, RectangleEdge edge) { 993 994 Rectangle2D result = new Rectangle2D.Double(); 995 String axisLabel = getLabel(); 996 if (axisLabel != null && !axisLabel.equals("")) { 997 FontMetrics fm = g2.getFontMetrics(getLabelFont()); 998 Rectangle2D bounds = TextUtilities.getTextBounds(axisLabel, g2, fm); 999 RectangleInsets insets = getLabelInsets(); 1000 bounds = insets.createOutsetRectangle(bounds); 1001 double angle = getLabelAngle(); 1002 if (edge == RectangleEdge.LEFT || edge == RectangleEdge.RIGHT) { 1003 angle = angle - Math.PI / 2.0; 1004 } 1005 double x = bounds.getCenterX(); 1006 double y = bounds.getCenterY(); 1007 AffineTransform transformer 1008 = AffineTransform.getRotateInstance(angle, x, y); 1009 Shape labelBounds = transformer.createTransformedShape(bounds); 1010 result = labelBounds.getBounds2D(); 1011 } 1012 1013 return result; 1014 1015 } 1016 1017 /** 1018 * Draws the axis label. 1019 * 1020 * @param label the label text. 1021 * @param g2 the graphics device. 1022 * @param plotArea the plot area. 1023 * @param dataArea the area inside the axes. 1024 * @param edge the location of the axis. 1025 * @param state the axis state (<code>null</code> not permitted). 1026 * 1027 * @return Information about the axis. 1028 */ 1029 protected AxisState drawLabel(String label, Graphics2D g2, 1030 Rectangle2D plotArea, Rectangle2D dataArea, RectangleEdge edge, 1031 AxisState state) { 1032 1033 // it is unlikely that 'state' will be null, but check anyway... 1034 if (state == null) { 1035 throw new IllegalArgumentException("Null 'state' argument."); 1036 } 1037 1038 if ((label == null) || (label.equals(""))) { 1039 return state; 1040 } 1041 1042 Font font = getLabelFont(); 1043 RectangleInsets insets = getLabelInsets(); 1044 g2.setFont(font); 1045 g2.setPaint(getLabelPaint()); 1046 FontMetrics fm = g2.getFontMetrics(); 1047 Rectangle2D labelBounds = TextUtilities.getTextBounds(label, g2, fm); 1048 1049 if (edge == RectangleEdge.TOP) { 1050 1051 AffineTransform t = AffineTransform.getRotateInstance( 1052 getLabelAngle(), labelBounds.getCenterX(), 1053 labelBounds.getCenterY()); 1054 Shape rotatedLabelBounds = t.createTransformedShape(labelBounds); 1055 labelBounds = rotatedLabelBounds.getBounds2D(); 1056 double labelx = dataArea.getCenterX(); 1057 double labely = state.getCursor() - insets.getBottom() 1058 - labelBounds.getHeight() / 2.0; 1059 TextUtilities.drawRotatedString(label, g2, (float) labelx, 1060 (float) labely, TextAnchor.CENTER, getLabelAngle(), 1061 TextAnchor.CENTER); 1062 state.cursorUp(insets.getTop() + labelBounds.getHeight() 1063 + insets.getBottom()); 1064 1065 } 1066 else if (edge == RectangleEdge.BOTTOM) { 1067 1068 AffineTransform t = AffineTransform.getRotateInstance( 1069 getLabelAngle(), labelBounds.getCenterX(), 1070 labelBounds.getCenterY()); 1071 Shape rotatedLabelBounds = t.createTransformedShape(labelBounds); 1072 labelBounds = rotatedLabelBounds.getBounds2D(); 1073 double labelx = dataArea.getCenterX(); 1074 double labely = state.getCursor() 1075 + insets.getTop() + labelBounds.getHeight() / 2.0; 1076 TextUtilities.drawRotatedString(label, g2, (float) labelx, 1077 (float) labely, TextAnchor.CENTER, getLabelAngle(), 1078 TextAnchor.CENTER); 1079 state.cursorDown(insets.getTop() + labelBounds.getHeight() 1080 + insets.getBottom()); 1081 1082 } 1083 else if (edge == RectangleEdge.LEFT) { 1084 1085 AffineTransform t = AffineTransform.getRotateInstance( 1086 getLabelAngle() - Math.PI / 2.0, labelBounds.getCenterX(), 1087 labelBounds.getCenterY()); 1088 Shape rotatedLabelBounds = t.createTransformedShape(labelBounds); 1089 labelBounds = rotatedLabelBounds.getBounds2D(); 1090 double labelx = state.getCursor() 1091 - insets.getRight() - labelBounds.getWidth() / 2.0; 1092 double labely = dataArea.getCenterY(); 1093 TextUtilities.drawRotatedString(label, g2, (float) labelx, 1094 (float) labely, TextAnchor.CENTER, 1095 getLabelAngle() - Math.PI / 2.0, TextAnchor.CENTER); 1096 state.cursorLeft(insets.getLeft() + labelBounds.getWidth() 1097 + insets.getRight()); 1098 } 1099 else if (edge == RectangleEdge.RIGHT) { 1100 1101 AffineTransform t = AffineTransform.getRotateInstance( 1102 getLabelAngle() + Math.PI / 2.0, 1103 labelBounds.getCenterX(), labelBounds.getCenterY()); 1104 Shape rotatedLabelBounds = t.createTransformedShape(labelBounds); 1105 labelBounds = rotatedLabelBounds.getBounds2D(); 1106 double labelx = state.getCursor() 1107 + insets.getLeft() + labelBounds.getWidth() / 2.0; 1108 double labely = dataArea.getY() + dataArea.getHeight() / 2.0; 1109 TextUtilities.drawRotatedString(label, g2, (float) labelx, 1110 (float) labely, TextAnchor.CENTER, 1111 getLabelAngle() + Math.PI / 2.0, TextAnchor.CENTER); 1112 state.cursorRight(insets.getLeft() + labelBounds.getWidth() 1113 + insets.getRight()); 1114 1115 } 1116 1117 return state; 1118 1119 } 1120 1121 /** 1122 * Draws an axis line at the current cursor position and edge. 1123 * 1124 * @param g2 the graphics device. 1125 * @param cursor the cursor position. 1126 * @param dataArea the data area. 1127 * @param edge the edge. 1128 */ 1129 protected void drawAxisLine(Graphics2D g2, double cursor, 1130 Rectangle2D dataArea, RectangleEdge edge) { 1131 1132 Line2D axisLine = null; 1133 if (edge == RectangleEdge.TOP) { 1134 axisLine = new Line2D.Double(dataArea.getX(), cursor, 1135 dataArea.getMaxX(), cursor); 1136 } 1137 else if (edge == RectangleEdge.BOTTOM) { 1138 axisLine = new Line2D.Double(dataArea.getX(), cursor, 1139 dataArea.getMaxX(), cursor); 1140 } 1141 else if (edge == RectangleEdge.LEFT) { 1142 axisLine = new Line2D.Double(cursor, dataArea.getY(), cursor, 1143 dataArea.getMaxY()); 1144 } 1145 else if (edge == RectangleEdge.RIGHT) { 1146 axisLine = new Line2D.Double(cursor, dataArea.getY(), cursor, 1147 dataArea.getMaxY()); 1148 } 1149 g2.setPaint(this.axisLinePaint); 1150 g2.setStroke(this.axisLineStroke); 1151 g2.draw(axisLine); 1152 1153 } 1154 1155 /** 1156 * Returns a clone of the axis. 1157 * 1158 * @return A clone. 1159 * 1160 * @throws CloneNotSupportedException if some component of the axis does 1161 * not support cloning. 1162 */ 1163 public Object clone() throws CloneNotSupportedException { 1164 Axis clone = (Axis) super.clone(); 1165 // It's up to the plot which clones up to restore the correct references 1166 clone.plot = null; 1167 clone.listenerList = new EventListenerList(); 1168 return clone; 1169 } 1170 1171 /** 1172 * Tests this axis for equality with another object. 1173 * 1174 * @param obj the object (<code>null</code> permitted). 1175 * 1176 * @return <code>true</code> or <code>false</code>. 1177 */ 1178 public boolean equals(Object obj) { 1179 if (obj == this) { 1180 return true; 1181 } 1182 if (!(obj instanceof Axis)) { 1183 return false; 1184 } 1185 Axis that = (Axis) obj; 1186 if (this.visible != that.visible) { 1187 return false; 1188 } 1189 if (!ObjectUtilities.equal(this.label, that.label)) { 1190 return false; 1191 } 1192 if (!ObjectUtilities.equal(this.labelFont, that.labelFont)) { 1193 return false; 1194 } 1195 if (!PaintUtilities.equal(this.labelPaint, that.labelPaint)) { 1196 return false; 1197 } 1198 if (!ObjectUtilities.equal(this.labelInsets, that.labelInsets)) { 1199 return false; 1200 } 1201 if (this.labelAngle != that.labelAngle) { 1202 return false; 1203 } 1204 if (this.axisLineVisible != that.axisLineVisible) { 1205 return false; 1206 } 1207 if (!ObjectUtilities.equal(this.axisLineStroke, that.axisLineStroke)) { 1208 return false; 1209 } 1210 if (!PaintUtilities.equal(this.axisLinePaint, that.axisLinePaint)) { 1211 return false; 1212 } 1213 if (this.tickLabelsVisible != that.tickLabelsVisible) { 1214 return false; 1215 } 1216 if (!ObjectUtilities.equal(this.tickLabelFont, that.tickLabelFont)) { 1217 return false; 1218 } 1219 if (!PaintUtilities.equal(this.tickLabelPaint, that.tickLabelPaint)) { 1220 return false; 1221 } 1222 if (!ObjectUtilities.equal( 1223 this.tickLabelInsets, that.tickLabelInsets 1224 )) { 1225 return false; 1226 } 1227 if (this.tickMarksVisible != that.tickMarksVisible) { 1228 return false; 1229 } 1230 if (this.tickMarkInsideLength != that.tickMarkInsideLength) { 1231 return false; 1232 } 1233 if (this.tickMarkOutsideLength != that.tickMarkOutsideLength) { 1234 return false; 1235 } 1236 if (!PaintUtilities.equal(this.tickMarkPaint, that.tickMarkPaint)) { 1237 return false; 1238 } 1239 if (!ObjectUtilities.equal(this.tickMarkStroke, that.tickMarkStroke)) { 1240 return false; 1241 } 1242 if (this.fixedDimension != that.fixedDimension) { 1243 return false; 1244 } 1245 return true; 1246 } 1247 1248 /** 1249 * Provides serialization support. 1250 * 1251 * @param stream the output stream. 1252 * 1253 * @throws IOException if there is an I/O error. 1254 */ 1255 private void writeObject(ObjectOutputStream stream) throws IOException { 1256 stream.defaultWriteObject(); 1257 SerialUtilities.writePaint(this.labelPaint, stream); 1258 SerialUtilities.writePaint(this.tickLabelPaint, stream); 1259 SerialUtilities.writeStroke(this.axisLineStroke, stream); 1260 SerialUtilities.writePaint(this.axisLinePaint, stream); 1261 SerialUtilities.writeStroke(this.tickMarkStroke, stream); 1262 SerialUtilities.writePaint(this.tickMarkPaint, stream); 1263 } 1264 1265 /** 1266 * Provides serialization support. 1267 * 1268 * @param stream the input stream. 1269 * 1270 * @throws IOException if there is an I/O error. 1271 * @throws ClassNotFoundException if there is a classpath problem. 1272 */ 1273 private void readObject(ObjectInputStream stream) 1274 throws IOException, ClassNotFoundException { 1275 stream.defaultReadObject(); 1276 this.labelPaint = SerialUtilities.readPaint(stream); 1277 this.tickLabelPaint = SerialUtilities.readPaint(stream); 1278 this.axisLineStroke = SerialUtilities.readStroke(stream); 1279 this.axisLinePaint = SerialUtilities.readPaint(stream); 1280 this.tickMarkStroke = SerialUtilities.readStroke(stream); 1281 this.tickMarkPaint = SerialUtilities.readPaint(stream); 1282 this.listenerList = new EventListenerList(); 1283 } 1284 1285 }