001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2006, 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-2006, 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 * $Id: Axis.java,v 1.11.2.4 2006/08/23 10:24:26 mungady Exp $ 036 * 037 * Changes (from 21-Aug-2001) 038 * -------------------------- 039 * 21-Aug-2001 : Added standard header, fixed DOS encoding problem (DG); 040 * 18-Sep-2001 : Updated header (DG); 041 * 07-Nov-2001 : Allow null axis labels (DG); 042 * : Added default font values (DG); 043 * 13-Nov-2001 : Modified the setPlot() method to check compatibility between 044 * the axis and the plot (DG); 045 * 30-Nov-2001 : Changed default font from "Arial" --> "SansSerif" (DG); 046 * 06-Dec-2001 : Allow null in setPlot() method (BK); 047 * 06-Mar-2002 : Added AxisConstants interface (DG); 048 * 23-Apr-2002 : Added a visible property. Moved drawVerticalString to 049 * RefineryUtilities. Added fixedDimension property for use in 050 * combined plots (DG); 051 * 25-Jun-2002 : Removed unnecessary imports (DG); 052 * 05-Sep-2002 : Added attribute for tick mark paint (DG); 053 * 18-Sep-2002 : Fixed errors reported by Checkstyle (DG); 054 * 07-Nov-2002 : Added attributes to control the inside and outside length of 055 * the tick marks (DG); 056 * 08-Nov-2002 : Moved to new package com.jrefinery.chart.axis (DG); 057 * 18-Nov-2002 : Added axis location to refreshTicks() parameters (DG); 058 * 15-Jan-2003 : Removed monolithic constructor (DG); 059 * 17-Jan-2003 : Moved plot classes to separate package (DG); 060 * 26-Mar-2003 : Implemented Serializable (DG); 061 * 03-Jul-2003 : Modified reserveSpace method (DG); 062 * 13-Aug-2003 : Implemented Cloneable (DG); 063 * 11-Sep-2003 : Took care of listeners while cloning (NB); 064 * 29-Oct-2003 : Added workaround for font alignment in PDF output (DG); 065 * 06-Nov-2003 : Modified refreshTicks() signature (DG); 066 * 06-Jan-2004 : Added axis line attributes (DG); 067 * 16-Mar-2004 : Added plot state to draw() method (DG); 068 * 07-Apr-2004 : Modified text bounds calculation (DG); 069 * 18-May-2004 : Eliminated AxisConstants.java (DG); 070 * 30-Sep-2004 : Moved drawRotatedString() from RefineryUtilities --> 071 * TextUtilities (DG); 072 * 04-Oct-2004 : Modified getLabelEnclosure() method to treat an empty String 073 * the same way as a null string - see bug 1026521 (DG); 074 * 21-Apr-2005 : Replaced Insets with RectangleInsets (DG); 075 * 26-Apr-2005 : Removed LOGGER (DG); 076 * 01-Jun-2005 : Added hasListener() method for unit testing (DG); 077 * 08-Jun-2005 : Fixed equals() method to handle GradientPaint (DG); 078 * ------------- JFREECHART 1.0.0 --------------------------------------------- 079 * 22-Aug-2006 : API doc updates (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 133 = new Font("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 153 = new Font("SansSerif", 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 if (insets == null) { 430 throw new IllegalArgumentException("Null 'insets' argument."); 431 } 432 if (!insets.equals(this.labelInsets)) { 433 this.labelInsets = insets; 434 notifyListeners(new AxisChangeEvent(this)); 435 } 436 } 437 438 /** 439 * Returns the angle of the axis label. 440 * 441 * @return The angle (in radians). 442 * 443 * @see #setLabelAngle(double) 444 */ 445 public double getLabelAngle() { 446 return this.labelAngle; 447 } 448 449 /** 450 * Sets the angle for the label and sends an {@link AxisChangeEvent} to all 451 * registered listeners. 452 * 453 * @param angle the angle (in radians). 454 * 455 * @see #getLabelAngle() 456 */ 457 public void setLabelAngle(double angle) { 458 this.labelAngle = angle; 459 notifyListeners(new AxisChangeEvent(this)); 460 } 461 462 /** 463 * A flag that controls whether or not the axis line is drawn. 464 * 465 * @return A boolean. 466 * 467 * @see #getAxisLinePaint() 468 * @see #getAxisLineStroke() 469 * @see #setAxisLineVisible(boolean) 470 */ 471 public boolean isAxisLineVisible() { 472 return this.axisLineVisible; 473 } 474 475 /** 476 * Sets a flag that controls whether or not the axis line is visible and 477 * sends an {@link AxisChangeEvent} to all registered listeners. 478 * 479 * @param visible the flag. 480 * 481 * @see #isAxisLineVisible() 482 * @see #setAxisLinePaint(Paint) 483 * @see #setAxisLineStroke(Stroke) 484 */ 485 public void setAxisLineVisible(boolean visible) { 486 this.axisLineVisible = visible; 487 notifyListeners(new AxisChangeEvent(this)); 488 } 489 490 /** 491 * Returns the paint used to draw the axis line. 492 * 493 * @return The paint (never <code>null</code>). 494 * 495 * @see #setAxisLinePaint(Paint) 496 */ 497 public Paint getAxisLinePaint() { 498 return this.axisLinePaint; 499 } 500 501 /** 502 * Sets the paint used to draw the axis line and sends an 503 * {@link AxisChangeEvent} to all registered listeners. 504 * 505 * @param paint the paint (<code>null</code> not permitted). 506 * 507 * @see #getAxisLinePaint() 508 */ 509 public void setAxisLinePaint(Paint paint) { 510 if (paint == null) { 511 throw new IllegalArgumentException("Null 'paint' argument."); 512 } 513 this.axisLinePaint = paint; 514 notifyListeners(new AxisChangeEvent(this)); 515 } 516 517 /** 518 * Returns the stroke used to draw the axis line. 519 * 520 * @return The stroke (never <code>null</code>). 521 * 522 * @see #setAxisLineStroke(Stroke) 523 */ 524 public Stroke getAxisLineStroke() { 525 return this.axisLineStroke; 526 } 527 528 /** 529 * Sets the stroke used to draw the axis line and sends an 530 * {@link AxisChangeEvent} to all registered listeners. 531 * 532 * @param stroke the stroke (<code>null</code> not permitted). 533 * 534 * @see #getAxisLineStroke() 535 */ 536 public void setAxisLineStroke(Stroke stroke) { 537 if (stroke == null) { 538 throw new IllegalArgumentException("Null 'stroke' argument."); 539 } 540 this.axisLineStroke = stroke; 541 notifyListeners(new AxisChangeEvent(this)); 542 } 543 544 /** 545 * Returns a flag indicating whether or not the tick labels are visible. 546 * 547 * @return The flag. 548 * 549 * @see #getTickLabelFont() 550 * @see #getTickLabelPaint() 551 * @see #setTickLabelsVisible(boolean) 552 */ 553 public boolean isTickLabelsVisible() { 554 return this.tickLabelsVisible; 555 } 556 557 /** 558 * Sets the flag that determines whether or not the tick labels are 559 * visible and sends an {@link AxisChangeEvent} to all registered 560 * listeners. 561 * 562 * @param flag the flag. 563 * 564 * @see #isTickLabelsVisible() 565 * @see #setTickLabelFont(Font) 566 * @see #setTickLabelPaint(Paint) 567 */ 568 public void setTickLabelsVisible(boolean flag) { 569 570 if (flag != this.tickLabelsVisible) { 571 this.tickLabelsVisible = flag; 572 notifyListeners(new AxisChangeEvent(this)); 573 } 574 575 } 576 577 /** 578 * Returns the font used for the tick labels (if showing). 579 * 580 * @return The font (never <code>null</code>). 581 * 582 * @see #setTickLabelFont(Font) 583 */ 584 public Font getTickLabelFont() { 585 return this.tickLabelFont; 586 } 587 588 /** 589 * Sets the font for the tick labels and sends an {@link AxisChangeEvent} 590 * to all registered listeners. 591 * 592 * @param font the font (<code>null</code> not allowed). 593 * 594 * @see #getTickLabelFont() 595 */ 596 public void setTickLabelFont(Font font) { 597 598 if (font == null) { 599 throw new IllegalArgumentException("Null 'font' argument."); 600 } 601 602 if (!this.tickLabelFont.equals(font)) { 603 this.tickLabelFont = font; 604 notifyListeners(new AxisChangeEvent(this)); 605 } 606 607 } 608 609 /** 610 * Returns the color/shade used for the tick labels. 611 * 612 * @return The paint used for the tick labels. 613 * 614 * @see #setTickLabelPaint(Paint) 615 */ 616 public Paint getTickLabelPaint() { 617 return this.tickLabelPaint; 618 } 619 620 /** 621 * Sets the paint used to draw tick labels (if they are showing) and 622 * sends an {@link AxisChangeEvent} to all registered listeners. 623 * 624 * @param paint the paint (<code>null</code> not permitted). 625 * 626 * @see #getTickLabelPaint() 627 */ 628 public void setTickLabelPaint(Paint paint) { 629 if (paint == null) { 630 throw new IllegalArgumentException("Null 'paint' argument."); 631 } 632 this.tickLabelPaint = paint; 633 notifyListeners(new AxisChangeEvent(this)); 634 } 635 636 /** 637 * Returns the insets for the tick labels. 638 * 639 * @return The insets (never <code>null</code>). 640 * 641 * @see #setTickLabelInsets(RectangleInsets) 642 */ 643 public RectangleInsets getTickLabelInsets() { 644 return this.tickLabelInsets; 645 } 646 647 /** 648 * Sets the insets for the tick labels and sends an {@link AxisChangeEvent} 649 * to all registered listeners. 650 * 651 * @param insets the insets (<code>null</code> not permitted). 652 * 653 * @see #getTickLabelInsets() 654 */ 655 public void setTickLabelInsets(RectangleInsets insets) { 656 if (insets == null) { 657 throw new IllegalArgumentException("Null 'insets' argument."); 658 } 659 if (!this.tickLabelInsets.equals(insets)) { 660 this.tickLabelInsets = insets; 661 notifyListeners(new AxisChangeEvent(this)); 662 } 663 } 664 665 /** 666 * Returns the flag that indicates whether or not the tick marks are 667 * showing. 668 * 669 * @return The flag that indicates whether or not the tick marks are 670 * showing. 671 * 672 * @see #setTickMarksVisible(boolean) 673 */ 674 public boolean isTickMarksVisible() { 675 return this.tickMarksVisible; 676 } 677 678 /** 679 * Sets the flag that indicates whether or not the tick marks are showing 680 * and sends an {@link AxisChangeEvent} to all registered listeners. 681 * 682 * @param flag the flag. 683 * 684 * @see #isTickMarksVisible() 685 */ 686 public void setTickMarksVisible(boolean flag) { 687 if (flag != this.tickMarksVisible) { 688 this.tickMarksVisible = flag; 689 notifyListeners(new AxisChangeEvent(this)); 690 } 691 } 692 693 /** 694 * Returns the inside length of the tick marks. 695 * 696 * @return The length. 697 * 698 * @see #getTickMarkOutsideLength() 699 * @see #setTickMarkInsideLength(float) 700 */ 701 public float getTickMarkInsideLength() { 702 return this.tickMarkInsideLength; 703 } 704 705 /** 706 * Sets the inside length of the tick marks and sends 707 * an {@link AxisChangeEvent} to all registered listeners. 708 * 709 * @param length the new length. 710 * 711 * @see #getTickMarkInsideLength() 712 */ 713 public void setTickMarkInsideLength(float length) { 714 this.tickMarkInsideLength = length; 715 notifyListeners(new AxisChangeEvent(this)); 716 } 717 718 /** 719 * Returns the outside length of the tick marks. 720 * 721 * @return The length. 722 * 723 * @see #getTickMarkInsideLength() 724 * @see #setTickMarkOutsideLength(float) 725 */ 726 public float getTickMarkOutsideLength() { 727 return this.tickMarkOutsideLength; 728 } 729 730 /** 731 * Sets the outside length of the tick marks and sends 732 * an {@link AxisChangeEvent} to all registered listeners. 733 * 734 * @param length the new length. 735 * 736 * @see #getTickMarkInsideLength() 737 */ 738 public void setTickMarkOutsideLength(float length) { 739 this.tickMarkOutsideLength = length; 740 notifyListeners(new AxisChangeEvent(this)); 741 } 742 743 /** 744 * Returns the stroke used to draw tick marks. 745 * 746 * @return The stroke (never <code>null</code>). 747 * 748 * @see #setTickMarkStroke(Stroke) 749 */ 750 public Stroke getTickMarkStroke() { 751 return this.tickMarkStroke; 752 } 753 754 /** 755 * Sets the stroke used to draw tick marks and sends 756 * an {@link AxisChangeEvent} to all registered listeners. 757 * 758 * @param stroke the stroke (<code>null</code> not permitted). 759 * 760 * @see #getTickMarkStroke() 761 */ 762 public void setTickMarkStroke(Stroke stroke) { 763 if (stroke == null) { 764 throw new IllegalArgumentException("Null 'stroke' argument."); 765 } 766 if (!this.tickMarkStroke.equals(stroke)) { 767 this.tickMarkStroke = stroke; 768 notifyListeners(new AxisChangeEvent(this)); 769 } 770 } 771 772 /** 773 * Returns the paint used to draw tick marks (if they are showing). 774 * 775 * @return The paint (never <code>null</code>). 776 * 777 * @see #setTickMarkPaint(Paint) 778 */ 779 public Paint getTickMarkPaint() { 780 return this.tickMarkPaint; 781 } 782 783 /** 784 * Sets the paint used to draw tick marks and sends an 785 * {@link AxisChangeEvent} to all registered listeners. 786 * 787 * @param paint the paint (<code>null</code> not permitted). 788 * 789 * @see #getTickMarkPaint() 790 */ 791 public void setTickMarkPaint(Paint paint) { 792 if (paint == null) { 793 throw new IllegalArgumentException("Null 'paint' argument."); 794 } 795 this.tickMarkPaint = paint; 796 notifyListeners(new AxisChangeEvent(this)); 797 } 798 799 /** 800 * Returns the plot that the axis is assigned to. This method will return 801 * <code>null</code> if the axis is not currently assigned to a plot. 802 * 803 * @return The plot that the axis is assigned to (possibly 804 * <code>null</code>). 805 * 806 * @see #setPlot(Plot) 807 */ 808 public Plot getPlot() { 809 return this.plot; 810 } 811 812 /** 813 * Sets a reference to the plot that the axis is assigned to. 814 * <P> 815 * This method is used internally, you shouldn't need to call it yourself. 816 * 817 * @param plot the plot. 818 * 819 * @see #getPlot() 820 */ 821 public void setPlot(Plot plot) { 822 this.plot = plot; 823 configure(); 824 } 825 826 /** 827 * Returns the fixed dimension for the axis. 828 * 829 * @return The fixed dimension. 830 * 831 * @see #setFixedDimension(double) 832 */ 833 public double getFixedDimension() { 834 return this.fixedDimension; 835 } 836 837 /** 838 * Sets the fixed dimension for the axis. 839 * <P> 840 * This is used when combining more than one plot on a chart. In this case, 841 * there may be several axes that need to have the same height or width so 842 * that they are aligned. This method is used to fix a dimension for the 843 * axis (the context determines whether the dimension is horizontal or 844 * vertical). 845 * 846 * @param dimension the fixed dimension. 847 * 848 * @see #getFixedDimension() 849 */ 850 public void setFixedDimension(double dimension) { 851 this.fixedDimension = dimension; 852 } 853 854 /** 855 * Configures the axis to work with the current plot. Override this method 856 * to perform any special processing (such as auto-rescaling). 857 */ 858 public abstract void configure(); 859 860 /** 861 * Estimates the space (height or width) required to draw the axis. 862 * 863 * @param g2 the graphics device. 864 * @param plot the plot that the axis belongs to. 865 * @param plotArea the area within which the plot (including axes) should 866 * be drawn. 867 * @param edge the axis location. 868 * @param space space already reserved. 869 * 870 * @return The space required to draw the axis (including pre-reserved 871 * space). 872 */ 873 public abstract AxisSpace reserveSpace(Graphics2D g2, Plot plot, 874 Rectangle2D plotArea, 875 RectangleEdge edge, 876 AxisSpace space); 877 878 /** 879 * Draws the axis on a Java 2D graphics device (such as the screen or a 880 * printer). 881 * 882 * @param g2 the graphics device (<code>null</code> not permitted). 883 * @param cursor the cursor location (determines where to draw the axis). 884 * @param plotArea the area within which the axes and plot should be drawn. 885 * @param dataArea the area within which the data should be drawn. 886 * @param edge the axis location (<code>null</code> not permitted). 887 * @param plotState collects information about the plot 888 * (<code>null</code> permitted). 889 * 890 * @return The axis state (never <code>null</code>). 891 */ 892 public abstract AxisState draw(Graphics2D g2, 893 double cursor, 894 Rectangle2D plotArea, 895 Rectangle2D dataArea, 896 RectangleEdge edge, 897 PlotRenderingInfo plotState); 898 899 /** 900 * Calculates the positions of the ticks for the axis, storing the results 901 * in the tick list (ready for drawing). 902 * 903 * @param g2 the graphics device. 904 * @param state the axis state. 905 * @param dataArea the area inside the axes. 906 * @param edge the edge on which the axis is located. 907 * 908 * @return The list of ticks. 909 */ 910 public abstract List refreshTicks(Graphics2D g2, 911 AxisState state, 912 Rectangle2D dataArea, 913 RectangleEdge edge); 914 915 /** 916 * Registers an object for notification of changes to the axis. 917 * 918 * @param listener the object that is being registered. 919 * 920 * @see #removeChangeListener(AxisChangeListener) 921 */ 922 public void addChangeListener(AxisChangeListener listener) { 923 this.listenerList.add(AxisChangeListener.class, listener); 924 } 925 926 /** 927 * Deregisters an object for notification of changes to the axis. 928 * 929 * @param listener the object to deregister. 930 * 931 * @see #addChangeListener(AxisChangeListener) 932 */ 933 public void removeChangeListener(AxisChangeListener listener) { 934 this.listenerList.remove(AxisChangeListener.class, listener); 935 } 936 937 /** 938 * Returns <code>true</code> if the specified object is registered with 939 * the dataset as a listener. Most applications won't need to call this 940 * method, it exists mainly for use by unit testing code. 941 * 942 * @param listener the listener. 943 * 944 * @return A boolean. 945 */ 946 public boolean hasListener(EventListener listener) { 947 List list = Arrays.asList(this.listenerList.getListenerList()); 948 return list.contains(listener); 949 } 950 951 /** 952 * Notifies all registered listeners that the axis has changed. 953 * The AxisChangeEvent provides information about the change. 954 * 955 * @param event information about the change to the axis. 956 */ 957 protected void notifyListeners(AxisChangeEvent event) { 958 959 Object[] listeners = this.listenerList.getListenerList(); 960 for (int i = listeners.length - 2; i >= 0; i -= 2) { 961 if (listeners[i] == AxisChangeListener.class) { 962 ((AxisChangeListener) listeners[i + 1]).axisChanged(event); 963 } 964 } 965 966 } 967 968 /** 969 * Returns a rectangle that encloses the axis label. This is typically 970 * used for layout purposes (it gives the maximum dimensions of the label). 971 * 972 * @param g2 the graphics device. 973 * @param edge the edge of the plot area along which the axis is measuring. 974 * 975 * @return The enclosing rectangle. 976 */ 977 protected Rectangle2D getLabelEnclosure(Graphics2D g2, RectangleEdge edge) { 978 979 Rectangle2D result = new Rectangle2D.Double(); 980 String axisLabel = getLabel(); 981 if (axisLabel != null && !axisLabel.equals("")) { 982 FontMetrics fm = g2.getFontMetrics(getLabelFont()); 983 Rectangle2D bounds = TextUtilities.getTextBounds(axisLabel, g2, fm); 984 RectangleInsets insets = getLabelInsets(); 985 bounds = insets.createOutsetRectangle(bounds); 986 double angle = getLabelAngle(); 987 if (edge == RectangleEdge.LEFT || edge == RectangleEdge.RIGHT) { 988 angle = angle - Math.PI / 2.0; 989 } 990 double x = bounds.getCenterX(); 991 double y = bounds.getCenterY(); 992 AffineTransform transformer 993 = AffineTransform.getRotateInstance(angle, x, y); 994 Shape labelBounds = transformer.createTransformedShape(bounds); 995 result = labelBounds.getBounds2D(); 996 } 997 998 return result; 999 1000 } 1001 1002 /** 1003 * Draws the axis label. 1004 * 1005 * @param label the label text. 1006 * @param g2 the graphics device. 1007 * @param plotArea the plot area. 1008 * @param dataArea the area inside the axes. 1009 * @param edge the location of the axis. 1010 * @param state the axis state (<code>null</code> not permitted). 1011 * 1012 * @return Information about the axis. 1013 */ 1014 protected AxisState drawLabel(String label, 1015 Graphics2D g2, 1016 Rectangle2D plotArea, 1017 Rectangle2D dataArea, 1018 RectangleEdge edge, 1019 AxisState state) { 1020 1021 // it is unlikely that 'state' will be null, but check anyway... 1022 if (state == null) { 1023 throw new IllegalArgumentException("Null 'state' argument."); 1024 } 1025 1026 if ((label == null) || (label.equals(""))) { 1027 return state; 1028 } 1029 1030 Font font = getLabelFont(); 1031 RectangleInsets insets = getLabelInsets(); 1032 g2.setFont(font); 1033 g2.setPaint(getLabelPaint()); 1034 FontMetrics fm = g2.getFontMetrics(); 1035 Rectangle2D labelBounds = TextUtilities.getTextBounds(label, g2, fm); 1036 1037 if (edge == RectangleEdge.TOP) { 1038 1039 AffineTransform t = AffineTransform.getRotateInstance( 1040 getLabelAngle(), labelBounds.getCenterX(), 1041 labelBounds.getCenterY()); 1042 Shape rotatedLabelBounds = t.createTransformedShape(labelBounds); 1043 labelBounds = rotatedLabelBounds.getBounds2D(); 1044 double labelx = dataArea.getCenterX(); 1045 double labely = state.getCursor() - insets.getBottom() 1046 - labelBounds.getHeight() / 2.0; 1047 TextUtilities.drawRotatedString(label, g2, (float) labelx, 1048 (float) labely, TextAnchor.CENTER, getLabelAngle(), 1049 TextAnchor.CENTER); 1050 state.cursorUp(insets.getTop() + labelBounds.getHeight() 1051 + insets.getBottom()); 1052 1053 } 1054 else if (edge == RectangleEdge.BOTTOM) { 1055 1056 AffineTransform t = AffineTransform.getRotateInstance( 1057 getLabelAngle(), labelBounds.getCenterX(), 1058 labelBounds.getCenterY()); 1059 Shape rotatedLabelBounds = t.createTransformedShape(labelBounds); 1060 labelBounds = rotatedLabelBounds.getBounds2D(); 1061 double labelx = dataArea.getCenterX(); 1062 double labely = state.getCursor() 1063 + insets.getTop() + labelBounds.getHeight() / 2.0; 1064 TextUtilities.drawRotatedString(label, g2, (float) labelx, 1065 (float) labely, TextAnchor.CENTER, getLabelAngle(), 1066 TextAnchor.CENTER); 1067 state.cursorDown(insets.getTop() + labelBounds.getHeight() 1068 + insets.getBottom()); 1069 1070 } 1071 else if (edge == RectangleEdge.LEFT) { 1072 1073 AffineTransform t = AffineTransform.getRotateInstance( 1074 getLabelAngle() - Math.PI / 2.0, labelBounds.getCenterX(), 1075 labelBounds.getCenterY()); 1076 Shape rotatedLabelBounds = t.createTransformedShape(labelBounds); 1077 labelBounds = rotatedLabelBounds.getBounds2D(); 1078 double labelx = state.getCursor() 1079 - insets.getRight() - labelBounds.getWidth() / 2.0; 1080 double labely = dataArea.getCenterY(); 1081 TextUtilities.drawRotatedString(label, g2, (float) labelx, 1082 (float) labely, TextAnchor.CENTER, 1083 getLabelAngle() - Math.PI / 2.0, TextAnchor.CENTER); 1084 state.cursorLeft(insets.getLeft() + labelBounds.getWidth() 1085 + insets.getRight()); 1086 } 1087 else if (edge == RectangleEdge.RIGHT) { 1088 1089 AffineTransform t = AffineTransform.getRotateInstance( 1090 getLabelAngle() + Math.PI / 2.0, 1091 labelBounds.getCenterX(), labelBounds.getCenterY()); 1092 Shape rotatedLabelBounds = t.createTransformedShape(labelBounds); 1093 labelBounds = rotatedLabelBounds.getBounds2D(); 1094 double labelx = state.getCursor() 1095 + insets.getLeft() + labelBounds.getWidth() / 2.0; 1096 double labely = dataArea.getY() + dataArea.getHeight() / 2.0; 1097 TextUtilities.drawRotatedString(label, g2, (float) labelx, 1098 (float) labely, TextAnchor.CENTER, 1099 getLabelAngle() + Math.PI / 2.0, TextAnchor.CENTER); 1100 state.cursorRight(insets.getLeft() + labelBounds.getWidth() 1101 + insets.getRight()); 1102 1103 } 1104 1105 return state; 1106 1107 } 1108 1109 /** 1110 * Draws an axis line at the current cursor position and edge. 1111 * 1112 * @param g2 the graphics device. 1113 * @param cursor the cursor position. 1114 * @param dataArea the data area. 1115 * @param edge the edge. 1116 */ 1117 protected void drawAxisLine(Graphics2D g2, double cursor, 1118 Rectangle2D dataArea, RectangleEdge edge) { 1119 1120 Line2D axisLine = null; 1121 if (edge == RectangleEdge.TOP) { 1122 axisLine = new Line2D.Double(dataArea.getX(), cursor, 1123 dataArea.getMaxX(), cursor); 1124 } 1125 else if (edge == RectangleEdge.BOTTOM) { 1126 axisLine = new Line2D.Double(dataArea.getX(), cursor, 1127 dataArea.getMaxX(), cursor); 1128 } 1129 else if (edge == RectangleEdge.LEFT) { 1130 axisLine = new Line2D.Double(cursor, dataArea.getY(), cursor, 1131 dataArea.getMaxY()); 1132 } 1133 else if (edge == RectangleEdge.RIGHT) { 1134 axisLine = new Line2D.Double(cursor, dataArea.getY(), cursor, 1135 dataArea.getMaxY()); 1136 } 1137 g2.setPaint(this.axisLinePaint); 1138 g2.setStroke(this.axisLineStroke); 1139 g2.draw(axisLine); 1140 1141 } 1142 1143 /** 1144 * Returns a clone of the axis. 1145 * 1146 * @return A clone. 1147 * 1148 * @throws CloneNotSupportedException if some component of the axis does 1149 * not support cloning. 1150 */ 1151 public Object clone() throws CloneNotSupportedException { 1152 Axis clone = (Axis) super.clone(); 1153 // It's up to the plot which clones up to restore the correct references 1154 clone.plot = null; 1155 clone.listenerList = new EventListenerList(); 1156 return clone; 1157 } 1158 1159 /** 1160 * Tests this axis for equality with another object. 1161 * 1162 * @param obj the object (<code>null</code> permitted). 1163 * 1164 * @return <code>true</code> or <code>false</code>. 1165 */ 1166 public boolean equals(Object obj) { 1167 if (obj == this) { 1168 return true; 1169 } 1170 if (!(obj instanceof Axis)) { 1171 return false; 1172 } 1173 Axis that = (Axis) obj; 1174 if (this.visible != that.visible) { 1175 return false; 1176 } 1177 if (!ObjectUtilities.equal(this.label, that.label)) { 1178 return false; 1179 } 1180 if (!ObjectUtilities.equal(this.labelFont, that.labelFont)) { 1181 return false; 1182 } 1183 if (!PaintUtilities.equal(this.labelPaint, that.labelPaint)) { 1184 return false; 1185 } 1186 if (!ObjectUtilities.equal(this.labelInsets, that.labelInsets)) { 1187 return false; 1188 } 1189 if (this.labelAngle != that.labelAngle) { 1190 return false; 1191 } 1192 if (this.axisLineVisible != that.axisLineVisible) { 1193 return false; 1194 } 1195 if (!ObjectUtilities.equal(this.axisLineStroke, that.axisLineStroke)) { 1196 return false; 1197 } 1198 if (!PaintUtilities.equal(this.axisLinePaint, that.axisLinePaint)) { 1199 return false; 1200 } 1201 if (this.tickLabelsVisible != that.tickLabelsVisible) { 1202 return false; 1203 } 1204 if (!ObjectUtilities.equal(this.tickLabelFont, that.tickLabelFont)) { 1205 return false; 1206 } 1207 if (!PaintUtilities.equal(this.tickLabelPaint, that.tickLabelPaint)) { 1208 return false; 1209 } 1210 if (!ObjectUtilities.equal( 1211 this.tickLabelInsets, that.tickLabelInsets 1212 )) { 1213 return false; 1214 } 1215 if (this.tickMarksVisible != that.tickMarksVisible) { 1216 return false; 1217 } 1218 if (this.tickMarkInsideLength != that.tickMarkInsideLength) { 1219 return false; 1220 } 1221 if (this.tickMarkOutsideLength != that.tickMarkOutsideLength) { 1222 return false; 1223 } 1224 if (!PaintUtilities.equal(this.tickMarkPaint, that.tickMarkPaint)) { 1225 return false; 1226 } 1227 if (!ObjectUtilities.equal(this.tickMarkStroke, that.tickMarkStroke)) { 1228 return false; 1229 } 1230 if (this.fixedDimension != that.fixedDimension) { 1231 return false; 1232 } 1233 return true; 1234 } 1235 1236 /** 1237 * Provides serialization support. 1238 * 1239 * @param stream the output stream. 1240 * 1241 * @throws IOException if there is an I/O error. 1242 */ 1243 private void writeObject(ObjectOutputStream stream) throws IOException { 1244 stream.defaultWriteObject(); 1245 SerialUtilities.writePaint(this.labelPaint, stream); 1246 SerialUtilities.writePaint(this.tickLabelPaint, stream); 1247 SerialUtilities.writeStroke(this.axisLineStroke, stream); 1248 SerialUtilities.writePaint(this.axisLinePaint, stream); 1249 SerialUtilities.writeStroke(this.tickMarkStroke, stream); 1250 SerialUtilities.writePaint(this.tickMarkPaint, stream); 1251 } 1252 1253 /** 1254 * Provides serialization support. 1255 * 1256 * @param stream the input stream. 1257 * 1258 * @throws IOException if there is an I/O error. 1259 * @throws ClassNotFoundException if there is a classpath problem. 1260 */ 1261 private void readObject(ObjectInputStream stream) 1262 throws IOException, ClassNotFoundException { 1263 stream.defaultReadObject(); 1264 this.labelPaint = SerialUtilities.readPaint(stream); 1265 this.tickLabelPaint = SerialUtilities.readPaint(stream); 1266 this.axisLineStroke = SerialUtilities.readStroke(stream); 1267 this.axisLinePaint = SerialUtilities.readPaint(stream); 1268 this.tickMarkStroke = SerialUtilities.readStroke(stream); 1269 this.tickMarkPaint = SerialUtilities.readPaint(stream); 1270 this.listenerList = new EventListenerList(); 1271 } 1272 1273 }