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