    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