001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
006     *
007     * Project Info:  http://www.jfree.org/jfreechart/index.html
008     *
009     * This library is free software; you can redistribute it and/or modify it 
010     * under the terms of the GNU Lesser General Public License as published by 
011     * the Free Software Foundation; either version 2.1 of the License, or 
012     * (at your option) any later version.
013     *
014     * This library is distributed in the hope that it will be useful, but 
015     * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
016     * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
017     * License for more details.
018     *
019     * You should have received a copy of the GNU Lesser General Public
020     * License along with this library; if not, write to the Free Software
021     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
022     * USA.  
023     *
024     * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
025     * in the United States and other countries.]
026     * 
027     * ---------------
028     * JFreeChart.java
029     * ---------------
030     * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
031     *
032     * Original Author:  David Gilbert (for Object Refinery Limited);
033     * Contributor(s):   Andrzej Porebski;
034     *                   David Li;
035     *                   Wolfgang Irler;
036     *                   Christian W. Zuckschwerdt;
037     *                   Klaus Rheinwald;
038     *                   Nicolas Brodu;
039     *
040     * $Id: JFreeChart.java,v 1.34.2.8 2005/11/24 11:04:25 mungady Exp $
041     *
042     * Changes (from 20-Jun-2001)
043     * --------------------------
044     * 20-Jun-2001 : Modifications submitted by Andrzej Porebski for legend 
045     *               placement;
046     * 21-Jun-2001 : Removed JFreeChart parameter from Plot constructors (DG);
047     * 22-Jun-2001 : Multiple titles added (original code by David Berry, with 
048     *               reworkings by DG);
049     * 18-Sep-2001 : Updated header (DG);
050     * 15-Oct-2001 : Moved data source classes into new package 
051     *               com.jrefinery.data.* (DG);
052     * 18-Oct-2001 : New factory method for creating VerticalXYBarChart (DG);
053     * 19-Oct-2001 : Moved series paint and stroke methods to the Plot class (DG);
054     *               Moved static chart creation methods to new ChartFactory 
055     *               class (DG);
056     * 22-Oct-2001 : Renamed DataSource.java --> Dataset.java etc. (DG);
057     *               Fixed bug where chart isn't registered with the dataset (DG);
058     * 07-Nov-2001 : Fixed bug where null title in constructor causes 
059     *               exception (DG);
060     *               Tidied up event notification code (DG);
061     * 17-Nov-2001 : Added getLegendItemCount() method (DG);
062     * 21-Nov-2001 : Set clipping in draw method to ensure that nothing gets drawn 
063     *               outside the chart area (DG);
064     * 11-Dec-2001 : Added the createBufferedImage() method, taken from the 
065     *               JFreeChartServletDemo class (DG);
066     * 13-Dec-2001 : Added tooltips (DG);
067     * 16-Jan-2002 : Added handleClick() method (DG);
068     * 22-Jan-2002 : Fixed bug correlating legend labels with pie data (DG);
069     * 05-Feb-2002 : Removed redundant tooltips code (DG);
070     * 19-Feb-2002 : Added accessor methods for the backgroundImage and 
071     *               backgroundImageAlpha attributes (DG);
072     * 21-Feb-2002 : Added static fields for INFO, COPYRIGHT, LICENCE, CONTRIBUTORS
073     *               and LIBRARIES.  These can be used to display information about
074     *               JFreeChart (DG);
075     * 06-Mar-2002 : Moved constants to JFreeChartConstants interface (DG);
076     * 18-Apr-2002 : PieDataset is no longer sorted (oldman);
077     * 23-Apr-2002 : Moved dataset to the Plot class (DG);
078     * 13-Jun-2002 : Added an extra draw() method (DG);
079     * 25-Jun-2002 : Implemented the Drawable interface and removed redundant 
080     *               imports (DG);
081     * 26-Jun-2002 : Added another createBufferedImage() method (DG);
082     * 18-Sep-2002 : Fixed issues reported by Checkstyle (DG);
083     * 23-Sep-2002 : Added new contributor (DG);
084     * 28-Oct-2002 : Created main title and subtitle list to replace existing title
085     *               list (DG);
086     * 08-Jan-2003 : Added contributor (DG);
087     * 17-Jan-2003 : Added new constructor (DG);
088     * 22-Jan-2003 : Added ChartColor class by Cameron Riley, and background image 
089     *               alignment code by Christian W. Zuckschwerdt (DG);
090     * 11-Feb-2003 : Added flag to allow suppression of chart change events, based 
091     *               on a suggestion by Klaus Rheinwald (DG);
092     * 04-Mar-2003 : Added small fix for suppressed chart change events (see bug id
093     *               690865) (DG);
094     * 10-Mar-2003 : Added Benoit Xhenseval to contributors (DG);
095     * 26-Mar-2003 : Implemented Serializable (DG);
096     * 15-Jul-2003 : Added an optional border for the chart (DG);
097     * 11-Sep-2003 : Took care of listeners while cloning (NB);
098     * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
099     * 22-Sep-2003 : Added nullpointer checks.
100     * 25-Sep-2003 : Added nullpointer checks too (NB).
101     * 03-Dec-2003 : Legends are now registered by this class instead of using the 
102     *               old constructor way (TM);
103     * 03-Dec-2003 : Added anchorPoint to draw() method (DG);
104     * 08-Jan-2004 : Reworked title code, introducing line wrapping (DG);
105     * 09-Feb-2004 : Created additional createBufferedImage() method (DG);
106     * 05-Apr-2004 : Added new createBufferedImage() method (DG);
107     * 27-May-2004 : Moved constants from JFreeChartConstants.java back to this 
108     *               class (DG);
109     * 25-Nov-2004 : Updates for changes to Title class (DG);
110     * 06-Jan-2005 : Change lookup for default background color (DG);
111     * 31-Jan-2005 : Added Don Elliott to contributors (DG);
112     * 02-Feb-2005 : Added clearSubtitles() method (DG);
113     * 03-Feb-2005 : Added Mofeed Shahin to contributors (DG);
114     * 08-Feb-2005 : Updated for RectangleConstraint changes (DG);
115     * 28-Mar-2005 : Renamed Legend --> OldLegend (DG);
116     * 12-Apr-2005 : Added methods to access legend(s) in subtitle list (DG);
117     * 13-Apr-2005 : Added removeLegend() and removeSubtitle() methods (DG);
118     * 20-Apr-2005 : Modified to collect chart entities from titles and 
119     *               subtitles (DG);
120     * 26-Apr-2005 : Removed LOGGER (DG);
121     * 06-Jun-2005 : Added addLegend() method and padding attribute, fixed equals() 
122     *               method (DG);
123     * 24-Nov-2005 : Removed OldLegend and related code - don't want to support
124     *               this in 1.0.0 final (DG);
125     *
126     */
127    
128    package org.jfree.chart;
129    
130    
131    import java.awt.AlphaComposite;
132    import java.awt.BasicStroke;
133    import java.awt.Color;
134    import java.awt.Composite;
135    import java.awt.Font;
136    import java.awt.Graphics2D;
137    import java.awt.Image;
138    import java.awt.Paint;
139    import java.awt.RenderingHints;
140    import java.awt.Shape;
141    import java.awt.Stroke;
142    import java.awt.geom.AffineTransform;
143    import java.awt.geom.Point2D;
144    import java.awt.geom.Rectangle2D;
145    import java.awt.image.BufferedImage;
146    import java.io.IOException;
147    import java.io.ObjectInputStream;
148    import java.io.ObjectOutputStream;
149    import java.io.Serializable;
150    import java.net.URL;
151    import java.util.ArrayList;
152    import java.util.Arrays;
153    import java.util.Iterator;
154    import java.util.List;
155    import java.util.ResourceBundle;
156    
157    import javax.swing.ImageIcon;
158    import javax.swing.UIManager;
159    import javax.swing.event.EventListenerList;
160    
161    import org.jfree.JCommon;
162    import org.jfree.chart.block.BlockBorder;
163    import org.jfree.chart.block.BlockParams;
164    import org.jfree.chart.block.EntityBlockResult;
165    import org.jfree.chart.block.LengthConstraintType;
166    import org.jfree.chart.block.RectangleConstraint;
167    import org.jfree.chart.entity.EntityCollection;
168    import org.jfree.chart.event.ChartChangeEvent;
169    import org.jfree.chart.event.ChartChangeListener;
170    import org.jfree.chart.event.ChartProgressEvent;
171    import org.jfree.chart.event.ChartProgressListener;
172    import org.jfree.chart.event.PlotChangeEvent;
173    import org.jfree.chart.event.PlotChangeListener;
174    import org.jfree.chart.event.TitleChangeEvent;
175    import org.jfree.chart.event.TitleChangeListener;
176    import org.jfree.chart.plot.CategoryPlot;
177    import org.jfree.chart.plot.Plot;
178    import org.jfree.chart.plot.PlotRenderingInfo;
179    import org.jfree.chart.plot.XYPlot;
180    import org.jfree.chart.title.LegendTitle;
181    import org.jfree.chart.title.TextTitle;
182    import org.jfree.chart.title.Title;
183    import org.jfree.data.Range;
184    import org.jfree.io.SerialUtilities;
185    import org.jfree.ui.Align;
186    import org.jfree.ui.Drawable;
187    import org.jfree.ui.HorizontalAlignment;
188    import org.jfree.ui.RectangleEdge;
189    import org.jfree.ui.RectangleInsets;
190    import org.jfree.ui.Size2D;
191    import org.jfree.ui.VerticalAlignment;
192    import org.jfree.ui.about.Contributor;
193    import org.jfree.ui.about.Licences;
194    import org.jfree.ui.about.ProjectInfo;
195    import org.jfree.util.ObjectUtilities;
196    import org.jfree.util.PaintUtilities;
197    
198    /**
199     * A chart class implemented using the Java 2D APIs.  The current version
200     * supports bar charts, line charts, pie charts and xy plots (including time
201     * series data).
202     * <P>
203     * JFreeChart coordinates several objects to achieve its aim of being able to
204     * draw a chart on a Java 2D graphics device: a list of {@link Title} objects
205     * (which often includes the chart's legend), a {@link Plot} and a 
206     * {@link org.jfree.data.general.Dataset} (the plot in turn manages a 
207     * horizontal axis and a vertical axis).
208     * <P>
209     * You should use a {@link ChartPanel} to display a chart in a GUI.
210     * <P>
211     * The {@link ChartFactory} class contains static methods for creating 
212     * 'ready-made' charts.
213     *
214     * @see ChartPanel
215     * @see ChartFactory
216     * @see Title
217     * @see Plot
218     *
219     */
220    public class JFreeChart implements Drawable,
221                                       TitleChangeListener,
222                                       PlotChangeListener,
223                                       Serializable,
224                                       Cloneable {
225    
226        /** For serialization. */    
227        private static final long serialVersionUID = -3470703747817429120L;
228        
229        /** Information about the project. */
230        public static final ProjectInfo INFO = new JFreeChartInfo();
231    
232        /** The default font for titles. */
233        public static final Font DEFAULT_TITLE_FONT 
234            = new Font("SansSerif", Font.BOLD, 18);
235    
236        /** The default background color. */
237        public static final Paint DEFAULT_BACKGROUND_PAINT 
238            = UIManager.getColor("Panel.background");
239    
240        /** The default background image. */
241        public static final Image DEFAULT_BACKGROUND_IMAGE = null;
242    
243        /** The default background image alignment. */
244        public static final int DEFAULT_BACKGROUND_IMAGE_ALIGNMENT = Align.FIT;
245    
246        /** The default background image alpha. */
247        public static final float DEFAULT_BACKGROUND_IMAGE_ALPHA = 0.5f;
248    
249        /** 
250         * Rendering hints that will be used for chart drawing.  This should never
251         * be <code>null</code>. 
252         */
253        private transient RenderingHints renderingHints;
254    
255        /** A flag that controls whether or not the chart border is drawn. */
256        private boolean borderVisible;
257    
258        /** The stroke used to draw the chart border (if visible). */
259        private transient Stroke borderStroke;
260    
261        /** The paint used to draw the chart border (if visible). */
262        private transient Paint borderPaint;
263    
264        /** The padding between the chart border and the chart drawing area. */
265        private RectangleInsets padding;
266        
267        /** The chart title (optional). */
268        private TextTitle title;
269    
270        /** The chart subtitles (zero, one or many). */
271        private List subtitles;
272    
273        /** Draws the visual representation of the data. */
274        private Plot plot;
275    
276        /** Paint used to draw the background of the chart. */
277        private transient Paint backgroundPaint;
278    
279        /** An optional background image for the chart. */
280        private transient Image backgroundImage;  // todo: not serialized yet
281    
282        /** The alignment for the background image. */
283        private int backgroundImageAlignment = Align.FIT;
284    
285        /** The alpha transparency for the background image. */
286        private float backgroundImageAlpha = 0.5f;
287    
288        /** Storage for registered change listeners. */
289        private transient EventListenerList changeListeners;
290    
291        /** Storage for registered progress listeners. */
292        private transient EventListenerList progressListeners;
293    
294        /** 
295         * A flag that can be used to enable/disable notification of chart change 
296         * events. 
297         */
298        private boolean notify;
299        
300        /**
301         * Creates a new chart based on the supplied plot.  The chart will have
302         * a legend added automatically, but no title (although you can easily add
303         * one later).  
304         * <br><br>
305         * Note that the  {@link ChartFactory} class contains a range 
306         * of static methods that will return ready-made charts, and often this
307         * is a more convenient way to create charts than using this constructor.
308         *
309         * @param plot  the plot (<code>null</code> not permitted).
310         */
311        public JFreeChart(Plot plot) {
312    
313            this(
314                null, // title
315                null, // font
316                plot,
317                true // create legend
318            );
319    
320        }
321    
322        /**
323         * Creates a new chart with the given title and plot.  A default font 
324         * (@link DEFAULT_TITLE_FONT) is used for the title, and the chart will 
325         * have a legend added automatically.  
326         * <br><br>
327         * Note that the  {@link ChartFactory} class contains a range 
328         * of static methods that will return ready-made charts, and often this
329         * is a more convenient way to create charts than using this constructor.
330         *
331         * @param title  the chart title (<code>null</code> permitted).
332         * @param plot  the plot (<code>null</code> not permitted).
333         */
334        public JFreeChart(String title, Plot plot) {
335            this(title, JFreeChart.DEFAULT_TITLE_FONT, plot, true);
336        }
337    
338        /**
339         * Creates a new chart with the given title and plot.  The 
340         * <code>createLegend</code> argument specifies whether or not a legend
341         * should be added to the chart.  
342         * <br><br>
343         * Note that the  {@link ChartFactory} class contains a range 
344         * of static methods that will return ready-made charts, and often this
345         * is a more convenient way to create charts than using this constructor.
346         *
347         * @param title  the chart title (<code>null</code> permitted).
348         * @param titleFont  the font for displaying the chart title 
349         *                   (<code>null</code> permitted).
350         * @param plot  controller of the visual representation of the data 
351         *              (<code>null</code> not permitted).
352         * @param createLegend  a flag indicating whether or not a legend should   
353         *                      be created for the chart.
354         */
355        public JFreeChart(String title, Font titleFont, Plot plot, 
356                          boolean createLegend) {
357    
358            if (plot == null) {
359                throw new NullPointerException("Null 'plot' argument.");
360            }
361    
362            // create storage for listeners...
363            this.progressListeners = new EventListenerList();
364            this.changeListeners = new EventListenerList();
365            this.notify = true;  // default is to notify listeners when the 
366                                 // chart changes
367    
368            this.renderingHints = new RenderingHints(
369                RenderingHints.KEY_ANTIALIASING,
370                RenderingHints.VALUE_ANTIALIAS_ON
371            );
372    
373            this.borderVisible = false;
374            this.borderStroke = new BasicStroke(1.0f);
375            this.borderPaint = Color.black;
376    
377            this.padding = RectangleInsets.ZERO_INSETS;
378            
379            this.plot = plot;
380            plot.addChangeListener(this);
381    
382            this.subtitles = new ArrayList();
383    
384            // create a legend, if requested...
385            if (createLegend) {
386                LegendTitle legend = new LegendTitle(this.plot);
387                legend.setMargin(new RectangleInsets(1.0, 1.0, 1.0, 1.0));
388                legend.setBorder(new BlockBorder());
389                legend.setBackgroundPaint(Color.white);
390                legend.setPosition(RectangleEdge.BOTTOM);
391                this.subtitles.add(legend);
392            }
393    
394            // add the chart title, if one has been specified...
395            if (title != null) {
396                if (titleFont == null) {
397                    titleFont = DEFAULT_TITLE_FONT;
398                }
399                this.title = new TextTitle(title, titleFont);
400                this.title.addChangeListener(this);
401            }
402    
403            this.backgroundPaint = DEFAULT_BACKGROUND_PAINT;
404    
405            this.backgroundImage = DEFAULT_BACKGROUND_IMAGE;
406            this.backgroundImageAlignment = DEFAULT_BACKGROUND_IMAGE_ALIGNMENT;
407            this.backgroundImageAlpha = DEFAULT_BACKGROUND_IMAGE_ALPHA;
408    
409        }
410    
411        /**
412         * Returns the collection of rendering hints for the chart.
413         *
414         * @return The rendering hints for the chart (never <code>null</code>).
415         */
416        public RenderingHints getRenderingHints() {
417            return this.renderingHints;
418        }
419    
420        /**
421         * Sets the rendering hints for the chart.  These will be added (using the 
422         * Graphics2D.addRenderingHints() method) near the start of the 
423         * JFreeChart.draw() method.
424         *
425         * @param renderingHints  the rendering hints (<code>null</code> not 
426         *                        permitted).
427         */
428        public void setRenderingHints(RenderingHints renderingHints) {
429            if (renderingHints == null) {
430                throw new NullPointerException("RenderingHints given are null");
431            }
432            this.renderingHints = renderingHints;
433            fireChartChanged();
434        }
435    
436        /**
437         * Returns a flag that controls whether or not a border is drawn around the
438         * outside of the chart.
439         *
440         * @return A boolean.
441         */
442        public boolean isBorderVisible() {
443            return this.borderVisible;
444        }
445    
446        /**
447         * Sets a flag that controls whether or not a border is drawn around the 
448         * outside of the chart.
449         *
450         * @param visible  the flag.
451         */
452        public void setBorderVisible(boolean visible) {
453            this.borderVisible = visible;
454            fireChartChanged();
455        }
456    
457        /**
458         * Returns the stroke used to draw the chart border (if visible).
459         *
460         * @return The border stroke.
461         */
462        public Stroke getBorderStroke() {
463            return this.borderStroke;
464        }
465    
466        /**
467         * Sets the stroke used to draw the chart border (if visible).
468         *
469         * @param stroke  the stroke.
470         */
471        public void setBorderStroke(Stroke stroke) {
472            this.borderStroke = stroke;
473            fireChartChanged();
474        }
475    
476        /**
477         * Returns the paint used to draw the chart border (if visible).
478         *
479         * @return The border paint.
480         */
481        public Paint getBorderPaint() {
482            return this.borderPaint;
483        }
484    
485        /**
486         * Sets the paint used to draw the chart border (if visible).
487         *
488         * @param paint  the paint.
489         */
490        public void setBorderPaint(Paint paint) {
491            this.borderPaint = paint;
492            fireChartChanged();
493        }
494        
495        /**
496         * Returns the padding between the chart border and the chart drawing area.
497         * 
498         * @return The padding (never <code>null</code>).
499         */
500        public RectangleInsets getPadding() {
501            return this.padding;   
502        }
503    
504        /**
505         * Sets the padding between the chart border and the chart drawing area,
506         * and sends a {@link ChartChangeEvent} to all registered listeners.
507         * 
508         * @param padding  the padding (<code>null</code> not permitted).
509         */
510        public void setPadding(RectangleInsets padding) {
511            if (padding == null) {
512                throw new IllegalArgumentException("Null 'padding' argument.");   
513            }
514            this.padding = padding;
515            notifyListeners(new ChartChangeEvent(this));
516        }
517        
518        /**
519         * Returns the main chart title.  Very often a chart will have just one
520         * title, so we make this case simple by providing accessor methods for
521         * the main title.  However, multiple titles are supported - see the
522         * {@link #addSubtitle(Title)} method.
523         *
524         * @return The chart title (possibly <code>null</code>).
525         */
526        public TextTitle getTitle() {
527            return this.title;
528        }
529    
530        /**
531         * Sets the main title for the chart and sends a {@link ChartChangeEvent} 
532         * to all registered listeners.  If you do not want a title for the 
533         * chart, set it to <code>null</code>.  If you want more than one title on
534         * a chart, use the {@link #addSubtitle(Title)} method.
535         *
536         * @param title  the title (<code>null</code> permitted).
537         */
538        public void setTitle(TextTitle title) {
539            this.title = title;
540            fireChartChanged();
541        }
542    
543        /**
544         * Sets the chart title and sends a {@link ChartChangeEvent} to all 
545         * registered listeners.  This is a convenience method that ends up calling 
546         * the {@link #setTitle(TextTitle)} method.  If there is an existing title,
547         * its text is updated, otherwise a new title using the default font is 
548         * added to the chart.  If <code>text</code> is <code>null</code> the chart
549         * title is set to <code>null</code>.
550         *
551         * @param text  the title text (<code>null</code> permitted).
552         */
553        public void setTitle(String text) {
554            if (text != null) {
555                if (this.title == null) {
556                    setTitle(new TextTitle(text, JFreeChart.DEFAULT_TITLE_FONT));
557                }
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</code> not permitted).
572         */
573        public void addLegend(LegendTitle legend) {
574            addSubtitle(legend);    
575        }
576        
577        /**
578         * Returns the legend for the chart, if there is one.  Note that a chart
579         * can have more than one legend - this method returns the first.
580         * 
581         * @return The legend (possibly <code>null</code>).
582         */
583        public LegendTitle getLegend() {
584            return getLegend(0);
585        }
586        
587        /**
588         * Returns the nth legend for a chart, or <code>null</code>.
589         * 
590         * @param index  the legend index (zero-based).
591         * 
592         * @return The legend (possibly <code>null</code>).
593         */
594        public LegendTitle getLegend(int index) {
595            int seen = 0;
596            Iterator iterator = this.subtitles.iterator();
597            while (iterator.hasNext()) {
598                Title subtitle = (Title) iterator.next();
599                if (subtitle instanceof LegendTitle) {
600                    if (seen == index) {
601                        return (LegendTitle) subtitle;
602                    }
603                    else {
604                        seen++;   
605                    }
606                }
607            }
608            return null;        
609        }
610        
611        /**
612         * Removes the first legend in the chart and sends a 
613         * {@link ChartChangeEvent} to all registered listeners.
614         */
615        public void removeLegend() {
616            removeSubtitle(getLegend());
617        }
618        
619        /**
620         * Returns the list of subtitles for the chart.
621         *
622         * @return The subtitle list (possibly empty, but never <code>null</code>).
623         */
624        public List getSubtitles() {
625            return this.subtitles;
626        }
627    
628        /**
629         * Sets the title list for the chart (completely replaces any existing 
630         * titles).
631         *
632         * @param subtitles  the new list of subtitles (<code>null</code> not 
633         *                   permitted).
634         */
635        public void setSubtitles(List subtitles) {
636            if (subtitles == null) {
637                throw new NullPointerException("Null 'subtitles' argument.");
638            }
639            this.subtitles = subtitles;
640            fireChartChanged();
641        }
642    
643        /**
644         * Returns the number of titles for the chart.
645         *
646         * @return The number of titles for the chart.
647         */
648        public int getSubtitleCount() {
649            return this.subtitles.size();
650        }
651    
652        /**
653         * Returns a chart subtitle.
654         *
655         * @param index  the index of the chart subtitle (zero based).
656         *
657         * @return A chart subtitle.
658         */
659        public Title getSubtitle(int index) {
660            if ((index < 0) || (index == getSubtitleCount())) {
661                throw new IllegalArgumentException("Index out of range.");
662            }
663            return (Title) this.subtitles.get(index);
664        }
665    
666        /**
667         * Adds a chart subtitle, and notifies registered listeners that the chart 
668         * has been modified.
669         *
670         * @param subtitle  the subtitle (<code>null</code> not permitted).
671         */
672        public void addSubtitle(Title subtitle) {
673            if (subtitle == null) {
674                throw new IllegalArgumentException("Null 'subtitle' argument.");
675            }
676            this.subtitles.add(subtitle);
677            subtitle.addChangeListener(this);
678            fireChartChanged();
679        }
680        
681        /**
682         * Clears all subtitles from the chart and sends a {@link ChartChangeEvent}
683         * to all registered listeners.
684         */
685        public void clearSubtitles() {
686            Iterator iterator = this.subtitles.iterator();
687            while (iterator.hasNext()) {
688                Title t = (Title) iterator.next();
689                t.removeChangeListener(this);
690            }
691            this.subtitles.clear();
692            fireChartChanged();
693        }
694    
695        /**
696         * Removes the specified subtitle and sends a {@link ChartChangeEvent} to
697         * all registered listeners.
698         * 
699         * @param title  the title.
700         */
701        public void removeSubtitle(Title title) {
702            this.subtitles.remove(title);
703            fireChartChanged();
704        }
705        
706        /**
707         * Returns the plot for the chart.  The plot is a class responsible for
708         * coordinating the visual representation of the data, including the axes
709         * (if any).
710         *
711         * @return The plot.
712         */
713        public Plot getPlot() {
714            return this.plot;
715        }
716    
717        /**
718         * Returns the plot cast as a {@link CategoryPlot}.
719         * <p>
720         * NOTE: if the plot is not an instance of {@link CategoryPlot}, then a
721         * <code>ClassCastException</code> is thrown.
722         *
723         * @return The plot.
724         */
725        public CategoryPlot getCategoryPlot() {
726            return (CategoryPlot) this.plot;
727        }
728    
729        /**
730         * Returns the plot cast as an {@link XYPlot}.
731         * <p>
732         * NOTE: if the plot is not an instance of {@link XYPlot}, then a
733         * <code>ClassCastException</code> is thrown.
734         *
735         * @return The plot.
736         */
737        public XYPlot getXYPlot() {
738            return (XYPlot) this.plot;
739        }
740    
741        /**
742         * Returns a flag that indicates whether or not anti-aliasing is used when
743         * the chart is drawn.
744         *
745         * @return The flag.
746         */
747        public boolean getAntiAlias() {
748            Object o = this.renderingHints.get(RenderingHints.KEY_ANTIALIASING);
749            if (o == null) {
750                return false;
751            }
752            return (o.equals(RenderingHints.VALUE_ANTIALIAS_ON));
753        }
754    
755        /**
756         * Sets a flag that indicates whether or not anti-aliasing is used when the
757         * chart is drawn.
758         * <P>
759         * Anti-aliasing usually improves the appearance of charts, but is slower.
760         *
761         * @param flag  the new value of the flag.
762         */
763        public void setAntiAlias(boolean flag) {
764    
765            Object o = this.renderingHints.get(RenderingHints.KEY_ANTIALIASING);
766            if (o == null) {
767                o = RenderingHints.VALUE_ANTIALIAS_DEFAULT;
768            }
769            if (!flag && RenderingHints.VALUE_ANTIALIAS_OFF.equals(o) 
770                || flag && RenderingHints.VALUE_ANTIALIAS_ON.equals(o)) {
771                // no change, do nothing
772                return;
773            }
774            if (flag) {
775                this.renderingHints.put(RenderingHints.KEY_ANTIALIASING, 
776                                        RenderingHints.VALUE_ANTIALIAS_ON);
777            }
778            else {
779                this.renderingHints.put(RenderingHints.KEY_ANTIALIASING, 
780                                        RenderingHints.VALUE_ANTIALIAS_OFF);
781            }
782            fireChartChanged();
783    
784        }
785    
786        /**
787         * Returns the paint used for the chart background.
788         *
789         * @return The paint (possibly <code>null</code>).
790         */
791        public Paint getBackgroundPaint() {
792            return this.backgroundPaint;
793        }
794    
795        /**
796         * Sets the paint used to fill the chart background and sends a 
797         * {@link ChartChangeEvent} to all registered listeners.
798         *
799         * @param paint  the paint (<code>null</code> permitted).
800         */
801        public void setBackgroundPaint(Paint paint) {
802    
803            if (this.backgroundPaint != null) {
804                if (!this.backgroundPaint.equals(paint)) {
805                    this.backgroundPaint = paint;
806                    fireChartChanged();
807                }
808            }
809            else {
810                if (paint != null) {
811                    this.backgroundPaint = paint;
812                    fireChartChanged();
813                }
814            }
815    
816        }
817    
818        /**
819         * Returns the background image for the chart, or <code>null</code> if 
820         * there is no image.
821         *
822         * @return The image (possibly <code>null</code>).
823         */
824        public Image getBackgroundImage() {
825            return this.backgroundImage;
826        }
827    
828        /**
829         * Sets the background image for the chart and sends a 
830         * {@link ChartChangeEvent} to all registered listeners.
831         *
832         * @param image  the image (<code>null</code> permitted).
833         */
834        public void setBackgroundImage(Image image) {
835    
836            if (this.backgroundImage != null) {
837                if (!this.backgroundImage.equals(image)) {
838                    this.backgroundImage = image;
839                    fireChartChanged();
840                }
841            }
842            else {
843                if (image != null) {
844                    this.backgroundImage = image;
845                    fireChartChanged();
846                }
847            }
848    
849        }
850    
851        /**
852         * Returns the background image alignment. Alignment constants are defined 
853         * in the <code>org.jfree.ui.Align</code> class in the JCommon class 
854         * library.
855         *
856         * @return The alignment.
857         */
858        public int getBackgroundImageAlignment() {
859            return this.backgroundImageAlignment;
860        }
861    
862        /**
863         * Sets the background alignment.  Alignment options are defined by the 
864         * {@link org.jfree.ui.Align} class.
865         *
866         * @param alignment  the alignment.
867         */
868        public void setBackgroundImageAlignment(int alignment) {
869            if (this.backgroundImageAlignment != alignment) {
870                this.backgroundImageAlignment = alignment;
871                fireChartChanged();
872            }
873        }
874    
875        /**
876         * Returns the alpha-transparency for the chart's background image.
877         *
878         * @return The alpha-transparency.
879         */
880        public float getBackgroundImageAlpha() {
881            return this.backgroundImageAlpha;
882        }
883    
884        /**
885         * Sets the alpha-transparency for the chart's background image.
886         * Registered listeners are notified that the chart has been changed.
887         *
888         * @param alpha  the alpha value.
889         */
890        public void setBackgroundImageAlpha(float alpha) {
891    
892            if (this.backgroundImageAlpha != alpha) {
893                this.backgroundImageAlpha = alpha;
894                fireChartChanged();
895            }
896    
897        }
898    
899        /**
900         * Returns a flag that controls whether or not change events are sent to 
901         * registered listeners.
902         *
903         * @return A boolean.
904         */
905        public boolean isNotify() {
906            return this.notify;
907        }
908    
909        /**
910         * Sets a flag that controls whether or not listeners receive 
911         * {@link ChartChangeEvent} notifications.
912         *
913         * @param notify  a boolean.
914         */
915        public void setNotify(boolean notify) {
916            this.notify = notify;
917            // if the flag is being set to true, there may be queued up changes...
918            if (notify) {
919                notifyListeners(new ChartChangeEvent(this));
920            }
921        }
922    
923        /**
924         * Draws the chart on a Java 2D graphics device (such as the screen or a
925         * printer).
926         * <P>
927         * This method is the focus of the entire JFreeChart library.
928         *
929         * @param g2  the graphics device.
930         * @param area  the area within which the chart should be drawn.
931         */
932        public void draw(Graphics2D g2, Rectangle2D area) {
933            draw(g2, area, null, null);
934        }
935    
936        /**
937         * Draws the chart on a Java 2D graphics device (such as the screen or a
938         * printer).  This method is the focus of the entire JFreeChart library.
939         *
940         * @param g2  the graphics device.
941         * @param area  the area within which the chart should be drawn.
942         * @param info  records info about the drawing (null means collect no info).
943         */
944        public void draw(Graphics2D g2, Rectangle2D area, ChartRenderingInfo info) {
945            draw(g2, area, null, info);
946        }
947        
948        /**
949         * Draws the chart on a Java 2D graphics device (such as the screen or a
950         * printer).
951         * <P>
952         * This method is the focus of the entire JFreeChart library.
953         *
954         * @param g2  the graphics device.
955         * @param chartArea  the area within which the chart should be drawn.
956         * @param anchor  the anchor point (in Java2D space) for the chart 
957         *                (<code>null</code> permitted).
958         * @param info  records info about the drawing (null means collect no info).
959         */
960        public void draw(Graphics2D g2, 
961                         Rectangle2D chartArea, Point2D anchor, 
962                         ChartRenderingInfo info) {
963    
964            notifyListeners(
965                new ChartProgressEvent(
966                    this, this, ChartProgressEvent.DRAWING_STARTED, 0
967                )
968            );
969    
970            // record the chart area, if info is requested...
971            if (info != null) {
972                info.clear();
973                info.setChartArea(chartArea);
974            }
975    
976            // ensure no drawing occurs outside chart area...
977            Shape savedClip = g2.getClip();
978            g2.clip(chartArea);
979    
980            g2.addRenderingHints(this.renderingHints);
981    
982            // draw the chart background...
983            if (this.backgroundPaint != null) {
984                g2.setPaint(this.backgroundPaint);
985                g2.fill(chartArea);
986            }
987    
988            if (this.backgroundImage != null) {
989                Composite originalComposite = g2.getComposite();
990                g2.setComposite(
991                    AlphaComposite.getInstance(
992                        AlphaComposite.SRC_OVER, 
993                        this.backgroundImageAlpha
994                    )
995                );
996                Rectangle2D dest = new Rectangle2D.Double(
997                    0.0, 0.0, this.backgroundImage.getWidth(null), 
998                    this.backgroundImage.getHeight(null)
999                );
1000                Align.align(dest, chartArea, this.backgroundImageAlignment);
1001                g2.drawImage(
1002                    this.backgroundImage, (int) dest.getX(), (int) dest.getY(),
1003                    (int) dest.getWidth(), (int) dest.getHeight(), null
1004                );
1005                g2.setComposite(originalComposite);
1006            }
1007    
1008            if (isBorderVisible()) {
1009                Paint paint = getBorderPaint();
1010                Stroke stroke = getBorderStroke();
1011                if (paint != null && stroke != null) {
1012                    Rectangle2D borderArea = new Rectangle2D.Double(
1013                        chartArea.getX(), chartArea.getY(),
1014                        chartArea.getWidth() - 1.0, chartArea.getHeight() - 1.0
1015                    );
1016                    g2.setPaint(paint);
1017                    g2.setStroke(stroke);
1018                    g2.draw(borderArea);
1019                }
1020            }
1021    
1022            // draw the title and subtitles...
1023            Rectangle2D nonTitleArea = new Rectangle2D.Double();
1024            nonTitleArea.setRect(chartArea);
1025            this.padding.trim(nonTitleArea);
1026            
1027            EntityCollection entities = null;
1028            if (info != null) {
1029                entities = info.getEntityCollection();   
1030            }
1031            if (this.title != null) {
1032                EntityCollection e = drawTitle(
1033                    this.title, g2, nonTitleArea, (entities != null)
1034                );
1035                if (e != null) {
1036                    entities.addAll(e);   
1037                }
1038            }
1039    
1040            Iterator iterator = this.subtitles.iterator();
1041            while (iterator.hasNext()) {
1042                Title currentTitle = (Title) iterator.next();
1043                EntityCollection e = drawTitle(
1044                    currentTitle, g2, nonTitleArea, (entities != null)
1045                );
1046                if (e != null) {
1047                    entities.addAll(e);   
1048                }
1049            }
1050    
1051            Rectangle2D plotArea = nonTitleArea;
1052     
1053            // draw the plot (axes and data visualisation)
1054            PlotRenderingInfo plotInfo = null;
1055            if (info != null) {
1056                plotInfo = info.getPlotInfo();
1057            }
1058            this.plot.draw(g2, plotArea, anchor, null, plotInfo);
1059    
1060            g2.setClip(savedClip);
1061    
1062            notifyListeners(
1063                new ChartProgressEvent(
1064                    this, this, ChartProgressEvent.DRAWING_FINISHED, 100
1065                )
1066            );
1067        }
1068    
1069        /**
1070         * Creates a rectangle that is aligned to the frame.
1071         * 
1072         * @param dimensions
1073         * @param frame
1074         * @param hAlign
1075         * @param vAlign
1076         * 
1077         * @return A rectangle.
1078         */
1079        private Rectangle2D createAlignedRectangle2D(Size2D dimensions, 
1080                Rectangle2D frame, HorizontalAlignment hAlign, 
1081                VerticalAlignment vAlign) {
1082            double x = Double.NaN;
1083            double y = Double.NaN;
1084            if (hAlign == HorizontalAlignment.LEFT) {
1085                x = frame.getX();   
1086            }
1087            else if (hAlign == HorizontalAlignment.CENTER) {
1088                x = frame.getCenterX() - (dimensions.width / 2.0);   
1089            }
1090            else if (hAlign == HorizontalAlignment.RIGHT) {
1091                x = frame.getMaxX() - dimensions.width;   
1092            }
1093            if (vAlign == VerticalAlignment.TOP) {
1094                y = frame.getY();   
1095            }
1096            else if (vAlign == VerticalAlignment.CENTER) {
1097                y = frame.getCenterY() - (dimensions.height / 2.0);   
1098            }
1099            else if (vAlign == VerticalAlignment.BOTTOM) {
1100                y = frame.getMaxY() - dimensions.height;   
1101            }
1102            
1103            return new Rectangle2D.Double(
1104                x, y, dimensions.width, dimensions.height
1105            );
1106        }
1107        
1108        /**
1109         * Draws a title.  The title should be drawn at the top, bottom, left or 
1110         * right of the specified area, and the area should be updated to reflect 
1111         * the amount of space used by the title.
1112         *
1113         * @param t  the title (<code>null</code> not permitted).
1114         * @param g2  the graphics device (<code>null</code> not permitted).
1115         * @param area  the chart area, excluding any existing titles 
1116         *              (<code>null</code> not permitted).
1117         * @param entities  a flag that controls whether or not an entity 
1118         *                  collection is returned for the title.
1119         * 
1120         * @return An entity collection for the title (possibly <code>null</code>).
1121         */
1122        protected EntityCollection drawTitle(Title t, Graphics2D g2, 
1123                                             Rectangle2D area, boolean entities) {
1124    
1125            if (t == null) {
1126                throw new IllegalArgumentException("Null 't' argument.");   
1127            }
1128            if (area == null) {
1129                throw new IllegalArgumentException("Null 'area' argument.");   
1130            }
1131            Rectangle2D titleArea = new Rectangle2D.Double();
1132            RectangleEdge position = t.getPosition();
1133            double ww = area.getWidth();
1134            if (ww <= 0.0) {
1135                return null;
1136            }
1137            double hh = area.getHeight();
1138            if (hh <= 0.0) {
1139                return null;
1140            }
1141            RectangleConstraint constraint = new RectangleConstraint(
1142                ww, new Range(0.0, ww), LengthConstraintType.RANGE,
1143                hh, new Range(0.0, hh), LengthConstraintType.RANGE
1144            );
1145            Object retValue = null;
1146            BlockParams p = new BlockParams();
1147            p.setGenerateEntities(entities);
1148            if (position == RectangleEdge.TOP) {
1149                Size2D size = t.arrange(g2, constraint);
1150                titleArea = createAlignedRectangle2D(
1151                    size, area, t.getHorizontalAlignment(), VerticalAlignment.TOP
1152                );
1153                retValue = t.draw(g2, titleArea, p);
1154                area.setRect(
1155                    area.getX(), 
1156                    Math.min(area.getY() + size.height, area.getMaxY()),
1157                    area.getWidth(), Math.max(area.getHeight() - size.height, 0)
1158                );
1159            }
1160            else if (position == RectangleEdge.BOTTOM) {
1161                Size2D size = t.arrange(g2, constraint);
1162                titleArea = createAlignedRectangle2D(
1163                    size, area, t.getHorizontalAlignment(), VerticalAlignment.BOTTOM
1164                );
1165                retValue = t.draw(g2, titleArea, p);
1166                area.setRect(
1167                    area.getX(), area.getY(), 
1168                    area.getWidth(), area.getHeight() - size.height
1169                );
1170            }
1171            else if (position == RectangleEdge.RIGHT) {
1172                Size2D size = t.arrange(g2, constraint);
1173                titleArea = createAlignedRectangle2D(
1174                    size, area, HorizontalAlignment.RIGHT, t.getVerticalAlignment()
1175                );
1176                retValue = t.draw(g2, titleArea, p);
1177                area.setRect(
1178                    area.getX(), area.getY(), 
1179                    area.getWidth() - size.width, area.getHeight()
1180                );
1181            }
1182    
1183            else if (position == RectangleEdge.LEFT) {
1184                Size2D size = t.arrange(g2, constraint);
1185                titleArea = createAlignedRectangle2D(
1186                    size, area, HorizontalAlignment.LEFT, t.getVerticalAlignment()
1187                );
1188                retValue = t.draw(g2, titleArea, p);
1189                area.setRect(
1190                    area.getX() + size.width, area.getY(),
1191                    area.getWidth() - size.width, area.getHeight()
1192                );
1193            }
1194            else {
1195                throw new RuntimeException("Unrecognised title position.");
1196            }
1197            EntityCollection result = null;
1198            if (retValue instanceof EntityBlockResult) {
1199                EntityBlockResult ebr = (EntityBlockResult) retValue;
1200                result = ebr.getEntityCollection();
1201            }
1202            return result;   
1203        }
1204    
1205        /**
1206         * Creates and returns a buffered image into which the chart has been drawn.
1207         *
1208         * @param width  the width.
1209         * @param height  the height.
1210         *
1211         * @return A buffered image.
1212         */
1213        public BufferedImage createBufferedImage(int width, int height) {
1214            return createBufferedImage(width, height, null);
1215        }
1216    
1217        /**
1218         * Creates and returns a buffered image into which the chart has been drawn.
1219         *
1220         * @param width  the width.
1221         * @param height  the height.
1222         * @param info  carries back chart state information (<code>null</code> 
1223         *              permitted).
1224         *
1225         * @return A buffered image.
1226         */
1227        public BufferedImage createBufferedImage(int width, int height, 
1228                                                 ChartRenderingInfo info) {
1229            return createBufferedImage(
1230                width, height, BufferedImage.TYPE_INT_RGB, info
1231            );
1232        }
1233    
1234        /**
1235         * Creates and returns a buffered image into which the chart has been drawn.
1236         *
1237         * @param width  the width.
1238         * @param height  the height.
1239         * @param imageType  the image type.
1240         * @param info  carries back chart state information (<code>null</code> 
1241         *              permitted).
1242         *
1243         * @return A buffered image.
1244         */
1245        public BufferedImage createBufferedImage(int width, int height, 
1246                                                 int imageType, 
1247                                                 ChartRenderingInfo info) {
1248            BufferedImage image = new BufferedImage(width, height, imageType);
1249            Graphics2D g2 = image.createGraphics();
1250            draw(g2, new Rectangle2D.Double(0, 0, width, height), null, info);
1251            g2.dispose();
1252            return image;
1253        }
1254    
1255        /**
1256         * Creates and returns a buffered image into which the chart has been drawn.
1257         *
1258         * @param imageWidth  the image width.
1259         * @param imageHeight  the image height.
1260         * @param drawWidth  the width for drawing the chart (will be scaled to 
1261         *                   fit image).
1262         * @param drawHeight  the height for drawing the chart (will be scaled to 
1263         *                    fit image).
1264         * @param info  optional object for collection chart dimension and entity 
1265         *              information.
1266         *
1267         * @return A buffered image.
1268         */
1269        public BufferedImage createBufferedImage(int imageWidth, 
1270                                                 int imageHeight,
1271                                                 double drawWidth, 
1272                                                 double drawHeight, 
1273                                                 ChartRenderingInfo info) {
1274    
1275            BufferedImage image = new BufferedImage(
1276                imageWidth, imageHeight, BufferedImage.TYPE_INT_RGB
1277            );
1278            Graphics2D g2 = image.createGraphics();
1279            double scaleX = imageWidth / drawWidth;
1280            double scaleY = imageHeight / drawHeight;
1281            AffineTransform st = AffineTransform.getScaleInstance(scaleX, scaleY);
1282            g2.transform(st);
1283            draw(
1284                g2, new Rectangle2D.Double(0, 0, drawWidth, drawHeight), null, info
1285            );
1286            g2.dispose();
1287            return image;
1288    
1289        }
1290    
1291        /**
1292         * Handles a 'click' on the chart.
1293         * <P>
1294         * JFreeChart is not a UI component, so some other object (e.g. ChartPanel)
1295         * needs to capture the click event and pass it onto the JFreeChart object.
1296         * If you are not using JFreeChart in a client application, then this
1297         * method is not required (and hopefully it doesn't get in the way).
1298         *
1299         * @param x  x-coordinate of the click (in Java2D space).
1300         * @param y  y-coordinate of the click (in Java2D space).
1301         * @param info  contains chart dimension and entity information.
1302         */
1303        public void handleClick(int x, int y, ChartRenderingInfo info) {
1304    
1305            // pass the click on to the plot...
1306            // rely on the plot to post a plot change event and redraw the chart...
1307            this.plot.handleClick(x, y, info.getPlotInfo());
1308    
1309        }
1310    
1311        /**
1312         * Registers an object for notification of changes to the chart.
1313         *
1314         * @param listener  the listener (<code>null</code> not permitted).
1315         */
1316        public void addChangeListener(ChartChangeListener listener) {
1317            if (listener == null) {
1318                throw new IllegalArgumentException("Null 'listener' argument.");
1319            }
1320            this.changeListeners.add(ChartChangeListener.class, listener);
1321        }
1322    
1323        /**
1324         * Deregisters an object for notification of changes to the chart.
1325         *
1326         * @param listener  the listener (<code>null</code> not permitted)
1327         */
1328        public void removeChangeListener(ChartChangeListener listener) {
1329            if (listener == null) {
1330                throw new IllegalArgumentException("Null 'listener' argument.");
1331            }
1332            this.changeListeners.remove(ChartChangeListener.class, listener);
1333        }
1334    
1335        /**
1336         * Sends a default {@link ChartChangeEvent} to all registered listeners.
1337         * <P>
1338         * This method is for convenience only.
1339         */
1340        public void fireChartChanged() {
1341            ChartChangeEvent event = new ChartChangeEvent(this);
1342            notifyListeners(event);
1343        }
1344    
1345        /**
1346         * Sends a {@link ChartChangeEvent} to all registered listeners.
1347         *
1348         * @param event  information about the event that triggered the 
1349         *               notification.
1350         */
1351        protected void notifyListeners(ChartChangeEvent event) {
1352            if (this.notify) {
1353                Object[] listeners = this.changeListeners.getListenerList();
1354                for (int i = listeners.length - 2; i >= 0; i -= 2) {
1355                    if (listeners[i] == ChartChangeListener.class) {
1356                        ((ChartChangeListener) listeners[i + 1]).chartChanged(
1357                            event
1358                        );
1359                    }
1360                }
1361            }
1362        }
1363    
1364        /**
1365         * Registers an object for notification of progress events relating to the 
1366         * chart.
1367         *
1368         * @param listener  the object being registered.
1369         */
1370        public void addProgressListener(ChartProgressListener listener) {
1371            this.progressListeners.add(ChartProgressListener.class, listener);
1372        }
1373    
1374        /**
1375         * Deregisters an object for notification of changes to the chart.
1376         *
1377         * @param listener  the object being deregistered.
1378         */
1379        public void removeProgressListener(ChartProgressListener listener) {
1380            this.progressListeners.remove(ChartProgressListener.class, listener);
1381        }
1382    
1383        /**
1384         * Sends a {@link ChartProgressEvent} to all registered listeners.
1385         *
1386         * @param event  information about the event that triggered the 
1387         *               notification.
1388         */
1389        protected void notifyListeners(ChartProgressEvent event) {
1390    
1391            Object[] listeners = this.progressListeners.getListenerList();
1392            for (int i = listeners.length - 2; i >= 0; i -= 2) {
1393                if (listeners[i] == ChartProgressListener.class) {
1394                    ((ChartProgressListener) listeners[i + 1]).chartProgress(event);
1395                }
1396            }
1397    
1398        }
1399    
1400        /**
1401         * Receives notification that a chart title has changed, and passes this
1402         * on to registered listeners.
1403         *
1404         * @param event  information about the chart title change.
1405         */
1406        public void titleChanged(TitleChangeEvent event) {
1407            event.setChart(this);
1408            notifyListeners(event);
1409        }
1410    
1411        /**
1412         * Receives notification that the plot has changed, and passes this on to
1413         * registered listeners.
1414         *
1415         * @param event  information about the plot change.
1416         */
1417        public void plotChanged(PlotChangeEvent event) {
1418            event.setChart(this);
1419            notifyListeners(event);
1420        }
1421    
1422        /**
1423         * Tests this chart for equality with another object.
1424         *
1425         * @param obj  the object (<code>null</code> permitted).
1426         *
1427         * @return A boolean.
1428         */
1429        public boolean equals(Object obj) {
1430            if (obj == this) {
1431                return true;
1432            }
1433            if (!(obj instanceof JFreeChart)) {
1434                return false;
1435            }
1436            JFreeChart that = (JFreeChart) obj;
1437            if (!this.renderingHints.equals(that.renderingHints)) {
1438                return false;   
1439            }
1440            if (this.borderVisible != that.borderVisible) {
1441                return false;   
1442            }
1443            if (!ObjectUtilities.equal(this.borderStroke, that.borderStroke)) {
1444                return false;   
1445            }
1446            if (!PaintUtilities.equal(this.borderPaint, that.borderPaint)) {
1447                return false;   
1448            }
1449            if (!this.padding.equals(that.padding)) {
1450                return false;   
1451            }
1452            if (!ObjectUtilities.equal(this.title, that.title)) {
1453                return false;
1454            }
1455            if (!ObjectUtilities.equal(this.subtitles, that.subtitles)) {
1456                return false;
1457            }
1458            if (!ObjectUtilities.equal(this.plot, that.plot)) {
1459                return false;
1460            }
1461            if (!PaintUtilities.equal(
1462                this.backgroundPaint, that.backgroundPaint
1463            )) {
1464                return false;
1465            }
1466            if (!ObjectUtilities.equal(
1467                this.backgroundImage, that.backgroundImage
1468            )) {
1469                return false;
1470            }
1471            if (this.backgroundImageAlignment != that.backgroundImageAlignment) {
1472                return false;
1473            }
1474            if (this.backgroundImageAlpha != that.backgroundImageAlpha) {
1475                return false;
1476            }
1477            if (this.notify != that.notify) {
1478                return false;
1479            }
1480            return true;
1481        }
1482    
1483        /**
1484         * Provides serialization support.
1485         *
1486         * @param stream  the output stream.
1487         *
1488         * @throws IOException  if there is an I/O error.
1489         */
1490        private void writeObject(ObjectOutputStream stream) throws IOException {
1491            stream.defaultWriteObject();
1492            SerialUtilities.writeStroke(this.borderStroke, stream);
1493            SerialUtilities.writePaint(this.borderPaint, stream);
1494            SerialUtilities.writePaint(this.backgroundPaint, stream);
1495        }
1496    
1497        /**
1498         * Provides serialization support.
1499         *
1500         * @param stream  the input stream.
1501         *
1502         * @throws IOException  if there is an I/O error.
1503         * @throws ClassNotFoundException  if there is a classpath problem.
1504         */
1505        private void readObject(ObjectInputStream stream) 
1506            throws IOException, ClassNotFoundException {
1507            stream.defaultReadObject();
1508            this.borderStroke = SerialUtilities.readStroke(stream);
1509            this.borderPaint = SerialUtilities.readPaint(stream);
1510            this.backgroundPaint = SerialUtilities.readPaint(stream);
1511            this.progressListeners = new EventListenerList();
1512            this.changeListeners = new EventListenerList();
1513            this.renderingHints = new RenderingHints(
1514                RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON
1515            );
1516    
1517            // register as a listener with sub-components...
1518            if (this.title != null) {
1519                this.title.addChangeListener(this);
1520            }
1521    
1522            for (int i = 0; i < getSubtitleCount(); i++) {
1523                getSubtitle(i).addChangeListener(this);
1524            }
1525            this.plot.addChangeListener(this);
1526        }
1527    
1528        /**
1529         * Prints information about JFreeChart to standard output.
1530         *
1531         * @param args  no arguments are honored.
1532         */
1533        public static void main(String[] args) {
1534            System.out.println(JFreeChart.INFO.toString());
1535        }
1536    
1537        /**
1538         * Clones the object, and takes care of listeners.
1539         * Note: caller shall register its own listeners on cloned graph.
1540         * 
1541         * @return A clone.
1542         * 
1543         * @throws CloneNotSupportedException if the chart is not cloneable.
1544         */
1545        public Object clone() throws CloneNotSupportedException {
1546            JFreeChart chart = (JFreeChart) super.clone();
1547    
1548            chart.renderingHints = (RenderingHints) this.renderingHints.clone();
1549            // private boolean borderVisible;
1550            // private transient Stroke borderStroke;
1551            // private transient Paint borderPaint;
1552    
1553            if (this.title != null) {
1554                chart.title = (TextTitle) this.title.clone();
1555                chart.title.addChangeListener(chart);
1556            }
1557    
1558            chart.subtitles = new ArrayList();
1559            for (int i = 0; i < getSubtitleCount(); i++) {
1560                Title subtitle = (Title) getSubtitle(i).clone();
1561                chart.subtitles.add(subtitle);
1562                subtitle.addChangeListener(chart);
1563            }
1564    
1565            if (this.plot != null) {
1566                chart.plot = (Plot) this.plot.clone();
1567                chart.plot.addChangeListener(chart);
1568            }
1569    
1570            chart.progressListeners = new EventListenerList();
1571            chart.changeListeners = new EventListenerList();
1572            return chart;
1573        }
1574    
1575    }
1576    
1577    /**
1578     * Information about the JFreeChart project.  One instance of this class is 
1579     * assigned to <code>JFreeChart.INFO<code>.
1580     */
1581    class JFreeChartInfo extends ProjectInfo {
1582    
1583        /** 
1584         * Default constructor. 
1585         */
1586        public JFreeChartInfo() {
1587    
1588            // get a locale-specific resource bundle...
1589            String baseResourceClass 
1590                = "org.jfree.chart.resources.JFreeChartResources";
1591            ResourceBundle resources = ResourceBundle.getBundle(baseResourceClass);
1592    
1593            setName(resources.getString("project.name"));
1594            setVersion(resources.getString("project.version"));
1595            setInfo(resources.getString("project.info"));
1596            setCopyright(resources.getString("project.copyright"));
1597            setLogo(null);  // load only when required
1598            setLicenceName("LGPL");
1599            setLicenceText(Licences.getInstance().getLGPL());
1600    
1601            setContributors(Arrays.asList(
1602                new Contributor[]{
1603                    new Contributor("Eric Alexander", "-"),
1604                    new Contributor(
1605                        "Richard Atkinson", "richard_c_atkinson@ntlworld.com"
1606                    ),
1607                    new Contributor("David Basten", "-"),
1608                    new Contributor("David Berry", "-"),
1609                    new Contributor("Anthony Boulestreau", "-"),
1610                    new Contributor("Jeremy Bowman", "-"),
1611                    new Contributor("Nicolas Brodu", "-"),
1612                    new Contributor("David Browning", "-"),
1613                    new Contributor("S???ren Caspersen", "-"),
1614                    new Contributor("Chuanhao Chiu", "-"),
1615                    new Contributor("Brian Cole", "-"),
1616                    new Contributor("Pascal Collet", "-"),
1617                    new Contributor("Martin Cordova", "-"),
1618                    new Contributor("Paolo Cova", "-"),
1619                    new Contributor("Mike Duffy", "-"),
1620                    new Contributor("Don Elliott", "-"),
1621                    new Contributor("Jonathan Gabbai", "-"),
1622                    new Contributor(
1623                        "David Gilbert", "david.gilbert@object-refinery.com"
1624                    ),
1625                    new Contributor("Serge V. Grachov", "-"),
1626                    new Contributor("Daniel Gredler", "-"),
1627                    new Contributor("Hans-Jurgen Greiner", "-"),
1628                    new Contributor("Joao Guilherme Del Valle", "-"),
1629                    new Contributor("Aiman Han", "-"),
1630                    new Contributor("Cameron Hayne", "-"),
1631                    new Contributor("Jon Iles", "-"),
1632                    new Contributor("Wolfgang Irler", "-"),
1633                    new Contributor("Xun Kang", "-"),
1634                    new Contributor("Bill Kelemen", "-"),
1635                    new Contributor("Norbert Kiesel", "-"),
1636                    new Contributor("Gideon Krause", "-"),
1637                    new Contributor("Pierre-Marie Le Biot", "-"),
1638                    new Contributor("Arnaud Lelievre", "-"),
1639                    new Contributor("Wolfgang Lenhard", "-"),
1640                    new Contributor("David Li", "-"),
1641                    new Contributor("Yan Liu", "-"),
1642                    new Contributor("Tin Luu", "-"),
1643                    new Contributor("Craig MacFarlane", "-"),
1644                    new Contributor("Achilleus Mantzios", "-"),
1645                    new Contributor("Thomas Meier", "-"),
1646                    new Contributor("Jim Moore", "-"),
1647                    new Contributor("Jonathan Nash", "-"),
1648                    new Contributor("Barak Naveh", "-"),
1649                    new Contributor("David M. O'Donnell", "-"),
1650                    new Contributor("Krzysztof Paz", "-"),
1651                    new Contributor("Tomer Peretz", "-"),
1652                    new Contributor("Andrzej Porebski", "-"),
1653                    new Contributor("Xavier Poinsard", "-"),
1654                    new Contributor("Viktor Rajewski", "-"),
1655                    new Contributor("Eduardo Ramalho", "-"),
1656                    new Contributor("Michael Rauch", "-"),
1657                    new Contributor("Cameron Riley", "-"),
1658                    new Contributor("Dan Rivett", "d.rivett@ukonline.co.uk"),
1659                    new Contributor("Scott Sams", "-"),
1660                    new Contributor("Michel Santos", "-"),
1661                    new Contributor("Thierry Saura", "-"),
1662                    new Contributor("Andreas Schneider", "-"),
1663                    new Contributor("Jean-Luc SCHWAB", "-"),
1664                    new Contributor("Bryan Scott", "-"),
1665                    new Contributor("Tobias Selb", "-"),
1666                    new Contributor("Mofeed Shahin", "-"),
1667                    new Contributor("Pady Srinivasan", "-"),
1668                    new Contributor("Greg Steckman", "-"),
1669                    new Contributor("Roger Studner", "-"),
1670                    new Contributor("Irv Thomae", "-"),
1671                    new Contributor("Eric Thomas", "-"),
1672                    new Contributor("Rich Unger", "-"),
1673                    new Contributor("Daniel van Enckevort", "-"),
1674                    new Contributor("Laurence Vanhelsuwe", "-"),
1675                    new Contributor("Sylvain Vieujot", "-"),
1676                    new Contributor("Mark Watson", "www.markwatson.com"),
1677                    new Contributor("Alex Weber", "-"),
1678                    new Contributor("Matthew Wright", "-"),
1679                    new Contributor("Benoit Xhenseval", "-"),
1680                    new Contributor(
1681                        "Christian W. Zuckschwerdt", 
1682                        "Christian.Zuckschwerdt@Informatik.Uni-Oldenburg.de"
1683                    ),
1684                    new Contributor("Hari", "-"),
1685                    new Contributor("Sam (oldman)", "-"),
1686                }
1687            ));
1688    
1689            addLibrary(JCommon.INFO);
1690    
1691        }
1692    
1693        /**
1694         * Returns the JFreeChart logo (a picture of a gorilla).
1695         *
1696         * @return The JFreeChart logo.
1697         */
1698        public Image getLogo() {
1699    
1700            Image logo = super.getLogo();
1701            if (logo == null) {
1702                URL imageURL = ClassLoader.getSystemResource(
1703                    "org/jfree/chart/gorilla.jpg"
1704                );
1705                if (imageURL != null) {
1706                    ImageIcon temp = new ImageIcon(imageURL);  
1707                        // use ImageIcon because it waits for the image to load...
1708                    logo = temp.getImage();
1709                    setLogo(logo);
1710                }
1711            }
1712            return logo;
1713    
1714        }
1715    
1716    }