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