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 * Plot.java 029 * --------- 030 * (C) Copyright 2000-present, by David Gilbert and Contributors. 031 * 032 * Original Author: David Gilbert; 033 * Contributor(s): Sylvain Vieujot; 034 * Jeremy Bowman; 035 * Andreas Schneider; 036 * Gideon Krause; 037 * Nicolas Brodu; 038 * Michal Krause; 039 * Richard West, Advanced Micro Devices, Inc.; 040 * Peter Kolb - patches 2603321, 2809117; 041 * Tracy Hiltbrand (equals/hashCode comply with EqualsVerifier); 042 * 043 */ 044 045package org.jfree.chart.plot; 046 047import java.awt.AlphaComposite; 048import java.awt.BasicStroke; 049import java.awt.Color; 050import java.awt.Composite; 051import java.awt.Font; 052import java.awt.GradientPaint; 053import java.awt.Graphics2D; 054import java.awt.Image; 055import java.awt.Paint; 056import java.awt.RenderingHints; 057import java.awt.Shape; 058import java.awt.Stroke; 059import java.awt.geom.Ellipse2D; 060import java.awt.geom.Point2D; 061import java.awt.geom.Rectangle2D; 062import java.io.IOException; 063import java.io.ObjectInputStream; 064import java.io.ObjectOutputStream; 065import java.io.Serializable; 066import java.util.Objects; 067 068import javax.swing.event.EventListenerList; 069 070import org.jfree.chart.JFreeChart; 071import org.jfree.chart.LegendItemCollection; 072import org.jfree.chart.LegendItemSource; 073import org.jfree.chart.annotations.Annotation; 074import org.jfree.chart.axis.AxisLocation; 075import org.jfree.chart.entity.EntityCollection; 076import org.jfree.chart.entity.PlotEntity; 077import org.jfree.chart.event.AnnotationChangeEvent; 078import org.jfree.chart.event.AnnotationChangeListener; 079import org.jfree.chart.event.AxisChangeEvent; 080import org.jfree.chart.event.AxisChangeListener; 081import org.jfree.chart.event.ChartChangeEventType; 082import org.jfree.chart.event.MarkerChangeEvent; 083import org.jfree.chart.event.MarkerChangeListener; 084import org.jfree.chart.event.PlotChangeEvent; 085import org.jfree.chart.event.PlotChangeListener; 086import org.jfree.chart.text.G2TextMeasurer; 087import org.jfree.chart.text.TextBlock; 088import org.jfree.chart.text.TextBlockAnchor; 089import org.jfree.chart.text.TextUtils; 090import org.jfree.chart.ui.Align; 091import org.jfree.chart.ui.RectangleEdge; 092import org.jfree.chart.ui.RectangleInsets; 093import org.jfree.chart.util.ObjectUtils; 094import org.jfree.chart.util.PaintUtils; 095import org.jfree.chart.util.Args; 096import org.jfree.chart.util.PublicCloneable; 097import org.jfree.chart.util.SerialUtils; 098import org.jfree.data.general.DatasetChangeEvent; 099import org.jfree.data.general.DatasetChangeListener; 100import org.jfree.data.general.DatasetGroup; 101 102/** 103 * The base class for all plots in JFreeChart. The {@link JFreeChart} class 104 * delegates the drawing of axes and data to the plot. This base class 105 * provides facilities common to most plot types. 106 */ 107public abstract class Plot implements AxisChangeListener, 108 DatasetChangeListener, AnnotationChangeListener, MarkerChangeListener, 109 LegendItemSource, PublicCloneable, Cloneable, Serializable { 110 111 /** For serialization. */ 112 private static final long serialVersionUID = -8831571430103671324L; 113 114 /** Useful constant representing zero. */ 115 public static final Number ZERO = 0; 116 117 /** The default insets. */ 118 public static final RectangleInsets DEFAULT_INSETS 119 = new RectangleInsets(4.0, 8.0, 4.0, 8.0); 120 121 /** The default outline stroke. */ 122 public static final Stroke DEFAULT_OUTLINE_STROKE = new BasicStroke(0.5f, 123 BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND); 124 125 /** The default outline color. */ 126 public static final Paint DEFAULT_OUTLINE_PAINT = Color.GRAY; 127 128 /** The default foreground alpha transparency. */ 129 public static final float DEFAULT_FOREGROUND_ALPHA = 1.0f; 130 131 /** The default background alpha transparency. */ 132 public static final float DEFAULT_BACKGROUND_ALPHA = 1.0f; 133 134 /** The default background color. */ 135 public static final Paint DEFAULT_BACKGROUND_PAINT = Color.WHITE; 136 137 /** The minimum width at which the plot should be drawn. */ 138 public static final int MINIMUM_WIDTH_TO_DRAW = 10; 139 140 /** The minimum height at which the plot should be drawn. */ 141 public static final int MINIMUM_HEIGHT_TO_DRAW = 10; 142 143 /** A default box shape for legend items. */ 144 public static final Shape DEFAULT_LEGEND_ITEM_BOX 145 = new Rectangle2D.Double(-4.0, -4.0, 8.0, 8.0); 146 147 /** A default circle shape for legend items. */ 148 public static final Shape DEFAULT_LEGEND_ITEM_CIRCLE 149 = new Ellipse2D.Double(-4.0, -4.0, 8.0, 8.0); 150 151 /** 152 * The chart that the plot is assigned to. It can be {@code null} if the 153 * plot is not assigned to a chart yet, or if the plot is a subplot of a 154 * another plot. 155 */ 156 private JFreeChart chart; 157 158 /** The parent plot ({@code null} if this is the root plot). */ 159 private Plot parent; 160 161 /** The dataset group (to be used for thread synchronisation). */ 162 private DatasetGroup datasetGroup; 163 164 /** The message to display if no data is available. */ 165 private String noDataMessage; 166 167 /** The font used to display the 'no data' message. */ 168 private Font noDataMessageFont; 169 170 /** The paint used to draw the 'no data' message. */ 171 private transient Paint noDataMessagePaint; 172 173 /** Amount of blank space around the plot area. */ 174 private RectangleInsets insets; 175 176 /** 177 * A flag that controls whether or not the plot outline is drawn. 178 */ 179 private boolean outlineVisible; 180 181 /** The Stroke used to draw an outline around the plot. */ 182 private transient Stroke outlineStroke; 183 184 /** The Paint used to draw an outline around the plot. */ 185 private transient Paint outlinePaint; 186 187 /** An optional color used to fill the plot background. */ 188 private transient Paint backgroundPaint; 189 190 /** An optional image for the plot background. */ 191 private transient Image backgroundImage; // not currently serialized 192 193 /** The alignment for the background image. */ 194 private int backgroundImageAlignment = Align.FIT; 195 196 /** The alpha value used to draw the background image. */ 197 private float backgroundImageAlpha = 0.5f; 198 199 /** The alpha-transparency for the plot. */ 200 private float foregroundAlpha; 201 202 /** The alpha transparency for the background paint. */ 203 private float backgroundAlpha; 204 205 /** The drawing supplier. */ 206 private DrawingSupplier drawingSupplier; 207 208 /** Storage for registered change listeners. */ 209 private transient EventListenerList listenerList; 210 211 /** 212 * A flag that controls whether or not the plot will notify listeners 213 * of changes (defaults to true, but sometimes it is useful to disable 214 * this). 215 */ 216 private boolean notify; 217 218 /** 219 * Creates a new plot. 220 */ 221 protected Plot() { 222 this.chart = null; 223 this.parent = null; 224 this.insets = DEFAULT_INSETS; 225 this.backgroundPaint = DEFAULT_BACKGROUND_PAINT; 226 this.backgroundAlpha = DEFAULT_BACKGROUND_ALPHA; 227 this.backgroundImage = null; 228 this.outlineVisible = true; 229 this.outlineStroke = DEFAULT_OUTLINE_STROKE; 230 this.outlinePaint = DEFAULT_OUTLINE_PAINT; 231 this.foregroundAlpha = DEFAULT_FOREGROUND_ALPHA; 232 233 this.noDataMessage = null; 234 this.noDataMessageFont = new Font("SansSerif", Font.PLAIN, 12); 235 this.noDataMessagePaint = Color.BLACK; 236 237 this.drawingSupplier = new DefaultDrawingSupplier(); 238 239 this.notify = true; 240 this.listenerList = new EventListenerList(); 241 } 242 243 /** 244 * Returns the chart that this plot is assigned to. This method can 245 * return {@code null} if the plot is not yet assigned to a plot, or if the 246 * plot is a subplot of another plot. 247 * 248 * @return The chart (possibly {@code null}). 249 */ 250 public JFreeChart getChart() { 251 return this.chart; 252 } 253 254 /** 255 * Sets the chart that the plot is assigned to. This method is not 256 * intended for external use. 257 * 258 * @param chart the chart ({@code null} permitted). 259 */ 260 public void setChart(JFreeChart chart) { 261 this.chart = chart; 262 } 263 264 /** 265 * Fetches the element hinting flag from the chart that this plot is 266 * assigned to. If the plot is not assigned (directly or indirectly) to 267 * a chart instance, this method will return {@code false}. 268 * 269 * @return A boolean. 270 */ 271 public boolean fetchElementHintingFlag() { 272 if (this.parent != null) { 273 return this.parent.fetchElementHintingFlag(); 274 } 275 if (this.chart != null) { 276 return this.chart.getElementHinting(); 277 } 278 return false; 279 } 280 281 /** 282 * Returns the dataset group for the plot (not currently used). 283 * 284 * @return The dataset group. 285 * 286 * @see #setDatasetGroup(DatasetGroup) 287 */ 288 public DatasetGroup getDatasetGroup() { 289 return this.datasetGroup; 290 } 291 292 /** 293 * Sets the dataset group (not currently used). 294 * 295 * @param group the dataset group ({@code null} permitted). 296 * 297 * @see #getDatasetGroup() 298 */ 299 protected void setDatasetGroup(DatasetGroup group) { 300 this.datasetGroup = group; 301 } 302 303 /** 304 * Returns the string that is displayed when the dataset is empty or 305 * {@code null}. 306 * 307 * @return The 'no data' message ({@code null} possible). 308 * 309 * @see #setNoDataMessage(String) 310 * @see #getNoDataMessageFont() 311 * @see #getNoDataMessagePaint() 312 */ 313 public String getNoDataMessage() { 314 return this.noDataMessage; 315 } 316 317 /** 318 * Sets the message that is displayed when the dataset is empty or 319 * {@code null}, and sends a {@link PlotChangeEvent} to all registered 320 * listeners. 321 * 322 * @param message the message ({@code null} permitted). 323 * 324 * @see #getNoDataMessage() 325 */ 326 public void setNoDataMessage(String message) { 327 this.noDataMessage = message; 328 fireChangeEvent(); 329 } 330 331 /** 332 * Returns the font used to display the 'no data' message. 333 * 334 * @return The font (never {@code null}). 335 * 336 * @see #setNoDataMessageFont(Font) 337 * @see #getNoDataMessage() 338 */ 339 public Font getNoDataMessageFont() { 340 return this.noDataMessageFont; 341 } 342 343 /** 344 * Sets the font used to display the 'no data' message and sends a 345 * {@link PlotChangeEvent} to all registered listeners. 346 * 347 * @param font the font ({@code null} not permitted). 348 * 349 * @see #getNoDataMessageFont() 350 */ 351 public void setNoDataMessageFont(Font font) { 352 Args.nullNotPermitted(font, "font"); 353 this.noDataMessageFont = font; 354 fireChangeEvent(); 355 } 356 357 /** 358 * Returns the paint used to display the 'no data' message. 359 * 360 * @return The paint (never {@code null}). 361 * 362 * @see #setNoDataMessagePaint(Paint) 363 * @see #getNoDataMessage() 364 */ 365 public Paint getNoDataMessagePaint() { 366 return this.noDataMessagePaint; 367 } 368 369 /** 370 * Sets the paint used to display the 'no data' message and sends a 371 * {@link PlotChangeEvent} to all registered listeners. 372 * 373 * @param paint the paint ({@code null} not permitted). 374 * 375 * @see #getNoDataMessagePaint() 376 */ 377 public void setNoDataMessagePaint(Paint paint) { 378 Args.nullNotPermitted(paint, "paint"); 379 this.noDataMessagePaint = paint; 380 fireChangeEvent(); 381 } 382 383 /** 384 * Returns a short string describing the plot type. 385 * <P> 386 * Note: this gets used in the chart property editing user interface, 387 * but there needs to be a better mechanism for identifying the plot type. 388 * 389 * @return A short string describing the plot type (never 390 * {@code null}). 391 */ 392 public abstract String getPlotType(); 393 394 /** 395 * Returns the parent plot (or {@code null} if this plot is not part 396 * of a combined plot). 397 * 398 * @return The parent plot. 399 * 400 * @see #setParent(Plot) 401 * @see #getRootPlot() 402 */ 403 public Plot getParent() { 404 return this.parent; 405 } 406 407 /** 408 * Sets the parent plot. This method is intended for internal use, you 409 * shouldn't need to call it directly. 410 * 411 * @param parent the parent plot ({@code null} permitted). 412 * 413 * @see #getParent() 414 */ 415 public void setParent(Plot parent) { 416 this.parent = parent; 417 } 418 419 /** 420 * Returns the root plot. 421 * 422 * @return The root plot. 423 * 424 * @see #getParent() 425 */ 426 public Plot getRootPlot() { 427 428 Plot p = getParent(); 429 if (p == null) { 430 return this; 431 } 432 return p.getRootPlot(); 433 434 } 435 436 /** 437 * Returns {@code true} if this plot is part of a combined plot 438 * structure (that is, {@link #getParent()} returns a non-{@code null} 439 * value), and {@code false} otherwise. 440 * 441 * @return {@code true} if this plot is part of a combined plot 442 * structure. 443 * 444 * @see #getParent() 445 */ 446 public boolean isSubplot() { 447 return (getParent() != null); 448 } 449 450 /** 451 * Returns the insets for the plot area. 452 * 453 * @return The insets (never {@code null}). 454 * 455 * @see #setInsets(RectangleInsets) 456 */ 457 public RectangleInsets getInsets() { 458 return this.insets; 459 } 460 461 /** 462 * Sets the insets for the plot and sends a {@link PlotChangeEvent} to 463 * all registered listeners. 464 * 465 * @param insets the new insets ({@code null} not permitted). 466 * 467 * @see #getInsets() 468 * @see #setInsets(RectangleInsets, boolean) 469 */ 470 public void setInsets(RectangleInsets insets) { 471 setInsets(insets, true); 472 } 473 474 /** 475 * Sets the insets for the plot and, if requested, and sends a 476 * {@link PlotChangeEvent} to all registered listeners. 477 * 478 * @param insets the new insets ({@code null} not permitted). 479 * @param notify a flag that controls whether the registered listeners are 480 * notified. 481 * 482 * @see #getInsets() 483 * @see #setInsets(RectangleInsets) 484 */ 485 public void setInsets(RectangleInsets insets, boolean notify) { 486 Args.nullNotPermitted(insets, "insets"); 487 if (!this.insets.equals(insets)) { 488 this.insets = insets; 489 if (notify) { 490 fireChangeEvent(); 491 } 492 } 493 494 } 495 496 /** 497 * Returns the background color of the plot area. 498 * 499 * @return The paint (possibly {@code null}). 500 * 501 * @see #setBackgroundPaint(Paint) 502 */ 503 public Paint getBackgroundPaint() { 504 return this.backgroundPaint; 505 } 506 507 /** 508 * Sets the background color of the plot area and sends a 509 * {@link PlotChangeEvent} to all registered listeners. 510 * 511 * @param paint the paint ({@code null} permitted). 512 * 513 * @see #getBackgroundPaint() 514 */ 515 public void setBackgroundPaint(Paint paint) { 516 517 if (paint == null) { 518 if (this.backgroundPaint != null) { 519 this.backgroundPaint = null; 520 fireChangeEvent(); 521 } 522 } 523 else { 524 if (this.backgroundPaint != null) { 525 if (this.backgroundPaint.equals(paint)) { 526 return; // nothing to do 527 } 528 } 529 this.backgroundPaint = paint; 530 fireChangeEvent(); 531 } 532 533 } 534 535 /** 536 * Returns the alpha transparency of the plot area background. 537 * 538 * @return The alpha transparency. 539 * 540 * @see #setBackgroundAlpha(float) 541 */ 542 public float getBackgroundAlpha() { 543 return this.backgroundAlpha; 544 } 545 546 /** 547 * Sets the alpha transparency of the plot area background, and notifies 548 * registered listeners that the plot has been modified. 549 * 550 * @param alpha the new alpha value (in the range 0.0f to 1.0f). 551 * 552 * @see #getBackgroundAlpha() 553 */ 554 public void setBackgroundAlpha(float alpha) { 555 if (this.backgroundAlpha != alpha) { 556 this.backgroundAlpha = alpha; 557 fireChangeEvent(); 558 } 559 } 560 561 /** 562 * Returns the drawing supplier for the plot. 563 * 564 * @return The drawing supplier (possibly {@code null}). 565 * 566 * @see #setDrawingSupplier(DrawingSupplier) 567 */ 568 public DrawingSupplier getDrawingSupplier() { 569 DrawingSupplier result; 570 Plot p = getParent(); 571 if (p != null) { 572 result = p.getDrawingSupplier(); 573 } 574 else { 575 result = this.drawingSupplier; 576 } 577 return result; 578 } 579 580 /** 581 * Sets the drawing supplier for the plot and sends a 582 * {@link PlotChangeEvent} to all registered listeners. The drawing 583 * supplier is responsible for supplying a limitless (possibly repeating) 584 * sequence of {@code Paint}, {@code Stroke} and 585 * {@code Shape} objects that the plot's renderer(s) can use to 586 * populate its (their) tables. 587 * 588 * @param supplier the new supplier. 589 * 590 * @see #getDrawingSupplier() 591 */ 592 public void setDrawingSupplier(DrawingSupplier supplier) { 593 this.drawingSupplier = supplier; 594 fireChangeEvent(); 595 } 596 597 /** 598 * Sets the drawing supplier for the plot and, if requested, sends a 599 * {@link PlotChangeEvent} to all registered listeners. The drawing 600 * supplier is responsible for supplying a limitless (possibly repeating) 601 * sequence of {@code Paint}, {@code Stroke} and 602 * {@code Shape} objects that the plot's renderer(s) can use to 603 * populate its (their) tables. 604 * 605 * @param supplier the new supplier. 606 * @param notify notify listeners? 607 * 608 * @see #getDrawingSupplier() 609 */ 610 public void setDrawingSupplier(DrawingSupplier supplier, boolean notify) { 611 this.drawingSupplier = supplier; 612 if (notify) { 613 fireChangeEvent(); 614 } 615 } 616 617 /** 618 * Returns the background image that is used to fill the plot's background 619 * area. 620 * 621 * @return The image (possibly {@code null}). 622 * 623 * @see #setBackgroundImage(Image) 624 */ 625 public Image getBackgroundImage() { 626 return this.backgroundImage; 627 } 628 629 /** 630 * Sets the background image for the plot and sends a 631 * {@link PlotChangeEvent} to all registered listeners. 632 * 633 * @param image the image ({@code null} permitted). 634 * 635 * @see #getBackgroundImage() 636 */ 637 public void setBackgroundImage(Image image) { 638 this.backgroundImage = image; 639 fireChangeEvent(); 640 } 641 642 /** 643 * Returns the background image alignment. Alignment constants are defined 644 * in the {@code Align} class. 645 * 646 * @return The alignment. 647 * 648 * @see #setBackgroundImageAlignment(int) 649 */ 650 public int getBackgroundImageAlignment() { 651 return this.backgroundImageAlignment; 652 } 653 654 /** 655 * Sets the alignment for the background image and sends a 656 * {@link PlotChangeEvent} to all registered listeners. Alignment options 657 * are defined by the {@link org.jfree.chart.ui.Align} class. 658 * 659 * @param alignment the alignment. 660 * 661 * @see #getBackgroundImageAlignment() 662 */ 663 public void setBackgroundImageAlignment(int alignment) { 664 if (this.backgroundImageAlignment != alignment) { 665 this.backgroundImageAlignment = alignment; 666 fireChangeEvent(); 667 } 668 } 669 670 /** 671 * Returns the alpha transparency used to draw the background image. This 672 * is a value in the range 0.0f to 1.0f, where 0.0f is fully transparent 673 * and 1.0f is fully opaque. 674 * 675 * @return The alpha transparency. 676 * 677 * @see #setBackgroundImageAlpha(float) 678 */ 679 public float getBackgroundImageAlpha() { 680 return this.backgroundImageAlpha; 681 } 682 683 /** 684 * Sets the alpha transparency used when drawing the background image. 685 * 686 * @param alpha the alpha transparency (in the range 0.0f to 1.0f, where 687 * 0.0f is fully transparent, and 1.0f is fully opaque). 688 * 689 * @throws IllegalArgumentException if {@code alpha} is not within 690 * the specified range. 691 * 692 * @see #getBackgroundImageAlpha() 693 */ 694 public void setBackgroundImageAlpha(float alpha) { 695 if (alpha < 0.0f || alpha > 1.0f) { 696 throw new IllegalArgumentException( 697 "The 'alpha' value must be in the range 0.0f to 1.0f."); 698 } 699 if (this.backgroundImageAlpha != alpha) { 700 this.backgroundImageAlpha = alpha; 701 fireChangeEvent(); 702 } 703 } 704 705 /** 706 * Returns the flag that controls whether or not the plot outline is 707 * drawn. The default value is {@code true}. Note that for 708 * historical reasons, the plot's outline paint and stroke can take on 709 * {@code null} values, in which case the outline will not be drawn 710 * even if this flag is set to {@code true}. 711 * 712 * @return The outline visibility flag. 713 * 714 * @see #setOutlineVisible(boolean) 715 */ 716 public boolean isOutlineVisible() { 717 return this.outlineVisible; 718 } 719 720 /** 721 * Sets the flag that controls whether or not the plot's outline is 722 * drawn, and sends a {@link PlotChangeEvent} to all registered listeners. 723 * 724 * @param visible the new flag value. 725 * 726 * @see #isOutlineVisible() 727 */ 728 public void setOutlineVisible(boolean visible) { 729 this.outlineVisible = visible; 730 fireChangeEvent(); 731 } 732 733 /** 734 * Returns the stroke used to outline the plot area. 735 * 736 * @return The stroke (possibly {@code null}). 737 * 738 * @see #setOutlineStroke(Stroke) 739 */ 740 public Stroke getOutlineStroke() { 741 return this.outlineStroke; 742 } 743 744 /** 745 * Sets the stroke used to outline the plot area and sends a 746 * {@link PlotChangeEvent} to all registered listeners. If you set this 747 * attribute to {@code null}, no outline will be drawn. 748 * 749 * @param stroke the stroke ({@code null} permitted). 750 * 751 * @see #getOutlineStroke() 752 */ 753 public void setOutlineStroke(Stroke stroke) { 754 if (stroke == null) { 755 if (this.outlineStroke != null) { 756 this.outlineStroke = null; 757 fireChangeEvent(); 758 } 759 } 760 else { 761 if (this.outlineStroke != null) { 762 if (this.outlineStroke.equals(stroke)) { 763 return; // nothing to do 764 } 765 } 766 this.outlineStroke = stroke; 767 fireChangeEvent(); 768 } 769 } 770 771 /** 772 * Returns the color used to draw the outline of the plot area. 773 * 774 * @return The color (possibly {@code null}). 775 * 776 * @see #setOutlinePaint(Paint) 777 */ 778 public Paint getOutlinePaint() { 779 return this.outlinePaint; 780 } 781 782 /** 783 * Sets the paint used to draw the outline of the plot area and sends a 784 * {@link PlotChangeEvent} to all registered listeners. If you set this 785 * attribute to {@code null}, no outline will be drawn. 786 * 787 * @param paint the paint ({@code null} permitted). 788 * 789 * @see #getOutlinePaint() 790 */ 791 public void setOutlinePaint(Paint paint) { 792 if (paint == null) { 793 if (this.outlinePaint != null) { 794 this.outlinePaint = null; 795 fireChangeEvent(); 796 } 797 } 798 else { 799 if (this.outlinePaint != null) { 800 if (this.outlinePaint.equals(paint)) { 801 return; // nothing to do 802 } 803 } 804 this.outlinePaint = paint; 805 fireChangeEvent(); 806 } 807 } 808 809 /** 810 * Returns the alpha-transparency for the plot foreground. 811 * 812 * @return The alpha-transparency. 813 * 814 * @see #setForegroundAlpha(float) 815 */ 816 public float getForegroundAlpha() { 817 return this.foregroundAlpha; 818 } 819 820 /** 821 * Sets the alpha-transparency for the plot and sends a 822 * {@link PlotChangeEvent} to all registered listeners. 823 * 824 * @param alpha the new alpha transparency. 825 * 826 * @see #getForegroundAlpha() 827 */ 828 public void setForegroundAlpha(float alpha) { 829 if (this.foregroundAlpha != alpha) { 830 this.foregroundAlpha = alpha; 831 fireChangeEvent(); 832 } 833 } 834 835 /** 836 * Returns the legend items for the plot. By default, this method returns 837 * {@code null}. Subclasses should override to return a 838 * {@link LegendItemCollection}. 839 * 840 * @return The legend items for the plot (possibly {@code null}). 841 */ 842 @Override 843 public LegendItemCollection getLegendItems() { 844 return null; 845 } 846 847 /** 848 * Returns a flag that controls whether or not change events are sent to 849 * registered listeners. 850 * 851 * @return A boolean. 852 * 853 * @see #setNotify(boolean) 854 */ 855 public boolean isNotify() { 856 return this.notify; 857 } 858 859 /** 860 * Sets a flag that controls whether or not listeners receive 861 * {@link PlotChangeEvent} notifications. 862 * 863 * @param notify a boolean. 864 * 865 * @see #isNotify() 866 */ 867 public void setNotify(boolean notify) { 868 this.notify = notify; 869 // if the flag is being set to true, there may be queued up changes... 870 if (notify) { 871 notifyListeners(new PlotChangeEvent(this)); 872 } 873 } 874 875 /** 876 * Registers an object for notification of changes to the plot. 877 * 878 * @param listener the object to be registered. 879 * 880 * @see #removeChangeListener(PlotChangeListener) 881 */ 882 public void addChangeListener(PlotChangeListener listener) { 883 this.listenerList.add(PlotChangeListener.class, listener); 884 } 885 886 /** 887 * Unregisters an object for notification of changes to the plot. 888 * 889 * @param listener the object to be unregistered. 890 * 891 * @see #addChangeListener(PlotChangeListener) 892 */ 893 public void removeChangeListener(PlotChangeListener listener) { 894 this.listenerList.remove(PlotChangeListener.class, listener); 895 } 896 897 /** 898 * Notifies all registered listeners that the plot has been modified. 899 * 900 * @param event information about the change event. 901 */ 902 public void notifyListeners(PlotChangeEvent event) { 903 // if the 'notify' flag has been switched to false, we don't notify 904 // the listeners 905 if (!this.notify) { 906 return; 907 } 908 Object[] listeners = this.listenerList.getListenerList(); 909 for (int i = listeners.length - 2; i >= 0; i -= 2) { 910 if (listeners[i] == PlotChangeListener.class) { 911 ((PlotChangeListener) listeners[i + 1]).plotChanged(event); 912 } 913 } 914 } 915 916 /** 917 * Sends a {@link PlotChangeEvent} to all registered listeners. 918 */ 919 protected void fireChangeEvent() { 920 notifyListeners(new PlotChangeEvent(this)); 921 } 922 923 /** 924 * Draws the plot within the specified area. The anchor is a point on the 925 * chart that is specified externally (for instance, it may be the last 926 * point of the last mouse click performed by the user) - plots can use or 927 * ignore this value as they see fit. 928 * <br><br> 929 * Subclasses need to provide an implementation of this method, obviously. 930 * 931 * @param g2 the graphics device. 932 * @param area the plot area. 933 * @param anchor the anchor point ({@code null} permitted). 934 * @param parentState the parent state (if any, {@code null} permitted). 935 * @param info carries back plot rendering info. 936 */ 937 public abstract void draw(Graphics2D g2, Rectangle2D area, Point2D anchor, 938 PlotState parentState, PlotRenderingInfo info); 939 940 /** 941 * Draws the plot background (the background color and/or image). 942 * <P> 943 * This method will be called during the chart drawing process and is 944 * declared public so that it can be accessed by the renderers used by 945 * certain subclasses. You shouldn't need to call this method directly. 946 * 947 * @param g2 the graphics device. 948 * @param area the area within which the plot should be drawn. 949 */ 950 public void drawBackground(Graphics2D g2, Rectangle2D area) { 951 // some subclasses override this method completely, so don't put 952 // anything here that *must* be done 953 fillBackground(g2, area); 954 drawBackgroundImage(g2, area); 955 } 956 957 /** 958 * Fills the specified area with the background paint. 959 * 960 * @param g2 the graphics device. 961 * @param area the area. 962 * 963 * @see #getBackgroundPaint() 964 * @see #getBackgroundAlpha() 965 * @see #fillBackground(Graphics2D, Rectangle2D, PlotOrientation) 966 */ 967 protected void fillBackground(Graphics2D g2, Rectangle2D area) { 968 fillBackground(g2, area, PlotOrientation.VERTICAL); 969 } 970 971 /** 972 * Fills the specified area with the background paint. If the background 973 * paint is an instance of {@code GradientPaint}, the gradient will 974 * run in the direction suggested by the plot's orientation. 975 * 976 * @param g2 the graphics target. 977 * @param area the plot area. 978 * @param orientation the plot orientation ({@code null} not 979 * permitted). 980 */ 981 protected void fillBackground(Graphics2D g2, Rectangle2D area, 982 PlotOrientation orientation) { 983 Args.nullNotPermitted(orientation, "orientation"); 984 if (this.backgroundPaint == null) { 985 return; 986 } 987 Paint p = this.backgroundPaint; 988 if (p instanceof GradientPaint) { 989 GradientPaint gp = (GradientPaint) p; 990 if (orientation == PlotOrientation.VERTICAL) { 991 p = new GradientPaint((float) area.getCenterX(), 992 (float) area.getMaxY(), gp.getColor1(), 993 (float) area.getCenterX(), (float) area.getMinY(), 994 gp.getColor2()); 995 } 996 else if (orientation == PlotOrientation.HORIZONTAL) { 997 p = new GradientPaint((float) area.getMinX(), 998 (float) area.getCenterY(), gp.getColor1(), 999 (float) area.getMaxX(), (float) area.getCenterY(), 1000 gp.getColor2()); 1001 } 1002 } 1003 Composite originalComposite = g2.getComposite(); 1004 g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1005 this.backgroundAlpha)); 1006 g2.setPaint(p); 1007 g2.fill(area); 1008 g2.setComposite(originalComposite); 1009 } 1010 1011 /** 1012 * Draws the background image (if there is one) aligned within the 1013 * specified area. 1014 * 1015 * @param g2 the graphics device. 1016 * @param area the area. 1017 * 1018 * @see #getBackgroundImage() 1019 * @see #getBackgroundImageAlignment() 1020 * @see #getBackgroundImageAlpha() 1021 */ 1022 public void drawBackgroundImage(Graphics2D g2, Rectangle2D area) { 1023 if (this.backgroundImage == null) { 1024 return; // nothing to do 1025 } 1026 Composite savedComposite = g2.getComposite(); 1027 g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1028 this.backgroundImageAlpha)); 1029 Rectangle2D dest = new Rectangle2D.Double(0.0, 0.0, 1030 this.backgroundImage.getWidth(null), 1031 this.backgroundImage.getHeight(null)); 1032 Align.align(dest, area, this.backgroundImageAlignment); 1033 Shape savedClip = g2.getClip(); 1034 g2.clip(area); 1035 g2.drawImage(this.backgroundImage, (int) dest.getX(), 1036 (int) dest.getY(), (int) dest.getWidth() + 1, 1037 (int) dest.getHeight() + 1, null); 1038 g2.setClip(savedClip); 1039 g2.setComposite(savedComposite); 1040 } 1041 1042 /** 1043 * Draws the plot outline. This method will be called during the chart 1044 * drawing process and is declared public so that it can be accessed by the 1045 * renderers used by certain subclasses. You shouldn't need to call this 1046 * method directly. 1047 * 1048 * @param g2 the graphics device. 1049 * @param area the area within which the plot should be drawn. 1050 */ 1051 public void drawOutline(Graphics2D g2, Rectangle2D area) { 1052 if (!this.outlineVisible) { 1053 return; 1054 } 1055 if ((this.outlineStroke != null) && (this.outlinePaint != null)) { 1056 g2.setStroke(this.outlineStroke); 1057 g2.setPaint(this.outlinePaint); 1058 Object saved = g2.getRenderingHint(RenderingHints.KEY_STROKE_CONTROL); 1059 g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE); 1060 g2.draw(area); 1061 g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, saved); 1062 } 1063 } 1064 1065 /** 1066 * Draws a message to state that there is no data to plot. 1067 * 1068 * @param g2 the graphics device. 1069 * @param area the area within which the plot should be drawn. 1070 */ 1071 protected void drawNoDataMessage(Graphics2D g2, Rectangle2D area) { 1072 Shape savedClip = g2.getClip(); 1073 g2.clip(area); 1074 String message = this.noDataMessage; 1075 if (message != null) { 1076 g2.setFont(this.noDataMessageFont); 1077 g2.setPaint(this.noDataMessagePaint); 1078 TextBlock block = TextUtils.createTextBlock( 1079 this.noDataMessage, this.noDataMessageFont, 1080 this.noDataMessagePaint, 0.9f * (float) area.getWidth(), 1081 new G2TextMeasurer(g2)); 1082 block.draw(g2, (float) area.getCenterX(), 1083 (float) area.getCenterY(), TextBlockAnchor.CENTER); 1084 } 1085 g2.setClip(savedClip); 1086 } 1087 1088 /** 1089 * Creates a plot entity that contains a reference to the plot and the 1090 * data area as shape. 1091 * 1092 * @param dataArea the data area used as hot spot for the entity. 1093 * @param plotState the plot rendering info containing a reference to the 1094 * EntityCollection. 1095 * @param toolTip the tool tip (defined in the respective Plot 1096 * subclass) ({@code null} permitted). 1097 * @param urlText the url (defined in the respective Plot subclass) 1098 * ({@code null} permitted). 1099 */ 1100 protected void createAndAddEntity(Rectangle2D dataArea, 1101 PlotRenderingInfo plotState, String toolTip, String urlText) { 1102 if (plotState != null && plotState.getOwner() != null) { 1103 EntityCollection e = plotState.getOwner().getEntityCollection(); 1104 if (e != null) { 1105 e.add(new PlotEntity(dataArea, this, toolTip, urlText)); 1106 } 1107 } 1108 } 1109 1110 /** 1111 * Handles a 'click' on the plot. Since the plot does not maintain any 1112 * information about where it has been drawn, the plot rendering info is 1113 * supplied as an argument so that the plot dimensions can be determined. 1114 * 1115 * @param x the x coordinate (in Java2D space). 1116 * @param y the y coordinate (in Java2D space). 1117 * @param info an object containing information about the dimensions of 1118 * the plot. 1119 */ 1120 public void handleClick(int x, int y, PlotRenderingInfo info) { 1121 // provides a 'no action' default 1122 } 1123 1124 /** 1125 * Performs a zoom on the plot. Subclasses should override if zooming is 1126 * appropriate for the type of plot. 1127 * 1128 * @param percent the zoom percentage. 1129 */ 1130 public void zoom(double percent) { 1131 // do nothing by default. 1132 } 1133 1134 /** 1135 * Receives notification of a change to an {@link Annotation} added to 1136 * this plot. 1137 * 1138 * @param event information about the event (not used here). 1139 */ 1140 @Override 1141 public void annotationChanged(AnnotationChangeEvent event) { 1142 fireChangeEvent(); 1143 } 1144 1145 /** 1146 * Receives notification of a change to one of the plot's axes. 1147 * 1148 * @param event information about the event (not used here). 1149 */ 1150 @Override 1151 public void axisChanged(AxisChangeEvent event) { 1152 fireChangeEvent(); 1153 } 1154 1155 /** 1156 * Receives notification of a change to the plot's dataset. 1157 * <P> 1158 * The plot reacts by passing on a plot change event to all registered 1159 * listeners. 1160 * 1161 * @param event information about the event (not used here). 1162 */ 1163 @Override 1164 public void datasetChanged(DatasetChangeEvent event) { 1165 PlotChangeEvent newEvent = new PlotChangeEvent(this); 1166 newEvent.setType(ChartChangeEventType.DATASET_UPDATED); 1167 notifyListeners(newEvent); 1168 } 1169 1170 /** 1171 * Receives notification of a change to a marker that is assigned to the 1172 * plot. 1173 * 1174 * @param event the event. 1175 */ 1176 @Override 1177 public void markerChanged(MarkerChangeEvent event) { 1178 fireChangeEvent(); 1179 } 1180 1181 /** 1182 * Adjusts the supplied x-value. 1183 * 1184 * @param x the x-value. 1185 * @param w1 width 1. 1186 * @param w2 width 2. 1187 * @param edge the edge (left or right). 1188 * 1189 * @return The adjusted x-value. 1190 */ 1191 protected double getRectX(double x, double w1, double w2, 1192 RectangleEdge edge) { 1193 1194 double result = x; 1195 if (edge == RectangleEdge.LEFT) { 1196 result = result + w1; 1197 } 1198 else if (edge == RectangleEdge.RIGHT) { 1199 result = result + w2; 1200 } 1201 return result; 1202 1203 } 1204 1205 /** 1206 * Adjusts the supplied y-value. 1207 * 1208 * @param y the x-value. 1209 * @param h1 height 1. 1210 * @param h2 height 2. 1211 * @param edge the edge (top or bottom). 1212 * 1213 * @return The adjusted y-value. 1214 */ 1215 protected double getRectY(double y, double h1, double h2, 1216 RectangleEdge edge) { 1217 1218 double result = y; 1219 if (edge == RectangleEdge.TOP) { 1220 result = result + h1; 1221 } 1222 else if (edge == RectangleEdge.BOTTOM) { 1223 result = result + h2; 1224 } 1225 return result; 1226 1227 } 1228 1229 /** 1230 * Tests this plot for equality with another object. 1231 * 1232 * @param obj the object ({@code null} permitted). 1233 * 1234 * @return {@code true} or {@code false}. 1235 */ 1236 @Override 1237 public boolean equals(Object obj) { 1238 if (obj == this) { 1239 return true; 1240 } 1241 if (!(obj instanceof Plot)) { 1242 return false; 1243 } 1244 Plot that = (Plot) obj; 1245 // fix the "equals not symmetric" problem 1246 if (!that.canEqual(this)) { 1247 return false; 1248 } 1249 if (!Objects.equals(this.noDataMessage, that.noDataMessage)) { 1250 return false; 1251 } 1252 if (!Objects.equals( 1253 this.noDataMessageFont, that.noDataMessageFont 1254 )) { 1255 return false; 1256 } 1257 if (!PaintUtils.equal(this.noDataMessagePaint, 1258 that.noDataMessagePaint)) { 1259 return false; 1260 } 1261 if (!Objects.equals(this.insets, that.insets)) { 1262 return false; 1263 } 1264 // There's a reason chart is not included in equals/hashCode - doing so 1265 // causes a StackOverflow error during EqualsVerifier's test! 1266// if (!Objects.equals(this.chart, that.chart)) { 1267// return false; 1268// } 1269 if (this.outlineVisible != that.outlineVisible) { 1270 return false; 1271 } 1272 if (!Objects.equals(this.outlineStroke, that.outlineStroke)) { 1273 return false; 1274 } 1275 if (!PaintUtils.equal(this.outlinePaint, that.outlinePaint)) { 1276 return false; 1277 } 1278 if (!PaintUtils.equal(this.backgroundPaint, that.backgroundPaint)) { 1279 return false; 1280 } 1281 if (!Objects.equals(this.backgroundImage, that.backgroundImage)) { 1282 return false; 1283 } 1284 if (this.backgroundImageAlignment != that.backgroundImageAlignment) { 1285 return false; 1286 } 1287 if (Float.compare(this.backgroundImageAlpha, 1288 that.backgroundImageAlpha) != 0 ){ 1289 return false; 1290 } 1291 if (Float.compare(this.foregroundAlpha, that.foregroundAlpha) != 0 ) { 1292 return false; 1293 } 1294 if (Float.compare(this.backgroundAlpha, that.backgroundAlpha) != 0 ) { 1295 return false; 1296 } 1297 if (!Objects.equals(this.drawingSupplier, that.drawingSupplier)) { 1298 return false; 1299 } 1300 if (this.notify != that.notify) { 1301 return false; 1302 } 1303 if (!Objects.equals(this.datasetGroup, that.datasetGroup)) { 1304 return false; 1305 } 1306 return true; 1307 } 1308 1309 /** 1310 * Ensures symmetry between super/subclass implementations of equals. For 1311 * more detail, see http://jqno.nl/equalsverifier/manual/inheritance. 1312 * 1313 * @param other Object 1314 * 1315 * @return true ONLY if the parameter is THIS class type 1316 */ 1317 public boolean canEqual(Object other) { 1318 // Solves Problem: equals not symmetric 1319 return (other instanceof Plot); 1320 } 1321 1322 @Override 1323 public int hashCode() { 1324 int hash = 7; 1325 hash = 41 * hash + Objects.hashCode(this.noDataMessage); 1326 hash = 41 * hash + Objects.hashCode(this.noDataMessageFont); 1327 hash = 41 * hash + Objects.hashCode(this.noDataMessagePaint); 1328 hash = 41 * hash + Objects.hashCode(this.insets); 1329// hash = 41 * hash + Objects.hashCode(this.chart); 1330 hash = 41 * hash + (this.outlineVisible ? 1 : 0); 1331 hash = 41 * hash + Objects.hashCode(this.outlineStroke); 1332 hash = 41 * hash + Objects.hashCode(this.outlinePaint); 1333 hash = 41 * hash + Objects.hashCode(this.backgroundPaint); 1334 hash = 41 * hash + Objects.hashCode(this.backgroundImage); 1335 hash = 41 * hash + this.backgroundImageAlignment; 1336 hash = 41 * hash + Float.floatToIntBits(this.backgroundImageAlpha); 1337 hash = 41 * hash + Float.floatToIntBits(this.foregroundAlpha); 1338 hash = 41 * hash + Float.floatToIntBits(this.backgroundAlpha); 1339 hash = 41 * hash + Objects.hashCode(this.drawingSupplier); 1340 hash = 41 * hash + (this.notify ? 1 : 0); 1341 hash = 41 * hash + Objects.hashCode(this.datasetGroup); 1342 return hash; 1343 } 1344 1345 /** 1346 * Creates a clone of the plot. 1347 * 1348 * @return A clone. 1349 * 1350 * @throws CloneNotSupportedException if some component of the plot does not 1351 * support cloning. 1352 */ 1353 @Override 1354 public Object clone() throws CloneNotSupportedException { 1355 1356 Plot clone = (Plot) super.clone(); 1357 // private Plot parent <-- don't clone the parent plot, but take care 1358 // childs in combined plots instead 1359 if (this.datasetGroup != null) { 1360 clone.datasetGroup 1361 = (DatasetGroup) ObjectUtils.clone(this.datasetGroup); 1362 } 1363 clone.drawingSupplier 1364 = (DrawingSupplier) ObjectUtils.clone(this.drawingSupplier); 1365 clone.listenerList = new EventListenerList(); 1366 return clone; 1367 1368 } 1369 1370 /** 1371 * Provides serialization support. 1372 * 1373 * @param stream the output stream. 1374 * 1375 * @throws IOException if there is an I/O error. 1376 */ 1377 private void writeObject(ObjectOutputStream stream) throws IOException { 1378 stream.defaultWriteObject(); 1379 SerialUtils.writePaint(this.noDataMessagePaint, stream); 1380 SerialUtils.writeStroke(this.outlineStroke, stream); 1381 SerialUtils.writePaint(this.outlinePaint, stream); 1382 // backgroundImage 1383 SerialUtils.writePaint(this.backgroundPaint, stream); 1384 } 1385 1386 /** 1387 * Provides serialization support. 1388 * 1389 * @param stream the input stream. 1390 * 1391 * @throws IOException if there is an I/O error. 1392 * @throws ClassNotFoundException if there is a classpath problem. 1393 */ 1394 private void readObject(ObjectInputStream stream) 1395 throws IOException, ClassNotFoundException { 1396 stream.defaultReadObject(); 1397 this.noDataMessagePaint = SerialUtils.readPaint(stream); 1398 this.outlineStroke = SerialUtils.readStroke(stream); 1399 this.outlinePaint = SerialUtils.readPaint(stream); 1400 // backgroundImage 1401 this.backgroundPaint = SerialUtils.readPaint(stream); 1402 1403 this.listenerList = new EventListenerList(); 1404 1405 } 1406 1407 /** 1408 * Resolves a domain axis location for a given plot orientation. 1409 * 1410 * @param location the location ({@code null} not permitted). 1411 * @param orientation the orientation ({@code null} not permitted). 1412 * 1413 * @return The edge (never {@code null}). 1414 */ 1415 public static RectangleEdge resolveDomainAxisLocation( 1416 AxisLocation location, PlotOrientation orientation) { 1417 1418 Args.nullNotPermitted(location, "location"); 1419 Args.nullNotPermitted(orientation, "orientation"); 1420 1421 RectangleEdge result = null; 1422 if (location == AxisLocation.TOP_OR_RIGHT) { 1423 if (orientation == PlotOrientation.HORIZONTAL) { 1424 result = RectangleEdge.RIGHT; 1425 } 1426 else if (orientation == PlotOrientation.VERTICAL) { 1427 result = RectangleEdge.TOP; 1428 } 1429 } 1430 else if (location == AxisLocation.TOP_OR_LEFT) { 1431 if (orientation == PlotOrientation.HORIZONTAL) { 1432 result = RectangleEdge.LEFT; 1433 } 1434 else if (orientation == PlotOrientation.VERTICAL) { 1435 result = RectangleEdge.TOP; 1436 } 1437 } 1438 else if (location == AxisLocation.BOTTOM_OR_RIGHT) { 1439 if (orientation == PlotOrientation.HORIZONTAL) { 1440 result = RectangleEdge.RIGHT; 1441 } 1442 else if (orientation == PlotOrientation.VERTICAL) { 1443 result = RectangleEdge.BOTTOM; 1444 } 1445 } 1446 else if (location == AxisLocation.BOTTOM_OR_LEFT) { 1447 if (orientation == PlotOrientation.HORIZONTAL) { 1448 result = RectangleEdge.LEFT; 1449 } 1450 else if (orientation == PlotOrientation.VERTICAL) { 1451 result = RectangleEdge.BOTTOM; 1452 } 1453 } 1454 // the above should cover all the options... 1455 if (result == null) { 1456 throw new IllegalStateException("resolveDomainAxisLocation()"); 1457 } 1458 return result; 1459 1460 } 1461 1462 /** 1463 * Resolves a range axis location for a given plot orientation. 1464 * 1465 * @param location the location ({@code null} not permitted). 1466 * @param orientation the orientation ({@code null} not permitted). 1467 * 1468 * @return The edge (never {@code null}). 1469 */ 1470 public static RectangleEdge resolveRangeAxisLocation( 1471 AxisLocation location, PlotOrientation orientation) { 1472 1473 Args.nullNotPermitted(location, "location"); 1474 Args.nullNotPermitted(orientation, "orientation"); 1475 1476 RectangleEdge result = null; 1477 if (location == AxisLocation.TOP_OR_RIGHT) { 1478 if (orientation == PlotOrientation.HORIZONTAL) { 1479 result = RectangleEdge.TOP; 1480 } 1481 else if (orientation == PlotOrientation.VERTICAL) { 1482 result = RectangleEdge.RIGHT; 1483 } 1484 } 1485 else if (location == AxisLocation.TOP_OR_LEFT) { 1486 if (orientation == PlotOrientation.HORIZONTAL) { 1487 result = RectangleEdge.TOP; 1488 } 1489 else if (orientation == PlotOrientation.VERTICAL) { 1490 result = RectangleEdge.LEFT; 1491 } 1492 } 1493 else if (location == AxisLocation.BOTTOM_OR_RIGHT) { 1494 if (orientation == PlotOrientation.HORIZONTAL) { 1495 result = RectangleEdge.BOTTOM; 1496 } 1497 else if (orientation == PlotOrientation.VERTICAL) { 1498 result = RectangleEdge.RIGHT; 1499 } 1500 } 1501 else if (location == AxisLocation.BOTTOM_OR_LEFT) { 1502 if (orientation == PlotOrientation.HORIZONTAL) { 1503 result = RectangleEdge.BOTTOM; 1504 } 1505 else if (orientation == PlotOrientation.VERTICAL) { 1506 result = RectangleEdge.LEFT; 1507 } 1508 } 1509 1510 // the above should cover all the options... 1511 if (result == null) { 1512 throw new IllegalStateException("resolveRangeAxisLocation()"); 1513 } 1514 return result; 1515 1516 } 1517 1518}