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