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