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