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