001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors. 006 * 007 * Project Info: http://www.jfree.org/jfreechart/index.html 008 * 009 * This library is free software; you can redistribute it and/or modify it 010 * under the terms of the GNU Lesser General Public License as published by 011 * the Free Software Foundation; either version 2.1 of the License, or 012 * (at your option) any later version. 013 * 014 * This library is distributed in the hope that it will be useful, but 015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 017 * License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this library; if not, write to the Free Software 021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 022 * USA. 023 * 024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 025 * in the United States and other countries.] 026 * 027 * --------- 028 * Plot.java 029 * --------- 030 * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): Sylvain Vieujot; 034 * Jeremy Bowman; 035 * Andreas Schneider; 036 * Gideon Krause; 037 * Nicolas Brodu; 038 * 039 * $Id: Plot.java,v 1.18.2.3 2005/10/25 20:52:07 mungady Exp $ 040 * 041 * Changes (from 21-Jun-2001) 042 * -------------------------- 043 * 21-Jun-2001 : Removed redundant JFreeChart parameter from constructors (DG); 044 * 18-Sep-2001 : Updated header info and fixed DOS encoding problem (DG); 045 * 19-Oct-2001 : Moved series paint and stroke methods from JFreeChart 046 * class (DG); 047 * 23-Oct-2001 : Created renderer for LinePlot class (DG); 048 * 07-Nov-2001 : Changed type names for ChartChangeEvent (DG); 049 * Tidied up some Javadoc comments (DG); 050 * 13-Nov-2001 : Changes to allow for null axes on plots such as PiePlot (DG); 051 * Added plot/axis compatibility checks (DG); 052 * 12-Dec-2001 : Changed constructors to protected, and removed unnecessary 053 * 'throws' clauses (DG); 054 * 13-Dec-2001 : Added tooltips (DG); 055 * 22-Jan-2002 : Added handleClick() method, as part of implementation for 056 * crosshairs (DG); 057 * Moved tooltips reference into ChartInfo class (DG); 058 * 23-Jan-2002 : Added test for null axes in chartChanged() method, thanks 059 * to Barry Evans for the bug report (number 506979 on 060 * SourceForge) (DG); 061 * Added a zoom() method (DG); 062 * 05-Feb-2002 : Updated setBackgroundPaint(), setOutlineStroke() and 063 * setOutlinePaint() to better handle null values, as suggested 064 * by Sylvain Vieujot (DG); 065 * 06-Feb-2002 : Added background image, plus alpha transparency for background 066 * and foreground (DG); 067 * 06-Mar-2002 : Added AxisConstants interface (DG); 068 * 26-Mar-2002 : Changed zoom method from empty to abstract (DG); 069 * 23-Apr-2002 : Moved dataset from JFreeChart class (DG); 070 * 11-May-2002 : Added ShapeFactory interface for getShape() methods, 071 * contributed by Jeremy Bowman (DG); 072 * 28-May-2002 : Fixed bug in setSeriesPaint(int, Paint) for subplots (AS); 073 * 25-Jun-2002 : Removed redundant imports (DG); 074 * 30-Jul-2002 : Added 'no data' message for charts with null or empty 075 * datasets (DG); 076 * 21-Aug-2002 : Added code to extend series array if necessary (refer to 077 * SourceForge bug id 594547 for details) (DG); 078 * 17-Sep-2002 : Fixed bug in getSeriesOutlineStroke() method, reported by 079 * Andreas Schroeder (DG); 080 * 23-Sep-2002 : Added getLegendItems() abstract method (DG); 081 * 24-Sep-2002 : Removed firstSeriesIndex, subplots now use their own paint 082 * settings, there is a new mechanism for the legend to collect 083 * the legend items (DG); 084 * 27-Sep-2002 : Added dataset group (DG); 085 * 14-Oct-2002 : Moved listener storage into EventListenerList. Changed some 086 * abstract methods to empty implementations (DG); 087 * 28-Oct-2002 : Added a getBackgroundImage() method (DG); 088 * 21-Nov-2002 : Added a plot index for identifying subplots in combined and 089 * overlaid charts (DG); 090 * 22-Nov-2002 : Changed all attributes from 'protected' to 'private'. Added 091 * dataAreaRatio attribute from David M O'Donnell's code (DG); 092 * 09-Jan-2003 : Integrated fix for plot border contributed by Gideon 093 * Krause (DG); 094 * 17-Jan-2003 : Moved to com.jrefinery.chart.plot (DG); 095 * 23-Jan-2003 : Removed one constructor (DG); 096 * 26-Mar-2003 : Implemented Serializable (DG); 097 * 14-Jul-2003 : Moved the dataset and secondaryDataset attributes to the 098 * CategoryPlot and XYPlot classes (DG); 099 * 21-Jul-2003 : Moved DrawingSupplier from CategoryPlot and XYPlot up to this 100 * class (DG); 101 * 20-Aug-2003 : Implemented Cloneable (DG); 102 * 11-Sep-2003 : Listeners and clone (NB); 103 * 29-Oct-2003 : Added workaround for font alignment in PDF output (DG); 104 * 03-Dec-2003 : Modified draw method to accept anchor (DG); 105 * 12-Mar-2004 : Fixed clipping bug in drawNoDataMessage() method (DG); 106 * 07-Apr-2004 : Modified string bounds calculation (DG); 107 * 04-Nov-2004 : Added default shapes for legend items (DG); 108 * 25-Nov-2004 : Some changes to the clone() method implementation (DG); 109 * 23-Feb-2005 : Implemented new LegendItemSource interface (and also 110 * PublicCloneable) (DG); 111 * 21-Apr-2005 : Replaced Insets with RectangleInsets (DG); 112 * 05-May-2005 : Removed unused draw() method (DG); 113 * 06-Jun-2005 : Fixed bugs in equals() method (DG); 114 * 01-Sep-2005 : Moved dataAreaRatio from here to ContourPlot (DG); 115 * 116 */ 117 118 package org.jfree.chart.plot; 119 120 import java.awt.AlphaComposite; 121 import java.awt.BasicStroke; 122 import java.awt.Color; 123 import java.awt.Composite; 124 import java.awt.Font; 125 import java.awt.Graphics2D; 126 import java.awt.Image; 127 import java.awt.Paint; 128 import java.awt.Shape; 129 import java.awt.Stroke; 130 import java.awt.geom.Ellipse2D; 131 import java.awt.geom.Point2D; 132 import java.awt.geom.Rectangle2D; 133 import java.io.IOException; 134 import java.io.ObjectInputStream; 135 import java.io.ObjectOutputStream; 136 import java.io.Serializable; 137 138 import javax.swing.event.EventListenerList; 139 140 import org.jfree.chart.LegendItemCollection; 141 import org.jfree.chart.LegendItemSource; 142 import org.jfree.chart.axis.AxisLocation; 143 import org.jfree.chart.event.AxisChangeEvent; 144 import org.jfree.chart.event.AxisChangeListener; 145 import org.jfree.chart.event.ChartChangeEventType; 146 import org.jfree.chart.event.PlotChangeEvent; 147 import org.jfree.chart.event.PlotChangeListener; 148 import org.jfree.data.general.DatasetChangeEvent; 149 import org.jfree.data.general.DatasetChangeListener; 150 import org.jfree.data.general.DatasetGroup; 151 import org.jfree.io.SerialUtilities; 152 import org.jfree.text.G2TextMeasurer; 153 import org.jfree.text.TextBlock; 154 import org.jfree.text.TextBlockAnchor; 155 import org.jfree.text.TextUtilities; 156 import org.jfree.ui.Align; 157 import org.jfree.ui.RectangleEdge; 158 import org.jfree.ui.RectangleInsets; 159 import org.jfree.util.ObjectUtilities; 160 import org.jfree.util.PaintUtilities; 161 import org.jfree.util.PublicCloneable; 162 163 /** 164 * The base class for all plots in JFreeChart. The 165 * {@link org.jfree.chart.JFreeChart} class delegates the drawing of axes and 166 * data to the plot. This base class provides facilities common to most plot 167 * types. 168 */ 169 public abstract class Plot implements AxisChangeListener, 170 DatasetChangeListener, 171 LegendItemSource, 172 PublicCloneable, 173 Cloneable, 174 Serializable { 175 176 /** For serialization. */ 177 private static final long serialVersionUID = -8831571430103671324L; 178 179 /** Useful constant representing zero. */ 180 public static final Number ZERO = new Integer(0); 181 182 /** The default insets. */ 183 public static final RectangleInsets DEFAULT_INSETS 184 = new RectangleInsets(4.0, 8.0, 4.0, 8.0); 185 186 /** The default outline stroke. */ 187 public static final Stroke DEFAULT_OUTLINE_STROKE = new BasicStroke(0.5f); 188 189 /** The default outline color. */ 190 public static final Paint DEFAULT_OUTLINE_PAINT = Color.gray; 191 192 /** The default foreground alpha transparency. */ 193 public static final float DEFAULT_FOREGROUND_ALPHA = 1.0f; 194 195 /** The default background alpha transparency. */ 196 public static final float DEFAULT_BACKGROUND_ALPHA = 1.0f; 197 198 /** The default background color. */ 199 public static final Paint DEFAULT_BACKGROUND_PAINT = Color.white; 200 201 /** The minimum width at which the plot should be drawn. */ 202 public static final int MINIMUM_WIDTH_TO_DRAW = 10; 203 204 /** The minimum height at which the plot should be drawn. */ 205 public static final int MINIMUM_HEIGHT_TO_DRAW = 10; 206 207 /** A default box shape for legend items. */ 208 public static final Shape DEFAULT_LEGEND_ITEM_BOX 209 = new Rectangle2D.Double(-4.0, -4.0, 8.0, 8.0); 210 211 /** A default circle shape for legend items. */ 212 public static final Shape DEFAULT_LEGEND_ITEM_CIRCLE 213 = new Ellipse2D.Double(-4.0, -4.0, 8.0, 8.0); 214 215 /** The parent plot (<code>null</code> if this is the root plot). */ 216 private Plot parent; 217 218 /** The dataset group (to be used for thread synchronisation). */ 219 private DatasetGroup datasetGroup; 220 221 /** The message to display if no data is available. */ 222 private String noDataMessage; 223 224 /** The font used to display the 'no data' message. */ 225 private Font noDataMessageFont; 226 227 /** The paint used to draw the 'no data' message. */ 228 private transient Paint noDataMessagePaint; 229 230 /** Amount of blank space around the plot area. */ 231 private RectangleInsets insets; 232 233 /** The Stroke used to draw an outline around the plot. */ 234 private transient Stroke outlineStroke; 235 236 /** The Paint used to draw an outline around the plot. */ 237 private transient Paint outlinePaint; 238 239 /** An optional color used to fill the plot background. */ 240 private transient Paint backgroundPaint; 241 242 /** An optional image for the plot background. */ 243 private transient Image backgroundImage; // not currently serialized 244 245 /** The alignment for the background image. */ 246 private int backgroundImageAlignment = Align.FIT; 247 248 /** The alpha-transparency for the plot. */ 249 private float foregroundAlpha; 250 251 /** The alpha transparency for the background paint. */ 252 private float backgroundAlpha; 253 254 /** The drawing supplier. */ 255 private DrawingSupplier drawingSupplier; 256 257 /** Storage for registered change listeners. */ 258 private transient EventListenerList listenerList; 259 260 /** 261 * Creates a new plot. 262 */ 263 protected Plot() { 264 265 this.parent = null; 266 this.insets = DEFAULT_INSETS; 267 this.backgroundPaint = DEFAULT_BACKGROUND_PAINT; 268 this.backgroundAlpha = DEFAULT_BACKGROUND_ALPHA; 269 this.backgroundImage = null; 270 this.outlineStroke = DEFAULT_OUTLINE_STROKE; 271 this.outlinePaint = DEFAULT_OUTLINE_PAINT; 272 this.foregroundAlpha = DEFAULT_FOREGROUND_ALPHA; 273 274 this.noDataMessage = null; 275 this.noDataMessageFont = new Font("SansSerif", Font.PLAIN, 12); 276 this.noDataMessagePaint = Color.black; 277 278 this.drawingSupplier = new DefaultDrawingSupplier(); 279 280 this.listenerList = new EventListenerList(); 281 282 } 283 284 /** 285 * Returns the dataset group for the plot (not currently used). 286 * 287 * @return The dataset group. 288 */ 289 public DatasetGroup getDatasetGroup() { 290 return this.datasetGroup; 291 } 292 293 /** 294 * Sets the dataset group (not currently used). 295 * 296 * @param group the dataset group (<code>null</code> permitted). 297 */ 298 protected void setDatasetGroup(DatasetGroup group) { 299 this.datasetGroup = group; 300 } 301 302 /** 303 * Returns the string that is displayed when the dataset is empty or 304 * <code>null</code>. 305 * 306 * @return The 'no data' message (<code>null</code> possible). 307 */ 308 public String getNoDataMessage() { 309 return this.noDataMessage; 310 } 311 312 /** 313 * Sets the message that is displayed when the dataset is empty or null. 314 * 315 * @param message the message (null permitted). 316 */ 317 public void setNoDataMessage(String message) { 318 this.noDataMessage = message; 319 } 320 321 /** 322 * Returns the font used to display the 'no data' message. 323 * 324 * @return The font. 325 */ 326 public Font getNoDataMessageFont() { 327 return this.noDataMessageFont; 328 } 329 330 /** 331 * Sets the font used to display the 'no data' message. 332 * 333 * @param font the font. 334 */ 335 public void setNoDataMessageFont(Font font) { 336 this.noDataMessageFont = font; 337 } 338 339 /** 340 * Returns the paint used to display the 'no data' message. 341 * 342 * @return The paint. 343 */ 344 public Paint getNoDataMessagePaint() { 345 return this.noDataMessagePaint; 346 } 347 348 /** 349 * Sets the paint used to display the 'no data' message. 350 * 351 * @param paint the paint. 352 */ 353 public void setNoDataMessagePaint(Paint paint) { 354 this.noDataMessagePaint = paint; 355 } 356 357 /** 358 * Returns a short string describing the plot type. 359 * <P> 360 * Note: this gets used in the chart property editing user interface, 361 * but there needs to be a better mechanism for identifying the plot type. 362 * 363 * @return A short string describing the plot type. 364 */ 365 public abstract String getPlotType(); 366 367 /** 368 * Returns the parent plot (or <code>null</code> if this plot is not part 369 * of a combined plot). 370 * 371 * @return The parent plot. 372 */ 373 public Plot getParent() { 374 return this.parent; 375 } 376 377 /** 378 * Sets the parent plot. 379 * 380 * @param parent the parent plot. 381 */ 382 public void setParent(Plot parent) { 383 this.parent = parent; 384 } 385 386 /** 387 * Returns the root plot. 388 * 389 * @return The root plot. 390 */ 391 public Plot getRootPlot() { 392 393 Plot p = getParent(); 394 if (p == null) { 395 return this; 396 } 397 else { 398 return p.getRootPlot(); 399 } 400 401 } 402 403 /** 404 * Returns true if this plot is part of a combined plot structure. 405 * 406 * @return <code>true</code> if this plot is part of a combined plot 407 * structure. 408 */ 409 public boolean isSubplot() { 410 return (getParent() != null); 411 } 412 413 /** 414 * Returns the insets for the plot area. 415 * 416 * @return The insets (never <code>null</code>). 417 */ 418 public RectangleInsets getInsets() { 419 return this.insets; 420 } 421 422 /** 423 * Sets the insets for the plot and sends a {@link PlotChangeEvent} to 424 * all registered listeners. 425 * 426 * @param insets the new insets (<code>null</code> not permitted). 427 */ 428 public void setInsets(RectangleInsets insets) { 429 setInsets(insets, true); 430 } 431 432 /** 433 * Sets the insets for the plot and, if requested, and sends a 434 * {@link PlotChangeEvent} to all registered listeners. 435 * 436 * @param insets the new insets (<code>null</code> not permitted). 437 * @param notify a flag that controls whether the registered listeners are 438 * notified. 439 */ 440 public void setInsets(RectangleInsets insets, boolean notify) { 441 if (insets == null) { 442 throw new IllegalArgumentException("Null 'insets' argument."); 443 } 444 if (!this.insets.equals(insets)) { 445 this.insets = insets; 446 if (notify) { 447 notifyListeners(new PlotChangeEvent(this)); 448 } 449 } 450 451 } 452 453 /** 454 * Returns the background color of the plot area. 455 * 456 * @return The paint (possibly <code>null</code>). 457 */ 458 public Paint getBackgroundPaint() { 459 return this.backgroundPaint; 460 } 461 462 /** 463 * Sets the background color of the plot area and sends a 464 * {@link PlotChangeEvent} to all registered listeners. 465 * 466 * @param paint the paint (<code>null</code> permitted). 467 */ 468 public void setBackgroundPaint(Paint paint) { 469 470 if (paint == null) { 471 if (this.backgroundPaint != null) { 472 this.backgroundPaint = null; 473 notifyListeners(new PlotChangeEvent(this)); 474 } 475 } 476 else { 477 if (this.backgroundPaint != null) { 478 if (this.backgroundPaint.equals(paint)) { 479 return; // nothing to do 480 } 481 } 482 this.backgroundPaint = paint; 483 notifyListeners(new PlotChangeEvent(this)); 484 } 485 486 } 487 488 /** 489 * Returns the alpha transparency of the plot area background. 490 * 491 * @return The alpha transparency. 492 */ 493 public float getBackgroundAlpha() { 494 return this.backgroundAlpha; 495 } 496 497 /** 498 * Sets the alpha transparency of the plot area background, and notifies 499 * registered listeners that the plot has been modified. 500 * 501 * @param alpha the new alpha value. 502 */ 503 public void setBackgroundAlpha(float alpha) { 504 505 if (this.backgroundAlpha != alpha) { 506 this.backgroundAlpha = alpha; 507 notifyListeners(new PlotChangeEvent(this)); 508 } 509 510 } 511 512 /** 513 * Returns the drawing supplier for the plot. 514 * 515 * @return The drawing supplier (possibly <code>null</code>). 516 */ 517 public DrawingSupplier getDrawingSupplier() { 518 DrawingSupplier result = null; 519 Plot p = getParent(); 520 if (p != null) { 521 result = p.getDrawingSupplier(); 522 } 523 else { 524 result = this.drawingSupplier; 525 } 526 return result; 527 } 528 529 /** 530 * Sets the drawing supplier for the plot. The drawing supplier is 531 * responsible for supplying a limitless (possibly repeating) sequence of 532 * <code>Paint</code>, <code>Stroke</code> and <code>Shape</code> objects 533 * that the plot's renderer(s) can use to populate its (their) tables. 534 * 535 * @param supplier the new supplier. 536 */ 537 public void setDrawingSupplier(DrawingSupplier supplier) { 538 this.drawingSupplier = supplier; 539 notifyListeners(new PlotChangeEvent(this)); 540 } 541 542 /** 543 * Returns the background image that is used to fill the plot's background 544 * area. 545 * 546 * @return The image (possibly <code>null</code>). 547 */ 548 public Image getBackgroundImage() { 549 return this.backgroundImage; 550 } 551 552 /** 553 * Sets the background image for the plot. 554 * 555 * @param image the image (<code>null</code> permitted). 556 */ 557 public void setBackgroundImage(Image image) { 558 this.backgroundImage = image; 559 notifyListeners(new PlotChangeEvent(this)); 560 } 561 562 /** 563 * Returns the background image alignment. Alignment constants are defined 564 * in the <code>org.jfree.ui.Align</code> class in the JCommon class 565 * library. 566 * 567 * @return The alignment. 568 */ 569 public int getBackgroundImageAlignment() { 570 return this.backgroundImageAlignment; 571 } 572 573 /** 574 * Sets the alignment for the background image and sends a 575 * {@link PlotChangeEvent} to all registered listeners. Alignment options 576 * are defined by the {@link org.jfree.ui.Align} class in the JCommon 577 * class library. 578 * 579 * @param alignment the alignment. 580 */ 581 public void setBackgroundImageAlignment(int alignment) { 582 if (this.backgroundImageAlignment != alignment) { 583 this.backgroundImageAlignment = alignment; 584 notifyListeners(new PlotChangeEvent(this)); 585 } 586 } 587 588 /** 589 * Returns the stroke used to outline the plot area. 590 * 591 * @return The stroke (possibly <code>null</code>). 592 */ 593 public Stroke getOutlineStroke() { 594 return this.outlineStroke; 595 } 596 597 /** 598 * Sets the stroke used to outline the plot area and sends a 599 * {@link PlotChangeEvent} to all registered listeners. If you set this 600 * attribute to <code>null<.code>, no outline will be drawn. 601 * 602 * @param stroke the stroke (<code>null</code> permitted). 603 */ 604 public void setOutlineStroke(Stroke stroke) { 605 606 if (stroke == null) { 607 if (this.outlineStroke != null) { 608 this.outlineStroke = null; 609 notifyListeners(new PlotChangeEvent(this)); 610 } 611 } 612 else { 613 if (this.outlineStroke != null) { 614 if (this.outlineStroke.equals(stroke)) { 615 return; // nothing to do 616 } 617 } 618 this.outlineStroke = stroke; 619 notifyListeners(new PlotChangeEvent(this)); 620 } 621 622 } 623 624 /** 625 * Returns the color used to draw the outline of the plot area. 626 * 627 * @return The color (possibly <code>null<code>). 628 */ 629 public Paint getOutlinePaint() { 630 return this.outlinePaint; 631 } 632 633 /** 634 * Sets the paint used to draw the outline of the plot area and sends a 635 * {@link PlotChangeEvent} to all registered listeners. If you set this 636 * attribute to <code>null</code>, no outline will be drawn. 637 * 638 * @param paint the paint (<code>null</code> permitted). 639 */ 640 public void setOutlinePaint(Paint paint) { 641 642 if (paint == null) { 643 if (this.outlinePaint != null) { 644 this.outlinePaint = null; 645 notifyListeners(new PlotChangeEvent(this)); 646 } 647 } 648 else { 649 if (this.outlinePaint != null) { 650 if (this.outlinePaint.equals(paint)) { 651 return; // nothing to do 652 } 653 } 654 this.outlinePaint = paint; 655 notifyListeners(new PlotChangeEvent(this)); 656 } 657 658 } 659 660 /** 661 * Returns the alpha-transparency for the plot foreground. 662 * 663 * @return The alpha-transparency. 664 */ 665 public float getForegroundAlpha() { 666 return this.foregroundAlpha; 667 } 668 669 /** 670 * Sets the alpha-transparency for the plot. 671 * 672 * @param alpha the new alpha transparency. 673 */ 674 public void setForegroundAlpha(float alpha) { 675 676 if (this.foregroundAlpha != alpha) { 677 this.foregroundAlpha = alpha; 678 notifyListeners(new PlotChangeEvent(this)); 679 } 680 681 } 682 683 /** 684 * Returns the legend items for the plot. By default, this method returns 685 * <code>null</code>. Subclasses should override to return a 686 * {@link LegendItemCollection}. 687 * 688 * @return The legend items for the plot (possibly <code>null</code>). 689 */ 690 public LegendItemCollection getLegendItems() { 691 return null; 692 } 693 694 /** 695 * Registers an object for notification of changes to the plot. 696 * 697 * @param listener the object to be registered. 698 */ 699 public void addChangeListener(PlotChangeListener listener) { 700 this.listenerList.add(PlotChangeListener.class, listener); 701 } 702 703 /** 704 * Unregisters an object for notification of changes to the plot. 705 * 706 * @param listener the object to be unregistered. 707 */ 708 public void removeChangeListener(PlotChangeListener listener) { 709 this.listenerList.remove(PlotChangeListener.class, listener); 710 } 711 712 /** 713 * Notifies all registered listeners that the plot has been modified. 714 * 715 * @param event information about the change event. 716 */ 717 public void notifyListeners(PlotChangeEvent event) { 718 719 Object[] listeners = this.listenerList.getListenerList(); 720 for (int i = listeners.length - 2; i >= 0; i -= 2) { 721 if (listeners[i] == PlotChangeListener.class) { 722 ((PlotChangeListener) listeners[i + 1]).plotChanged(event); 723 } 724 } 725 726 } 727 728 /** 729 * Draws the plot within the specified area. The anchor is a point on the 730 * chart that is specified externally (for instance, it may be the last 731 * point of the last mouse click performed by the user) - plots can use or 732 * ignore this value as they see fit. 733 * <br><br> 734 * Subclasses need to provide an implementation of this method, obviously. 735 * 736 * @param g2 the graphics device. 737 * @param area the plot area. 738 * @param anchor the anchor point (<code>null</code> permitted). 739 * @param parentState the parent state (if any). 740 * @param info carries back plot rendering info. 741 */ 742 public abstract void draw(Graphics2D g2, 743 Rectangle2D area, 744 Point2D anchor, 745 PlotState parentState, 746 PlotRenderingInfo info); 747 748 /** 749 * Draws the plot background (the background color and/or image). 750 * <P> 751 * This method will be called during the chart drawing process and is 752 * declared public so that it can be accessed by the renderers used by 753 * certain subclasses. You shouldn't need to call this method directly. 754 * 755 * @param g2 the graphics device. 756 * @param area the area within which the plot should be drawn. 757 */ 758 public void drawBackground(Graphics2D g2, Rectangle2D area) { 759 fillBackground(g2, area); 760 drawBackgroundImage(g2, area); 761 } 762 763 /** 764 * Fills the specified area with the background paint. 765 * 766 * @param g2 the graphics device. 767 * @param area the area. 768 */ 769 protected void fillBackground(Graphics2D g2, Rectangle2D area) { 770 if (this.backgroundPaint != null) { 771 Composite originalComposite = g2.getComposite(); 772 g2.setComposite( 773 AlphaComposite.getInstance( 774 AlphaComposite.SRC_OVER, this.backgroundAlpha 775 ) 776 ); 777 g2.setPaint(this.backgroundPaint); 778 g2.fill(area); 779 g2.setComposite(originalComposite); 780 } 781 } 782 783 /** 784 * Draws the background image (if there is one) aligned within the 785 * specified area. 786 * 787 * @param g2 the graphics device. 788 * @param area the area. 789 */ 790 protected void drawBackgroundImage(Graphics2D g2, Rectangle2D area) { 791 if (this.backgroundImage != null) { 792 Composite originalComposite = g2.getComposite(); 793 g2.setComposite(AlphaComposite.getInstance( 794 AlphaComposite.SRC, this.backgroundAlpha 795 )); 796 Rectangle2D dest = new Rectangle2D.Double( 797 0.0, 0.0, 798 this.backgroundImage.getWidth(null), 799 this.backgroundImage.getHeight(null) 800 ); 801 Align.align(dest, area, this.backgroundImageAlignment); 802 g2.drawImage( 803 this.backgroundImage, 804 (int) dest.getX(), (int) dest.getY(), 805 (int) dest.getWidth() + 1, (int) dest.getHeight() + 1, null 806 ); 807 g2.setComposite(originalComposite); 808 } 809 } 810 811 /** 812 * Draws the plot outline. This method will be called during the chart 813 * drawing process and is declared public so that it can be accessed by the 814 * renderers used by certain subclasses. You shouldn't need to call this 815 * method directly. 816 * 817 * @param g2 the graphics device. 818 * @param area the area within which the plot should be drawn. 819 */ 820 public void drawOutline(Graphics2D g2, Rectangle2D area) { 821 if ((this.outlineStroke != null) && (this.outlinePaint != null)) { 822 g2.setStroke(this.outlineStroke); 823 g2.setPaint(this.outlinePaint); 824 g2.draw(area); 825 } 826 } 827 828 /** 829 * Draws a message to state that there is no data to plot. 830 * 831 * @param g2 the graphics device. 832 * @param area the area within which the plot should be drawn. 833 */ 834 protected void drawNoDataMessage(Graphics2D g2, Rectangle2D area) { 835 836 Shape savedClip = g2.getClip(); 837 g2.clip(area); 838 String message = this.noDataMessage; 839 if (message != null) { 840 g2.setFont(this.noDataMessageFont); 841 g2.setPaint(this.noDataMessagePaint); 842 TextBlock block = TextUtilities.createTextBlock( 843 this.noDataMessage, this.noDataMessageFont, 844 this.noDataMessagePaint, 845 0.9f * (float) area.getWidth(), new G2TextMeasurer(g2) 846 ); 847 block.draw( 848 g2, (float) area.getCenterX(), (float) area.getCenterY(), 849 TextBlockAnchor.CENTER 850 ); 851 } 852 g2.setClip(savedClip); 853 854 } 855 856 /** 857 * Handles a 'click' on the plot. Since the plot does not maintain any 858 * information about where it has been drawn, the plot rendering info is 859 * supplied as an argument. 860 * 861 * @param x the x coordinate (in Java2D space). 862 * @param y the y coordinate (in Java2D space). 863 * @param info an object containing information about the dimensions of 864 * the plot. 865 */ 866 public void handleClick(int x, int y, PlotRenderingInfo info) { 867 // provides a 'no action' default 868 } 869 870 /** 871 * Performs a zoom on the plot. Subclasses should override if zooming is 872 * appropriate for the type of plot. 873 * 874 * @param percent the zoom percentage. 875 */ 876 public void zoom(double percent) { 877 // do nothing by default. 878 } 879 880 /** 881 * Receives notification of a change to one of the plot's axes. 882 * 883 * @param event information about the event (not used here). 884 */ 885 public void axisChanged(AxisChangeEvent event) { 886 notifyListeners(new PlotChangeEvent(this)); 887 } 888 889 /** 890 * Receives notification of a change to the plot's dataset. 891 * <P> 892 * The plot reacts by passing on a plot change event to all registered 893 * listeners. 894 * 895 * @param event information about the event (not used here). 896 */ 897 public void datasetChanged(DatasetChangeEvent event) { 898 PlotChangeEvent newEvent = new PlotChangeEvent(this); 899 newEvent.setType(ChartChangeEventType.DATASET_UPDATED); 900 notifyListeners(newEvent); 901 } 902 903 /** 904 * Adjusts the supplied x-value. 905 * 906 * @param x the x-value. 907 * @param w1 width 1. 908 * @param w2 width 2. 909 * @param edge the edge (left or right). 910 * 911 * @return The adjusted x-value. 912 */ 913 protected double getRectX(double x, double w1, double w2, 914 RectangleEdge edge) { 915 916 double result = x; 917 if (edge == RectangleEdge.LEFT) { 918 result = result + w1; 919 } 920 else if (edge == RectangleEdge.RIGHT) { 921 result = result + w2; 922 } 923 return result; 924 925 } 926 927 /** 928 * Adjusts the supplied y-value. 929 * 930 * @param y the x-value. 931 * @param h1 height 1. 932 * @param h2 height 2. 933 * @param edge the edge (top or bottom). 934 * 935 * @return The adjusted y-value. 936 */ 937 protected double getRectY(double y, double h1, double h2, 938 RectangleEdge edge) { 939 940 double result = y; 941 if (edge == RectangleEdge.TOP) { 942 result = result + h1; 943 } 944 else if (edge == RectangleEdge.BOTTOM) { 945 result = result + h2; 946 } 947 return result; 948 949 } 950 951 /** 952 * Tests this plot for equality with another object. 953 * 954 * @param obj the object (<code>null</code> permitted). 955 * 956 * @return <code>true</code> or <code>false</code>. 957 */ 958 public boolean equals(Object obj) { 959 if (obj == this) { 960 return true; 961 } 962 if (!(obj instanceof Plot)) { 963 return false; 964 } 965 Plot that = (Plot) obj; 966 if (!ObjectUtilities.equal(this.noDataMessage, that.noDataMessage)) { 967 return false; 968 } 969 if (!ObjectUtilities.equal( 970 this.noDataMessageFont, that.noDataMessageFont 971 )) { 972 return false; 973 } 974 if (!PaintUtilities.equal( 975 this.noDataMessagePaint, that.noDataMessagePaint 976 )) { 977 return false; 978 } 979 if (!ObjectUtilities.equal(this.insets, that.insets)) { 980 return false; 981 } 982 if (!ObjectUtilities.equal(this.outlineStroke, that.outlineStroke)) { 983 return false; 984 } 985 if (!PaintUtilities.equal(this.outlinePaint, that.outlinePaint)) { 986 return false; 987 } 988 if (!PaintUtilities.equal(this.backgroundPaint, that.backgroundPaint)) { 989 return false; 990 } 991 if (!ObjectUtilities.equal( 992 this.backgroundImage, that.backgroundImage 993 )) { 994 return false; 995 } 996 if (this.backgroundImageAlignment != that.backgroundImageAlignment) { 997 return false; 998 } 999 if (this.foregroundAlpha != that.foregroundAlpha) { 1000 return false; 1001 } 1002 if (this.backgroundAlpha != that.backgroundAlpha) { 1003 return false; 1004 } 1005 if (!this.drawingSupplier.equals(that.drawingSupplier)) { 1006 return false; 1007 } 1008 return true; 1009 } 1010 1011 /** 1012 * Creates a clone of the plot. 1013 * 1014 * @return A clone. 1015 * 1016 * @throws CloneNotSupportedException if some component of the plot does not 1017 * support cloning. 1018 */ 1019 public Object clone() throws CloneNotSupportedException { 1020 1021 Plot clone = (Plot) super.clone(); 1022 // private Plot parent <-- don't clone the parent plot, but take care 1023 // childs in combined plots instead 1024 if (this.datasetGroup != null) { 1025 clone.datasetGroup 1026 = (DatasetGroup) ObjectUtilities.clone(this.datasetGroup); 1027 } 1028 clone.drawingSupplier 1029 = (DrawingSupplier) ObjectUtilities.clone(this.drawingSupplier); 1030 clone.listenerList = new EventListenerList(); 1031 return clone; 1032 1033 } 1034 1035 /** 1036 * Provides serialization support. 1037 * 1038 * @param stream the output stream. 1039 * 1040 * @throws IOException if there is an I/O error. 1041 */ 1042 private void writeObject(ObjectOutputStream stream) throws IOException { 1043 stream.defaultWriteObject(); 1044 SerialUtilities.writePaint(this.noDataMessagePaint, stream); 1045 SerialUtilities.writeStroke(this.outlineStroke, stream); 1046 SerialUtilities.writePaint(this.outlinePaint, stream); 1047 // backgroundImage 1048 SerialUtilities.writePaint(this.backgroundPaint, stream); 1049 } 1050 1051 /** 1052 * Provides serialization support. 1053 * 1054 * @param stream the input stream. 1055 * 1056 * @throws IOException if there is an I/O error. 1057 * @throws ClassNotFoundException if there is a classpath problem. 1058 */ 1059 private void readObject(ObjectInputStream stream) 1060 throws IOException, ClassNotFoundException { 1061 stream.defaultReadObject(); 1062 this.noDataMessagePaint = SerialUtilities.readPaint(stream); 1063 this.outlineStroke = SerialUtilities.readStroke(stream); 1064 this.outlinePaint = SerialUtilities.readPaint(stream); 1065 // backgroundImage 1066 this.backgroundPaint = SerialUtilities.readPaint(stream); 1067 1068 this.listenerList = new EventListenerList(); 1069 1070 } 1071 1072 /** 1073 * Resolves a domain axis location for a given plot orientation. 1074 * 1075 * @param location the location (<code>null</code> not permitted). 1076 * @param orientation the orientation (<code>null</code> not permitted). 1077 * 1078 * @return The edge (never <code>null</code>). 1079 */ 1080 public static RectangleEdge resolveDomainAxisLocation( 1081 AxisLocation location, PlotOrientation orientation) { 1082 1083 if (location == null) { 1084 throw new IllegalArgumentException("Null 'location' argument."); 1085 } 1086 if (orientation == null) { 1087 throw new IllegalArgumentException("Null 'orientation' argument."); 1088 } 1089 1090 RectangleEdge result = null; 1091 1092 if (location == AxisLocation.TOP_OR_RIGHT) { 1093 if (orientation == PlotOrientation.HORIZONTAL) { 1094 result = RectangleEdge.RIGHT; 1095 } 1096 else if (orientation == PlotOrientation.VERTICAL) { 1097 result = RectangleEdge.TOP; 1098 } 1099 } 1100 else if (location == AxisLocation.TOP_OR_LEFT) { 1101 if (orientation == PlotOrientation.HORIZONTAL) { 1102 result = RectangleEdge.LEFT; 1103 } 1104 else if (orientation == PlotOrientation.VERTICAL) { 1105 result = RectangleEdge.TOP; 1106 } 1107 } 1108 else if (location == AxisLocation.BOTTOM_OR_RIGHT) { 1109 if (orientation == PlotOrientation.HORIZONTAL) { 1110 result = RectangleEdge.RIGHT; 1111 } 1112 else if (orientation == PlotOrientation.VERTICAL) { 1113 result = RectangleEdge.BOTTOM; 1114 } 1115 } 1116 else if (location == AxisLocation.BOTTOM_OR_LEFT) { 1117 if (orientation == PlotOrientation.HORIZONTAL) { 1118 result = RectangleEdge.LEFT; 1119 } 1120 else if (orientation == PlotOrientation.VERTICAL) { 1121 result = RectangleEdge.BOTTOM; 1122 } 1123 } 1124 // the above should cover all the options... 1125 if (result == null) { 1126 throw new IllegalStateException("resolveDomainAxisLocation()"); 1127 } 1128 return result; 1129 1130 } 1131 1132 /** 1133 * Resolves a range axis location for a given plot orientation. 1134 * 1135 * @param location the location (<code>null</code> not permitted). 1136 * @param orientation the orientation (<code>null</code> not permitted). 1137 * 1138 * @return The edge (never <code>null</code>). 1139 */ 1140 public static RectangleEdge resolveRangeAxisLocation( 1141 AxisLocation location, PlotOrientation orientation) { 1142 1143 if (location == null) { 1144 throw new IllegalArgumentException("Null 'location' argument."); 1145 } 1146 if (orientation == null) { 1147 throw new IllegalArgumentException("Null 'orientation' argument."); 1148 } 1149 1150 RectangleEdge result = null; 1151 1152 if (location == AxisLocation.TOP_OR_RIGHT) { 1153 if (orientation == PlotOrientation.HORIZONTAL) { 1154 result = RectangleEdge.TOP; 1155 } 1156 else if (orientation == PlotOrientation.VERTICAL) { 1157 result = RectangleEdge.RIGHT; 1158 } 1159 } 1160 else if (location == AxisLocation.TOP_OR_LEFT) { 1161 if (orientation == PlotOrientation.HORIZONTAL) { 1162 result = RectangleEdge.TOP; 1163 } 1164 else if (orientation == PlotOrientation.VERTICAL) { 1165 result = RectangleEdge.LEFT; 1166 } 1167 } 1168 else if (location == AxisLocation.BOTTOM_OR_RIGHT) { 1169 if (orientation == PlotOrientation.HORIZONTAL) { 1170 result = RectangleEdge.BOTTOM; 1171 } 1172 else if (orientation == PlotOrientation.VERTICAL) { 1173 result = RectangleEdge.RIGHT; 1174 } 1175 } 1176 else if (location == AxisLocation.BOTTOM_OR_LEFT) { 1177 if (orientation == PlotOrientation.HORIZONTAL) { 1178 result = RectangleEdge.BOTTOM; 1179 } 1180 else if (orientation == PlotOrientation.VERTICAL) { 1181 result = RectangleEdge.LEFT; 1182 } 1183 } 1184 1185 // the above should cover all the options... 1186 if (result == null) { 1187 throw new IllegalStateException("resolveRangeAxisLocation()"); 1188 } 1189 return result; 1190 1191 } 1192 1193 } 1194