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