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