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