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