001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2022, by David Gilbert and Contributors.
006 *
007 * Project Info:  http://www.jfree.org/jfreechart/index.html
008 *
009 * This library is free software; you can redistribute it and/or modify it
010 * under the terms of the GNU Lesser General Public License as published by
011 * the Free Software Foundation; either version 2.1 of the License, or
012 * (at your option) any later version.
013 *
014 * This library is distributed in the hope that it will be useful, but
015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017 * License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this library; if not, write to the Free Software
021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
022 * USA.
023 *
024 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
025 * Other names may be trademarks of their respective owners.]
026 *
027 * ---------------
028 * JFreeChart.java
029 * ---------------
030 * (C) Copyright 2000-2022, 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 *
041 * NOTE: The above list of contributors lists only the people that have
042 * contributed to this source file (JFreeChart.java) - for a list of ALL
043 * contributors to the project, please see the README.txt file.
044 *
045 */
046
047package org.jfree.chart;
048
049import java.awt.AlphaComposite;
050import java.awt.BasicStroke;
051import java.awt.Color;
052import java.awt.Composite;
053import java.awt.Font;
054import java.awt.Graphics2D;
055import java.awt.Image;
056import java.awt.Paint;
057import java.awt.RenderingHints;
058import java.awt.Shape;
059import java.awt.Stroke;
060import java.awt.geom.AffineTransform;
061import java.awt.geom.Point2D;
062import java.awt.geom.Rectangle2D;
063import java.awt.image.BufferedImage;
064import java.io.IOException;
065import java.io.ObjectInputStream;
066import java.io.ObjectOutputStream;
067import java.io.Serializable;
068import java.util.ArrayList;
069import java.util.HashMap;
070import java.util.List;
071import java.util.Map;
072import java.util.Objects;
073import javax.swing.UIManager;
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.ChartProgressEventType;
086import org.jfree.chart.event.ChartProgressListener;
087import org.jfree.chart.event.PlotChangeEvent;
088import org.jfree.chart.event.PlotChangeListener;
089import org.jfree.chart.event.TitleChangeEvent;
090import org.jfree.chart.event.TitleChangeListener;
091import org.jfree.chart.plot.Plot;
092import org.jfree.chart.plot.PlotRenderingInfo;
093import org.jfree.chart.legend.LegendTitle;
094import org.jfree.chart.title.TextTitle;
095import org.jfree.chart.title.Title;
096import org.jfree.chart.api.HorizontalAlignment;
097import org.jfree.chart.api.RectangleAlignment;
098import org.jfree.chart.api.RectangleEdge;
099import org.jfree.chart.api.RectangleInsets;
100import org.jfree.chart.block.Size2D;
101import org.jfree.chart.api.VerticalAlignment;
102import org.jfree.chart.internal.Args;
103import org.jfree.chart.internal.PaintUtils;
104import org.jfree.chart.internal.SerialUtils;
105import org.jfree.chart.swing.ChartPanel;
106import org.jfree.data.Range;
107
108/**
109 * A chart class implemented using the Java 2D APIs.  The current version
110 * supports bar charts, line charts, pie charts and xy plots (including time
111 * series data).
112 * <P>
113 * JFreeChart coordinates several objects to achieve its aim of being able to
114 * draw a chart on a Java 2D graphics device: a list of {@link Title} objects
115 * (which often includes the chart's legend), a {@link Plot} and a
116 * {@link org.jfree.data.general.Dataset} (the plot in turn manages a
117 * domain axis and a range axis).
118 * <P>
119 * You should use a {@link ChartPanel} to display a chart in a GUI.
120 * <P>
121 * The {@link ChartFactory} class contains static methods for creating
122 * 'ready-made' charts.
123 *
124 * @see ChartPanel
125 * @see ChartFactory
126 * @see Title
127 * @see Plot
128 */
129public class JFreeChart implements Drawable, TitleChangeListener,
130        PlotChangeListener, ChartElement, Serializable, Cloneable {
131
132    /** For serialization. */
133    private static final long serialVersionUID = -3470703747817429120L;
134
135    /** The default font for titles. */
136    public static final Font DEFAULT_TITLE_FONT
137            = new Font("SansSerif", Font.BOLD, 18);
138
139    /** The default background color. */
140    public static final Paint DEFAULT_BACKGROUND_PAINT
141            = UIManager.getColor("Panel.background");
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 RectangleAlignment DEFAULT_BACKGROUND_IMAGE_ALIGNMENT = RectangleAlignment.FILL;
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 or not 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 RectangleAlignment backgroundImageAlignment = RectangleAlignment.FILL;
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 or not 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 or not 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 or not 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 or not 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 or not 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 or not 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 or not 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                }
604                else {
605                    seen++;
606                }
607            }
608        }
609        return null;
610    }
611
612    /**
613     * Removes the first legend in the chart and sends a
614     * {@link ChartChangeEvent} to all registered listeners.
615     *
616     * @see #getLegend()
617     */
618    public void removeLegend() {
619        removeSubtitle(getLegend());
620    }
621
622    /**
623     * Returns a new list containing all the subtitles for the chart.
624     *
625     * @return The subtitle list (possibly empty, but never {@code null}).
626     *
627     * @see #setSubtitles(List)
628     */
629    public List<Title> getSubtitles() {
630        return new ArrayList<>(this.subtitles);
631    }
632
633    /**
634     * Sets the title list for the chart (completely replaces any existing
635     * titles) and sends a {@link ChartChangeEvent} to all registered
636     * listeners.
637     *
638     * @param subtitles  the new list of subtitles ({@code null} not
639     *                   permitted).
640     *
641     * @see #getSubtitles()
642     */
643    public void setSubtitles(List<Title> subtitles) {
644        Args.nullNotPermitted(subtitles, "subtitles");
645        setNotify(false);
646        clearSubtitles();
647        for (Title t: subtitles) {
648            if (t != null) {
649                addSubtitle(t);
650            }
651        }
652        setNotify(true);  // this fires a ChartChangeEvent
653    }
654
655    /**
656     * Returns the number of titles for the chart.
657     *
658     * @return The number of titles for the chart.
659     *
660     * @see #getSubtitles()
661     */
662    public int getSubtitleCount() {
663        return this.subtitles.size();
664    }
665
666    /**
667     * Returns a chart subtitle.
668     *
669     * @param index  the index of the chart subtitle (zero based).
670     *
671     * @return A chart subtitle.
672     *
673     * @see #addSubtitle(Title)
674     */
675    public Title getSubtitle(int index) {
676        if ((index < 0) || (index >= getSubtitleCount())) {
677            throw new IllegalArgumentException("Index out of range.");
678        }
679        return this.subtitles.get(index);
680    }
681
682    /**
683     * Adds a chart subtitle, and notifies registered listeners that the chart
684     * has been modified.
685     *
686     * @param subtitle  the subtitle ({@code null} not permitted).
687     *
688     * @see #getSubtitle(int)
689     */
690    public void addSubtitle(Title subtitle) {
691        Args.nullNotPermitted(subtitle, "subtitle");
692        this.subtitles.add(subtitle);
693        subtitle.addChangeListener(this);
694        fireChartChanged();
695    }
696
697    /**
698     * Adds a subtitle at a particular position in the subtitle list, and sends
699     * a {@link ChartChangeEvent} to all registered listeners.
700     *
701     * @param index  the index (in the range 0 to {@link #getSubtitleCount()}).
702     * @param subtitle  the subtitle to add ({@code null} not permitted).
703     */
704    public void addSubtitle(int index, Title subtitle) {
705        Args.requireInRange(index, "index", 0, getSubtitleCount());
706        Args.nullNotPermitted(subtitle, "subtitle");
707        this.subtitles.add(index, subtitle);
708        subtitle.addChangeListener(this);
709        fireChartChanged();
710    }
711
712    /**
713     * Clears all subtitles from the chart and sends a {@link ChartChangeEvent}
714     * to all registered listeners.
715     *
716     * @see #addSubtitle(Title)
717     */
718    public void clearSubtitles() {
719        for (Title t : this.subtitles) {
720            t.removeChangeListener(this);
721        }
722        this.subtitles.clear();
723        fireChartChanged();
724    }
725
726    /**
727     * Removes the specified subtitle and sends a {@link ChartChangeEvent} to
728     * all registered listeners.
729     *
730     * @param title  the title.
731     *
732     * @see #addSubtitle(Title)
733     */
734    public void removeSubtitle(Title title) {
735        this.subtitles.remove(title);
736        fireChartChanged();
737    }
738
739    /**
740     * Returns the plot for the chart.  The plot is a class responsible for
741     * coordinating the visual representation of the data, including the axes
742     * (if any).
743     *
744     * @return The plot.
745     */
746    public Plot getPlot() {
747        return this.plot;
748    }
749
750    /**
751     * Returns a flag that indicates whether or not anti-aliasing is used when
752     * the chart is drawn.
753     *
754     * @return The flag.
755     *
756     * @see #setAntiAlias(boolean)
757     */
758    public boolean getAntiAlias() {
759        Object val = this.renderingHints.get(RenderingHints.KEY_ANTIALIASING);
760        return RenderingHints.VALUE_ANTIALIAS_ON.equals(val);
761    }
762
763    /**
764     * Sets a flag that indicates whether or not anti-aliasing is used when the
765     * chart is drawn.
766     * <P>
767     * Anti-aliasing usually improves the appearance of charts, but is slower.
768     *
769     * @param flag  the new value of the flag.
770     *
771     * @see #getAntiAlias()
772     */
773    public void setAntiAlias(boolean flag) {
774        Object hint = flag ? RenderingHints.VALUE_ANTIALIAS_ON 
775                : RenderingHints.VALUE_ANTIALIAS_OFF;
776        this.renderingHints.put(RenderingHints.KEY_ANTIALIASING, hint);
777        fireChartChanged();
778    }
779
780    /**
781     * Returns the current value stored in the rendering hints table for
782     * {@link RenderingHints#KEY_TEXT_ANTIALIASING}.
783     *
784     * @return The hint value (possibly {@code null}).
785     *
786     * @see #setTextAntiAlias(Object)
787     */
788    public Object getTextAntiAlias() {
789        return this.renderingHints.get(RenderingHints.KEY_TEXT_ANTIALIASING);
790    }
791
792    /**
793     * Sets the value in the rendering hints table for
794     * {@link RenderingHints#KEY_TEXT_ANTIALIASING} to either
795     * {@link RenderingHints#VALUE_TEXT_ANTIALIAS_ON} or
796     * {@link RenderingHints#VALUE_TEXT_ANTIALIAS_OFF}, then sends a
797     * {@link ChartChangeEvent} to all registered listeners.
798     *
799     * @param flag  the new value of the flag.
800     *
801     * @see #getTextAntiAlias()
802     * @see #setTextAntiAlias(Object)
803     */
804    public void setTextAntiAlias(boolean flag) {
805        if (flag) {
806            setTextAntiAlias(RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
807        } else {
808            setTextAntiAlias(RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
809        }
810    }
811
812    /**
813     * Sets the value in the rendering hints table for
814     * {@link RenderingHints#KEY_TEXT_ANTIALIASING} and sends a
815     * {@link ChartChangeEvent} to all registered listeners.
816     *
817     * @param val  the new value ({@code null} permitted).
818     *
819     * @see #getTextAntiAlias()
820     * @see #setTextAntiAlias(boolean)
821     */
822    public void setTextAntiAlias(Object val) {
823        this.renderingHints.put(RenderingHints.KEY_TEXT_ANTIALIASING, val);
824        notifyListeners(new ChartChangeEvent(this));
825    }
826
827    /**
828     * Returns the paint used for the chart background.
829     *
830     * @return The paint (possibly {@code null}).
831     *
832     * @see #setBackgroundPaint(Paint)
833     */
834    public Paint getBackgroundPaint() {
835        return this.backgroundPaint;
836    }
837
838    /**
839     * Sets the paint used to fill the chart background and sends a
840     * {@link ChartChangeEvent} to all registered listeners.
841     *
842     * @param paint  the paint ({@code null} permitted).
843     *
844     * @see #getBackgroundPaint()
845     */
846    public void setBackgroundPaint(Paint paint) {
847        if (this.backgroundPaint != null) {
848            if (!this.backgroundPaint.equals(paint)) {
849                this.backgroundPaint = paint;
850                fireChartChanged();
851            }
852        } else {
853            if (paint != null) {
854                this.backgroundPaint = paint;
855                fireChartChanged();
856            }
857        }
858    }
859
860    /**
861     * Returns the background image for the chart, or {@code null} if
862     * there is no image.
863     *
864     * @return The image (possibly {@code null}).
865     *
866     * @see #setBackgroundImage(Image)
867     */
868    public Image getBackgroundImage() {
869        return this.backgroundImage;
870    }
871
872    /**
873     * Sets the background image for the chart and sends a
874     * {@link ChartChangeEvent} to all registered listeners.
875     *
876     * @param image  the image ({@code null} permitted).
877     *
878     * @see #getBackgroundImage()
879     */
880    public void setBackgroundImage(Image image) {
881        if (this.backgroundImage != null) {
882            if (!this.backgroundImage.equals(image)) {
883                this.backgroundImage = image;
884                fireChartChanged();
885            }
886        } else {
887            if (image != null) {
888                this.backgroundImage = image;
889                fireChartChanged();
890            }
891        }
892    }
893
894    /**
895     * Returns the background image alignment. 
896     *
897     * @return The alignment (never {@code null}).
898     *
899     * @see #setBackgroundImageAlignment(RectangleAlignment)
900     */
901    public RectangleAlignment getBackgroundImageAlignment() {
902        return this.backgroundImageAlignment;
903    }
904
905    /**
906     * Sets the background alignment and sends a change notification to all
907     * registered listeners.
908     *
909     * @param alignment  the alignment ({@code null} not permitted).
910     *
911     * @see #getBackgroundImageAlignment()
912     */
913    public void setBackgroundImageAlignment(RectangleAlignment alignment) {
914        Args.nullNotPermitted(alignment, "alignment");
915        if (this.backgroundImageAlignment != alignment) {
916            this.backgroundImageAlignment = alignment;
917            fireChartChanged();
918        }
919    }
920
921    /**
922     * Returns the alpha-transparency for the chart's background image.
923     *
924     * @return The alpha-transparency.
925     *
926     * @see #setBackgroundImageAlpha(float)
927     */
928    public float getBackgroundImageAlpha() {
929        return this.backgroundImageAlpha;
930    }
931
932    /**
933     * Sets the alpha-transparency for the chart's background image.
934     * Registered listeners are notified that the chart has been changed.
935     *
936     * @param alpha  the alpha value.
937     *
938     * @see #getBackgroundImageAlpha()
939     */
940    public void setBackgroundImageAlpha(float alpha) {
941        if (this.backgroundImageAlpha != alpha) {
942            this.backgroundImageAlpha = alpha;
943            fireChartChanged();
944        }
945    }
946
947    /**
948     * Returns a flag that controls whether or not change events are sent to
949     * registered listeners.
950     *
951     * @return A boolean.
952     *
953     * @see #setNotify(boolean)
954     */
955    public boolean isNotify() {
956        return this.notify;
957    }
958
959    /**
960     * Sets a flag that controls whether or not listeners receive
961     * {@link ChartChangeEvent} notifications.
962     *
963     * @param notify  a boolean.
964     *
965     * @see #isNotify()
966     */
967    public void setNotify(boolean notify) {
968        this.notify = notify;
969        // if the flag is being set to true, there may be queued up changes...
970        if (notify) {
971            notifyListeners(new ChartChangeEvent(this));
972        }
973    }
974
975    @Override
976    public void receive(ChartElementVisitor visitor) {
977        this.title.receive(visitor);
978        this.subtitles.forEach(subtitle -> {
979            subtitle.receive(visitor);
980        });
981        this.plot.receive(visitor);
982        visitor.visit(this);
983    }
984    
985    /**
986     * Draws the chart on a Java 2D graphics device (such as the screen or a
987     * printer).
988     * <P>
989     * This method is the focus of the entire JFreeChart library.
990     *
991     * @param g2  the graphics device.
992     * @param area  the area within which the chart should be drawn.
993     */
994    @Override
995    public void draw(Graphics2D g2, Rectangle2D area) {
996        draw(g2, area, null, null);
997    }
998
999    /**
1000     * Draws the chart on a Java 2D graphics device (such as the screen or a
1001     * printer).  This method is the focus of the entire JFreeChart library.
1002     *
1003     * @param g2  the graphics device.
1004     * @param area  the area within which the chart should be drawn.
1005     * @param info  records info about the drawing (null means collect no info).
1006     */
1007    public void draw(Graphics2D g2, Rectangle2D area, ChartRenderingInfo info) {
1008        draw(g2, area, null, info);
1009    }
1010
1011    /**
1012     * Draws the chart on a Java 2D graphics device (such as the screen or a
1013     * printer).
1014     * <P>
1015     * This method is the focus of the entire JFreeChart library.
1016     *
1017     * @param g2  the graphics device.
1018     * @param chartArea  the area within which the chart should be drawn.
1019     * @param anchor  the anchor point (in Java2D space) for the chart
1020     *                ({@code null} permitted).
1021     * @param info  records info about the drawing (null means collect no info).
1022     */
1023    public void draw(Graphics2D g2, Rectangle2D chartArea, Point2D anchor,
1024             ChartRenderingInfo info) {
1025
1026        notifyListeners(new ChartProgressEvent(this, this,
1027                ChartProgressEventType.DRAWING_STARTED, 0));
1028        
1029        if (this.elementHinting) {
1030            Map<String, String> m = new HashMap<>();
1031            if (this.id != null) {
1032                m.put("id", this.id);
1033            }
1034            m.put("ref", "JFREECHART_TOP_LEVEL");            
1035            g2.setRenderingHint(ChartHints.KEY_BEGIN_ELEMENT, m);            
1036        }
1037        
1038        EntityCollection entities = null;
1039        // record the chart area, if info is requested...
1040        if (info != null) {
1041            info.clear();
1042            info.setChartArea(chartArea);
1043            entities = info.getEntityCollection();
1044        }
1045        if (entities != null) {
1046            entities.add(new JFreeChartEntity((Rectangle2D) chartArea.clone(),
1047                    this));
1048        }
1049
1050        // ensure no drawing occurs outside chart area...
1051        Shape savedClip = g2.getClip();
1052        g2.clip(chartArea);
1053
1054        g2.addRenderingHints(this.renderingHints);
1055
1056        // draw the chart background...
1057        if (this.backgroundPaint != null) {
1058            g2.setPaint(this.backgroundPaint);
1059            g2.fill(chartArea);
1060        }
1061
1062        if (this.backgroundImage != null) {
1063            Composite originalComposite = g2.getComposite();
1064            g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
1065                    this.backgroundImageAlpha));
1066            Rectangle2D dest = new Rectangle2D.Double(0.0, 0.0,
1067                    this.backgroundImage.getWidth(null),
1068                    this.backgroundImage.getHeight(null));
1069            this.backgroundImageAlignment.align(dest, chartArea);
1070            g2.drawImage(this.backgroundImage, (int) dest.getX(),
1071                    (int) dest.getY(), (int) dest.getWidth(),
1072                    (int) dest.getHeight(), null);
1073            g2.setComposite(originalComposite);
1074        }
1075
1076        if (isBorderVisible()) {
1077            Paint paint = getBorderPaint();
1078            Stroke stroke = getBorderStroke();
1079            if (paint != null && stroke != null) {
1080                Rectangle2D borderArea = new Rectangle2D.Double(
1081                        chartArea.getX(), chartArea.getY(),
1082                        chartArea.getWidth() - 1.0, chartArea.getHeight()
1083                        - 1.0);
1084                g2.setPaint(paint);
1085                g2.setStroke(stroke);
1086                g2.draw(borderArea);
1087            }
1088        }
1089
1090        // draw the title and subtitles...
1091        Rectangle2D nonTitleArea = new Rectangle2D.Double();
1092        nonTitleArea.setRect(chartArea);
1093        this.padding.trim(nonTitleArea);
1094
1095        if (this.title != null && this.title.isVisible()) {
1096            EntityCollection e = drawTitle(this.title, g2, nonTitleArea,
1097                    (entities != null));
1098            if (e != null && entities != null) {
1099                entities.addAll(e);
1100            }
1101        }
1102
1103        for (Title currentTitle : this.subtitles) {
1104            if (currentTitle.isVisible()) {
1105                EntityCollection e = drawTitle(currentTitle, g2, nonTitleArea,
1106                        (entities != null));
1107                if (e != null && entities != null) {
1108                    entities.addAll(e);
1109                }
1110            }
1111        }
1112
1113        Rectangle2D plotArea = nonTitleArea;
1114
1115        // draw the plot (axes and data visualisation)
1116        PlotRenderingInfo plotInfo = null;
1117        if (info != null) {
1118            plotInfo = info.getPlotInfo();
1119        }
1120        this.plot.draw(g2, plotArea, anchor, null, plotInfo);
1121        g2.setClip(savedClip);
1122        if (this.elementHinting) {         
1123            g2.setRenderingHint(ChartHints.KEY_END_ELEMENT, Boolean.TRUE);            
1124        }
1125
1126        notifyListeners(new ChartProgressEvent(this, this,
1127                ChartProgressEventType.DRAWING_FINISHED, 100));
1128    }
1129
1130    /**
1131     * Creates a rectangle that is aligned to the frame.
1132     *
1133     * @param dimensions  the dimensions for the rectangle.
1134     * @param frame  the frame to align to.
1135     * @param hAlign  the horizontal alignment ({@code null} not permitted).
1136     * @param vAlign  the vertical alignment ({@code null} not permitted).
1137     *
1138     * @return A rectangle.
1139     */
1140    private Rectangle2D createAlignedRectangle2D(Size2D dimensions,
1141            Rectangle2D frame, HorizontalAlignment hAlign,
1142            VerticalAlignment vAlign) {
1143        Args.nullNotPermitted(hAlign, "hAlign");
1144        Args.nullNotPermitted(vAlign, "vAlign");
1145        double x = Double.NaN;
1146        double y = Double.NaN;
1147        switch (hAlign) {
1148            case LEFT:
1149                x = frame.getX();
1150                break;
1151            case CENTER:
1152                x = frame.getCenterX() - (dimensions.width / 2.0);
1153                break;
1154            case RIGHT:
1155                x = frame.getMaxX() - dimensions.width;
1156                break;
1157            default:
1158                throw new IllegalStateException("Unexpected enum value " + hAlign);
1159        }
1160        switch (vAlign) {
1161            case TOP:
1162                y = frame.getY();
1163                break;
1164            case CENTER:
1165                y = frame.getCenterY() - (dimensions.height / 2.0);
1166                break;
1167            case BOTTOM:
1168                y = frame.getMaxY() - dimensions.height;
1169                break;
1170            default:
1171                throw new IllegalStateException("Unexpected enum value " + hAlign);
1172        }
1173
1174        return new Rectangle2D.Double(x, y, dimensions.width,
1175                dimensions.height);
1176    }
1177
1178    /**
1179     * Draws a title.  The title should be drawn at the top, bottom, left or
1180     * right of the specified area, and the area should be updated to reflect
1181     * the amount of space used by the title.
1182     *
1183     * @param t  the title ({@code null} not permitted).
1184     * @param g2  the graphics device ({@code null} not permitted).
1185     * @param area  the chart area, excluding any existing titles
1186     *              ({@code null} not permitted).
1187     * @param entities  a flag that controls whether or not an entity
1188     *                  collection is returned for the title.
1189     *
1190     * @return An entity collection for the title (possibly {@code null}).
1191     */
1192    protected EntityCollection drawTitle(Title t, Graphics2D g2,
1193                                         Rectangle2D area, boolean entities) {
1194
1195        Args.nullNotPermitted(t, "t");
1196        Args.nullNotPermitted(area, "area");
1197        Rectangle2D titleArea;
1198        RectangleEdge position = t.getPosition();
1199        double ww = area.getWidth();
1200        if (ww <= 0.0) {
1201            return null;
1202        }
1203        double hh = area.getHeight();
1204        if (hh <= 0.0) {
1205            return null;
1206        }
1207        RectangleConstraint constraint = new RectangleConstraint(ww,
1208                new Range(0.0, ww), LengthConstraintType.RANGE, hh,
1209                new Range(0.0, hh), LengthConstraintType.RANGE);
1210        Object retValue = null;
1211        BlockParams p = new BlockParams();
1212        p.setGenerateEntities(entities);
1213        switch (position) {
1214            case TOP: {
1215                Size2D size = t.arrange(g2, constraint);
1216                titleArea = createAlignedRectangle2D(size, area,
1217                        t.getHorizontalAlignment(), VerticalAlignment.TOP);
1218                retValue = t.draw(g2, titleArea, p);
1219                area.setRect(area.getX(), Math.min(area.getY() + size.height,
1220                        area.getMaxY()), area.getWidth(), Math.max(area.getHeight()
1221                        - size.height, 0));
1222                break;
1223            }
1224            case BOTTOM: {
1225                Size2D size = t.arrange(g2, constraint);
1226                titleArea = createAlignedRectangle2D(size, area,
1227                        t.getHorizontalAlignment(), VerticalAlignment.BOTTOM);
1228                retValue = t.draw(g2, titleArea, p);
1229                area.setRect(area.getX(), area.getY(), area.getWidth(),
1230                        area.getHeight() - size.height);
1231                break;
1232            }
1233            case RIGHT: {
1234                Size2D size = t.arrange(g2, constraint);
1235                titleArea = createAlignedRectangle2D(size, area,
1236                        HorizontalAlignment.RIGHT, t.getVerticalAlignment());
1237                retValue = t.draw(g2, titleArea, p);
1238                area.setRect(area.getX(), area.getY(), area.getWidth()
1239                        - size.width, area.getHeight());
1240                break;
1241            }
1242            case LEFT: {
1243                Size2D size = t.arrange(g2, constraint);
1244                titleArea = createAlignedRectangle2D(size, area,
1245                        HorizontalAlignment.LEFT, t.getVerticalAlignment());
1246                retValue = t.draw(g2, titleArea, p);
1247                area.setRect(area.getX() + size.width, area.getY(), area.getWidth()
1248                        - size.width, area.getHeight());
1249                break;
1250            }
1251            default: {
1252                throw new RuntimeException("Unrecognised title position.");
1253            }
1254        }
1255        EntityCollection result = null;
1256        if (retValue instanceof EntityBlockResult) {
1257            EntityBlockResult ebr = (EntityBlockResult) retValue;
1258            result = ebr.getEntityCollection();
1259        }
1260        return result;
1261    }
1262
1263    /**
1264     * Creates and returns a buffered image into which the chart has been drawn.
1265     *
1266     * @param width  the width.
1267     * @param height  the height.
1268     *
1269     * @return A buffered image.
1270     */
1271    public BufferedImage createBufferedImage(int width, int height) {
1272        return createBufferedImage(width, height, null);
1273    }
1274
1275    /**
1276     * Creates and returns a buffered image into which the chart has been drawn.
1277     *
1278     * @param width  the width.
1279     * @param height  the height.
1280     * @param info  carries back chart state information ({@code null}
1281     *              permitted).
1282     *
1283     * @return A buffered image.
1284     */
1285    public BufferedImage createBufferedImage(int width, int height,
1286                                             ChartRenderingInfo info) {
1287        return createBufferedImage(width, height, BufferedImage.TYPE_INT_ARGB,
1288                info);
1289    }
1290
1291    /**
1292     * Creates and returns a buffered image into which the chart has been drawn.
1293     *
1294     * @param width  the width.
1295     * @param height  the height.
1296     * @param imageType  the image type.
1297     * @param info  carries back chart state information ({@code null}
1298     *              permitted).
1299     *
1300     * @return A buffered image.
1301     */
1302    public BufferedImage createBufferedImage(int width, int height,
1303            int imageType, ChartRenderingInfo info) {
1304        BufferedImage image = new BufferedImage(width, height, imageType);
1305        Graphics2D g2 = image.createGraphics();
1306        draw(g2, new Rectangle2D.Double(0, 0, width, height), null, info);
1307        g2.dispose();
1308        return image;
1309    }
1310
1311    /**
1312     * Creates and returns a buffered image into which the chart has been drawn.
1313     *
1314     * @param imageWidth  the image width.
1315     * @param imageHeight  the image height.
1316     * @param drawWidth  the width for drawing the chart (will be scaled to
1317     *                   fit image).
1318     * @param drawHeight  the height for drawing the chart (will be scaled to
1319     *                    fit image).
1320     * @param info  optional object for collection chart dimension and entity
1321     *              information.
1322     *
1323     * @return A buffered image.
1324     */
1325    public BufferedImage createBufferedImage(int imageWidth,
1326                                             int imageHeight,
1327                                             double drawWidth,
1328                                             double drawHeight,
1329                                             ChartRenderingInfo info) {
1330
1331        BufferedImage image = new BufferedImage(imageWidth, imageHeight,
1332                BufferedImage.TYPE_INT_ARGB);
1333        Graphics2D g2 = image.createGraphics();
1334        double scaleX = imageWidth / drawWidth;
1335        double scaleY = imageHeight / drawHeight;
1336        AffineTransform st = AffineTransform.getScaleInstance(scaleX, scaleY);
1337        g2.transform(st);
1338        draw(g2, new Rectangle2D.Double(0, 0, drawWidth, drawHeight), null,
1339                info);
1340        g2.dispose();
1341        return image;
1342    }
1343
1344    /**
1345     * Handles a 'click' on the chart.  JFreeChart is not a UI component, so
1346     * some other object (for example, {@link ChartPanel}) needs to capture
1347     * the click event and pass it onto the JFreeChart object.
1348     * If you are not using JFreeChart in a client application, then this
1349     * method is not required.
1350     *
1351     * @param x  x-coordinate of the click (in Java2D space).
1352     * @param y  y-coordinate of the click (in Java2D space).
1353     * @param info  contains chart dimension and entity information
1354     *              ({@code null} not permitted).
1355     */
1356    public void handleClick(int x, int y, ChartRenderingInfo info) {
1357        // pass the click on to the plot...
1358        // rely on the plot to post a plot change event and redraw the chart...
1359        this.plot.handleClick(x, y, info.getPlotInfo());
1360    }
1361
1362    /**
1363     * Registers an object for notification of changes to the chart.
1364     *
1365     * @param listener  the listener ({@code null} not permitted).
1366     *
1367     * @see #removeChangeListener(ChartChangeListener)
1368     */
1369    public void addChangeListener(ChartChangeListener listener) {
1370        Args.nullNotPermitted(listener, "listener");
1371        this.changeListeners.add(ChartChangeListener.class, listener);
1372    }
1373
1374    /**
1375     * Deregisters an object for notification of changes to the chart.
1376     *
1377     * @param listener  the listener ({@code null} not permitted)
1378     *
1379     * @see #addChangeListener(ChartChangeListener)
1380     */
1381    public void removeChangeListener(ChartChangeListener listener) {
1382        Args.nullNotPermitted(listener, "listener");
1383        this.changeListeners.remove(ChartChangeListener.class, listener);
1384    }
1385
1386    /**
1387     * Sends a default {@link ChartChangeEvent} to all registered listeners.
1388     * <P>
1389     * This method is for convenience only.
1390     */
1391    public void fireChartChanged() {
1392        ChartChangeEvent event = new ChartChangeEvent(this);
1393        notifyListeners(event);
1394    }
1395
1396    /**
1397     * Sends a {@link ChartChangeEvent} to all registered listeners.
1398     *
1399     * @param event  information about the event that triggered the
1400     *               notification.
1401     */
1402    protected void notifyListeners(ChartChangeEvent event) {
1403        if (this.notify) {
1404            Object[] listeners = this.changeListeners.getListenerList();
1405            for (int i = listeners.length - 2; i >= 0; i -= 2) {
1406                if (listeners[i] == ChartChangeListener.class) {
1407                    ((ChartChangeListener) listeners[i + 1]).chartChanged(
1408                            event);
1409                }
1410            }
1411        }
1412    }
1413
1414    /**
1415     * Registers an object for notification of progress events relating to the
1416     * chart.
1417     *
1418     * @param listener  the object being registered.
1419     *
1420     * @see #removeProgressListener(ChartProgressListener)
1421     */
1422    public void addProgressListener(ChartProgressListener listener) {
1423        this.progressListeners.add(ChartProgressListener.class, listener);
1424    }
1425
1426    /**
1427     * Deregisters an object for notification of changes to the chart.
1428     *
1429     * @param listener  the object being deregistered.
1430     *
1431     * @see #addProgressListener(ChartProgressListener)
1432     */
1433    public void removeProgressListener(ChartProgressListener listener) {
1434        this.progressListeners.remove(ChartProgressListener.class, listener);
1435    }
1436
1437    /**
1438     * Sends a {@link ChartProgressEvent} to all registered listeners.
1439     *
1440     * @param event  information about the event that triggered the
1441     *               notification.
1442     */
1443    protected void notifyListeners(ChartProgressEvent event) {
1444        Object[] listeners = this.progressListeners.getListenerList();
1445        for (int i = listeners.length - 2; i >= 0; i -= 2) {
1446            if (listeners[i] == ChartProgressListener.class) {
1447                ((ChartProgressListener) listeners[i + 1]).chartProgress(event);
1448            }
1449        }
1450    }
1451
1452    /**
1453     * Receives notification that a chart title has changed, and passes this
1454     * on to registered listeners.
1455     *
1456     * @param event  information about the chart title change.
1457     */
1458    @Override
1459    public void titleChanged(TitleChangeEvent event) {
1460        event.setChart(this);
1461        notifyListeners(event);
1462    }
1463
1464    /**
1465     * Receives notification that the plot has changed, and passes this on to
1466     * registered listeners.
1467     *
1468     * @param event  information about the plot change.
1469     */
1470    @Override
1471    public void plotChanged(PlotChangeEvent event) {
1472        event.setChart(this);
1473        notifyListeners(event);
1474    }
1475
1476    /**
1477     * Tests this chart for equality with another object.
1478     *
1479     * @param obj  the object ({@code null} permitted).
1480     *
1481     * @return A boolean.
1482     */
1483    @Override
1484    public boolean equals(Object obj) {
1485        if (obj == this) {
1486            return true;
1487        }
1488        if (!(obj instanceof JFreeChart)) {
1489            return false;
1490        }
1491        JFreeChart that = (JFreeChart) obj;
1492        if (!this.renderingHints.equals(that.renderingHints)) {
1493            return false;
1494        }
1495        if (this.borderVisible != that.borderVisible) {
1496            return false;
1497        }
1498        if (!Objects.equals(this.borderStroke, that.borderStroke)) {
1499            return false;
1500        }
1501        if (!PaintUtils.equal(this.borderPaint, that.borderPaint)) {
1502            return false;
1503        }
1504        if (!this.padding.equals(that.padding)) {
1505            return false;
1506        }
1507        if (!Objects.equals(this.title, that.title)) {
1508            return false;
1509        }
1510        if (!Objects.equals(this.subtitles, that.subtitles)) {
1511            return false;
1512        }
1513        if (!Objects.equals(this.plot, that.plot)) {
1514            return false;
1515        }
1516        if (!PaintUtils.equal(
1517            this.backgroundPaint, that.backgroundPaint
1518        )) {
1519            return false;
1520        }
1521        if (!Objects.equals(this.backgroundImage, that.backgroundImage)) {
1522            return false;
1523        }
1524        if (this.backgroundImageAlignment != that.backgroundImageAlignment) {
1525            return false;
1526        }
1527        if (this.backgroundImageAlpha != that.backgroundImageAlpha) {
1528            return false;
1529        }
1530        if (this.notify != that.notify) {
1531            return false;
1532        }
1533        return true;
1534    }
1535
1536    @Override
1537    public int hashCode() {
1538        int hash = 7;
1539        hash = 59 * hash + Objects.hashCode(this.renderingHints);
1540        hash = 59 * hash + (this.borderVisible ? 1 : 0);
1541        hash = 59 * hash + Objects.hashCode(this.borderStroke);
1542        hash = 59 * hash + Objects.hashCode(this.borderPaint);
1543        hash = 59 * hash + Objects.hashCode(this.padding);
1544        hash = 59 * hash + Objects.hashCode(this.title);
1545        hash = 59 * hash + Objects.hashCode(this.subtitles);
1546        hash = 59 * hash + Objects.hashCode(this.plot);
1547        hash = 59 * hash + Objects.hashCode(this.backgroundPaint);
1548        hash = 59 * hash + Objects.hashCode(this.backgroundImage);
1549        hash = 59 * hash + Objects.hashCode(this.backgroundImageAlignment);
1550        hash = 59 * hash + Float.floatToIntBits(this.backgroundImageAlpha);
1551        hash = 59 * hash + (this.notify ? 1 : 0);
1552        return hash;
1553    }
1554
1555    /**
1556     * Provides serialization support.
1557     *
1558     * @param stream  the output stream.
1559     *
1560     * @throws IOException  if there is an I/O error.
1561     */
1562    private void writeObject(ObjectOutputStream stream) throws IOException {
1563        stream.defaultWriteObject();
1564        SerialUtils.writeStroke(this.borderStroke, stream);
1565        SerialUtils.writePaint(this.borderPaint, stream);
1566        SerialUtils.writePaint(this.backgroundPaint, stream);
1567    }
1568
1569    /**
1570     * Provides serialization support.
1571     *
1572     * @param stream  the input stream.
1573     *
1574     * @throws IOException  if there is an I/O error.
1575     * @throws ClassNotFoundException  if there is a classpath problem.
1576     */
1577    private void readObject(ObjectInputStream stream)
1578        throws IOException, ClassNotFoundException {
1579        stream.defaultReadObject();
1580        this.borderStroke = SerialUtils.readStroke(stream);
1581        this.borderPaint = SerialUtils.readPaint(stream);
1582        this.backgroundPaint = SerialUtils.readPaint(stream);
1583        this.progressListeners = new EventListenerList();
1584        this.changeListeners = new EventListenerList();
1585        this.renderingHints = new RenderingHints(
1586                RenderingHints.KEY_ANTIALIASING,
1587                RenderingHints.VALUE_ANTIALIAS_ON);
1588        this.renderingHints.put(RenderingHints.KEY_STROKE_CONTROL,
1589                RenderingHints.VALUE_STROKE_PURE);
1590        
1591        // register as a listener with sub-components...
1592        if (this.title != null) {
1593            this.title.addChangeListener(this);
1594        }
1595
1596        for (int i = 0; i < getSubtitleCount(); i++) {
1597            getSubtitle(i).addChangeListener(this);
1598        }
1599        this.plot.addChangeListener(this);
1600    }
1601
1602    /**
1603     * Clones the object, and takes care of listeners.
1604     * Note: caller shall register its own listeners on cloned graph.
1605     *
1606     * @return A clone.
1607     *
1608     * @throws CloneNotSupportedException if the chart is not cloneable.
1609     */
1610    @Override
1611    public Object clone() throws CloneNotSupportedException {
1612        JFreeChart chart = (JFreeChart) super.clone();
1613
1614        chart.renderingHints = (RenderingHints) this.renderingHints.clone();
1615        // private boolean borderVisible;
1616        // private transient Stroke borderStroke;
1617        // private transient Paint borderPaint;
1618
1619        if (this.title != null) {
1620            chart.title = (TextTitle) this.title.clone();
1621            chart.title.addChangeListener(chart);
1622        }
1623
1624        chart.subtitles = new ArrayList<>();
1625        for (int i = 0; i < getSubtitleCount(); i++) {
1626            Title subtitle = (Title) getSubtitle(i).clone();
1627            chart.subtitles.add(subtitle);
1628            subtitle.addChangeListener(chart);
1629        }
1630
1631        if (this.plot != null) {
1632            chart.plot = (Plot) this.plot.clone();
1633            chart.plot.addChangeListener(chart);
1634        }
1635
1636        chart.progressListeners = new EventListenerList();
1637        chart.changeListeners = new EventListenerList();
1638        return chart;
1639    }
1640
1641
1642}