001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2013, 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 025 * Other names may be trademarks of their respective owners.] 026 * 027 * --------- 028 * Axis.java 029 * --------- 030 * (C) Copyright 2000-2013, by Object Refinery Limited and Contributors. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): Bill Kelemen; 034 * Nicolas Brodu; 035 * Peter Kolb (patches 1934255 and 2603321); 036 * Andrew Mickish (patch 1870189); 037 * 038 * Changes 039 * ------- 040 * 21-Aug-2001 : Added standard header, fixed DOS encoding problem (DG); 041 * 18-Sep-2001 : Updated header (DG); 042 * 07-Nov-2001 : Allow null axis labels (DG); 043 * : Added default font values (DG); 044 * 13-Nov-2001 : Modified the setPlot() method to check compatibility between 045 * the axis and the plot (DG); 046 * 30-Nov-2001 : Changed default font from "Arial" --> "SansSerif" (DG); 047 * 06-Dec-2001 : Allow null in setPlot() method (BK); 048 * 06-Mar-2002 : Added AxisConstants interface (DG); 049 * 23-Apr-2002 : Added a visible property. Moved drawVerticalString to 050 * RefineryUtilities. Added fixedDimension property for use in 051 * combined plots (DG); 052 * 25-Jun-2002 : Removed unnecessary imports (DG); 053 * 05-Sep-2002 : Added attribute for tick mark paint (DG); 054 * 18-Sep-2002 : Fixed errors reported by Checkstyle (DG); 055 * 07-Nov-2002 : Added attributes to control the inside and outside length of 056 * the tick marks (DG); 057 * 08-Nov-2002 : Moved to new package com.jrefinery.chart.axis (DG); 058 * 18-Nov-2002 : Added axis location to refreshTicks() parameters (DG); 059 * 15-Jan-2003 : Removed monolithic constructor (DG); 060 * 17-Jan-2003 : Moved plot classes to separate package (DG); 061 * 26-Mar-2003 : Implemented Serializable (DG); 062 * 03-Jul-2003 : Modified reserveSpace method (DG); 063 * 13-Aug-2003 : Implemented Cloneable (DG); 064 * 11-Sep-2003 : Took care of listeners while cloning (NB); 065 * 29-Oct-2003 : Added workaround for font alignment in PDF output (DG); 066 * 06-Nov-2003 : Modified refreshTicks() signature (DG); 067 * 06-Jan-2004 : Added axis line attributes (DG); 068 * 16-Mar-2004 : Added plot state to draw() method (DG); 069 * 07-Apr-2004 : Modified text bounds calculation (DG); 070 * 18-May-2004 : Eliminated AxisConstants.java (DG); 071 * 30-Sep-2004 : Moved drawRotatedString() from RefineryUtilities --> 072 * TextUtilities (DG); 073 * 04-Oct-2004 : Modified getLabelEnclosure() method to treat an empty String 074 * the same way as a null string - see bug 1026521 (DG); 075 * 21-Apr-2005 : Replaced Insets with RectangleInsets (DG); 076 * 26-Apr-2005 : Removed LOGGER (DG); 077 * 01-Jun-2005 : Added hasListener() method for unit testing (DG); 078 * 08-Jun-2005 : Fixed equals() method to handle GradientPaint (DG); 079 * ------------- JFREECHART 1.0.x --------------------------------------------- 080 * 22-Aug-2006 : API doc updates (DG); 081 * 06-Jun-2008 : Added setTickLabelInsets(RectangleInsets, boolean) (DG); 082 * 25-Sep-2008 : Added minor tick support, see patch 1934255 by Peter Kolb (DG); 083 * 26-Sep-2008 : Added fireChangeEvent() method (DG); 084 * 19-Mar-2009 : Added entity support - see patch 2603321 by Peter Kolb (DG); 085 * 02-Jul-2013 : Use ParamChecks (DG); 086 * 01-Aug-2013 : Added attributedLabel override to support superscripts, 087 * subscripts and more (DG); 088 */ 089 090package org.jfree.chart.axis; 091 092import java.awt.BasicStroke; 093import java.awt.Color; 094import java.awt.Font; 095import java.awt.FontMetrics; 096import java.awt.Graphics2D; 097import java.awt.Paint; 098import java.awt.Shape; 099import java.awt.Stroke; 100import java.awt.font.TextLayout; 101import java.awt.geom.AffineTransform; 102import java.awt.geom.Line2D; 103import java.awt.geom.Rectangle2D; 104import java.io.IOException; 105import java.io.ObjectInputStream; 106import java.io.ObjectOutputStream; 107import java.io.Serializable; 108import java.text.AttributedString; 109import java.util.Arrays; 110import java.util.EventListener; 111import java.util.List; 112 113import javax.swing.event.EventListenerList; 114 115import org.jfree.chart.entity.AxisEntity; 116import org.jfree.chart.entity.EntityCollection; 117import org.jfree.chart.event.AxisChangeEvent; 118import org.jfree.chart.event.AxisChangeListener; 119import org.jfree.chart.plot.Plot; 120import org.jfree.chart.plot.PlotRenderingInfo; 121import org.jfree.chart.util.AttrStringUtils; 122import org.jfree.chart.util.ParamChecks; 123import org.jfree.io.SerialUtilities; 124import org.jfree.text.TextUtilities; 125import org.jfree.ui.RectangleEdge; 126import org.jfree.ui.RectangleInsets; 127import org.jfree.ui.TextAnchor; 128import org.jfree.util.AttributedStringUtilities; 129import org.jfree.util.ObjectUtilities; 130import org.jfree.util.PaintUtilities; 131 132/** 133 * The base class for all axes in JFreeChart. Subclasses are divided into 134 * those that display values ({@link ValueAxis}) and those that display 135 * categories ({@link CategoryAxis}). 136 */ 137public abstract class Axis implements Cloneable, Serializable { 138 139 /** For serialization. */ 140 private static final long serialVersionUID = 7719289504573298271L; 141 142 /** The default axis visibility. */ 143 public static final boolean DEFAULT_AXIS_VISIBLE = true; 144 145 /** The default axis label font. */ 146 public static final Font DEFAULT_AXIS_LABEL_FONT = new Font( 147 "SansSerif", Font.PLAIN, 12); 148 149 /** The default axis label paint. */ 150 public static final Paint DEFAULT_AXIS_LABEL_PAINT = Color.black; 151 152 /** The default axis label insets. */ 153 public static final RectangleInsets DEFAULT_AXIS_LABEL_INSETS 154 = new RectangleInsets(3.0, 3.0, 3.0, 3.0); 155 156 /** The default axis line paint. */ 157 public static final Paint DEFAULT_AXIS_LINE_PAINT = Color.gray; 158 159 /** The default axis line stroke. */ 160 public static final Stroke DEFAULT_AXIS_LINE_STROKE = new BasicStroke(1.0f); 161 162 /** The default tick labels visibility. */ 163 public static final boolean DEFAULT_TICK_LABELS_VISIBLE = true; 164 165 /** The default tick label font. */ 166 public static final Font DEFAULT_TICK_LABEL_FONT = new Font("SansSerif", 167 Font.PLAIN, 10); 168 169 /** The default tick label paint. */ 170 public static final Paint DEFAULT_TICK_LABEL_PAINT = Color.black; 171 172 /** The default tick label insets. */ 173 public static final RectangleInsets DEFAULT_TICK_LABEL_INSETS 174 = new RectangleInsets(2.0, 4.0, 2.0, 4.0); 175 176 /** The default tick marks visible. */ 177 public static final boolean DEFAULT_TICK_MARKS_VISIBLE = true; 178 179 /** The default tick stroke. */ 180 public static final Stroke DEFAULT_TICK_MARK_STROKE = new BasicStroke(1); 181 182 /** The default tick paint. */ 183 public static final Paint DEFAULT_TICK_MARK_PAINT = Color.gray; 184 185 /** The default tick mark inside length. */ 186 public static final float DEFAULT_TICK_MARK_INSIDE_LENGTH = 0.0f; 187 188 /** The default tick mark outside length. */ 189 public static final float DEFAULT_TICK_MARK_OUTSIDE_LENGTH = 2.0f; 190 191 /** A flag indicating whether or not the axis is visible. */ 192 private boolean visible; 193 194 /** The label for the axis. */ 195 private String label; 196 197 /** 198 * An attributed label for the axis (overrides label if non-null). 199 * We have to use this override method to preserve the API compatibility. 200 */ 201 private transient AttributedString attributedLabel; 202 203 /** The font for displaying the axis label. */ 204 private Font labelFont; 205 206 /** The paint for drawing the axis label. */ 207 private transient Paint labelPaint; 208 209 /** The insets for the axis label. */ 210 private RectangleInsets labelInsets; 211 212 /** The label angle. */ 213 private double labelAngle; 214 215 /** The axis label location (new in 1.0.16). */ 216 private AxisLabelLocation labelLocation; 217 218 /** A flag that controls whether or not the axis line is visible. */ 219 private boolean axisLineVisible; 220 221 /** The stroke used for the axis line. */ 222 private transient Stroke axisLineStroke; 223 224 /** The paint used for the axis line. */ 225 private transient Paint axisLinePaint; 226 227 /** 228 * A flag that indicates whether or not tick labels are visible for the 229 * axis. 230 */ 231 private boolean tickLabelsVisible; 232 233 /** The font used to display the tick labels. */ 234 private Font tickLabelFont; 235 236 /** The color used to display the tick labels. */ 237 private transient Paint tickLabelPaint; 238 239 /** The blank space around each tick label. */ 240 private RectangleInsets tickLabelInsets; 241 242 /** 243 * A flag that indicates whether or not major tick marks are visible for 244 * the axis. 245 */ 246 private boolean tickMarksVisible; 247 248 /** 249 * The length of the major tick mark inside the data area (zero 250 * permitted). 251 */ 252 private float tickMarkInsideLength; 253 254 /** 255 * The length of the major tick mark outside the data area (zero 256 * permitted). 257 */ 258 private float tickMarkOutsideLength; 259 260 /** 261 * A flag that indicates whether or not minor tick marks are visible for the 262 * axis. 263 * 264 * @since 1.0.12 265 */ 266 private boolean minorTickMarksVisible; 267 268 /** 269 * The length of the minor tick mark inside the data area (zero permitted). 270 * 271 * @since 1.0.12 272 */ 273 private float minorTickMarkInsideLength; 274 275 /** 276 * The length of the minor tick mark outside the data area (zero permitted). 277 * 278 * @since 1.0.12 279 */ 280 private float minorTickMarkOutsideLength; 281 282 /** The stroke used to draw tick marks. */ 283 private transient Stroke tickMarkStroke; 284 285 /** The paint used to draw tick marks. */ 286 private transient Paint tickMarkPaint; 287 288 /** The fixed (horizontal or vertical) dimension for the axis. */ 289 private double fixedDimension; 290 291 /** 292 * A reference back to the plot that the axis is assigned to (can be 293 * <code>null</code>). 294 */ 295 private transient Plot plot; 296 297 /** Storage for registered listeners. */ 298 private transient EventListenerList listenerList; 299 300 /** 301 * Constructs an axis, using default values where necessary. 302 * 303 * @param label the axis label (<code>null</code> permitted). 304 */ 305 protected Axis(String label) { 306 307 this.label = label; 308 this.visible = DEFAULT_AXIS_VISIBLE; 309 this.labelFont = DEFAULT_AXIS_LABEL_FONT; 310 this.labelPaint = DEFAULT_AXIS_LABEL_PAINT; 311 this.labelInsets = DEFAULT_AXIS_LABEL_INSETS; 312 this.labelAngle = 0.0; 313 this.labelLocation = AxisLabelLocation.MIDDLE; 314 315 this.axisLineVisible = true; 316 this.axisLinePaint = DEFAULT_AXIS_LINE_PAINT; 317 this.axisLineStroke = DEFAULT_AXIS_LINE_STROKE; 318 319 this.tickLabelsVisible = DEFAULT_TICK_LABELS_VISIBLE; 320 this.tickLabelFont = DEFAULT_TICK_LABEL_FONT; 321 this.tickLabelPaint = DEFAULT_TICK_LABEL_PAINT; 322 this.tickLabelInsets = DEFAULT_TICK_LABEL_INSETS; 323 324 this.tickMarksVisible = DEFAULT_TICK_MARKS_VISIBLE; 325 this.tickMarkStroke = DEFAULT_TICK_MARK_STROKE; 326 this.tickMarkPaint = DEFAULT_TICK_MARK_PAINT; 327 this.tickMarkInsideLength = DEFAULT_TICK_MARK_INSIDE_LENGTH; 328 this.tickMarkOutsideLength = DEFAULT_TICK_MARK_OUTSIDE_LENGTH; 329 330 this.minorTickMarksVisible = false; 331 this.minorTickMarkInsideLength = 0.0f; 332 this.minorTickMarkOutsideLength = 2.0f; 333 334 this.plot = null; 335 336 this.listenerList = new EventListenerList(); 337 } 338 339 /** 340 * Returns <code>true</code> if the axis is visible, and 341 * <code>false</code> otherwise. 342 * 343 * @return A boolean. 344 * 345 * @see #setVisible(boolean) 346 */ 347 public boolean isVisible() { 348 return this.visible; 349 } 350 351 /** 352 * Sets a flag that controls whether or not the axis is visible and sends 353 * an {@link AxisChangeEvent} to all registered listeners. 354 * 355 * @param flag the flag. 356 * 357 * @see #isVisible() 358 */ 359 public void setVisible(boolean flag) { 360 if (flag != this.visible) { 361 this.visible = flag; 362 fireChangeEvent(); 363 } 364 } 365 366 /** 367 * Returns the label for the axis. 368 * 369 * @return The label for the axis (<code>null</code> possible). 370 * 371 * @see #getLabelFont() 372 * @see #getLabelPaint() 373 * @see #setLabel(String) 374 */ 375 public String getLabel() { 376 return this.label; 377 } 378 379 /** 380 * Sets the label for the axis and sends an {@link AxisChangeEvent} to all 381 * registered listeners. 382 * 383 * @param label the new label (<code>null</code> permitted). 384 * 385 * @see #getLabel() 386 * @see #setLabelFont(Font) 387 * @see #setLabelPaint(Paint) 388 */ 389 public void setLabel(String label) { 390 this.label = label; 391 fireChangeEvent(); 392 } 393 394 /** 395 * Returns the attributed label (the returned value is a copy, so 396 * modifying it will not impact the state of the axis). The default value 397 * is <code>null</code>. 398 * 399 * @return The attributed label (possibly <code>null</code>). 400 * 401 * @since 1.0.16 402 */ 403 public AttributedString getAttributedLabel() { 404 if (this.attributedLabel != null) { 405 return new AttributedString(this.attributedLabel.getIterator()); 406 } else { 407 return null; 408 } 409 } 410 411 /** 412 * Sets the attributed label for the axis and sends an 413 * {@link AxisChangeEvent} to all registered listeners. This is a 414 * convenience method that converts the string into an 415 * <code>AttributedString</code> using the current font attributes. 416 * 417 * @param label the label (<code>null</<code> permitted). 418 * 419 * @since 1.0.16 420 */ 421 public void setAttributedLabel(String label) { 422 setAttributedLabel(createAttributedLabel(label)); 423 } 424 425 /** 426 * Sets the attributed label for the axis and sends an 427 * {@link AxisChangeEvent} to all registered listeners. 428 * 429 * @param label the label (<code>null</code> permitted). 430 * 431 * @since 1.0.16 432 */ 433 public void setAttributedLabel(AttributedString label) { 434 if (label != null) { 435 this.attributedLabel = new AttributedString(label.getIterator()); 436 } else { 437 this.attributedLabel = null; 438 } 439 fireChangeEvent(); 440 } 441 442 /** 443 * Creates and returns an <code>AttributedString</code> with the specified 444 * text and the labelFont and labelPaint applied as attributes. 445 * 446 * @param label the label (<code>null</code> permitted). 447 * 448 * @return An attributed string or <code>null</code>. 449 * 450 * @since 1.0.16 451 */ 452 public AttributedString createAttributedLabel(String label) { 453 if (label == null) { 454 return null; 455 } 456 AttributedString s = new AttributedString(label); 457 s.addAttributes(this.labelFont.getAttributes(), 0, label.length()); 458 return s; 459 } 460 461 /** 462 * Returns the font for the axis label. 463 * 464 * @return The font (never <code>null</code>). 465 * 466 * @see #setLabelFont(Font) 467 */ 468 public Font getLabelFont() { 469 return this.labelFont; 470 } 471 472 /** 473 * Sets the font for the axis label and sends an {@link AxisChangeEvent} 474 * to all registered listeners. 475 * 476 * @param font the font (<code>null</code> not permitted). 477 * 478 * @see #getLabelFont() 479 */ 480 public void setLabelFont(Font font) { 481 ParamChecks.nullNotPermitted(font, "font"); 482 if (!this.labelFont.equals(font)) { 483 this.labelFont = font; 484 fireChangeEvent(); 485 } 486 } 487 488 /** 489 * Returns the color/shade used to draw the axis label. 490 * 491 * @return The paint (never <code>null</code>). 492 * 493 * @see #setLabelPaint(Paint) 494 */ 495 public Paint getLabelPaint() { 496 return this.labelPaint; 497 } 498 499 /** 500 * Sets the paint used to draw the axis label and sends an 501 * {@link AxisChangeEvent} to all registered listeners. 502 * 503 * @param paint the paint (<code>null</code> not permitted). 504 * 505 * @see #getLabelPaint() 506 */ 507 public void setLabelPaint(Paint paint) { 508 ParamChecks.nullNotPermitted(paint, "paint"); 509 this.labelPaint = paint; 510 fireChangeEvent(); 511 } 512 513 /** 514 * Returns the insets for the label (that is, the amount of blank space 515 * that should be left around the label). 516 * 517 * @return The label insets (never <code>null</code>). 518 * 519 * @see #setLabelInsets(RectangleInsets) 520 */ 521 public RectangleInsets getLabelInsets() { 522 return this.labelInsets; 523 } 524 525 /** 526 * Sets the insets for the axis label, and sends an {@link AxisChangeEvent} 527 * to all registered listeners. 528 * 529 * @param insets the insets (<code>null</code> not permitted). 530 * 531 * @see #getLabelInsets() 532 */ 533 public void setLabelInsets(RectangleInsets insets) { 534 setLabelInsets(insets, true); 535 } 536 537 /** 538 * Sets the insets for the axis label, and sends an {@link AxisChangeEvent} 539 * to all registered listeners. 540 * 541 * @param insets the insets (<code>null</code> not permitted). 542 * @param notify notify listeners? 543 * 544 * @since 1.0.10 545 */ 546 public void setLabelInsets(RectangleInsets insets, boolean notify) { 547 ParamChecks.nullNotPermitted(insets, "insets"); 548 if (!insets.equals(this.labelInsets)) { 549 this.labelInsets = insets; 550 if (notify) { 551 fireChangeEvent(); 552 } 553 } 554 } 555 556 /** 557 * Returns the angle of the axis label. 558 * 559 * @return The angle (in radians). 560 * 561 * @see #setLabelAngle(double) 562 */ 563 public double getLabelAngle() { 564 return this.labelAngle; 565 } 566 567 /** 568 * Sets the angle for the label and sends an {@link AxisChangeEvent} to all 569 * registered listeners. 570 * 571 * @param angle the angle (in radians). 572 * 573 * @see #getLabelAngle() 574 */ 575 public void setLabelAngle(double angle) { 576 this.labelAngle = angle; 577 fireChangeEvent(); 578 } 579 580 /** 581 * Returns the location of the axis label. The default is 582 * {@link AxisLabelLocation#MIDDLE}. 583 * 584 * @return The location of the axis label (never <code>null</code>). 585 * 586 * @since 1.0.16 587 */ 588 public AxisLabelLocation getLabelLocation() { 589 return this.labelLocation; 590 } 591 592 /** 593 * Sets the axis label location and sends an {@link AxisChangeEvent} to 594 * all registered listeners. 595 * 596 * @param location the new location (<code>null</code> not permitted). 597 * 598 * @since 1.0.16 599 */ 600 public void setLabelLocation(AxisLabelLocation location) { 601 ParamChecks.nullNotPermitted(location, "location"); 602 this.labelLocation = location; 603 fireChangeEvent(); 604 } 605 606 /** 607 * A flag that controls whether or not the axis line is drawn. 608 * 609 * @return A boolean. 610 * 611 * @see #getAxisLinePaint() 612 * @see #getAxisLineStroke() 613 * @see #setAxisLineVisible(boolean) 614 */ 615 public boolean isAxisLineVisible() { 616 return this.axisLineVisible; 617 } 618 619 /** 620 * Sets a flag that controls whether or not the axis line is visible and 621 * sends an {@link AxisChangeEvent} to all registered listeners. 622 * 623 * @param visible the flag. 624 * 625 * @see #isAxisLineVisible() 626 * @see #setAxisLinePaint(Paint) 627 * @see #setAxisLineStroke(Stroke) 628 */ 629 public void setAxisLineVisible(boolean visible) { 630 this.axisLineVisible = visible; 631 fireChangeEvent(); 632 } 633 634 /** 635 * Returns the paint used to draw the axis line. 636 * 637 * @return The paint (never <code>null</code>). 638 * 639 * @see #setAxisLinePaint(Paint) 640 */ 641 public Paint getAxisLinePaint() { 642 return this.axisLinePaint; 643 } 644 645 /** 646 * Sets the paint used to draw the axis line and sends an 647 * {@link AxisChangeEvent} to all registered listeners. 648 * 649 * @param paint the paint (<code>null</code> not permitted). 650 * 651 * @see #getAxisLinePaint() 652 */ 653 public void setAxisLinePaint(Paint paint) { 654 ParamChecks.nullNotPermitted(paint, "paint"); 655 this.axisLinePaint = paint; 656 fireChangeEvent(); 657 } 658 659 /** 660 * Returns the stroke used to draw the axis line. 661 * 662 * @return The stroke (never <code>null</code>). 663 * 664 * @see #setAxisLineStroke(Stroke) 665 */ 666 public Stroke getAxisLineStroke() { 667 return this.axisLineStroke; 668 } 669 670 /** 671 * Sets the stroke used to draw the axis line and sends an 672 * {@link AxisChangeEvent} to all registered listeners. 673 * 674 * @param stroke the stroke (<code>null</code> not permitted). 675 * 676 * @see #getAxisLineStroke() 677 */ 678 public void setAxisLineStroke(Stroke stroke) { 679 ParamChecks.nullNotPermitted(stroke, "stroke"); 680 this.axisLineStroke = stroke; 681 fireChangeEvent(); 682 } 683 684 /** 685 * Returns a flag indicating whether or not the tick labels are visible. 686 * 687 * @return The flag. 688 * 689 * @see #getTickLabelFont() 690 * @see #getTickLabelPaint() 691 * @see #setTickLabelsVisible(boolean) 692 */ 693 public boolean isTickLabelsVisible() { 694 return this.tickLabelsVisible; 695 } 696 697 /** 698 * Sets the flag that determines whether or not the tick labels are 699 * visible and sends an {@link AxisChangeEvent} to all registered 700 * listeners. 701 * 702 * @param flag the flag. 703 * 704 * @see #isTickLabelsVisible() 705 * @see #setTickLabelFont(Font) 706 * @see #setTickLabelPaint(Paint) 707 */ 708 public void setTickLabelsVisible(boolean flag) { 709 710 if (flag != this.tickLabelsVisible) { 711 this.tickLabelsVisible = flag; 712 fireChangeEvent(); 713 } 714 715 } 716 717 /** 718 * Returns the flag that indicates whether or not the minor tick marks are 719 * showing. 720 * 721 * @return The flag that indicates whether or not the minor tick marks are 722 * showing. 723 * 724 * @see #setMinorTickMarksVisible(boolean) 725 * 726 * @since 1.0.12 727 */ 728 public boolean isMinorTickMarksVisible() { 729 return this.minorTickMarksVisible; 730 } 731 732 /** 733 * Sets the flag that indicates whether or not the minor tick marks are 734 * showing and sends an {@link AxisChangeEvent} to all registered 735 * listeners. 736 * 737 * @param flag the flag. 738 * 739 * @see #isMinorTickMarksVisible() 740 * 741 * @since 1.0.12 742 */ 743 public void setMinorTickMarksVisible(boolean flag) { 744 if (flag != this.minorTickMarksVisible) { 745 this.minorTickMarksVisible = flag; 746 fireChangeEvent(); 747 } 748 } 749 750 /** 751 * Returns the font used for the tick labels (if showing). 752 * 753 * @return The font (never <code>null</code>). 754 * 755 * @see #setTickLabelFont(Font) 756 */ 757 public Font getTickLabelFont() { 758 return this.tickLabelFont; 759 } 760 761 /** 762 * Sets the font for the tick labels and sends an {@link AxisChangeEvent} 763 * to all registered listeners. 764 * 765 * @param font the font (<code>null</code> not allowed). 766 * 767 * @see #getTickLabelFont() 768 */ 769 public void setTickLabelFont(Font font) { 770 ParamChecks.nullNotPermitted(font, "font"); 771 if (!this.tickLabelFont.equals(font)) { 772 this.tickLabelFont = font; 773 fireChangeEvent(); 774 } 775 } 776 777 /** 778 * Returns the color/shade used for the tick labels. 779 * 780 * @return The paint used for the tick labels. 781 * 782 * @see #setTickLabelPaint(Paint) 783 */ 784 public Paint getTickLabelPaint() { 785 return this.tickLabelPaint; 786 } 787 788 /** 789 * Sets the paint used to draw tick labels (if they are showing) and 790 * sends an {@link AxisChangeEvent} to all registered listeners. 791 * 792 * @param paint the paint (<code>null</code> not permitted). 793 * 794 * @see #getTickLabelPaint() 795 */ 796 public void setTickLabelPaint(Paint paint) { 797 ParamChecks.nullNotPermitted(paint, "paint"); 798 this.tickLabelPaint = paint; 799 fireChangeEvent(); 800 } 801 802 /** 803 * Returns the insets for the tick labels. 804 * 805 * @return The insets (never <code>null</code>). 806 * 807 * @see #setTickLabelInsets(RectangleInsets) 808 */ 809 public RectangleInsets getTickLabelInsets() { 810 return this.tickLabelInsets; 811 } 812 813 /** 814 * Sets the insets for the tick labels and sends an {@link AxisChangeEvent} 815 * to all registered listeners. 816 * 817 * @param insets the insets (<code>null</code> not permitted). 818 * 819 * @see #getTickLabelInsets() 820 */ 821 public void setTickLabelInsets(RectangleInsets insets) { 822 ParamChecks.nullNotPermitted(insets, "insets"); 823 if (!this.tickLabelInsets.equals(insets)) { 824 this.tickLabelInsets = insets; 825 fireChangeEvent(); 826 } 827 } 828 829 /** 830 * Returns the flag that indicates whether or not the tick marks are 831 * showing. 832 * 833 * @return The flag that indicates whether or not the tick marks are 834 * showing. 835 * 836 * @see #setTickMarksVisible(boolean) 837 */ 838 public boolean isTickMarksVisible() { 839 return this.tickMarksVisible; 840 } 841 842 /** 843 * Sets the flag that indicates whether or not the tick marks are showing 844 * and sends an {@link AxisChangeEvent} to all registered listeners. 845 * 846 * @param flag the flag. 847 * 848 * @see #isTickMarksVisible() 849 */ 850 public void setTickMarksVisible(boolean flag) { 851 if (flag != this.tickMarksVisible) { 852 this.tickMarksVisible = flag; 853 fireChangeEvent(); 854 } 855 } 856 857 /** 858 * Returns the inside length of the tick marks. 859 * 860 * @return The length. 861 * 862 * @see #getTickMarkOutsideLength() 863 * @see #setTickMarkInsideLength(float) 864 */ 865 public float getTickMarkInsideLength() { 866 return this.tickMarkInsideLength; 867 } 868 869 /** 870 * Sets the inside length of the tick marks and sends 871 * an {@link AxisChangeEvent} to all registered listeners. 872 * 873 * @param length the new length. 874 * 875 * @see #getTickMarkInsideLength() 876 */ 877 public void setTickMarkInsideLength(float length) { 878 this.tickMarkInsideLength = length; 879 fireChangeEvent(); 880 } 881 882 /** 883 * Returns the outside length of the tick marks. 884 * 885 * @return The length. 886 * 887 * @see #getTickMarkInsideLength() 888 * @see #setTickMarkOutsideLength(float) 889 */ 890 public float getTickMarkOutsideLength() { 891 return this.tickMarkOutsideLength; 892 } 893 894 /** 895 * Sets the outside length of the tick marks and sends 896 * an {@link AxisChangeEvent} to all registered listeners. 897 * 898 * @param length the new length. 899 * 900 * @see #getTickMarkInsideLength() 901 */ 902 public void setTickMarkOutsideLength(float length) { 903 this.tickMarkOutsideLength = length; 904 fireChangeEvent(); 905 } 906 907 /** 908 * Returns the stroke used to draw tick marks. 909 * 910 * @return The stroke (never <code>null</code>). 911 * 912 * @see #setTickMarkStroke(Stroke) 913 */ 914 public Stroke getTickMarkStroke() { 915 return this.tickMarkStroke; 916 } 917 918 /** 919 * Sets the stroke used to draw tick marks and sends 920 * an {@link AxisChangeEvent} to all registered listeners. 921 * 922 * @param stroke the stroke (<code>null</code> not permitted). 923 * 924 * @see #getTickMarkStroke() 925 */ 926 public void setTickMarkStroke(Stroke stroke) { 927 ParamChecks.nullNotPermitted(stroke, "stroke"); 928 if (!this.tickMarkStroke.equals(stroke)) { 929 this.tickMarkStroke = stroke; 930 fireChangeEvent(); 931 } 932 } 933 934 /** 935 * Returns the paint used to draw tick marks (if they are showing). 936 * 937 * @return The paint (never <code>null</code>). 938 * 939 * @see #setTickMarkPaint(Paint) 940 */ 941 public Paint getTickMarkPaint() { 942 return this.tickMarkPaint; 943 } 944 945 /** 946 * Sets the paint used to draw tick marks and sends an 947 * {@link AxisChangeEvent} to all registered listeners. 948 * 949 * @param paint the paint (<code>null</code> not permitted). 950 * 951 * @see #getTickMarkPaint() 952 */ 953 public void setTickMarkPaint(Paint paint) { 954 ParamChecks.nullNotPermitted(paint, "paint"); 955 this.tickMarkPaint = paint; 956 fireChangeEvent(); 957 } 958 959 /** 960 * Returns the inside length of the minor tick marks. 961 * 962 * @return The length. 963 * 964 * @see #getMinorTickMarkOutsideLength() 965 * @see #setMinorTickMarkInsideLength(float) 966 * 967 * @since 1.0.12 968 */ 969 public float getMinorTickMarkInsideLength() { 970 return this.minorTickMarkInsideLength; 971 } 972 973 /** 974 * Sets the inside length of the minor tick marks and sends 975 * an {@link AxisChangeEvent} to all registered listeners. 976 * 977 * @param length the new length. 978 * 979 * @see #getMinorTickMarkInsideLength() 980 * 981 * @since 1.0.12 982 */ 983 public void setMinorTickMarkInsideLength(float length) { 984 this.minorTickMarkInsideLength = length; 985 fireChangeEvent(); 986 } 987 988 /** 989 * Returns the outside length of the minor tick marks. 990 * 991 * @return The length. 992 * 993 * @see #getMinorTickMarkInsideLength() 994 * @see #setMinorTickMarkOutsideLength(float) 995 * 996 * @since 1.0.12 997 */ 998 public float getMinorTickMarkOutsideLength() { 999 return this.minorTickMarkOutsideLength; 1000 } 1001 1002 /** 1003 * Sets the outside length of the minor tick marks and sends 1004 * an {@link AxisChangeEvent} to all registered listeners. 1005 * 1006 * @param length the new length. 1007 * 1008 * @see #getMinorTickMarkInsideLength() 1009 * 1010 * @since 1.0.12 1011 */ 1012 public void setMinorTickMarkOutsideLength(float length) { 1013 this.minorTickMarkOutsideLength = length; 1014 fireChangeEvent(); 1015 } 1016 1017 /** 1018 * Returns the plot that the axis is assigned to. This method will return 1019 * <code>null</code> if the axis is not currently assigned to a plot. 1020 * 1021 * @return The plot that the axis is assigned to (possibly 1022 * <code>null</code>). 1023 * 1024 * @see #setPlot(Plot) 1025 */ 1026 public Plot getPlot() { 1027 return this.plot; 1028 } 1029 1030 /** 1031 * Sets a reference to the plot that the axis is assigned to. 1032 * <P> 1033 * This method is used internally, you shouldn't need to call it yourself. 1034 * 1035 * @param plot the plot. 1036 * 1037 * @see #getPlot() 1038 */ 1039 public void setPlot(Plot plot) { 1040 this.plot = plot; 1041 configure(); 1042 } 1043 1044 /** 1045 * Returns the fixed dimension for the axis. 1046 * 1047 * @return The fixed dimension. 1048 * 1049 * @see #setFixedDimension(double) 1050 */ 1051 public double getFixedDimension() { 1052 return this.fixedDimension; 1053 } 1054 1055 /** 1056 * Sets the fixed dimension for the axis. 1057 * <P> 1058 * This is used when combining more than one plot on a chart. In this case, 1059 * there may be several axes that need to have the same height or width so 1060 * that they are aligned. This method is used to fix a dimension for the 1061 * axis (the context determines whether the dimension is horizontal or 1062 * vertical). 1063 * 1064 * @param dimension the fixed dimension. 1065 * 1066 * @see #getFixedDimension() 1067 */ 1068 public void setFixedDimension(double dimension) { 1069 this.fixedDimension = dimension; 1070 } 1071 1072 /** 1073 * Configures the axis to work with the current plot. Override this method 1074 * to perform any special processing (such as auto-rescaling). 1075 */ 1076 public abstract void configure(); 1077 1078 /** 1079 * Estimates the space (height or width) required to draw the axis. 1080 * 1081 * @param g2 the graphics device. 1082 * @param plot the plot that the axis belongs to. 1083 * @param plotArea the area within which the plot (including axes) should 1084 * be drawn. 1085 * @param edge the axis location. 1086 * @param space space already reserved. 1087 * 1088 * @return The space required to draw the axis (including pre-reserved 1089 * space). 1090 */ 1091 public abstract AxisSpace reserveSpace(Graphics2D g2, Plot plot, 1092 Rectangle2D plotArea, 1093 RectangleEdge edge, 1094 AxisSpace space); 1095 1096 /** 1097 * Draws the axis on a Java 2D graphics device (such as the screen or a 1098 * printer). 1099 * 1100 * @param g2 the graphics device (<code>null</code> not permitted). 1101 * @param cursor the cursor location (determines where to draw the axis). 1102 * @param plotArea the area within which the axes and plot should be drawn. 1103 * @param dataArea the area within which the data should be drawn. 1104 * @param edge the axis location (<code>null</code> not permitted). 1105 * @param plotState collects information about the plot 1106 * (<code>null</code> permitted). 1107 * 1108 * @return The axis state (never <code>null</code>). 1109 */ 1110 public abstract AxisState draw(Graphics2D g2, 1111 double cursor, 1112 Rectangle2D plotArea, 1113 Rectangle2D dataArea, 1114 RectangleEdge edge, 1115 PlotRenderingInfo plotState); 1116 1117 /** 1118 * Calculates the positions of the ticks for the axis, storing the results 1119 * in the tick list (ready for drawing). 1120 * 1121 * @param g2 the graphics device. 1122 * @param state the axis state. 1123 * @param dataArea the area inside the axes. 1124 * @param edge the edge on which the axis is located. 1125 * 1126 * @return The list of ticks. 1127 */ 1128 public abstract List refreshTicks(Graphics2D g2, AxisState state, 1129 Rectangle2D dataArea, RectangleEdge edge); 1130 1131 /** 1132 * Created an entity for the axis. 1133 * 1134 * @param cursor the initial cursor value. 1135 * @param state the axis state after completion of the drawing with a 1136 * possibly updated cursor position. 1137 * @param dataArea the data area. 1138 * @param edge the edge. 1139 * @param plotState the PlotRenderingInfo from which a reference to the 1140 * entity collection can be obtained. 1141 * 1142 * @since 1.0.13 1143 */ 1144 protected void createAndAddEntity(double cursor, AxisState state, 1145 Rectangle2D dataArea, RectangleEdge edge, 1146 PlotRenderingInfo plotState) { 1147 1148 if (plotState == null || plotState.getOwner() == null) { 1149 return; // no need to create entity if we can't save it anyways... 1150 } 1151 Rectangle2D hotspot = null; 1152 if (edge.equals(RectangleEdge.TOP)) { 1153 hotspot = new Rectangle2D.Double(dataArea.getX(), 1154 state.getCursor(), dataArea.getWidth(), 1155 cursor - state.getCursor()); 1156 } 1157 else if (edge.equals(RectangleEdge.BOTTOM)) { 1158 hotspot = new Rectangle2D.Double(dataArea.getX(), cursor, 1159 dataArea.getWidth(), state.getCursor() - cursor); 1160 } 1161 else if (edge.equals(RectangleEdge.LEFT)) { 1162 hotspot = new Rectangle2D.Double(state.getCursor(), 1163 dataArea.getY(), cursor - state.getCursor(), 1164 dataArea.getHeight()); 1165 } 1166 else if (edge.equals(RectangleEdge.RIGHT)) { 1167 hotspot = new Rectangle2D.Double(cursor, dataArea.getY(), 1168 state.getCursor() - cursor, dataArea.getHeight()); 1169 } 1170 EntityCollection e = plotState.getOwner().getEntityCollection(); 1171 if (e != null) { 1172 e.add(new AxisEntity(hotspot, this)); 1173 } 1174 } 1175 1176 /** 1177 * Registers an object for notification of changes to the axis. 1178 * 1179 * @param listener the object that is being registered. 1180 * 1181 * @see #removeChangeListener(AxisChangeListener) 1182 */ 1183 public void addChangeListener(AxisChangeListener listener) { 1184 this.listenerList.add(AxisChangeListener.class, listener); 1185 } 1186 1187 /** 1188 * Deregisters an object for notification of changes to the axis. 1189 * 1190 * @param listener the object to deregister. 1191 * 1192 * @see #addChangeListener(AxisChangeListener) 1193 */ 1194 public void removeChangeListener(AxisChangeListener listener) { 1195 this.listenerList.remove(AxisChangeListener.class, listener); 1196 } 1197 1198 /** 1199 * Returns <code>true</code> if the specified object is registered with 1200 * the dataset as a listener. Most applications won't need to call this 1201 * method, it exists mainly for use by unit testing code. 1202 * 1203 * @param listener the listener. 1204 * 1205 * @return A boolean. 1206 */ 1207 public boolean hasListener(EventListener listener) { 1208 List list = Arrays.asList(this.listenerList.getListenerList()); 1209 return list.contains(listener); 1210 } 1211 1212 /** 1213 * Notifies all registered listeners that the axis has changed. 1214 * The AxisChangeEvent provides information about the change. 1215 * 1216 * @param event information about the change to the axis. 1217 */ 1218 protected void notifyListeners(AxisChangeEvent event) { 1219 Object[] listeners = this.listenerList.getListenerList(); 1220 for (int i = listeners.length - 2; i >= 0; i -= 2) { 1221 if (listeners[i] == AxisChangeListener.class) { 1222 ((AxisChangeListener) listeners[i + 1]).axisChanged(event); 1223 } 1224 } 1225 } 1226 1227 /** 1228 * Sends an {@link AxisChangeEvent} to all registered listeners. 1229 * 1230 * @since 1.0.12 1231 */ 1232 protected void fireChangeEvent() { 1233 notifyListeners(new AxisChangeEvent(this)); 1234 } 1235 1236 /** 1237 * Returns a rectangle that encloses the axis label. This is typically 1238 * used for layout purposes (it gives the maximum dimensions of the label). 1239 * 1240 * @param g2 the graphics device. 1241 * @param edge the edge of the plot area along which the axis is measuring. 1242 * 1243 * @return The enclosing rectangle. 1244 */ 1245 protected Rectangle2D getLabelEnclosure(Graphics2D g2, RectangleEdge edge) { 1246 Rectangle2D result = new Rectangle2D.Double(); 1247 Rectangle2D bounds = null; 1248 if (this.attributedLabel != null) { 1249 TextLayout layout = new TextLayout( 1250 this.attributedLabel.getIterator(), 1251 g2.getFontRenderContext()); 1252 bounds = layout.getBounds(); 1253 } else { 1254 String axisLabel = getLabel(); 1255 if (axisLabel != null && !axisLabel.equals("")) { 1256 FontMetrics fm = g2.getFontMetrics(getLabelFont()); 1257 bounds = TextUtilities.getTextBounds(axisLabel, g2, fm); 1258 } 1259 } 1260 if (bounds != null) { 1261 RectangleInsets insets = getLabelInsets(); 1262 bounds = insets.createOutsetRectangle(bounds); 1263 double angle = getLabelAngle(); 1264 if (edge == RectangleEdge.LEFT || edge == RectangleEdge.RIGHT) { 1265 angle = angle - Math.PI / 2.0; 1266 } 1267 double x = bounds.getCenterX(); 1268 double y = bounds.getCenterY(); 1269 AffineTransform transformer 1270 = AffineTransform.getRotateInstance(angle, x, y); 1271 Shape labelBounds = transformer.createTransformedShape(bounds); 1272 result = labelBounds.getBounds2D(); 1273 } 1274 return result; 1275 } 1276 1277 protected double labelLocationX(AxisLabelLocation location, 1278 Rectangle2D dataArea) { 1279 if (location.equals(AxisLabelLocation.HIGH_END)) { 1280 return dataArea.getMaxX(); 1281 } 1282 if (location.equals(AxisLabelLocation.MIDDLE)) { 1283 return dataArea.getCenterX(); 1284 } 1285 if (location.equals(AxisLabelLocation.LOW_END)) { 1286 return dataArea.getMinX(); 1287 } 1288 throw new RuntimeException("Unexpected AxisLabelLocation: " + location); 1289 } 1290 1291 protected double labelLocationY(AxisLabelLocation location, 1292 Rectangle2D dataArea) { 1293 if (location.equals(AxisLabelLocation.HIGH_END)) { 1294 return dataArea.getMinY(); 1295 } 1296 if (location.equals(AxisLabelLocation.MIDDLE)) { 1297 return dataArea.getCenterY(); 1298 } 1299 if (location.equals(AxisLabelLocation.LOW_END)) { 1300 return dataArea.getMaxY(); 1301 } 1302 throw new RuntimeException("Unexpected AxisLabelLocation: " + location); 1303 } 1304 1305 protected TextAnchor labelAnchorH(AxisLabelLocation location) { 1306 if (location.equals(AxisLabelLocation.HIGH_END)) { 1307 return TextAnchor.CENTER_RIGHT; 1308 } 1309 if (location.equals(AxisLabelLocation.MIDDLE)) { 1310 return TextAnchor.CENTER; 1311 } 1312 if (location.equals(AxisLabelLocation.LOW_END)) { 1313 return TextAnchor.CENTER_LEFT; 1314 } 1315 throw new RuntimeException("Unexpected AxisLabelLocation: " + location); 1316 } 1317 1318 protected TextAnchor labelAnchorV(AxisLabelLocation location) { 1319 if (location.equals(AxisLabelLocation.HIGH_END)) { 1320 return TextAnchor.CENTER_RIGHT; 1321 } 1322 if (location.equals(AxisLabelLocation.MIDDLE)) { 1323 return TextAnchor.CENTER; 1324 } 1325 if (location.equals(AxisLabelLocation.LOW_END)) { 1326 return TextAnchor.CENTER_LEFT; 1327 } 1328 throw new RuntimeException("Unexpected AxisLabelLocation: " + location); 1329 } 1330 1331 /** 1332 * Draws the axis label. 1333 * 1334 * @param label the label text. 1335 * @param g2 the graphics device. 1336 * @param plotArea the plot area. 1337 * @param dataArea the area inside the axes. 1338 * @param edge the location of the axis. 1339 * @param state the axis state (<code>null</code> not permitted). 1340 * 1341 * @return Information about the axis. 1342 */ 1343 protected AxisState drawLabel(String label, Graphics2D g2, 1344 Rectangle2D plotArea, Rectangle2D dataArea, RectangleEdge edge, 1345 AxisState state) { 1346 1347 // it is unlikely that 'state' will be null, but check anyway... 1348 ParamChecks.nullNotPermitted(state, "state"); 1349 1350 if ((label == null) || (label.equals(""))) { 1351 return state; 1352 } 1353 1354 Font font = getLabelFont(); 1355 RectangleInsets insets = getLabelInsets(); 1356 g2.setFont(font); 1357 g2.setPaint(getLabelPaint()); 1358 FontMetrics fm = g2.getFontMetrics(); 1359 Rectangle2D labelBounds = TextUtilities.getTextBounds(label, g2, fm); 1360 1361 if (edge == RectangleEdge.TOP) { 1362 AffineTransform t = AffineTransform.getRotateInstance( 1363 getLabelAngle(), labelBounds.getCenterX(), 1364 labelBounds.getCenterY()); 1365 Shape rotatedLabelBounds = t.createTransformedShape(labelBounds); 1366 labelBounds = rotatedLabelBounds.getBounds2D(); 1367 double labelx = labelLocationX(this.labelLocation, dataArea); 1368 double labely = state.getCursor() - insets.getBottom() 1369 - labelBounds.getHeight() / 2.0; 1370 TextAnchor anchor = labelAnchorH(this.labelLocation); 1371 TextUtilities.drawRotatedString(label, g2, (float) labelx, 1372 (float) labely, anchor, getLabelAngle(), TextAnchor.CENTER); 1373 state.cursorUp(insets.getTop() + labelBounds.getHeight() 1374 + insets.getBottom()); 1375 } 1376 else if (edge == RectangleEdge.BOTTOM) { 1377 AffineTransform t = AffineTransform.getRotateInstance( 1378 getLabelAngle(), labelBounds.getCenterX(), 1379 labelBounds.getCenterY()); 1380 Shape rotatedLabelBounds = t.createTransformedShape(labelBounds); 1381 labelBounds = rotatedLabelBounds.getBounds2D(); 1382 double labelx = labelLocationX(this.labelLocation, dataArea); 1383 double labely = state.getCursor() 1384 + insets.getTop() + labelBounds.getHeight() / 2.0; 1385 TextAnchor anchor = labelAnchorH(this.labelLocation); 1386 TextUtilities.drawRotatedString(label, g2, (float) labelx, 1387 (float) labely, anchor, getLabelAngle(), TextAnchor.CENTER); 1388 state.cursorDown(insets.getTop() + labelBounds.getHeight() 1389 + insets.getBottom()); 1390 } 1391 else if (edge == RectangleEdge.LEFT) { 1392 AffineTransform t = AffineTransform.getRotateInstance( 1393 getLabelAngle() - Math.PI / 2.0, labelBounds.getCenterX(), 1394 labelBounds.getCenterY()); 1395 Shape rotatedLabelBounds = t.createTransformedShape(labelBounds); 1396 labelBounds = rotatedLabelBounds.getBounds2D(); 1397 double labelx = state.getCursor() 1398 - insets.getRight() - labelBounds.getWidth() / 2.0; 1399 double labely = labelLocationY(this.labelLocation, dataArea); 1400 TextAnchor anchor = labelAnchorV(this.labelLocation); 1401 TextUtilities.drawRotatedString(label, g2, (float) labelx, 1402 (float) labely, anchor, getLabelAngle() - Math.PI / 2.0, 1403 anchor); 1404 state.cursorLeft(insets.getLeft() + labelBounds.getWidth() 1405 + insets.getRight()); 1406 } 1407 else if (edge == RectangleEdge.RIGHT) { 1408 AffineTransform t = AffineTransform.getRotateInstance( 1409 getLabelAngle() + Math.PI / 2.0, 1410 labelBounds.getCenterX(), labelBounds.getCenterY()); 1411 Shape rotatedLabelBounds = t.createTransformedShape(labelBounds); 1412 labelBounds = rotatedLabelBounds.getBounds2D(); 1413 double labelx = state.getCursor() 1414 + insets.getLeft() + labelBounds.getWidth() / 2.0; 1415 double labely = labelLocationY(this.labelLocation, dataArea); 1416 TextAnchor anchor = labelAnchorV(this.labelLocation); 1417 TextUtilities.drawRotatedString(label, g2, (float) labelx, 1418 (float) labely, anchor, getLabelAngle() + Math.PI / 2.0, 1419 anchor); 1420 state.cursorRight(insets.getLeft() + labelBounds.getWidth() 1421 + insets.getRight()); 1422 } 1423 1424 return state; 1425 1426 } 1427 1428 /** 1429 * Draws the axis label. 1430 * 1431 * @param label the label text. 1432 * @param g2 the graphics device. 1433 * @param plotArea the plot area. 1434 * @param dataArea the area inside the axes. 1435 * @param edge the location of the axis. 1436 * @param state the axis state (<code>null</code> not permitted). 1437 * 1438 * @return Information about the axis. 1439 * 1440 * @since 1.0.16 1441 */ 1442 protected AxisState drawAttributedLabel(AttributedString label, 1443 Graphics2D g2, Rectangle2D plotArea, Rectangle2D dataArea, 1444 RectangleEdge edge, AxisState state) { 1445 1446 // it is unlikely that 'state' will be null, but check anyway... 1447 ParamChecks.nullNotPermitted(state, "state"); 1448 1449 if (label == null) { 1450 return state; 1451 } 1452 1453 RectangleInsets insets = getLabelInsets(); 1454 g2.setFont(getLabelFont()); 1455 g2.setPaint(getLabelPaint()); 1456 TextLayout layout = new TextLayout(this.attributedLabel.getIterator(), 1457 g2.getFontRenderContext()); 1458 Rectangle2D labelBounds = layout.getBounds(); 1459 1460 if (edge == RectangleEdge.TOP) { 1461 AffineTransform t = AffineTransform.getRotateInstance( 1462 getLabelAngle(), labelBounds.getCenterX(), 1463 labelBounds.getCenterY()); 1464 Shape rotatedLabelBounds = t.createTransformedShape(labelBounds); 1465 labelBounds = rotatedLabelBounds.getBounds2D(); 1466 double labelx = labelLocationX(this.labelLocation, dataArea); 1467 double labely = state.getCursor() - insets.getBottom() 1468 - labelBounds.getHeight() / 2.0; 1469 TextAnchor anchor = labelAnchorH(this.labelLocation); 1470 AttrStringUtils.drawRotatedString(label, g2, (float) labelx, 1471 (float) labely, anchor, getLabelAngle(), TextAnchor.CENTER); 1472 state.cursorUp(insets.getTop() + labelBounds.getHeight() 1473 + insets.getBottom()); 1474 } 1475 else if (edge == RectangleEdge.BOTTOM) { 1476 AffineTransform t = AffineTransform.getRotateInstance( 1477 getLabelAngle(), labelBounds.getCenterX(), 1478 labelBounds.getCenterY()); 1479 Shape rotatedLabelBounds = t.createTransformedShape(labelBounds); 1480 labelBounds = rotatedLabelBounds.getBounds2D(); 1481 double labelx = labelLocationX(this.labelLocation, dataArea); 1482 double labely = state.getCursor() 1483 + insets.getTop() + labelBounds.getHeight() / 2.0; 1484 TextAnchor anchor = labelAnchorH(this.labelLocation); 1485 AttrStringUtils.drawRotatedString(label, g2, (float) labelx, 1486 (float) labely, anchor, getLabelAngle(), TextAnchor.CENTER); 1487 state.cursorDown(insets.getTop() + labelBounds.getHeight() 1488 + insets.getBottom()); 1489 } 1490 else if (edge == RectangleEdge.LEFT) { 1491 AffineTransform t = AffineTransform.getRotateInstance( 1492 getLabelAngle() - Math.PI / 2.0, labelBounds.getCenterX(), 1493 labelBounds.getCenterY()); 1494 Shape rotatedLabelBounds = t.createTransformedShape(labelBounds); 1495 labelBounds = rotatedLabelBounds.getBounds2D(); 1496 double labelx = state.getCursor() 1497 - insets.getRight() - labelBounds.getWidth() / 2.0; 1498 double labely = labelLocationY(this.labelLocation, dataArea); 1499 TextAnchor anchor = labelAnchorV(this.labelLocation); 1500 AttrStringUtils.drawRotatedString(label, g2, (float) labelx, 1501 (float) labely, anchor, getLabelAngle() - Math.PI / 2.0, 1502 anchor); 1503 state.cursorLeft(insets.getLeft() + labelBounds.getWidth() 1504 + insets.getRight()); 1505 } 1506 else if (edge == RectangleEdge.RIGHT) { 1507 AffineTransform t = AffineTransform.getRotateInstance( 1508 getLabelAngle() + Math.PI / 2.0, 1509 labelBounds.getCenterX(), labelBounds.getCenterY()); 1510 Shape rotatedLabelBounds = t.createTransformedShape(labelBounds); 1511 labelBounds = rotatedLabelBounds.getBounds2D(); 1512 double labelx = state.getCursor() 1513 + insets.getLeft() + labelBounds.getWidth() / 2.0; 1514 double labely = labelLocationY(this.labelLocation, dataArea); 1515 TextAnchor anchor = labelAnchorV(this.labelLocation); 1516 AttrStringUtils.drawRotatedString(label, g2, (float) labelx, 1517 (float) labely, anchor, getLabelAngle() + Math.PI / 2.0, 1518 anchor); 1519 state.cursorRight(insets.getLeft() + labelBounds.getWidth() 1520 + insets.getRight()); 1521 } 1522 return state; 1523 } 1524 1525 /** 1526 * Draws an axis line at the current cursor position and edge. 1527 * 1528 * @param g2 the graphics device. 1529 * @param cursor the cursor position. 1530 * @param dataArea the data area. 1531 * @param edge the edge. 1532 */ 1533 protected void drawAxisLine(Graphics2D g2, double cursor, 1534 Rectangle2D dataArea, RectangleEdge edge) { 1535 1536 Line2D axisLine = null; 1537 if (edge == RectangleEdge.TOP) { 1538 axisLine = new Line2D.Double(dataArea.getX(), cursor, 1539 dataArea.getMaxX(), cursor); 1540 } 1541 else if (edge == RectangleEdge.BOTTOM) { 1542 axisLine = new Line2D.Double(dataArea.getX(), cursor, 1543 dataArea.getMaxX(), cursor); 1544 } 1545 else if (edge == RectangleEdge.LEFT) { 1546 axisLine = new Line2D.Double(cursor, dataArea.getY(), cursor, 1547 dataArea.getMaxY()); 1548 } 1549 else if (edge == RectangleEdge.RIGHT) { 1550 axisLine = new Line2D.Double(cursor, dataArea.getY(), cursor, 1551 dataArea.getMaxY()); 1552 } 1553 g2.setPaint(this.axisLinePaint); 1554 g2.setStroke(this.axisLineStroke); 1555 g2.draw(axisLine); 1556 1557 } 1558 1559 /** 1560 * Returns a clone of the axis. 1561 * 1562 * @return A clone. 1563 * 1564 * @throws CloneNotSupportedException if some component of the axis does 1565 * not support cloning. 1566 */ 1567 @Override 1568 public Object clone() throws CloneNotSupportedException { 1569 Axis clone = (Axis) super.clone(); 1570 // It's up to the plot which clones up to restore the correct references 1571 clone.plot = null; 1572 clone.listenerList = new EventListenerList(); 1573 return clone; 1574 } 1575 1576 /** 1577 * Tests this axis for equality with another object. 1578 * 1579 * @param obj the object (<code>null</code> permitted). 1580 * 1581 * @return <code>true</code> or <code>false</code>. 1582 */ 1583 @Override 1584 public boolean equals(Object obj) { 1585 if (obj == this) { 1586 return true; 1587 } 1588 if (!(obj instanceof Axis)) { 1589 return false; 1590 } 1591 Axis that = (Axis) obj; 1592 if (this.visible != that.visible) { 1593 return false; 1594 } 1595 if (!ObjectUtilities.equal(this.label, that.label)) { 1596 return false; 1597 } 1598 if (!AttributedStringUtilities.equal(this.attributedLabel, 1599 that.attributedLabel)) { 1600 return false; 1601 } 1602 if (!ObjectUtilities.equal(this.labelFont, that.labelFont)) { 1603 return false; 1604 } 1605 if (!PaintUtilities.equal(this.labelPaint, that.labelPaint)) { 1606 return false; 1607 } 1608 if (!ObjectUtilities.equal(this.labelInsets, that.labelInsets)) { 1609 return false; 1610 } 1611 if (this.labelAngle != that.labelAngle) { 1612 return false; 1613 } 1614 if (!this.labelLocation.equals(that.labelLocation)) { 1615 return false; 1616 } 1617 if (this.axisLineVisible != that.axisLineVisible) { 1618 return false; 1619 } 1620 if (!ObjectUtilities.equal(this.axisLineStroke, that.axisLineStroke)) { 1621 return false; 1622 } 1623 if (!PaintUtilities.equal(this.axisLinePaint, that.axisLinePaint)) { 1624 return false; 1625 } 1626 if (this.tickLabelsVisible != that.tickLabelsVisible) { 1627 return false; 1628 } 1629 if (!ObjectUtilities.equal(this.tickLabelFont, that.tickLabelFont)) { 1630 return false; 1631 } 1632 if (!PaintUtilities.equal(this.tickLabelPaint, that.tickLabelPaint)) { 1633 return false; 1634 } 1635 if (!ObjectUtilities.equal( 1636 this.tickLabelInsets, that.tickLabelInsets 1637 )) { 1638 return false; 1639 } 1640 if (this.tickMarksVisible != that.tickMarksVisible) { 1641 return false; 1642 } 1643 if (this.tickMarkInsideLength != that.tickMarkInsideLength) { 1644 return false; 1645 } 1646 if (this.tickMarkOutsideLength != that.tickMarkOutsideLength) { 1647 return false; 1648 } 1649 if (!PaintUtilities.equal(this.tickMarkPaint, that.tickMarkPaint)) { 1650 return false; 1651 } 1652 if (!ObjectUtilities.equal(this.tickMarkStroke, that.tickMarkStroke)) { 1653 return false; 1654 } 1655 if (this.minorTickMarksVisible != that.minorTickMarksVisible) { 1656 return false; 1657 } 1658 if (this.minorTickMarkInsideLength != that.minorTickMarkInsideLength) { 1659 return false; 1660 } 1661 if (this.minorTickMarkOutsideLength 1662 != that.minorTickMarkOutsideLength) { 1663 return false; 1664 } 1665 if (this.fixedDimension != that.fixedDimension) { 1666 return false; 1667 } 1668 return true; 1669 } 1670 1671 /** 1672 * Returns a hash code for this instance. 1673 * 1674 * @return A hash code. 1675 */ 1676 @Override 1677 public int hashCode() { 1678 int hash = 3; 1679 if (this.label != null) { 1680 hash = 83 * hash + this.label.hashCode(); 1681 } 1682 return hash; 1683 } 1684 1685 /** 1686 * Provides serialization support. 1687 * 1688 * @param stream the output stream. 1689 * 1690 * @throws IOException if there is an I/O error. 1691 */ 1692 private void writeObject(ObjectOutputStream stream) throws IOException { 1693 stream.defaultWriteObject(); 1694 SerialUtilities.writeAttributedString(this.attributedLabel, stream); 1695 SerialUtilities.writePaint(this.labelPaint, stream); 1696 SerialUtilities.writePaint(this.tickLabelPaint, stream); 1697 SerialUtilities.writeStroke(this.axisLineStroke, stream); 1698 SerialUtilities.writePaint(this.axisLinePaint, stream); 1699 SerialUtilities.writeStroke(this.tickMarkStroke, stream); 1700 SerialUtilities.writePaint(this.tickMarkPaint, stream); 1701 } 1702 1703 /** 1704 * Provides serialization support. 1705 * 1706 * @param stream the input stream. 1707 * 1708 * @throws IOException if there is an I/O error. 1709 * @throws ClassNotFoundException if there is a classpath problem. 1710 */ 1711 private void readObject(ObjectInputStream stream) 1712 throws IOException, ClassNotFoundException { 1713 stream.defaultReadObject(); 1714 this.attributedLabel = SerialUtilities.readAttributedString(stream); 1715 this.labelPaint = SerialUtilities.readPaint(stream); 1716 this.tickLabelPaint = SerialUtilities.readPaint(stream); 1717 this.axisLineStroke = SerialUtilities.readStroke(stream); 1718 this.axisLinePaint = SerialUtilities.readPaint(stream); 1719 this.tickMarkStroke = SerialUtilities.readStroke(stream); 1720 this.tickMarkPaint = SerialUtilities.readPaint(stream); 1721 this.listenerList = new EventListenerList(); 1722 } 1723 1724}