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