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