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