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