Source for org.jfree.chart.plot.Plot

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