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