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