001/* ======================================================
002 * JFreeChart : a chart library for the Java(tm) platform
003 * ======================================================
004 *
005 * (C) Copyright 2000-present, by David Gilbert and Contributors.
006 *
007 * Project Info:  https://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 * JFreeChart.java
029 * ---------------
030 * (C) Copyright 2000-present, by David Gilbert and Contributors.
031 *
032 * Original Author:  David Gilbert;
033 * Contributor(s):   Andrzej Porebski;
034 *                   David Li;
035 *                   Wolfgang Irler;
036 *                   Christian W. Zuckschwerdt;
037 *                   Klaus Rheinwald;
038 *                   Nicolas Brodu;
039 *                   Peter Kolb (patch 2603321);
040 *                   Tracy Hiltbrand (equals/hashCode comply with EqualsVerifier);
041 *
042 * NOTE: The above list of contributors lists only the people that have
043 * contributed to this source file (JFreeChart.java) - for a list of ALL
044 * contributors to the project, please see the README.md file.
045 *
046 */
047
048package org.jfree.chart;
049
050import java.awt.AlphaComposite;
051import java.awt.BasicStroke;
052import java.awt.Color;
053import java.awt.Composite;
054import java.awt.Font;
055import java.awt.Graphics2D;
056import java.awt.Image;
057import java.awt.Paint;
058import java.awt.RenderingHints;
059import java.awt.Shape;
060import java.awt.Stroke;
061import java.awt.geom.AffineTransform;
062import java.awt.geom.Point2D;
063import java.awt.geom.Rectangle2D;
064import java.awt.image.BufferedImage;
065import java.io.IOException;
066import java.io.ObjectInputStream;
067import java.io.ObjectOutputStream;
068import java.io.Serializable;
069import java.util.ArrayList;
070import java.util.HashMap;
071import java.util.List;
072import java.util.Map;
073import java.util.Objects;
074import javax.swing.event.EventListenerList;
075
076import org.jfree.chart.block.BlockParams;
077import org.jfree.chart.block.EntityBlockResult;
078import org.jfree.chart.block.LengthConstraintType;
079import org.jfree.chart.block.RectangleConstraint;
080import org.jfree.chart.entity.EntityCollection;
081import org.jfree.chart.entity.JFreeChartEntity;
082import org.jfree.chart.event.ChartChangeEvent;
083import org.jfree.chart.event.ChartChangeListener;
084import org.jfree.chart.event.ChartProgressEvent;
085import org.jfree.chart.event.ChartProgressListener;
086import org.jfree.chart.event.PlotChangeEvent;
087import org.jfree.chart.event.PlotChangeListener;
088import org.jfree.chart.event.TitleChangeEvent;
089import org.jfree.chart.event.TitleChangeListener;
090import org.jfree.chart.plot.CategoryPlot;
091import org.jfree.chart.plot.Plot;
092import org.jfree.chart.plot.PlotRenderingInfo;
093import org.jfree.chart.plot.XYPlot;
094import org.jfree.chart.title.LegendTitle;
095import org.jfree.chart.title.TextTitle;
096import org.jfree.chart.title.Title;
097import org.jfree.chart.ui.Align;
098import org.jfree.chart.ui.Drawable;
099import org.jfree.chart.ui.HorizontalAlignment;
100import org.jfree.chart.ui.RectangleEdge;
101import org.jfree.chart.ui.RectangleInsets;
102import org.jfree.chart.ui.Size2D;
103import org.jfree.chart.ui.VerticalAlignment;
104import org.jfree.chart.util.PaintUtils;
105import org.jfree.chart.util.Args;
106import org.jfree.chart.util.SerialUtils;
107import org.jfree.data.Range;
108
109/**
110 * A chart class implemented using the Java 2D APIs.  The current version
111 * supports bar charts, line charts, pie charts and xy plots (including time
112 * series data).
113 * <P>
114 * JFreeChart coordinates several objects to achieve its aim of being able to
115 * draw a chart on a Java 2D graphics device: a list of {@link Title} objects
116 * (which often includes the chart's legend), a {@link Plot} and a
117 * {@link org.jfree.data.general.Dataset} (the plot in turn manages a
118 * domain axis and a range axis).
119 * <P>
120 * You should use a {@link ChartPanel} to display a chart in a GUI.
121 * <P>
122 * The {@link ChartFactory} class contains static methods for creating
123 * 'ready-made' charts.
124 *
125 * @see ChartPanel
126 * @see ChartFactory
127 * @see Title
128 * @see Plot
129 */
130public class JFreeChart implements Drawable, TitleChangeListener,
131        PlotChangeListener, Serializable, Cloneable {
132
133    /** For serialization. */
134    private static final long serialVersionUID = -3470703747817429120L;
135
136    /** The default font for titles. */
137    public static final Font DEFAULT_TITLE_FONT
138            = new Font("SansSerif", Font.BOLD, 18);
139
140    /** The default background color. */
141    public static final Paint DEFAULT_BACKGROUND_PAINT = Color.LIGHT_GRAY;
142
143    /** The default background image. */
144    public static final Image DEFAULT_BACKGROUND_IMAGE = null;
145
146    /** The default background image alignment. */
147    public static final int DEFAULT_BACKGROUND_IMAGE_ALIGNMENT = Align.FIT;
148
149    /** The default background image alpha. */
150    public static final float DEFAULT_BACKGROUND_IMAGE_ALPHA = 0.5f;
151
152    /**
153     * The key for a rendering hint that can suppress the generation of a 
154     * shadow effect when drawing the chart.  The hint value must be a 
155     * Boolean.
156     */
157    public static final RenderingHints.Key KEY_SUPPRESS_SHADOW_GENERATION
158            = new RenderingHints.Key(0) {
159        @Override
160        public boolean isCompatibleValue(Object val) {
161            return val instanceof Boolean;
162        }
163    };
164    
165    /**
166     * Rendering hints that will be used for chart drawing.  This should never
167     * be {@code null}.
168     */
169    private transient RenderingHints renderingHints;
170
171    /** The chart id (optional, will be used by JFreeSVG export). */
172    private String id;
173    
174    /** A flag that controls whether the chart border is drawn. */
175    private boolean borderVisible;
176
177    /** The stroke used to draw the chart border (if visible). */
178    private transient Stroke borderStroke;
179
180    /** The paint used to draw the chart border (if visible). */
181    private transient Paint borderPaint;
182
183    /** The padding between the chart border and the chart drawing area. */
184    private RectangleInsets padding;
185
186    /** The chart title (optional). */
187    private TextTitle title;
188
189    /**
190     * The chart subtitles (zero, one or many).  This field should never be
191     * {@code null}.
192     */
193    private List<Title> subtitles;
194
195    /** Draws the visual representation of the data. */
196    private Plot plot;
197
198    /** Paint used to draw the background of the chart. */
199    private transient Paint backgroundPaint;
200
201    /** An optional background image for the chart. */
202    private transient Image backgroundImage;  // todo: not serialized yet
203
204    /** The alignment for the background image. */
205    private int backgroundImageAlignment = Align.FIT;
206
207    /** The alpha transparency for the background image. */
208    private float backgroundImageAlpha = 0.5f;
209
210    /** Storage for registered change listeners. */
211    private transient EventListenerList changeListeners;
212
213    /** Storage for registered progress listeners. */
214    private transient EventListenerList progressListeners;
215
216    /**
217     * A flag that can be used to enable/disable notification of chart change
218     * events.
219     */
220    private boolean notify;
221
222    /** 
223     * A flag that controls whether rendering hints that identify
224     * chart element should be added during rendering.  This defaults to false
225     * and it should only be enabled if the output target will use the hints.
226     * JFreeSVG is one output target that supports these hints.
227     */
228    private boolean elementHinting;
229    
230    /**
231     * Creates a new chart based on the supplied plot.  The chart will have
232     * a legend added automatically, but no title (although you can easily add
233     * one later).
234     * <br><br>
235     * Note that the  {@link ChartFactory} class contains a range
236     * of static methods that will return ready-made charts, and often this
237     * is a more convenient way to create charts than using this constructor.
238     *
239     * @param plot  the plot ({@code null} not permitted).
240     */
241    public JFreeChart(Plot plot) {
242        this(null, null, plot, true);
243    }
244
245    /**
246     * Creates a new chart with the given title and plot.  A default font
247     * ({@link #DEFAULT_TITLE_FONT}) is used for the title, and the chart will
248     * have a legend added automatically.
249     * <br><br>
250     * Note that the {@link ChartFactory} class contains a range
251     * of static methods that will return ready-made charts, and often this
252     * is a more convenient way to create charts than using this constructor.
253     *
254     * @param title  the chart title ({@code null} permitted).
255     * @param plot  the plot ({@code null} not permitted).
256     */
257    public JFreeChart(String title, Plot plot) {
258        this(title, JFreeChart.DEFAULT_TITLE_FONT, plot, true);
259    }
260
261    /**
262     * Creates a new chart with the given title and plot.  The
263     * {@code createLegend} argument specifies whether a legend
264     * should be added to the chart.
265     * <br><br>
266     * Note that the  {@link ChartFactory} class contains a range
267     * of static methods that will return ready-made charts, and often this
268     * is a more convenient way to create charts than using this constructor.
269     *
270     * @param title  the chart title ({@code null} permitted).
271     * @param titleFont  the font for displaying the chart title
272     *                   ({@code null} permitted).
273     * @param plot  controller of the visual representation of the data
274     *              ({@code null} not permitted).
275     * @param createLegend  a flag indicating whether a legend should
276     *                      be created for the chart.
277     */
278    public JFreeChart(String title, Font titleFont, Plot plot,
279                      boolean createLegend) {
280
281        Args.nullNotPermitted(plot, "plot");
282        this.id = null;
283        plot.setChart(this);
284        
285        // create storage for listeners...
286        this.progressListeners = new EventListenerList();
287        this.changeListeners = new EventListenerList();
288        this.notify = true;  // default is to notify listeners when the
289                             // chart changes
290
291        this.renderingHints = new RenderingHints(
292                RenderingHints.KEY_ANTIALIASING,
293                RenderingHints.VALUE_ANTIALIAS_ON);
294        // added the following hint because of 
295        // http://stackoverflow.com/questions/7785082/
296        this.renderingHints.put(RenderingHints.KEY_STROKE_CONTROL,
297                RenderingHints.VALUE_STROKE_PURE);
298        
299        this.borderVisible = false;
300        this.borderStroke = new BasicStroke(1.0f);
301        this.borderPaint = Color.BLACK;
302
303        this.padding = RectangleInsets.ZERO_INSETS;
304
305        this.plot = plot;
306        plot.addChangeListener(this);
307
308        this.subtitles = new ArrayList<>();
309
310        // create a legend, if requested...
311        if (createLegend) {
312            LegendTitle legend = new LegendTitle(this.plot);
313            legend.setMargin(new RectangleInsets(1.0, 1.0, 1.0, 1.0));
314            legend.setBackgroundPaint(Color.WHITE);
315            legend.setPosition(RectangleEdge.BOTTOM);
316            this.subtitles.add(legend);
317            legend.addChangeListener(this);
318        }
319
320        // add the chart title, if one has been specified...
321        if (title != null) {
322            if (titleFont == null) {
323                titleFont = DEFAULT_TITLE_FONT;
324            }
325            this.title = new TextTitle(title, titleFont);
326            this.title.addChangeListener(this);
327        }
328
329        this.backgroundPaint = DEFAULT_BACKGROUND_PAINT;
330
331        this.backgroundImage = DEFAULT_BACKGROUND_IMAGE;
332        this.backgroundImageAlignment = DEFAULT_BACKGROUND_IMAGE_ALIGNMENT;
333        this.backgroundImageAlpha = DEFAULT_BACKGROUND_IMAGE_ALPHA;
334    }
335
336    /**
337     * Returns the ID for the chart.
338     * 
339     * @return The ID for the chart (possibly {@code null}).
340     */
341    public String getID() {
342        return this.id;
343    }
344    
345    /**
346     * Sets the ID for the chart.
347     * 
348     * @param id  the id ({@code null} permitted).
349     */
350    public void setID(String id) {
351        this.id = id;
352    }
353    
354    /**
355     * Returns the flag that controls whether rendering hints 
356     * ({@link ChartHints#KEY_BEGIN_ELEMENT} and 
357     * {@link ChartHints#KEY_END_ELEMENT}) that identify chart elements are 
358     * added during rendering.  The default value is {@code false}.
359     * 
360     * @return A boolean.
361     * 
362     * @see #setElementHinting(boolean) 
363     */
364    public boolean getElementHinting() {
365        return this.elementHinting;
366    }
367    
368    /**
369     * Sets the flag that controls whether rendering hints 
370     * ({@link ChartHints#KEY_BEGIN_ELEMENT} and 
371     * {@link ChartHints#KEY_END_ELEMENT}) that identify chart elements are 
372     * added during rendering.
373     * 
374     * @param hinting  the new flag value.
375     * 
376     * @see #getElementHinting() 
377     */
378    public void setElementHinting(boolean hinting) {
379        this.elementHinting = hinting;
380    }
381    
382    /**
383     * Returns the collection of rendering hints for the chart.
384     *
385     * @return The rendering hints for the chart (never {@code null}).
386     *
387     * @see #setRenderingHints(RenderingHints)
388     */
389    public RenderingHints getRenderingHints() {
390        return this.renderingHints;
391    }
392
393    /**
394     * Sets the rendering hints for the chart.  These will be added (using the
395     * {@code Graphics2D.addRenderingHints()} method) near the start of the
396     * {@code JFreeChart.draw()} method.
397     *
398     * @param renderingHints  the rendering hints ({@code null} not permitted).
399     *
400     * @see #getRenderingHints()
401     */
402    public void setRenderingHints(RenderingHints renderingHints) {
403        Args.nullNotPermitted(renderingHints, "renderingHints");
404        this.renderingHints = renderingHints;
405        fireChartChanged();
406    }
407
408    /**
409     * Returns a flag that controls whether a border is drawn around the
410     * outside of the chart.
411     *
412     * @return A boolean.
413     *
414     * @see #setBorderVisible(boolean)
415     */
416    public boolean isBorderVisible() {
417        return this.borderVisible;
418    }
419
420    /**
421     * Sets a flag that controls whether a border is drawn around the
422     * outside of the chart.
423     *
424     * @param visible  the flag.
425     *
426     * @see #isBorderVisible()
427     */
428    public void setBorderVisible(boolean visible) {
429        this.borderVisible = visible;
430        fireChartChanged();
431    }
432
433    /**
434     * Returns the stroke used to draw the chart border (if visible).
435     *
436     * @return The border stroke.
437     *
438     * @see #setBorderStroke(Stroke)
439     */
440    public Stroke getBorderStroke() {
441        return this.borderStroke;
442    }
443
444    /**
445     * Sets the stroke used to draw the chart border (if visible).
446     *
447     * @param stroke  the stroke.
448     *
449     * @see #getBorderStroke()
450     */
451    public void setBorderStroke(Stroke stroke) {
452        this.borderStroke = stroke;
453        fireChartChanged();
454    }
455
456    /**
457     * Returns the paint used to draw the chart border (if visible).
458     *
459     * @return The border paint.
460     *
461     * @see #setBorderPaint(Paint)
462     */
463    public Paint getBorderPaint() {
464        return this.borderPaint;
465    }
466
467    /**
468     * Sets the paint used to draw the chart border (if visible).
469     *
470     * @param paint  the paint.
471     *
472     * @see #getBorderPaint()
473     */
474    public void setBorderPaint(Paint paint) {
475        this.borderPaint = paint;
476        fireChartChanged();
477    }
478
479    /**
480     * Returns the padding between the chart border and the chart drawing area.
481     *
482     * @return The padding (never {@code null}).
483     *
484     * @see #setPadding(RectangleInsets)
485     */
486    public RectangleInsets getPadding() {
487        return this.padding;
488    }
489
490    /**
491     * Sets the padding between the chart border and the chart drawing area,
492     * and sends a {@link ChartChangeEvent} to all registered listeners.
493     *
494     * @param padding  the padding ({@code null} not permitted).
495     *
496     * @see #getPadding()
497     */
498    public void setPadding(RectangleInsets padding) {
499        Args.nullNotPermitted(padding, "padding");
500        this.padding = padding;
501        notifyListeners(new ChartChangeEvent(this));
502    }
503
504    /**
505     * Returns the main chart title.  Very often a chart will have just one
506     * title, so we make this case simple by providing accessor methods for
507     * the main title.  However, multiple titles are supported - see the
508     * {@link #addSubtitle(Title)} method.
509     *
510     * @return The chart title (possibly {@code null}).
511     *
512     * @see #setTitle(TextTitle)
513     */
514    public TextTitle getTitle() {
515        return this.title;
516    }
517
518    /**
519     * Sets the main title for the chart and sends a {@link ChartChangeEvent}
520     * to all registered listeners.  If you do not want a title for the
521     * chart, set it to {@code null}.  If you want more than one title on
522     * a chart, use the {@link #addSubtitle(Title)} method.
523     *
524     * @param title  the title ({@code null} permitted).
525     *
526     * @see #getTitle()
527     */
528    public void setTitle(TextTitle title) {
529        if (this.title != null) {
530            this.title.removeChangeListener(this);
531        }
532        this.title = title;
533        if (title != null) {
534            title.addChangeListener(this);
535        }
536        fireChartChanged();
537    }
538
539    /**
540     * Sets the chart title and sends a {@link ChartChangeEvent} to all
541     * registered listeners.  This is a convenience method that ends up calling
542     * the {@link #setTitle(TextTitle)} method.  If there is an existing title,
543     * its text is updated, otherwise a new title using the default font is
544     * added to the chart.  If {@code text} is {@code null} the chart
545     * title is set to {@code null}.
546     *
547     * @param text  the title text ({@code null} permitted).
548     *
549     * @see #getTitle()
550     */
551    public void setTitle(String text) {
552        if (text != null) {
553            if (this.title == null) {
554                setTitle(new TextTitle(text, JFreeChart.DEFAULT_TITLE_FONT));
555            } else {
556                this.title.setText(text);
557            }
558        }
559        else {
560            setTitle((TextTitle) null);
561        }
562    }
563
564    /**
565     * Adds a legend to the plot and sends a {@link ChartChangeEvent} to all
566     * registered listeners.
567     *
568     * @param legend  the legend ({@code null} not permitted).
569     *
570     * @see #removeLegend()
571     */
572    public void addLegend(LegendTitle legend) {
573        addSubtitle(legend);
574    }
575
576    /**
577     * Returns the legend for the chart, if there is one.  Note that a chart
578     * can have more than one legend - this method returns the first.
579     *
580     * @return The legend (possibly {@code null}).
581     *
582     * @see #getLegend(int)
583     */
584    public LegendTitle getLegend() {
585        return getLegend(0);
586    }
587
588    /**
589     * Returns the nth legend for a chart, or {@code null}.
590     *
591     * @param index  the legend index (zero-based).
592     *
593     * @return The legend (possibly {@code null}).
594     *
595     * @see #addLegend(LegendTitle)
596     */
597    public LegendTitle getLegend(int index) {
598        int seen = 0;
599        for (Title subtitle : this.subtitles) {
600            if (subtitle instanceof LegendTitle) {
601                if (seen == index) {
602                    return (LegendTitle) subtitle;
603                } else {
604                    seen++;
605                }
606            }
607        }
608        return null;
609    }
610
611    /**
612     * Removes the first legend in the chart and sends a
613     * {@link ChartChangeEvent} to all registered listeners.
614     *
615     * @see #getLegend()
616     */
617    public void removeLegend() {
618        removeSubtitle(getLegend());
619    }
620
621    /**
622     * Returns the list of subtitles for the chart.
623     *
624     * @return The subtitle list (possibly empty, but never {@code null}).
625     *
626     * @see #setSubtitles(List)
627     */
628    public List<Title> getSubtitles() {
629        return new ArrayList<>(this.subtitles);
630    }
631
632    /**
633     * Sets the title list for the chart (completely replaces any existing
634     * titles) and sends a {@link ChartChangeEvent} to all registered
635     * listeners.
636     *
637     * @param subtitles  the new list of subtitles ({@code null} not
638     *                   permitted).
639     *
640     * @see #getSubtitles()
641     */
642    public void setSubtitles(List<Title> subtitles) {
643        if (subtitles == null) {
644            throw new NullPointerException("Null 'subtitles' argument.");
645        }
646        setNotify(false);
647        clearSubtitles();
648        for (Title t : subtitles) {
649            if (t != null) {
650                addSubtitle(t);
651            }
652        }
653        setNotify(true);  // this fires a ChartChangeEvent
654    }
655
656    /**
657     * Returns the number of titles for the chart.
658     *
659     * @return The number of titles for the chart.
660     *
661     * @see #getSubtitles()
662     */
663    public int getSubtitleCount() {
664        return this.subtitles.size();
665    }
666
667    /**
668     * Returns a chart subtitle.
669     *
670     * @param index  the index of the chart subtitle (zero based).
671     *
672     * @return A chart subtitle.
673     *
674     * @see #addSubtitle(Title)
675     */
676    public Title getSubtitle(int index) {
677        if ((index < 0) || (index >= getSubtitleCount())) {
678            throw new IllegalArgumentException("Index out of range.");
679        }
680        return this.subtitles.get(index);
681    }
682
683    /**
684     * Adds a chart subtitle, and notifies registered listeners that the chart
685     * has been modified.
686     *
687     * @param subtitle  the subtitle ({@code null} not permitted).
688     *
689     * @see #getSubtitle(int)
690     */
691    public void addSubtitle(Title subtitle) {
692        Args.nullNotPermitted(subtitle, "subtitle");
693        this.subtitles.add(subtitle);
694        subtitle.addChangeListener(this);
695        fireChartChanged();
696    }
697
698    /**
699     * Adds a subtitle at a particular position in the subtitle list, and sends
700     * a {@link ChartChangeEvent} to all registered listeners.
701     *
702     * @param index  the index (in the range 0 to {@link #getSubtitleCount()}).
703     * @param subtitle  the subtitle to add ({@code null} not permitted).
704     */
705    public void addSubtitle(int index, Title subtitle) {
706        if (index < 0 || index > getSubtitleCount()) {
707            throw new IllegalArgumentException(
708                    "The 'index' argument is out of range.");
709        }
710        Args.nullNotPermitted(subtitle, "subtitle");
711        this.subtitles.add(index, subtitle);
712        subtitle.addChangeListener(this);
713        fireChartChanged();
714    }
715
716    /**
717     * Clears all subtitles from the chart and sends a {@link ChartChangeEvent}
718     * to all registered listeners.
719     *
720     * @see #addSubtitle(Title)
721     */
722    public void clearSubtitles() {
723        for (Title t : this.subtitles) {
724            t.removeChangeListener(this);
725        }
726        this.subtitles.clear();
727        fireChartChanged();
728    }
729
730    /**
731     * Removes the specified subtitle and sends a {@link ChartChangeEvent} to
732     * all registered listeners.
733     *
734     * @param title  the title.
735     *
736     * @see #addSubtitle(Title)
737     */
738    public void removeSubtitle(Title title) {
739        this.subtitles.remove(title);
740        fireChartChanged();
741    }
742
743    /**
744     * Returns the plot for the chart.  The plot is a class responsible for
745     * coordinating the visual representation of the data, including the axes
746     * (if any).
747     *
748     * @return The plot.
749     */
750    public Plot getPlot() {
751        return this.plot;
752    }
753
754    /**
755     * Returns the plot cast as a {@link CategoryPlot}.
756     * <p>
757     * NOTE: if the plot is not an instance of {@link CategoryPlot}, then a
758     * {@code ClassCastException} is thrown.
759     *
760     * @return The plot.
761     *
762     * @see #getPlot()
763     */
764    public CategoryPlot getCategoryPlot() {
765        return (CategoryPlot) this.plot;
766    }
767
768    /**
769     * Returns the plot cast as an {@link XYPlot}.
770     * <p>
771     * NOTE: if the plot is not an instance of {@link XYPlot}, then a
772     * {@code ClassCastException} is thrown.
773     *
774     * @return The plot.
775     *
776     * @see #getPlot()
777     */
778    public XYPlot getXYPlot() {
779        return (XYPlot) this.plot;
780    }
781
782    /**
783     * Returns a flag that indicates whether antialiasing is used when
784     * the chart is drawn.
785     *
786     * @return The flag.
787     *
788     * @see #setAntiAlias(boolean)
789     */
790    public boolean getAntiAlias() {
791        Object val = this.renderingHints.get(RenderingHints.KEY_ANTIALIASING);
792        return RenderingHints.VALUE_ANTIALIAS_ON.equals(val);
793    }
794
795    /**
796     * Sets a flag that indicates whether antialiasing is used when the
797     * chart is drawn.
798     * <P>
799     * Antialiasing usually improves the appearance of charts, but is slower.
800     *
801     * @param flag  the new value of the flag.
802     *
803     * @see #getAntiAlias()
804     */
805    public void setAntiAlias(boolean flag) {
806        Object hint = flag ? RenderingHints.VALUE_ANTIALIAS_ON 
807                : RenderingHints.VALUE_ANTIALIAS_OFF;
808        this.renderingHints.put(RenderingHints.KEY_ANTIALIASING, hint);
809        fireChartChanged();
810    }
811
812    /**
813     * Returns the current value stored in the rendering hints table for
814     * {@link RenderingHints#KEY_TEXT_ANTIALIASING}.
815     *
816     * @return The hint value (possibly {@code null}).
817     *
818     * @see #setTextAntiAlias(Object)
819     */
820    public Object getTextAntiAlias() {
821        return this.renderingHints.get(RenderingHints.KEY_TEXT_ANTIALIASING);
822    }
823
824    /**
825     * Sets the value in the rendering hints table for
826     * {@link RenderingHints#KEY_TEXT_ANTIALIASING} to either
827     * {@link RenderingHints#VALUE_TEXT_ANTIALIAS_ON} or
828     * {@link RenderingHints#VALUE_TEXT_ANTIALIAS_OFF}, then sends a
829     * {@link ChartChangeEvent} to all registered listeners.
830     *
831     * @param flag  the new value of the flag.
832     *
833     * @see #getTextAntiAlias()
834     * @see #setTextAntiAlias(Object)
835     */
836    public void setTextAntiAlias(boolean flag) {
837        if (flag) {
838            setTextAntiAlias(RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
839        }
840        else {
841            setTextAntiAlias(RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
842        }
843    }
844
845    /**
846     * Sets the value in the rendering hints table for
847     * {@link RenderingHints#KEY_TEXT_ANTIALIASING} and sends a
848     * {@link ChartChangeEvent} to all registered listeners.
849     *
850     * @param val  the new value ({@code null} permitted).
851     *
852     * @see #getTextAntiAlias()
853     * @see #setTextAntiAlias(boolean)
854     */
855    public void setTextAntiAlias(Object val) {
856        this.renderingHints.put(RenderingHints.KEY_TEXT_ANTIALIASING, val);
857        notifyListeners(new ChartChangeEvent(this));
858    }
859
860    /**
861     * Returns the paint used for the chart background.
862     *
863     * @return The paint (possibly {@code null}).
864     *
865     * @see #setBackgroundPaint(Paint)
866     */
867    public Paint getBackgroundPaint() {
868        return this.backgroundPaint;
869    }
870
871    /**
872     * Sets the paint used to fill the chart background and sends a
873     * {@link ChartChangeEvent} to all registered listeners.
874     *
875     * @param paint  the paint ({@code null} permitted).
876     *
877     * @see #getBackgroundPaint()
878     */
879    public void setBackgroundPaint(Paint paint) {
880
881        if (this.backgroundPaint != null) {
882            if (!this.backgroundPaint.equals(paint)) {
883                this.backgroundPaint = paint;
884                fireChartChanged();
885            }
886        }
887        else {
888            if (paint != null) {
889                this.backgroundPaint = paint;
890                fireChartChanged();
891            }
892        }
893
894    }
895
896    /**
897     * Returns the background image for the chart, or {@code null} if
898     * there is no image.
899     *
900     * @return The image (possibly {@code null}).
901     *
902     * @see #setBackgroundImage(Image)
903     */
904    public Image getBackgroundImage() {
905        return this.backgroundImage;
906    }
907
908    /**
909     * Sets the background image for the chart and sends a
910     * {@link ChartChangeEvent} to all registered listeners.
911     *
912     * @param image  the image ({@code null} permitted).
913     *
914     * @see #getBackgroundImage()
915     */
916    public void setBackgroundImage(Image image) {
917
918        if (this.backgroundImage != null) {
919            if (!this.backgroundImage.equals(image)) {
920                this.backgroundImage = image;
921                fireChartChanged();
922            }
923        }
924        else {
925            if (image != null) {
926                this.backgroundImage = image;
927                fireChartChanged();
928            }
929        }
930
931    }
932
933    /**
934     * Returns the background image alignment. Alignment constants are defined
935     * in the {@link Align} class.
936     *
937     * @return The alignment.
938     *
939     * @see #setBackgroundImageAlignment(int)
940     */
941    public int getBackgroundImageAlignment() {
942        return this.backgroundImageAlignment;
943    }
944
945    /**
946     * Sets the background alignment.  Alignment options are defined by the
947     * {@link org.jfree.chart.ui.Align} class.
948     *
949     * @param alignment  the alignment.
950     *
951     * @see #getBackgroundImageAlignment()
952     */
953    public void setBackgroundImageAlignment(int alignment) {
954        if (this.backgroundImageAlignment != alignment) {
955            this.backgroundImageAlignment = alignment;
956            fireChartChanged();
957        }
958    }
959
960    /**
961     * Returns the alpha-transparency for the chart's background image.
962     *
963     * @return The alpha-transparency.
964     *
965     * @see #setBackgroundImageAlpha(float)
966     */
967    public float getBackgroundImageAlpha() {
968        return this.backgroundImageAlpha;
969    }
970
971    /**
972     * Sets the alpha-transparency for the chart's background image.
973     * Registered listeners are notified that the chart has been changed.
974     *
975     * @param alpha  the alpha value.
976     *
977     * @see #getBackgroundImageAlpha()
978     */
979    public void setBackgroundImageAlpha(float alpha) {
980        if (this.backgroundImageAlpha != alpha) {
981            this.backgroundImageAlpha = alpha;
982            fireChartChanged();
983        }
984    }
985
986    /**
987     * Returns a flag that controls whether change events are sent to
988     * registered listeners.
989     *
990     * @return A boolean.
991     *
992     * @see #setNotify(boolean)
993     */
994    public boolean isNotify() {
995        return this.notify;
996    }
997
998    /**
999     * Sets a flag that controls whether listeners receive
1000     * {@link ChartChangeEvent} notifications.
1001     *
1002     * @param notify  a boolean.
1003     *
1004     * @see #isNotify()
1005     */
1006    public void setNotify(boolean notify) {
1007        this.notify = notify;
1008        // if the flag is being set to true, there may be queued up changes...
1009        if (notify) {
1010            notifyListeners(new ChartChangeEvent(this));
1011        }
1012    }
1013
1014    /**
1015     * Draws the chart on a Java 2D graphics device (such as the screen or a
1016     * printer).
1017     * <P>
1018     * This method is the focus of the entire JFreeChart library.
1019     *
1020     * @param g2  the graphics device.
1021     * @param area  the area within which the chart should be drawn.
1022     */
1023    @Override
1024    public void draw(Graphics2D g2, Rectangle2D area) {
1025        draw(g2, area, null, null);
1026    }
1027
1028    /**
1029     * Draws the chart on a Java 2D graphics device (such as the screen or a
1030     * printer).  This method is the focus of the entire JFreeChart library.
1031     *
1032     * @param g2  the graphics device.
1033     * @param area  the area within which the chart should be drawn.
1034     * @param info  records info about the drawing (null means collect no info).
1035     */
1036    public void draw(Graphics2D g2, Rectangle2D area, ChartRenderingInfo info) {
1037        draw(g2, area, null, info);
1038    }
1039
1040    /**
1041     * Draws the chart on a Java 2D graphics device (such as the screen or a
1042     * printer).
1043     * <P>
1044     * This method is the focus of the entire JFreeChart library.
1045     *
1046     * @param g2  the graphics device.
1047     * @param chartArea  the area within which the chart should be drawn.
1048     * @param anchor  the anchor point (in Java2D space) for the chart
1049     *                ({@code null} permitted).
1050     * @param info  records info about the drawing (null means collect no info).
1051     */
1052    public void draw(Graphics2D g2, Rectangle2D chartArea, Point2D anchor,
1053             ChartRenderingInfo info) {
1054
1055        notifyListeners(new ChartProgressEvent(this, this,
1056                ChartProgressEvent.DRAWING_STARTED, 0));
1057        
1058        if (this.elementHinting) {
1059            Map<String, String> m = new HashMap<>();
1060            if (this.id != null) {
1061                m.put("id", this.id);
1062            }
1063            m.put("ref", "JFREECHART_TOP_LEVEL");            
1064            g2.setRenderingHint(ChartHints.KEY_BEGIN_ELEMENT, m);            
1065        }
1066        
1067        EntityCollection entities = null;
1068        // record the chart area, if info is requested...
1069        if (info != null) {
1070            info.clear();
1071            info.setChartArea(chartArea);
1072            entities = info.getEntityCollection();
1073        }
1074        if (entities != null) {
1075            entities.add(new JFreeChartEntity((Rectangle2D) chartArea.clone(),
1076                    this));
1077        }
1078
1079        // ensure no drawing occurs outside chart area...
1080        Shape savedClip = g2.getClip();
1081        g2.clip(chartArea);
1082
1083        g2.addRenderingHints(this.renderingHints);
1084
1085        // draw the chart background...
1086        if (this.backgroundPaint != null) {
1087            g2.setPaint(this.backgroundPaint);
1088            g2.fill(chartArea);
1089        }
1090
1091        if (this.backgroundImage != null) {
1092            Composite originalComposite = g2.getComposite();
1093            g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
1094                    this.backgroundImageAlpha));
1095            Rectangle2D dest = new Rectangle2D.Double(0.0, 0.0,
1096                    this.backgroundImage.getWidth(null),
1097                    this.backgroundImage.getHeight(null));
1098            Align.align(dest, chartArea, this.backgroundImageAlignment);
1099            g2.drawImage(this.backgroundImage, (int) dest.getX(),
1100                    (int) dest.getY(), (int) dest.getWidth(),
1101                    (int) dest.getHeight(), null);
1102            g2.setComposite(originalComposite);
1103        }
1104
1105        if (isBorderVisible()) {
1106            Paint paint = getBorderPaint();
1107            Stroke stroke = getBorderStroke();
1108            if (paint != null && stroke != null) {
1109                Rectangle2D borderArea = new Rectangle2D.Double(
1110                        chartArea.getX(), chartArea.getY(),
1111                        chartArea.getWidth() - 1.0, chartArea.getHeight()
1112                        - 1.0);
1113                g2.setPaint(paint);
1114                g2.setStroke(stroke);
1115                g2.draw(borderArea);
1116            }
1117        }
1118
1119        // draw the title and subtitles...
1120        Rectangle2D nonTitleArea = new Rectangle2D.Double();
1121        nonTitleArea.setRect(chartArea);
1122        this.padding.trim(nonTitleArea);
1123
1124        if (this.title != null && this.title.isVisible()) {
1125            EntityCollection e = drawTitle(this.title, g2, nonTitleArea,
1126                    (entities != null));
1127            if (e != null && entities != null) {
1128                entities.addAll(e);
1129            }
1130        }
1131
1132        for (Title currentTitle : this.subtitles) {
1133            if (currentTitle.isVisible()) {
1134                EntityCollection e = drawTitle(currentTitle, g2, nonTitleArea,
1135                        (entities != null));
1136                if (e != null && entities != null) {
1137                    entities.addAll(e);
1138                }
1139            }
1140        }
1141
1142        Rectangle2D plotArea = nonTitleArea;
1143
1144        // draw the plot (axes and data visualisation)
1145        PlotRenderingInfo plotInfo = null;
1146        if (info != null) {
1147            plotInfo = info.getPlotInfo();
1148        }
1149        this.plot.draw(g2, plotArea, anchor, null, plotInfo);
1150        g2.setClip(savedClip);
1151        if (this.elementHinting) {         
1152            g2.setRenderingHint(ChartHints.KEY_END_ELEMENT, Boolean.TRUE);            
1153        }
1154
1155        notifyListeners(new ChartProgressEvent(this, this,
1156                ChartProgressEvent.DRAWING_FINISHED, 100));
1157    }
1158
1159    /**
1160     * Creates a rectangle that is aligned to the frame.
1161     *
1162     * @param dimensions  the dimensions for the rectangle.
1163     * @param frame  the frame to align to.
1164     * @param hAlign  the horizontal alignment.
1165     * @param vAlign  the vertical alignment.
1166     *
1167     * @return A rectangle.
1168     */
1169    private Rectangle2D createAlignedRectangle2D(Size2D dimensions,
1170            Rectangle2D frame, HorizontalAlignment hAlign,
1171            VerticalAlignment vAlign) {
1172        double x = Double.NaN;
1173        double y = Double.NaN;
1174        if (hAlign == HorizontalAlignment.LEFT) {
1175            x = frame.getX();
1176        }
1177        else if (hAlign == HorizontalAlignment.CENTER) {
1178            x = frame.getCenterX() - (dimensions.width / 2.0);
1179        }
1180        else if (hAlign == HorizontalAlignment.RIGHT) {
1181            x = frame.getMaxX() - dimensions.width;
1182        }
1183        if (vAlign == VerticalAlignment.TOP) {
1184            y = frame.getY();
1185        }
1186        else if (vAlign == VerticalAlignment.CENTER) {
1187            y = frame.getCenterY() - (dimensions.height / 2.0);
1188        }
1189        else if (vAlign == VerticalAlignment.BOTTOM) {
1190            y = frame.getMaxY() - dimensions.height;
1191        }
1192
1193        return new Rectangle2D.Double(x, y, dimensions.width,
1194                dimensions.height);
1195    }
1196
1197    /**
1198     * Draws a title.  The title should be drawn at the top, bottom, left or
1199     * right of the specified area, and the area should be updated to reflect
1200     * the amount of space used by the title.
1201     *
1202     * @param t  the title ({@code null} not permitted).
1203     * @param g2  the graphics device ({@code null} not permitted).
1204     * @param area  the chart area, excluding any existing titles
1205     *              ({@code null} not permitted).
1206     * @param entities  a flag that controls whether an entity
1207     *                  collection is returned for the title.
1208     *
1209     * @return An entity collection for the title (possibly {@code null}).
1210     */
1211    protected EntityCollection drawTitle(Title t, Graphics2D g2,
1212                                         Rectangle2D area, boolean entities) {
1213
1214        Args.nullNotPermitted(t, "t");
1215        Args.nullNotPermitted(area, "area");
1216        Rectangle2D titleArea;
1217        RectangleEdge position = t.getPosition();
1218        double ww = area.getWidth();
1219        if (ww <= 0.0) {
1220            return null;
1221        }
1222        double hh = area.getHeight();
1223        if (hh <= 0.0) {
1224            return null;
1225        }
1226        RectangleConstraint constraint = new RectangleConstraint(ww,
1227                new Range(0.0, ww), LengthConstraintType.RANGE, hh,
1228                new Range(0.0, hh), LengthConstraintType.RANGE);
1229        Object retValue = null;
1230        BlockParams p = new BlockParams();
1231        p.setGenerateEntities(entities);
1232        if (position == RectangleEdge.TOP) {
1233            Size2D size = t.arrange(g2, constraint);
1234            titleArea = createAlignedRectangle2D(size, area,
1235                    t.getHorizontalAlignment(), VerticalAlignment.TOP);
1236            retValue = t.draw(g2, titleArea, p);
1237            area.setRect(area.getX(), Math.min(area.getY() + size.height,
1238                    area.getMaxY()), area.getWidth(), Math.max(area.getHeight()
1239                    - size.height, 0));
1240        } else if (position == RectangleEdge.BOTTOM) {
1241            Size2D size = t.arrange(g2, constraint);
1242            titleArea = createAlignedRectangle2D(size, area,
1243                    t.getHorizontalAlignment(), VerticalAlignment.BOTTOM);
1244            retValue = t.draw(g2, titleArea, p);
1245            area.setRect(area.getX(), area.getY(), area.getWidth(),
1246                    area.getHeight() - size.height);
1247        } else if (position == RectangleEdge.RIGHT) {
1248            Size2D size = t.arrange(g2, constraint);
1249            titleArea = createAlignedRectangle2D(size, area,
1250                    HorizontalAlignment.RIGHT, t.getVerticalAlignment());
1251            retValue = t.draw(g2, titleArea, p);
1252            area.setRect(area.getX(), area.getY(), area.getWidth()
1253                    - size.width, area.getHeight());
1254        } else if (position == RectangleEdge.LEFT) {
1255            Size2D size = t.arrange(g2, constraint);
1256            titleArea = createAlignedRectangle2D(size, area,
1257                    HorizontalAlignment.LEFT, t.getVerticalAlignment());
1258            retValue = t.draw(g2, titleArea, p);
1259            area.setRect(area.getX() + size.width, area.getY(), area.getWidth()
1260                    - size.width, area.getHeight());
1261        }
1262        else {
1263            throw new RuntimeException("Unrecognised title position.");
1264        }
1265        EntityCollection result = null;
1266        if (retValue instanceof EntityBlockResult) {
1267            EntityBlockResult ebr = (EntityBlockResult) retValue;
1268            result = ebr.getEntityCollection();
1269        }
1270        return result;
1271    }
1272
1273    /**
1274     * Creates and returns a buffered image into which the chart has been drawn.
1275     *
1276     * @param width  the width.
1277     * @param height  the height.
1278     *
1279     * @return A buffered image.
1280     */
1281    public BufferedImage createBufferedImage(int width, int height) {
1282        return createBufferedImage(width, height, null);
1283    }
1284
1285    /**
1286     * Creates and returns a buffered image into which the chart has been drawn.
1287     *
1288     * @param width  the width.
1289     * @param height  the height.
1290     * @param info  carries back chart state information ({@code null}
1291     *              permitted).
1292     *
1293     * @return A buffered image.
1294     */
1295    public BufferedImage createBufferedImage(int width, int height,
1296                                             ChartRenderingInfo info) {
1297        return createBufferedImage(width, height, BufferedImage.TYPE_INT_ARGB,
1298                info);
1299    }
1300
1301    /**
1302     * Creates and returns a buffered image into which the chart has been drawn.
1303     *
1304     * @param width  the width.
1305     * @param height  the height.
1306     * @param imageType  the image type.
1307     * @param info  carries back chart state information ({@code null}
1308     *              permitted).
1309     *
1310     * @return A buffered image.
1311     */
1312    public BufferedImage createBufferedImage(int width, int height,
1313            int imageType, ChartRenderingInfo info) {
1314        BufferedImage image = new BufferedImage(width, height, imageType);
1315        Graphics2D g2 = image.createGraphics();
1316        draw(g2, new Rectangle2D.Double(0, 0, width, height), null, info);
1317        g2.dispose();
1318        return image;
1319    }
1320
1321    /**
1322     * Creates and returns a buffered image into which the chart has been drawn.
1323     *
1324     * @param imageWidth  the image width.
1325     * @param imageHeight  the image height.
1326     * @param drawWidth  the width for drawing the chart (will be scaled to
1327     *                   fit image).
1328     * @param drawHeight  the height for drawing the chart (will be scaled to
1329     *                    fit image).
1330     * @param info  optional object for collection chart dimension and entity
1331     *              information.
1332     *
1333     * @return A buffered image.
1334     */
1335    public BufferedImage createBufferedImage(int imageWidth,
1336                                             int imageHeight,
1337                                             double drawWidth,
1338                                             double drawHeight,
1339                                             ChartRenderingInfo info) {
1340
1341        BufferedImage image = new BufferedImage(imageWidth, imageHeight,
1342                BufferedImage.TYPE_INT_ARGB);
1343        Graphics2D g2 = image.createGraphics();
1344        double scaleX = imageWidth / drawWidth;
1345        double scaleY = imageHeight / drawHeight;
1346        AffineTransform st = AffineTransform.getScaleInstance(scaleX, scaleY);
1347        g2.transform(st);
1348        draw(g2, new Rectangle2D.Double(0, 0, drawWidth, drawHeight), null,
1349                info);
1350        g2.dispose();
1351        return image;
1352    }
1353
1354    /**
1355     * Handles a 'click' on the chart.  JFreeChart is not a UI component, so
1356     * some other object (for example, {@link ChartPanel}) needs to capture
1357     * the click event and pass it onto the JFreeChart object.
1358     * If you are not using JFreeChart in a client application, then this
1359     * method is not required.
1360     *
1361     * @param x  x-coordinate of the click (in Java2D space).
1362     * @param y  y-coordinate of the click (in Java2D space).
1363     * @param info  contains chart dimension and entity information
1364     *              ({@code null} not permitted).
1365     */
1366    public void handleClick(int x, int y, ChartRenderingInfo info) {
1367        // pass the click on to the plot...
1368        // rely on the plot to post a plot change event and redraw the chart...
1369        this.plot.handleClick(x, y, info.getPlotInfo());
1370    }
1371
1372    /**
1373     * Registers an object for notification of changes to the chart.
1374     *
1375     * @param listener  the listener ({@code null} not permitted).
1376     *
1377     * @see #removeChangeListener(ChartChangeListener)
1378     */
1379    public void addChangeListener(ChartChangeListener listener) {
1380        Args.nullNotPermitted(listener, "listener");
1381        this.changeListeners.add(ChartChangeListener.class, listener);
1382    }
1383
1384    /**
1385     * Deregisters an object for notification of changes to the chart.
1386     *
1387     * @param listener  the listener ({@code null} not permitted)
1388     *
1389     * @see #addChangeListener(ChartChangeListener)
1390     */
1391    public void removeChangeListener(ChartChangeListener listener) {
1392        Args.nullNotPermitted(listener, "listener");
1393        this.changeListeners.remove(ChartChangeListener.class, listener);
1394    }
1395
1396    /**
1397     * Sends a default {@link ChartChangeEvent} to all registered listeners.
1398     * <P>
1399     * This method is for convenience only.
1400     */
1401    public void fireChartChanged() {
1402        ChartChangeEvent event = new ChartChangeEvent(this);
1403        notifyListeners(event);
1404    }
1405
1406    /**
1407     * Sends a {@link ChartChangeEvent} to all registered listeners.
1408     *
1409     * @param event  information about the event that triggered the
1410     *               notification.
1411     */
1412    protected void notifyListeners(ChartChangeEvent event) {
1413        if (this.notify) {
1414            Object[] listeners = this.changeListeners.getListenerList();
1415            for (int i = listeners.length - 2; i >= 0; i -= 2) {
1416                if (listeners[i] == ChartChangeListener.class) {
1417                    ((ChartChangeListener) listeners[i + 1]).chartChanged(
1418                            event);
1419                }
1420            }
1421        }
1422    }
1423
1424    /**
1425     * Registers an object for notification of progress events relating to the
1426     * chart.
1427     *
1428     * @param listener  the object being registered.
1429     *
1430     * @see #removeProgressListener(ChartProgressListener)
1431     */
1432    public void addProgressListener(ChartProgressListener listener) {
1433        this.progressListeners.add(ChartProgressListener.class, listener);
1434    }
1435
1436    /**
1437     * Deregisters an object for notification of changes to the chart.
1438     *
1439     * @param listener  the object being deregistered.
1440     *
1441     * @see #addProgressListener(ChartProgressListener)
1442     */
1443    public void removeProgressListener(ChartProgressListener listener) {
1444        this.progressListeners.remove(ChartProgressListener.class, listener);
1445    }
1446
1447    /**
1448     * Sends a {@link ChartProgressEvent} to all registered listeners.
1449     *
1450     * @param event  information about the event that triggered the
1451     *               notification.
1452     */
1453    protected void notifyListeners(ChartProgressEvent event) {
1454        Object[] listeners = this.progressListeners.getListenerList();
1455        for (int i = listeners.length - 2; i >= 0; i -= 2) {
1456            if (listeners[i] == ChartProgressListener.class) {
1457                ((ChartProgressListener) listeners[i + 1]).chartProgress(event);
1458            }
1459        }
1460    }
1461
1462    /**
1463     * Receives notification that a chart title has changed, and passes this
1464     * on to registered listeners.
1465     *
1466     * @param event  information about the chart title change.
1467     */
1468    @Override
1469    public void titleChanged(TitleChangeEvent event) {
1470        event.setChart(this);
1471        notifyListeners(event);
1472    }
1473
1474    /**
1475     * Receives notification that the plot has changed, and passes this on to
1476     * registered listeners.
1477     *
1478     * @param event  information about the plot change.
1479     */
1480    @Override
1481    public void plotChanged(PlotChangeEvent event) {
1482        event.setChart(this);
1483        notifyListeners(event);
1484    }
1485
1486    /**
1487     * Tests this chart for equality with another object.
1488     *
1489     * @param obj  the object ({@code null} permitted).
1490     *
1491     * @return A boolean.
1492     */
1493    @Override
1494    public boolean equals(Object obj) {
1495        if (obj == this) {
1496            return true;
1497        }
1498        if (!(obj instanceof JFreeChart)) {
1499            return false;
1500        }
1501        JFreeChart that = (JFreeChart) obj;
1502        if (!Objects.equals(this.renderingHints, that.renderingHints)) {
1503            return false;
1504        }
1505        if (this.borderVisible != that.borderVisible) {
1506            return false;
1507        }
1508        if (this.elementHinting != that.elementHinting) {
1509            return false;
1510        }
1511        if (!Objects.equals(this.borderStroke, that.borderStroke)) {
1512            return false;
1513        }
1514        if (!PaintUtils.equal(this.borderPaint, that.borderPaint)) {
1515            return false;
1516        }
1517        if (!Objects.equals(this.padding, that.padding)) {
1518            return false;
1519        }
1520        if (!Objects.equals(this.title, that.title)) {
1521            return false;
1522        }
1523        if (!Objects.equals(this.subtitles, that.subtitles)) {
1524            return false;
1525        }
1526        if (!Objects.equals(this.plot, that.plot)) {
1527            return false;
1528        }
1529        if (!PaintUtils.equal(this.backgroundPaint, that.backgroundPaint)) {
1530            return false;
1531        }
1532        if (!Objects.equals(this.backgroundImage, that.backgroundImage)) {
1533            return false;
1534        }
1535        if (this.backgroundImageAlignment != that.backgroundImageAlignment) {
1536            return false;
1537        }
1538        if (Float.floatToIntBits(this.backgroundImageAlpha) !=
1539            Float.floatToIntBits(that.backgroundImageAlpha)) {
1540            return false;
1541        }
1542        if (this.notify != that.notify) {
1543            return false;
1544        }
1545        if (!Objects.equals(this.id, that.id)) {
1546            return false;
1547        }
1548        return true;
1549    }
1550
1551    @Override
1552    public int hashCode() {
1553        int hash = 7;
1554        hash = 43 * hash + Objects.hashCode(this.renderingHints);
1555        hash = 43 * hash + Objects.hashCode(this.id);
1556        hash = 43 * hash + (this.borderVisible ? 1 : 0);
1557        hash = 43 * hash + Objects.hashCode(this.borderStroke);
1558        hash = 43 * hash + HashUtils.hashCodeForPaint(this.borderPaint);
1559        hash = 43 * hash + Objects.hashCode(this.padding);
1560        hash = 43 * hash + Objects.hashCode(this.title);
1561        hash = 43 * hash + Objects.hashCode(this.subtitles);
1562        hash = 43 * hash + Objects.hashCode(this.plot);
1563        hash = 43 * hash + HashUtils.hashCodeForPaint(this.backgroundPaint);
1564        hash = 43 * hash + Objects.hashCode(this.backgroundImage);
1565        hash = 43 * hash + this.backgroundImageAlignment;
1566        hash = 43 * hash + Float.floatToIntBits(this.backgroundImageAlpha);
1567        hash = 43 * hash + (this.notify ? 1 : 0);
1568        hash = 43 * hash + (this.elementHinting ? 1 : 0);
1569        return hash;
1570    }
1571
1572    /**
1573     * Provides serialization support.
1574     *
1575     * @param stream  the output stream.
1576     *
1577     * @throws IOException  if there is an I/O error.
1578     */
1579    private void writeObject(ObjectOutputStream stream) throws IOException {
1580        stream.defaultWriteObject();
1581        SerialUtils.writeStroke(this.borderStroke, stream);
1582        SerialUtils.writePaint(this.borderPaint, stream);
1583        SerialUtils.writePaint(this.backgroundPaint, stream);
1584    }
1585
1586    /**
1587     * Provides serialization support.
1588     *
1589     * @param stream  the input stream.
1590     *
1591     * @throws IOException  if there is an I/O error.
1592     * @throws ClassNotFoundException  if there is a classpath problem.
1593     */
1594    private void readObject(ObjectInputStream stream)
1595        throws IOException, ClassNotFoundException {
1596        stream.defaultReadObject();
1597        this.borderStroke = SerialUtils.readStroke(stream);
1598        this.borderPaint = SerialUtils.readPaint(stream);
1599        this.backgroundPaint = SerialUtils.readPaint(stream);
1600        this.progressListeners = new EventListenerList();
1601        this.changeListeners = new EventListenerList();
1602        this.renderingHints = new RenderingHints(
1603                RenderingHints.KEY_ANTIALIASING,
1604                RenderingHints.VALUE_ANTIALIAS_ON);
1605        this.renderingHints.put(RenderingHints.KEY_STROKE_CONTROL,
1606                RenderingHints.VALUE_STROKE_PURE);
1607        
1608        // register as a listener with subcomponents...
1609        if (this.title != null) {
1610            this.title.addChangeListener(this);
1611        }
1612
1613        for (int i = 0; i < getSubtitleCount(); i++) {
1614            getSubtitle(i).addChangeListener(this);
1615        }
1616        this.plot.addChangeListener(this);
1617    }
1618
1619    /**
1620     * Clones the object, and takes care of listeners.
1621     * Note: caller shall register its own listeners on cloned graph.
1622     *
1623     * @return A clone.
1624     *
1625     * @throws CloneNotSupportedException if the chart is not cloneable.
1626     */
1627    @Override
1628    public Object clone() throws CloneNotSupportedException {
1629        JFreeChart chart = (JFreeChart) super.clone();
1630
1631        chart.renderingHints = (RenderingHints) this.renderingHints.clone();
1632        // private boolean borderVisible;
1633        // private transient Stroke borderStroke;
1634        // private transient Paint borderPaint;
1635
1636        if (this.title != null) {
1637            chart.title = (TextTitle) this.title.clone();
1638            chart.title.addChangeListener(chart);
1639        }
1640
1641        chart.subtitles = new ArrayList<>();
1642        for (int i = 0; i < getSubtitleCount(); i++) {
1643            Title subtitle = (Title) getSubtitle(i).clone();
1644            chart.subtitles.add(subtitle);
1645            subtitle.addChangeListener(chart);
1646        }
1647
1648        if (this.plot != null) {
1649            chart.plot = (Plot) this.plot.clone();
1650            chart.plot.addChangeListener(chart);
1651        }
1652
1653        chart.progressListeners = new EventListenerList();
1654        chart.changeListeners = new EventListenerList();
1655        return chart;
1656    }
1657
1658}