    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    }