    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     * PiePlot.java
    029     * ------------
    030     * (C) Copyright 2000-2007, by Andrzej Porebski and Contributors.
    031     *
    032     * Original Author:  Andrzej Porebski;
    033     * Contributor(s):   David Gilbert (for Object Refinery Limited);
    034     *                   Martin Cordova (percentages in labels);
    035     *                   Richard Atkinson (URL support for image maps);
    036     *                   Christian W. Zuckschwerdt;
    037     *                   Arnaud Lelievre;
    038     *                   Andreas Schroeder (very minor);
    039     *
    040     * $Id: PiePlot.java,v 1.17.2.20 2007/01/16 10:17:09 mungady Exp $
    041     *
    042     * Changes (from 21-Jun-2001)
    043     * --------------------------
    044     * 21-Jun-2001 : Removed redundant JFreeChart parameter from constructors (DG);
    045     * 18-Sep-2001 : Updated header (DG);
    046     * 15-Oct-2001 : Data source classes moved to com.jrefinery.data.* (DG);
    047     * 19-Oct-2001 : Moved series paint and stroke methods from JFreeChart.java to
    048     *               Plot.java (DG);
    049     * 22-Oct-2001 : Renamed DataSource.java --> Dataset.java etc. (DG);
    050     * 13-Nov-2001 : Modified plot subclasses so that null axes are possible for
    051     *               pie plot (DG);
    052     * 17-Nov-2001 : Added PieDataset interface and amended this class accordingly,
    053     *               and completed removal of BlankAxis class as it is no longer
    054     *               required (DG);
    055     * 19-Nov-2001 : Changed 'drawCircle' property to 'circular' property (DG);
    056     * 21-Nov-2001 : Added options for exploding pie sections and filled out range
    057     *               of properties (DG);
    058     *               Added option for percentages in chart labels, based on code
    059     *               by Martin Cordova (DG);
    060     * 30-Nov-2001 : Changed default font from "Arial" --> "SansSerif" (DG);
    061     * 12-Dec-2001 : Removed unnecessary 'throws' clause in constructor (DG);
    062     * 13-Dec-2001 : Added tooltips (DG);
    063     * 16-Jan-2002 : Renamed tooltips class (DG);
    064     * 22-Jan-2002 : Fixed bug correlating legend labels with pie data (DG);
    065     * 05-Feb-2002 : Added alpha-transparency to plot class, and updated
    066     *               constructors accordingly (DG);
    067     * 06-Feb-2002 : Added optional background image and alpha-transparency to Plot
    068     *               and subclasses.  Clipped drawing within plot area (DG);
    069     * 26-Mar-2002 : Added an empty zoom method (DG);
    070     * 18-Apr-2002 : PieDataset is no longer sorted (oldman);
    071     * 23-Apr-2002 : Moved dataset from JFreeChart to Plot.  Added
    072     *               getLegendItemLabels() method (DG);
    073     * 19-Jun-2002 : Added attributes to control starting angle and direction
    074     *               (default is now clockwise) (DG);
    075     * 25-Jun-2002 : Removed redundant imports (DG);
    076     * 02-Jul-2002 : Fixed sign of percentage bug introduced in 0.9.2 (DG);
    077     * 16-Jul-2002 : Added check for null dataset in getLegendItemLabels() (DG);
    078     * 30-Jul-2002 : Moved summation code to DatasetUtilities (DG);
    079     * 05-Aug-2002 : Added URL support for image maps - new member variable for
    080     *               urlGenerator, modified constructor and minor change to the
    081     *               draw method (RA);
    082     * 18-Sep-2002 : Modified the percent label creation and added setters for the
    083     *               formatters (AS);
    084     * 24-Sep-2002 : Added getLegendItems() method (DG);
    085     * 02-Oct-2002 : Fixed errors reported by Checkstyle (DG);
    086     * 09-Oct-2002 : Added check for null entity collection (DG);
    087     * 30-Oct-2002 : Changed PieDataset interface (DG);
    088     * 18-Nov-2002 : Changed CategoryDataset to TableDataset (DG);
    089     * 02-Jan-2003 : Fixed "no data" message (DG);
    090     * 23-Jan-2003 : Modified to extract data from rows OR columns in
    091     *               CategoryDataset (DG);
    092     * 14-Feb-2003 : Fixed label drawing so that foreground alpha does not apply
    093     *               (bug id 685536) (DG);
    094     * 07-Mar-2003 : Modified to pass pieIndex on to PieSectionEntity and tooltip
    095     *               and URL generators (DG);
    096     * 21-Mar-2003 : Added a minimum angle for drawing arcs
    097     *               (see bug id 620031) (DG);
    098     * 24-Apr-2003 : Switched around PieDataset and KeyedValuesDataset (DG);
    099     * 02-Jun-2003 : Fixed bug 721733 (DG);
    100     * 30-Jul-2003 : Modified entity constructor (CZ);
    101     * 19-Aug-2003 : Implemented Cloneable (DG);
    102     * 29-Aug-2003 : Fixed bug 796936 (null pointer on setOutlinePaint()) (DG);
    103     * 08-Sep-2003 : Added internationalization via use of properties
    104     *               resourceBundle (RFE 690236) (AL);
    105     * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
    106     * 29-Oct-2003 : Added workaround for font alignment in PDF output (DG);
    107     * 05-Nov-2003 : Fixed missing legend bug (DG);
    108     * 10-Nov-2003 : Re-added the DatasetChangeListener to constructors (CZ);
    109     * 29-Jan-2004 : Fixed clipping bug in draw() method (DG);
    110     * 11-Mar-2004 : Major overhaul to improve labelling (DG);
    111     * 31-Mar-2004 : Made an adjustment for the plot area when the label generator
    112     *               is null.  Fixed null pointer exception when the label
    113     *               generator returns null for a label (DG);
    114     * 06-Apr-2004 : Added getter, setter, serialization and draw support for
    115     *               labelBackgroundPaint (AS);
    116     * 08-Apr-2004 : Added flag to control whether null values are ignored or
    117     *               not (DG);
    118     * 15-Apr-2004 : Fixed some minor warnings from Eclipse (DG);
    119     * 26-Apr-2004 : Added attributes for label outline and shadow (DG);
    120     * 04-Oct-2004 : Renamed ShapeUtils --> ShapeUtilities (DG);
    121     * 04-Nov-2004 : Fixed null pointer exception with new LegendTitle class (DG);
    122     * 09-Nov-2004 : Added user definable legend item shape (DG);
    123     * 25-Nov-2004 : Added new legend label generator (DG);
    124     * 20-Apr-2005 : Added a tool tip generator for legend labels (DG);
    125     * 26-Apr-2005 : Removed LOGGER (DG);
    126     * 05-May-2005 : Updated draw() method parameters (DG);
    127     * 10-May-2005 : Added flag to control visibility of label linking lines, plus
    128     *               another flag to control the handling of zero values (DG);
    129     * 08-Jun-2005 : Fixed bug in getLegendItems() method (not respecting flags
    130     *               for ignoring null and zero values), and fixed equals() method
    131     *               to handle GradientPaint (DG);
    132     * 15-Jul-2005 : Added sectionOutlinesVisible attribute (DG);
    133     * ------------- JFREECHART 1.0.x ---------------------------------------------
    134     * 09-Jan-2006 : Fixed bug 1400442, inconsistent treatment of null and zero
    135     *               values in dataset (DG);
    136     * 28-Feb-2006 : Fixed bug 1440415, bad distribution of pie section
    137     *               labels (DG);
    138     * 27-Sep-2006 : Initialised baseSectionPaint correctly, added lookup methods
    139     *               for section paint, outline paint and outline stroke (DG);
    140     * 27-Sep-2006 : Refactored paint and stroke methods to use keys rather than
    141     *               section indices (DG);
    142     * 03-Oct-2006 : Replaced call to JRE 1.5 method (DG);
    143     * 23-Nov-2006 : Added support for URLs for the legend items (DG);
    144     * 24-Nov-2006 : Cloning fixes (DG);
    145     *
    146     */
    147
    148    package org.jfree.chart.plot;
    149
    150    import java.awt.AlphaComposite;
    151    import java.awt.BasicStroke;
    152    import java.awt.Color;
    153    import java.awt.Composite;
    154    import java.awt.Font;
    155    import java.awt.Graphics2D;
    156    import java.awt.Paint;
    157    import java.awt.Shape;
    158    import java.awt.Stroke;
    159    import java.awt.geom.Arc2D;
    160    import java.awt.geom.Line2D;
    161    import java.awt.geom.Point2D;
    162    import java.awt.geom.Rectangle2D;
    163    import java.io.IOException;
    164    import java.io.ObjectInputStream;
    165    import java.io.ObjectOutputStream;
    166    import java.io.Serializable;
    167    import java.util.Iterator;
    168    import java.util.List;
    169    import java.util.Map;
    170    import java.util.ResourceBundle;
    171    import java.util.TreeMap;
    172
    173    import org.jfree.chart.LegendItem;
    174    import org.jfree.chart.LegendItemCollection;
    175    import org.jfree.chart.PaintMap;
    176    import org.jfree.chart.StrokeMap;
    177    import org.jfree.chart.entity.EntityCollection;
    178    import org.jfree.chart.entity.PieSectionEntity;
    179    import org.jfree.chart.event.PlotChangeEvent;
    180    import org.jfree.chart.labels.PieSectionLabelGenerator;
    181    import org.jfree.chart.labels.PieToolTipGenerator;
    182    import org.jfree.chart.labels.StandardPieSectionLabelGenerator;
    183    import org.jfree.chart.urls.PieURLGenerator;
    184    import org.jfree.data.DefaultKeyedValues;
    185    import org.jfree.data.KeyedValues;
    186    import org.jfree.data.general.DatasetChangeEvent;
    187    import org.jfree.data.general.DatasetUtilities;
    188    import org.jfree.data.general.PieDataset;
    189    import org.jfree.io.SerialUtilities;
    190    import org.jfree.text.G2TextMeasurer;
    191    import org.jfree.text.TextBlock;
    192    import org.jfree.text.TextBox;
    193    import org.jfree.text.TextUtilities;
    194    import org.jfree.ui.RectangleAnchor;
    195    import org.jfree.ui.RectangleInsets;
    196    import org.jfree.util.ObjectUtilities;
    197    import org.jfree.util.PaintUtilities;
    198    import org.jfree.util.PublicCloneable;
    199    import org.jfree.util.Rotation;
    200    import org.jfree.util.ShapeUtilities;
    201
    202    /**
    203     * A plot that displays data in the form of a pie chart, using data from any
    204     * class that implements the {@link PieDataset} interface.
    205     * <P>
    206     * Special notes:
    207     * <ol>
    208     * <li>the default starting point is 12 o'clock and the pie sections proceed
    209     * in a clockwise direction, but these settings can be changed;</li>
    210     * <li>negative values in the dataset are ignored;</li>
    211     * <li>there are utility methods for creating a {@link PieDataset} from a
    212     * {@link org.jfree.data.category.CategoryDataset};</li>
    213     * </ol>
    214     *
    215     * @see Plot
    216     * @see PieDataset
    217     */
    218    public class PiePlot extends Plot implements Cloneable, Serializable {
    219
    220        /** For serialization. */
    221        private static final long serialVersionUID = -795612466005590431L;
    222
    223        /** The default interior gap. */
    224        public static final double DEFAULT_INTERIOR_GAP = 0.25;
    225
    226        /** The maximum interior gap (currently 40%). */
    227        public static final double MAX_INTERIOR_GAP = 0.40;
    228
    229        /** The default starting angle for the pie chart. */
    230        public static final double DEFAULT_START_ANGLE = 90.0;
    231
    232        /** The default section label font. */
    233        public static final Font DEFAULT_LABEL_FONT
    234            = new Font("SansSerif", Font.PLAIN, 10);
    235
    236        /** The default section label paint. */
    237        public static final Paint DEFAULT_LABEL_PAINT = Color.black;
    238
    239        /** The default section label background paint. */
    240        public static final Paint DEFAULT_LABEL_BACKGROUND_PAINT
    241            = new Color(255, 255, 192);
    242
    243        /** The default section label outline paint. */
    244        public static final Paint DEFAULT_LABEL_OUTLINE_PAINT = Color.black;
    245
    246        /** The default section label outline stroke. */
    247        public static final Stroke DEFAULT_LABEL_OUTLINE_STROKE
    248            = new BasicStroke(0.5f);
    249
    250        /** The default section label shadow paint. */
    251        public static final Paint DEFAULT_LABEL_SHADOW_PAINT = Color.lightGray;
    252
    253        /** The default minimum arc angle to draw. */
    254        public static final double DEFAULT_MINIMUM_ARC_ANGLE_TO_DRAW = 0.00001;
    255
    256        /** The dataset for the pie chart. */
    257        private PieDataset dataset;
    258
    259        /** The pie index (used by the {@link MultiplePiePlot} class). */
    260        private int pieIndex;
    261
    262        /**
    263         * The amount of space left around the outside of the pie plot, expressed
    264         * as a percentage.
    265         */
    266        private double interiorGap;
    267
    268        /** Flag determining whether to draw an ellipse or a perfect circle. */
    269        private boolean circular;
    270
    271        /** The starting angle. */
    272        private double startAngle;
    273
    274        /** The direction for the pie segments. */
    275        private Rotation direction;
    276
    277        /** The paint for ALL sections (overrides list). */
    278        private transient Paint sectionPaint;
    279
    280        /** The section paint map. */
    281        private PaintMap sectionPaintMap;
    282
    283        /** The base section paint (fallback). */
    284        private transient Paint baseSectionPaint;
    285
    286        /**
    287         * A flag that controls whether or not an outline is drawn for each
    288         * section in the plot.
    289         */
    290        private boolean sectionOutlinesVisible;
    291
    292        /** The outline paint for ALL sections (overrides list). */
    293        private transient Paint sectionOutlinePaint;
    294
    295        /** The section outline paint map. */
    296        private PaintMap sectionOutlinePaintMap;
    297
    298        /** The base section outline paint (fallback). */
    299        private transient Paint baseSectionOutlinePaint;
    300
    301        /** The outline stroke for ALL sections (overrides list). */
    302        private transient Stroke sectionOutlineStroke;
    303
    304        /** The section outline stroke map. */
    305        private StrokeMap sectionOutlineStrokeMap;
    306
    307        /** The base section outline stroke (fallback). */
    308        private transient Stroke baseSectionOutlineStroke;
    309
    310        /** The shadow paint. */
    311        private transient Paint shadowPaint = Color.gray;
    312
    313        /** The x-offset for the shadow effect. */
    314        private double shadowXOffset = 4.0f;
    315
    316        /** The y-offset for the shadow effect. */
    317        private double shadowYOffset = 4.0f;
    318
    319        /** The percentage amount to explode each pie section. */
    320        private Map explodePercentages;
    321
    322        /** The section label generator. */
    323        private PieSectionLabelGenerator labelGenerator;
    324
    325        /** The font used to display the section labels. */
    326        private Font labelFont;
    327
    328        /** The color used to draw the section labels. */
    329        private transient Paint labelPaint;
    330
    331        /** The color used to draw the background of the section labels. */
    332        private transient Paint labelBackgroundPaint;
    333
    334        /**
    335         * The paint used to draw the outline of the section labels
    336         * (<code>null</code> permitted).
    337         */
    338        private transient Paint labelOutlinePaint;
    339
    340        /**
    341         * The stroke used to draw the outline of the section labels
    342         * (<code>null</code> permitted).
    343         */
    344        private transient Stroke labelOutlineStroke;
    345
    346        /**
    347         * The paint used to draw the shadow for the section labels
    348         * (<code>null</code> permitted).
    349         */
    350        private transient Paint labelShadowPaint;
    351
    352        /** The maximum label width as a percentage of the plot width. */
    353        private double maximumLabelWidth = 0.20;
    354
    355        /**
    356         * The gap between the labels and the plot as a percentage of the plot
    357         * width.
    358         */
    359        private double labelGap = 0.05;
    360
    361        /** A flag that controls whether or not the label links are drawn. */
    362        private boolean labelLinksVisible;
    363
    364        /** The link margin. */
    365        private double labelLinkMargin = 0.05;
    366
    367        /** The paint used for the label linking lines. */
    368        private transient Paint labelLinkPaint = Color.black;
    369
    370        /** The stroke used for the label linking lines. */
    371        private transient Stroke labelLinkStroke = new BasicStroke(0.5f);
    372
    373        /** The tooltip generator. */
    374        private PieToolTipGenerator toolTipGenerator;
    375
    376        /** The URL generator. */
    377        private PieURLGenerator urlGenerator;
    378
    379        /** The legend label generator. */
    380        private PieSectionLabelGenerator legendLabelGenerator;
    381
    382        /** A tool tip generator for the legend. */
    383        private PieSectionLabelGenerator legendLabelToolTipGenerator;
    384
    385        /**
    386         * A URL generator for the legend items (optional).
    387         *
    388         * @since 1.0.4.
    389         */
    390        private PieURLGenerator legendLabelURLGenerator;
    391
    392        /**
    393         * A flag that controls whether <code>null</code> values are ignored.
    394         */
    395        private boolean ignoreNullValues;
    396
    397        /**
    398         * A flag that controls whether zero values are ignored.
    399         */
    400        private boolean ignoreZeroValues;
    401
    402        /** The legend item shape. */
    403        private transient Shape legendItemShape;
    404
    405        /**
    406         * The smallest arc angle that will get drawn (this is to avoid a bug in
    407         * various Java implementations that causes the JVM to crash).  See this
    408         * link for details:
    409         *
    410         * http://www.jfree.org/phpBB2/viewtopic.php?t=2707
    411         *
    412         * ...and this bug report in the Java Bug Parade:
    413         *
    414         * http://developer.java.sun.com/developer/bugParade/bugs/4836495.html
    415         */
    416        private double minimumArcAngleToDraw;
    417
    418        /** The resourceBundle for the localization. */
    419        protected static ResourceBundle localizationResources =
    420            ResourceBundle.getBundle("org.jfree.chart.plot.LocalizationBundle");
    421
    422        /**
    423         * Creates a new plot.  The dataset is initially set to <code>null</code>.
    424         */
    425        public PiePlot() {
    426            this(null);
    427        }
    428
    429        /**
    430         * Creates a plot that will draw a pie chart for the specified dataset.
    431         *
    432         * @param dataset  the dataset (<code>null</code> permitted).
    433         */
    434        public PiePlot(PieDataset dataset) {
    435            super();
    436            this.dataset = dataset;
    437            if (dataset != null) {
    438                dataset.addChangeListener(this);
    439            }
    440            this.pieIndex = 0;
    441
    442            this.interiorGap = DEFAULT_INTERIOR_GAP;
    443            this.circular = true;
    444            this.startAngle = DEFAULT_START_ANGLE;
    445            this.direction = Rotation.CLOCKWISE;
    446            this.minimumArcAngleToDraw = DEFAULT_MINIMUM_ARC_ANGLE_TO_DRAW;
    447
    448            this.sectionPaint = null;
    449            this.sectionPaintMap = new PaintMap();
    450            this.baseSectionPaint = Color.gray;
    451
    452            this.sectionOutlinesVisible = true;
    453            this.sectionOutlinePaint = null;
    454            this.sectionOutlinePaintMap = new PaintMap();
    455            this.baseSectionOutlinePaint = DEFAULT_OUTLINE_PAINT;
    456
    457            this.sectionOutlineStroke = null;
    458            this.sectionOutlineStrokeMap = new StrokeMap();
    459            this.baseSectionOutlineStroke = DEFAULT_OUTLINE_STROKE;
    460
    461            this.explodePercentages = new TreeMap();
    462
    463            this.labelGenerator = new StandardPieSectionLabelGenerator();
    464            this.labelFont = DEFAULT_LABEL_FONT;
    465            this.labelPaint = DEFAULT_LABEL_PAINT;
    466            this.labelBackgroundPaint = DEFAULT_LABEL_BACKGROUND_PAINT;
    467            this.labelOutlinePaint = DEFAULT_LABEL_OUTLINE_PAINT;
    468            this.labelOutlineStroke = DEFAULT_LABEL_OUTLINE_STROKE;
    469            this.labelShadowPaint = DEFAULT_LABEL_SHADOW_PAINT;
    470            this.labelLinksVisible = true;
    471
    472            this.toolTipGenerator = null;
    473            this.urlGenerator = null;
    474            this.legendLabelGenerator = new StandardPieSectionLabelGenerator();
    475            this.legendLabelToolTipGenerator = null;
    476            this.legendLabelURLGenerator = null;
    477            this.legendItemShape = Plot.DEFAULT_LEGEND_ITEM_CIRCLE;
    478
    479            this.ignoreNullValues = false;
    480            this.ignoreZeroValues = false;
    481        }
    482
    483        /**
    484         * Returns the dataset.
    485         *
    486         * @return The dataset (possibly <code>null</code>).
    487         *
    488         * @see #setDataset(PieDataset)
    489         */
    490        public PieDataset getDataset() {
    491            return this.dataset;
    492        }
    493
    494        /**
    495         * Sets the dataset and sends a {@link DatasetChangeEvent} to 'this'.
    496         *
    497         * @param dataset  the dataset (<code>null</code> permitted).
    498         *
    499         * @see #getDataset()
    500         */
    501        public void setDataset(PieDataset dataset) {
    502            // if there is an existing dataset, remove the plot from the list of
    503            // change listeners...
    504            PieDataset existing = this.dataset;
    505            if (existing != null) {
    506                existing.removeChangeListener(this);
    507            }
    508
    509            // set the new dataset, and register the chart as a change listener...
    510            this.dataset = dataset;
    511            if (dataset != null) {
    512                setDatasetGroup(dataset.getGroup());
    513                dataset.addChangeListener(this);
    514            }
    515
    516            // send a dataset change event to self...
    517            DatasetChangeEvent event = new DatasetChangeEvent(this, dataset);
    518            datasetChanged(event);
    519        }
    520
    521        /**
    522         * Returns the pie index (this is used by the {@link MultiplePiePlot} class
    523         * to track subplots).
    524         *
    525         * @return The pie index.
    526         *
    527         * @see #setPieIndex(int)
    528         */
    529        public int getPieIndex() {
    530            return this.pieIndex;
    531        }
    532
    533        /**
    534         * Sets the pie index (this is used by the {@link MultiplePiePlot} class to
    535         * track subplots).
    536         *
    537         * @param index  the index.
    538         *
    539         * @see #getPieIndex()
    540         */
    541        public void setPieIndex(int index) {
    542            this.pieIndex = index;
    543        }
    544
    545        /**
    546         * Returns the start angle for the first pie section.  This is measured in
    547         * degrees starting from 3 o'clock and measuring anti-clockwise.
    548         *
    549         * @return The start angle.
    550         *
    551         * @see #setStartAngle(double)
    552         */
    553        public double getStartAngle() {
    554            return this.startAngle;
    555        }
    556
    557        /**
    558         * Sets the starting angle and sends a {@link PlotChangeEvent} to all
    559         * registered listeners.  The initial default value is 90 degrees, which
    560         * corresponds to 12 o'clock.  A value of zero corresponds to 3 o'clock...
    561         * this is the encoding used by Java's Arc2D class.
    562         *
    563         * @param angle  the angle (in degrees).
    564         *
    565         * @see #getStartAngle()
    566         */
    567        public void setStartAngle(double angle) {
    568            this.startAngle = angle;
    569            notifyListeners(new PlotChangeEvent(this));
    570        }
    571
    572        /**
    573         * Returns the direction in which the pie sections are drawn (clockwise or
    574         * anti-clockwise).
    575         *
    576         * @return The direction (never <code>null</code>).
    577         *
    578         * @see #setDirection(Rotation)
    579         */
    580        public Rotation getDirection() {
    581            return this.direction;
    582        }
    583
    584        /**
    585         * Sets the direction in which the pie sections are drawn and sends a
    586         * {@link PlotChangeEvent} to all registered listeners.
    587         *
    588         * @param direction  the direction (<code>null</code> not permitted).
    589         *
    590         * @see #getDirection()
    591         */
    592        public void setDirection(Rotation direction) {
    593            if (direction == null) {
    594                throw new IllegalArgumentException("Null 'direction' argument.");
    595            }
    596            this.direction = direction;
    597            notifyListeners(new PlotChangeEvent(this));
    598
    599        }
    600
    601        /**
    602         * Returns the interior gap, measured as a percentage of the available
    603         * drawing space.
    604         *
    605         * @return The gap (as a percentage of the available drawing space).
    606         *
    607         * @see #setInteriorGap(double)
    608         */
    609        public double getInteriorGap() {
    610            return this.interiorGap;
    611        }
    612
    613        /**
    614         * Sets the interior gap and sends a {@link PlotChangeEvent} to all
    615         * registered listeners.  This controls the space between the edges of the
    616         * pie plot and the plot area itself (the region where the section labels
    617         * appear).
    618         *
    619         * @param percent  the gap (as a percentage of the available drawing space).
    620         *
    621         * @see #getInteriorGap()
    622         */
    623        public void setInteriorGap(double percent) {
    624
    625            // check arguments...
    626            if ((percent < 0.0) || (percent > MAX_INTERIOR_GAP)) {
    627                throw new IllegalArgumentException(
    628                    "Invalid 'percent' (" + percent + ") argument.");
    629            }
    630
    631            // make the change...
    632            if (this.interiorGap != percent) {
    633                this.interiorGap = percent;
    634                notifyListeners(new PlotChangeEvent(this));
    635            }
    636
    637        }
    638
    639        /**
    640         * Returns a flag indicating whether the pie chart is circular, or
    641         * stretched into an elliptical shape.
    642         *
    643         * @return A flag indicating whether the pie chart is circular.
    644         *
    645         * @see #setCircular(boolean)
    646         */
    647        public boolean isCircular() {
    648            return this.circular;
    649        }
    650
    651        /**
    652         * A flag indicating whether the pie chart is circular, or stretched into
    653         * an elliptical shape.
    654         *
    655         * @param flag  the new value.
    656         *
    657         * @see #isCircular()
    658         */
    659        public void setCircular(boolean flag) {
    660            setCircular(flag, true);
    661        }
    662
    663        /**
    664         * Sets the circular attribute and, if requested, sends a
    665         * {@link PlotChangeEvent} to all registered listeners.
    666         *
    667         * @param circular  the new value of the flag.
    668         * @param notify  notify listeners?
    669         *
    670         * @see #isCircular()
    671         */
    672        public void setCircular(boolean circular, boolean notify) {
    673            this.circular = circular;
    674            if (notify) {
    675                notifyListeners(new PlotChangeEvent(this));
    676            }
    677        }
    678
    679        /**
    680         * Returns the flag that controls whether <code>null</code> values in the
    681         * dataset are ignored.
    682         *
    683         * @return A boolean.
    684         *
    685         * @see #setIgnoreNullValues(boolean)
    686         */
    687        public boolean getIgnoreNullValues() {
    688            return this.ignoreNullValues;
    689        }
    690
    691        /**
    692         * Sets a flag that controls whether <code>null</code> values are ignored,
    693         * and sends a {@link PlotChangeEvent} to all registered listeners.  At
    694         * present, this only affects whether or not the key is presented in the
    695         * legend.
    696         *
    697         * @param flag  the flag.
    698         *
    699         * @see #getIgnoreNullValues()
    700         * @see #setIgnoreZeroValues(boolean)
    701         */
    702        public void setIgnoreNullValues(boolean flag) {
    703            this.ignoreNullValues = flag;
    704            notifyListeners(new PlotChangeEvent(this));
    705        }
    706
    707        /**
    708         * Returns the flag that controls whether zero values in the
    709         * dataset are ignored.
    710         *
    711         * @return A boolean.
    712         *
    713         * @see #setIgnoreZeroValues(boolean)
    714         */
    715        public boolean getIgnoreZeroValues() {
    716            return this.ignoreZeroValues;
    717        }
    718
    719        /**
    720         * Sets a flag that controls whether zero values are ignored,
    721         * and sends a {@link PlotChangeEvent} to all registered listeners.  This
    722         * only affects whether or not a label appears for the non-visible
    723         * pie section.
    724         *
    725         * @param flag  the flag.
    726         *
    727         * @see #getIgnoreZeroValues()
    728         * @see #setIgnoreNullValues(boolean)
    729         */
    730        public void setIgnoreZeroValues(boolean flag) {
    731            this.ignoreZeroValues = flag;
    732            notifyListeners(new PlotChangeEvent(this));
    733        }
    734
    735        //// SECTION PAINT ////////////////////////////////////////////////////////
    736
    737        /**
    738         * Returns the paint for the specified section.  This is equivalent to
    739         * <code>lookupSectionPaint(section, false)</code>.
    740         *
    741         * @param key  the section key.
    742         *
    743         * @return The paint for the specified section.
    744         *
    745         * @since 1.0.3
    746         *
    747         * @see #lookupSectionPaint(Comparable, boolean)
    748         */
    749        protected Paint lookupSectionPaint(Comparable key) {
    750            return lookupSectionPaint(key, false);
    751        }
    752
    753        /**
    754         * Returns the paint for the specified section.  The lookup involves these
    755         * steps:
    756         * <ul>
    757         * <li>if {@link #getSectionPaint()} is non-<code>null</code>, return
    758         *         it;</li>
    759         * <li>if {@link #getSectionPaint(int)} is non-<code>null</code> return
    760         *         it;</li>
    761         * <li>if {@link #getSectionPaint(int)} is <code>null</code> but
    762         *         <code>autoPopulate</code> is <code>true</code>, attempt to fetch
    763         *         a new paint from the drawing supplier
    764         *         ({@link #getDrawingSupplier()});
    765         * <li>if all else fails, return {@link #getBaseSectionPaint()}.
    766         * </ul>
    767         *
    768         * @param key  the section key.
    769         * @param autoPopulate  a flag that controls whether the drawing supplier
    770         *     is used to auto-populate the section paint settings.
    771         *
    772         * @return The paint.
    773         *
    774         * @since 1.0.3
    775         */
    776        protected Paint lookupSectionPaint(Comparable key, boolean autoPopulate) {
    777
    778            // is there an override?
    779            Paint result = getSectionPaint();
    780            if (result != null) {
    781                return result;
    782            }
    783
    784            // if not, check if there is a paint defined for the specified key
    785            result = this.sectionPaintMap.getPaint(key);
    786            if (result != null) {
    787                return result;
    788            }
    789
    790            // nothing defined - do we autoPopulate?
    791            if (autoPopulate) {
    792                DrawingSupplier ds = getDrawingSupplier();
    793                if (ds != null) {
    794                    result = ds.getNextPaint();
    795                    this.sectionPaintMap.put(key, result);
    796                }
    797                else {
    798                    result = this.baseSectionPaint;
    799                }
    800            }
    801            else {
    802                result = this.baseSectionPaint;
    803            }
    804            return result;
    805        }
    806
    807        /**
    808         * Returns the paint for ALL sections in the plot.
    809         *
    810         * @return The paint (possibly <code>null</code>).
    811         *
    812         * @see #setSectionPaint(Paint)
    813         */
    814        public Paint getSectionPaint() {
    815            return this.sectionPaint;
    816        }
    817
    818        /**
    819         * Sets the paint for ALL sections in the plot.  If this is set to
    820         * </code>null</code>, then a list of paints is used instead (to allow
    821         * different colors to be used for each section).
    822         *
    823         * @param paint  the paint (<code>null</code> permitted).
    824         *
    825         * @see #getSectionPaint()
    826         */
    827        public void setSectionPaint(Paint paint) {
    828            this.sectionPaint = paint;
    829            notifyListeners(new PlotChangeEvent(this));
    830        }
    831
    832        /**
    833         * Returns a key for the specified section.  If there is no such section
    834         * in the dataset, we generate a key.  This is to provide some backward
    835         * compatibility for the (now deprecated) methods that get/set attributes
    836         * based on section indices.  The preferred way of doing this now is to
    837         * link the attributes directly to the section key (there are new methods
    838         * for this, starting from version 1.0.3).
    839         *
    840         * @param section  the section index.
    841         *
    842         * @return The key.
    843         *
    844         * @since 1.0.3
    845         */
    846        protected Comparable getSectionKey(int section) {
    847            Comparable key = null;
    848            if (this.dataset != null) {
    849                if (section >= 0 && section < this.dataset.getItemCount()) {
    850                    key = this.dataset.getKey(section);
    851                }
    852            }
    853            if (key == null) {
    854                key = new Integer(section);
    855            }
    856            return key;
    857        }
    858
    859        /**
    860         * Returns the paint associated with the specified key, or
    861         * <code>null</code> if there is no paint associated with the key.
    862         *
    863         * @param key  the key (<code>null</code> not permitted).
    864         *
    865         * @return The paint associated with the specified key, or
    866         *     <code>null</code>.
    867         *
    868         * @throws IllegalArgumentException if <code>key</code> is
    869         *     <code>null</code>.
    870         *
    871         * @see #setSectionPaint(Comparable, Paint)
    872         *
    873         * @since 1.0.3
    874         */
    875        public Paint getSectionPaint(Comparable key) {
    876            // null argument check delegated...
    877            return this.sectionPaintMap.getPaint(key);
    878        }
    879
    880        /**
    881         * Sets the paint associated with the specified key, and sends a
    882         * {@link PlotChangeEvent} to all registered listeners.
    883         *
    884         * @param key  the key (<code>null</code> not permitted).
    885         * @param paint  the paint.
    886         *
    887         * @throws IllegalArgumentException if <code>key</code> is
    888         *     <code>null</code>.
    889         *
    890         * @see #getSectionPaint(Comparable)
    891         *
    892         * @since 1.0.3
    893         */
    894        public void setSectionPaint(Comparable key, Paint paint) {
    895            // null argument check delegated...
    896            this.sectionPaintMap.put(key, paint);
    897            notifyListeners(new PlotChangeEvent(this));
    898        }
    899
    900        /**
    901         * Returns the base section paint.  This is used when no other paint is
    902         * defined, which is rare.  The default value is <code>Color.gray</code>.
    903         *
    904         * @return The paint (never <code>null</code>).
    905         *
    906         * @see #setBaseSectionPaint(Paint)
    907         */
    908        public Paint getBaseSectionPaint() {
    909            return this.baseSectionPaint;
    910        }
    911
    912        /**
    913         * Sets the base section paint and sends a {@link PlotChangeEvent} to all
    914         * registered listeners.
    915         *
    916         * @param paint  the paint (<code>null</code> not permitted).
    917         *
    918         * @see #getBaseSectionPaint()
    919         */
    920        public void setBaseSectionPaint(Paint paint) {
    921            if (paint == null) {
    922                throw new IllegalArgumentException("Null 'paint' argument.");
    923            }
    924            this.baseSectionPaint = paint;
    925            notifyListeners(new PlotChangeEvent(this));
    926        }
    927
    928        //// SECTION OUTLINE PAINT ////////////////////////////////////////////////
    929
    930        /**
    931         * Returns the flag that controls whether or not the outline is drawn for
    932         * each pie section.
    933         *
    934         * @return The flag that controls whether or not the outline is drawn for
    935         *         each pie section.
    936         *
    937         * @see #setSectionOutlinesVisible(boolean)
    938         */
    939        public boolean getSectionOutlinesVisible() {
    940            return this.sectionOutlinesVisible;
    941        }
    942
    943        /**
    944         * Sets the flag that controls whether or not the outline is drawn for
    945         * each pie section, and sends a {@link PlotChangeEvent} to all registered
    946         * listeners.
    947         *
    948         * @param visible  the flag.
    949         *
    950         * @see #getSectionOutlinesVisible()
    951         */
    952        public void setSectionOutlinesVisible(boolean visible) {
    953            this.sectionOutlinesVisible = visible;
    954            notifyListeners(new PlotChangeEvent(this));
    955        }
    956
    957        /**
    958         * Returns the outline paint for the specified section.  This is equivalent
    959         * to <code>lookupSectionPaint(section, false)</code>.
    960         *
    961         * @param key  the section key.
    962         *
    963         * @return The paint for the specified section.
    964         *
    965         * @since 1.0.3
    966         *
    967         * @see #lookupSectionOutlinePaint(Comparable, boolean)
    968         */
    969        protected Paint lookupSectionOutlinePaint(Comparable key) {
    970            return lookupSectionOutlinePaint(key, false);
    971        }
    972
    973        /**
    974         * Returns the outline paint for the specified section.  The lookup
    975         * involves these steps:
    976         * <ul>
    977         * <li>if {@link #getSectionOutlinePaint()} is non-<code>null</code>,
    978         *         return it;</li>
    979         * <li>otherwise, if {@link #getSectionOutlinePaint(int)} is
    980         *         non-<code>null</code> return it;</li>
    981         * <li>if {@link #getSectionOutlinePaint(int)} is <code>null</code> but
    982         *         <code>autoPopulate</code> is <code>true</code>, attempt to fetch
    983         *         a new outline paint from the drawing supplier
    984         *         ({@link #getDrawingSupplier()});
    985         * <li>if all else fails, return {@link #getBaseSectionOutlinePaint()}.
    986         * </ul>
    987         *
    988         * @param key  the section key.
    989         * @param autoPopulate  a flag that controls whether the drawing supplier
    990         *     is used to auto-populate the section outline paint settings.
    991         *
    992         * @return The paint.
    993         *
    994         * @since 1.0.3
    995         */
    996        protected Paint lookupSectionOutlinePaint(Comparable key,
    997                boolean autoPopulate) {
    998
    999            // is there an override?
    1000            Paint result = getSectionOutlinePaint();
    1001            if (result != null) {
    1002                return result;
    1003            }
    1004
    1005            // if not, check if there is a paint defined for the specified key
    1006            result = this.sectionOutlinePaintMap.getPaint(key);
    1007            if (result != null) {
    1008                return result;
    1009            }
    1010
    1011            // nothing defined - do we autoPopulate?
    1012            if (autoPopulate) {
    1013                DrawingSupplier ds = getDrawingSupplier();
    1014                if (ds != null) {
    1015                    result = ds.getNextOutlinePaint();
    1016                    this.sectionOutlinePaintMap.put(key, result);
    1017                }
    1018                else {
    1019                    result = this.baseSectionOutlinePaint;
    1020                }
    1021            }
    1022            else {
    1023                result = this.baseSectionOutlinePaint;
    1024            }
    1025            return result;
    1026        }
    1027
    1028        /**
    1029         * Returns the outline paint for ALL sections in the plot.
    1030         *
    1031         * @return The paint (possibly <code>null</code>).
    1032         *
    1033         * @see #setSectionOutlinePaint(Paint)
    1034         */
    1035        public Paint getSectionOutlinePaint() {
    1036            return this.sectionOutlinePaint;
    1037        }
    1038
    1039        /**
    1040         * Sets the outline paint for ALL sections in the plot.  If this is set to
    1041         * </code>null</code>, then a list of paints is used instead (to allow
    1042         * different colors to be used for each section).
    1043         *
    1044         * @param paint  the paint (<code>null</code> permitted).
    1045         *
    1046         * @see #getSectionOutlinePaint()
    1047         */
    1048        public void setSectionOutlinePaint(Paint paint) {
    1049            this.sectionOutlinePaint = paint;
    1050            notifyListeners(new PlotChangeEvent(this));
    1051        }
    1052
    1053        /**
    1054         * Returns the outline paint associated with the specified key, or
    1055         * <code>null</code> if there is no paint associated with the key.
    1056         *
    1057         * @param key  the key (<code>null</code> not permitted).
    1058         *
    1059         * @return The paint associated with the specified key, or
    1060         *     <code>null</code>.
    1061         *
    1062         * @throws IllegalArgumentException if <code>key</code> is
    1063         *     <code>null</code>.
    1064         *
    1065         * @see #setSectionOutlinePaint(Comparable, Paint)
    1066         *
    1067         * @since 1.0.3
    1068         */
    1069        public Paint getSectionOutlinePaint(Comparable key) {
    1070            // null argument check delegated...
    1071            return this.sectionOutlinePaintMap.getPaint(key);
    1072        }
    1073
    1074        /**
    1075         * Sets the outline paint associated with the specified key, and sends a
    1076         * {@link PlotChangeEvent} to all registered listeners.
    1077         *
    1078         * @param key  the key (<code>null</code> not permitted).
    1079         * @param paint  the paint.
    1080         *
    1081         * @throws IllegalArgumentException if <code>key</code> is
    1082         *     <code>null</code>.
    1083         *
    1084         * @see #getSectionOutlinePaint(Comparable)
    1085         *
    1086         * @since 1.0.3
    1087         */
    1088        public void setSectionOutlinePaint(Comparable key, Paint paint) {
    1089            // null argument check delegated...
    1090            this.sectionOutlinePaintMap.put(key, paint);
    1091            notifyListeners(new PlotChangeEvent(this));
    1092        }
    1093
    1094        /**
    1095         * Returns the base section paint.  This is used when no other paint is
    1096         * available.
    1097         *
    1098         * @return The paint (never <code>null</code>).
    1099         *
    1100         * @see #setBaseSectionOutlinePaint(Paint)
    1101         */
    1102        public Paint getBaseSectionOutlinePaint() {
    1103            return this.baseSectionOutlinePaint;
    1104        }
    1105
    1106        /**
    1107         * Sets the base section paint.
    1108         *
    1109         * @param paint  the paint (<code>null</code> not permitted).
    1110         *
    1111         * @see #getBaseSectionOutlinePaint()
    1112         */
    1113        public void setBaseSectionOutlinePaint(Paint paint) {
    1114            if (paint == null) {
    1115                throw new IllegalArgumentException("Null 'paint' argument.");
    1116            }
    1117            this.baseSectionOutlinePaint = paint;
    1118            notifyListeners(new PlotChangeEvent(this));
    1119        }
    1120
    1121        //// SECTION OUTLINE STROKE ///////////////////////////////////////////////
    1122
    1123        /**
    1124         * Returns the outline stroke for the specified section.  This is equivalent
    1125         * to <code>lookupSectionOutlineStroke(section, false)</code>.
    1126         *
    1127         * @param key  the section key.
    1128         *
    1129         * @return The stroke for the specified section.
    1130         *
    1131         * @since 1.0.3
    1132         *
    1133         * @see #lookupSectionOutlineStroke(Comparable, boolean)
    1134         */
    1135        protected Stroke lookupSectionOutlineStroke(Comparable key) {
    1136            return lookupSectionOutlineStroke(key, false);
    1137        }
    1138
    1139        /**
    1140         * Returns the outline stroke for the specified section.  The lookup
    1141         * involves these steps:
    1142         * <ul>
    1143         * <li>if {@link #getSectionOutlineStroke()} is non-<code>null</code>,
    1144         *         return it;</li>
    1145         * <li>otherwise, if {@link #getSectionOutlineStroke(int)} is
    1146         *         non-<code>null</code> return it;</li>
    1147         * <li>if {@link #getSectionOutlineStroke(int)} is <code>null</code> but
    1148         *         <code>autoPopulate</code> is <code>true</code>, attempt to fetch
    1149         *         a new outline stroke from the drawing supplier
    1150         *         ({@link #getDrawingSupplier()});
    1151         * <li>if all else fails, return {@link #getBaseSectionOutlineStroke()}.
    1152         * </ul>
    1153         *
    1154         * @param key  the section key.
    1155         * @param autoPopulate  a flag that controls whether the drawing supplier
    1156         *     is used to auto-populate the section outline stroke settings.
    1157         *
    1158         * @return The stroke.
    1159         *
    1160         * @since 1.0.3
    1161         */
    1162        protected Stroke lookupSectionOutlineStroke(Comparable key,
    1163                boolean autoPopulate) {
    1164
    1165            // is there an override?
    1166            Stroke result = getSectionOutlineStroke();
    1167            if (result != null) {
    1168                return result;
    1169            }
    1170
    1171            // if not, check if there is a stroke defined for the specified key
    1172            result = this.sectionOutlineStrokeMap.getStroke(key);
    1173            if (result != null) {
    1174                return result;
    1175            }
    1176
    1177            // nothing defined - do we autoPopulate?
    1178            if (autoPopulate) {
    1179                DrawingSupplier ds = getDrawingSupplier();
    1180                if (ds != null) {
    1181                    result = ds.getNextOutlineStroke();
    1182                    this.sectionOutlineStrokeMap.put(key, result);
    1183                }
    1184                else {
    1185                    result = this.baseSectionOutlineStroke;
    1186                }
    1187            }
    1188            else {
    1189                result = this.baseSectionOutlineStroke;
    1190            }
    1191            return result;
    1192        }
    1193
    1194        /**
    1195         * Returns the outline stroke for ALL sections in the plot.
    1196         *
    1197         * @return The stroke (possibly <code>null</code>).
    1198         *
    1199         * @see #setSectionOutlineStroke(Stroke)
    1200         */
    1201        public Stroke getSectionOutlineStroke() {
    1202            return this.sectionOutlineStroke;
    1203        }
    1204
    1205        /**
    1206         * Sets the outline stroke for ALL sections in the plot.  If this is set to
    1207         * </code>null</code>, then a list of paints is used instead (to allow
    1208         * different colors to be used for each section).
    1209         *
    1210         * @param stroke  the stroke (<code>null</code> permitted).
    1211         *
    1212         * @see #getSectionOutlineStroke()
    1213         */
    1214        public void setSectionOutlineStroke(Stroke stroke) {
    1215            this.sectionOutlineStroke = stroke;
    1216            notifyListeners(new PlotChangeEvent(this));
    1217        }
    1218
    1219        /**
    1220         * Returns the outline stroke associated with the specified key, or
    1221         * <code>null</code> if there is no stroke associated with the key.
    1222         *
    1223         * @param key  the key (<code>null</code> not permitted).
    1224         *
    1225         * @return The stroke associated with the specified key, or
    1226         *     <code>null</code>.
    1227         *
    1228         * @throws IllegalArgumentException if <code>key</code> is
    1229         *     <code>null</code>.
    1230         *
    1231         * @see #setSectionOutlineStroke(Comparable, Stroke)
    1232         *
    1233         * @since 1.0.3
    1234         */
    1235        public Stroke getSectionOutlineStroke(Comparable key) {
    1236            // null argument check delegated...
    1237            return this.sectionOutlineStrokeMap.getStroke(key);
    1238        }
    1239
    1240        /**
    1241         * Sets the outline stroke associated with the specified key, and sends a
    1242         * {@link PlotChangeEvent} to all registered listeners.
    1243         *
    1244         * @param key  the key (<code>null</code> not permitted).
    1245         * @param stroke  the stroke.
    1246         *
    1247         * @throws IllegalArgumentException if <code>key</code> is
    1248         *     <code>null</code>.
    1249         *
    1250         * @see #getSectionOutlineStroke(Comparable)
    1251         *
    1252         * @since 1.0.3
    1253         */
    1254        public void setSectionOutlineStroke(Comparable key, Stroke stroke) {
    1255            // null argument check delegated...
    1256            this.sectionOutlineStrokeMap.put(key, stroke);
    1257            notifyListeners(new PlotChangeEvent(this));
    1258        }
    1259
    1260        /**
    1261         * Returns the base section stroke.  This is used when no other stroke is
    1262         * available.
    1263         *
    1264         * @return The stroke (never <code>null</code>).
    1265         *
    1266         * @see #setBaseSectionOutlineStroke(Stroke)
    1267         */
    1268        public Stroke getBaseSectionOutlineStroke() {
    1269            return this.baseSectionOutlineStroke;
    1270        }
    1271
    1272        /**
    1273         * Sets the base section stroke.
    1274         *
    1275         * @param stroke  the stroke (<code>null</code> not permitted).
    1276         *
    1277         * @see #getBaseSectionOutlineStroke()
    1278         */
    1279        public void setBaseSectionOutlineStroke(Stroke stroke) {
    1280            if (stroke == null) {
    1281                throw new IllegalArgumentException("Null 'stroke' argument.");
    1282            }
    1283            this.baseSectionOutlineStroke = stroke;
    1284            notifyListeners(new PlotChangeEvent(this));
    1285        }
    1286
    1287        /**
    1288         * Returns the shadow paint.
    1289         *
    1290         * @return The paint (possibly <code>null</code>).
    1291         *
    1292         * @see #setShadowPaint(Paint)
    1293         */
    1294        public Paint getShadowPaint() {
    1295            return this.shadowPaint;
    1296        }
    1297
    1298        /**
    1299         * Sets the shadow paint and sends a {@link PlotChangeEvent} to all
    1300         * registered listeners.
    1301         *
    1302         * @param paint  the paint (<code>null</code> permitted).
    1303         *
    1304         * @see #getShadowPaint()
    1305         */
    1306        public void setShadowPaint(Paint paint) {
    1307            this.shadowPaint = paint;
    1308            notifyListeners(new PlotChangeEvent(this));
    1309        }
    1310
    1311        /**
    1312         * Returns the x-offset for the shadow effect.
    1313         *
    1314         * @return The offset (in Java2D units).
    1315         *
    1316         * @see #setShadowXOffset(double)
    1317         */
    1318        public double getShadowXOffset() {
    1319            return this.shadowXOffset;
    1320        }
    1321
    1322        /**
    1323         * Sets the x-offset for the shadow effect and sends a
    1324         * {@link PlotChangeEvent} to all registered listeners.
    1325         *
    1326         * @param offset  the offset (in Java2D units).
    1327         *
    1328         * @see #getShadowXOffset()
    1329         */
    1330        public void setShadowXOffset(double offset) {
    1331            this.shadowXOffset = offset;
    1332            notifyListeners(new PlotChangeEvent(this));
    1333        }
    1334
    1335        /**
    1336         * Returns the y-offset for the shadow effect.
    1337         *
    1338         * @return The offset (in Java2D units).
    1339         *
    1340         * @see #setShadowYOffset(double)
    1341         */
    1342        public double getShadowYOffset() {
    1343            return this.shadowYOffset;
    1344        }
    1345
    1346        /**
    1347         * Sets the y-offset for the shadow effect and sends a
    1348         * {@link PlotChangeEvent} to all registered listeners.
    1349         *
    1350         * @param offset  the offset (in Java2D units).
    1351         *
    1352         * @see #getShadowYOffset()
    1353         */
    1354        public void setShadowYOffset(double offset) {
    1355            this.shadowYOffset = offset;
    1356            notifyListeners(new PlotChangeEvent(this));
    1357        }
    1358
    1359        /**
    1360         * Returns the amount that the section with the specified key should be
    1361         * exploded.
    1362         *
    1363         * @param key  the key (<code>null</code> not permitted).
    1364         *
    1365         * @return The amount that the section with the specified key should be
    1366         *     exploded.
    1367         *
    1368         * @throws IllegalArgumentException if <code>key</code> is
    1369         *     <code>null</code>.
    1370         *
    1371         * @since 1.0.3
    1372         *
    1373         * @see #setExplodePercent(Comparable, double)
    1374         */
    1375        public double getExplodePercent(Comparable key) {
    1376            double result = 0.0;
    1377            if (this.explodePercentages != null) {
    1378                Number percent = (Number) this.explodePercentages.get(key);
    1379                if (percent != null) {
    1380                    result = percent.doubleValue();
    1381                }
    1382            }
    1383            return result;
    1384        }
    1385
    1386        /**
    1387         * Sets the amount that a pie section should be exploded and sends a
    1388         * {@link PlotChangeEvent} to all registered listeners.
    1389         *
    1390         * @param key  the section key (<code>null</code> not permitted).
    1391         * @param percent  the explode percentage (0.30 = 30 percent).
    1392         *
    1393         * @since 1.0.3
    1394         *
    1395         * @see #getExplodePercent(Comparable)
    1396         */
    1397        public void setExplodePercent(Comparable key, double percent) {
    1398            if (key == null) {
    1399                throw new IllegalArgumentException("Null 'key' argument.");
    1400            }
    1401            if (this.explodePercentages == null) {
    1402                this.explodePercentages = new TreeMap();
    1403            }
    1404            this.explodePercentages.put(key, new Double(percent));
    1405            notifyListeners(new PlotChangeEvent(this));
    1406        }
    1407
    1408        /**
    1409         * Returns the maximum explode percent.
    1410         *
    1411         * @return The percent.
    1412         */
    1413        public double getMaximumExplodePercent() {
    1414            double result = 0.0;
    1415            Iterator iterator = this.dataset.getKeys().iterator();
    1416            while (iterator.hasNext()) {
    1417                Comparable key = (Comparable) iterator.next();
    1418                Number explode = (Number) this.explodePercentages.get(key);
    1419                if (explode != null) {
    1420                    result = Math.max(result, explode.doubleValue());
    1421                }
    1422            }
    1423            return result;
    1424        }
    1425
    1426        /**
    1427         * Returns the section label generator.
    1428         *
    1429         * @return The generator (possibly <code>null</code>).
    1430         *
    1431         * @see #setLabelGenerator(PieSectionLabelGenerator)
    1432         */
    1433        public PieSectionLabelGenerator getLabelGenerator() {
    1434            return this.labelGenerator;
    1435        }
    1436
    1437        /**
    1438         * Sets the section label generator and sends a {@link PlotChangeEvent} to
    1439         * all registered listeners.
    1440         *
    1441         * @param generator  the generator (<code>null</code> permitted).
    1442         *
    1443         * @see #getLabelGenerator()
    1444         */
    1445        public void setLabelGenerator(PieSectionLabelGenerator generator) {
    1446            this.labelGenerator = generator;
    1447            notifyListeners(new PlotChangeEvent(this));
    1448        }
    1449
    1450        /**
    1451         * Returns the gap between the edge of the pie and the labels, expressed as
    1452         * a percentage of the plot width.
    1453         *
    1454         * @return The gap (a percentage, where 0.05 = five percent).
    1455         *
    1456         * @see #setLabelGap(double)
    1457         */
    1458        public double getLabelGap() {
    1459            return this.labelGap;
    1460        }
    1461
    1462        /**
    1463         * Sets the gap between the edge of the pie and the labels (expressed as a
    1464         * percentage of the plot width) and sends a {@link PlotChangeEvent} to all
    1465         * registered listeners.
    1466         *
    1467         * @param gap  the gap (a percentage, where 0.05 = five percent).
    1468         *
    1469         * @see #getLabelGap()
    1470         */
    1471        public void setLabelGap(double gap) {
    1472            this.labelGap = gap;
    1473            notifyListeners(new PlotChangeEvent(this));
    1474        }
    1475
    1476        /**
    1477         * Returns the maximum label width as a percentage of the plot width.
    1478         *
    1479         * @return The width (a percentage, where 0.20 = 20 percent).
    1480         *
    1481         * @see #setMaximumLabelWidth(double)
    1482         */
    1483        public double getMaximumLabelWidth() {
    1484            return this.maximumLabelWidth;
    1485        }
    1486
    1487        /**
    1488         * Sets the maximum label width as a percentage of the plot width and sends
    1489         * a {@link PlotChangeEvent} to all registered listeners.
    1490         *
    1491         * @param width  the width (a percentage, where 0.20 = 20 percent).
    1492         *
    1493         * @see #getMaximumLabelWidth()
    1494         */
    1495        public void setMaximumLabelWidth(double width) {
    1496            this.maximumLabelWidth = width;
    1497            notifyListeners(new PlotChangeEvent(this));
    1498        }
    1499
    1500        /**
    1501         * Returns the flag that controls whether or not label linking lines are
    1502         * visible.
    1503         *
    1504         * @return A boolean.
    1505         *
    1506         * @see #setLabelLinksVisible(boolean)
    1507         */
    1508        public boolean getLabelLinksVisible() {
    1509            return this.labelLinksVisible;
    1510        }
    1511
    1512        /**
    1513         * Sets the flag that controls whether or not label linking lines are
    1514         * visible and sends a {@link PlotChangeEvent} to all registered listeners.
    1515         * Please take care when hiding the linking lines - depending on the data
    1516         * values, the labels can be displayed some distance away from the
    1517         * corresponding pie section.
    1518         *
    1519         * @param visible  the flag.
    1520         *
    1521         * @see #getLabelLinksVisible()
    1522         */
    1523        public void setLabelLinksVisible(boolean visible) {
    1524            this.labelLinksVisible = visible;
    1525            notifyListeners(new PlotChangeEvent(this));
    1526        }
    1527
    1528        /**
    1529         * Returns the margin (expressed as a percentage of the width or height)
    1530         * between the edge of the pie and the link point.
    1531         *
    1532         * @return The link margin (as a percentage, where 0.05 is five percent).
    1533         *
    1534         * @see #setLabelLinkMargin(double)
    1535         */
    1536        public double getLabelLinkMargin() {
    1537            return this.labelLinkMargin;
    1538        }
    1539
    1540        /**
    1541         * Sets the link margin and sends a {@link PlotChangeEvent} to all
    1542         * registered listeners.
    1543         *
    1544         * @param margin  the margin.
    1545         *
    1546         * @see #getLabelLinkMargin()
    1547         */
    1548        public void setLabelLinkMargin(double margin) {
    1549            this.labelLinkMargin = margin;
    1550            notifyListeners(new PlotChangeEvent(this));
    1551        }
    1552
    1553        /**
    1554         * Returns the paint used for the lines that connect pie sections to their
    1555         * corresponding labels.
    1556         *
    1557         * @return The paint (never <code>null</code>).
    1558         *
    1559         * @see #setLabelLinkPaint(Paint)
    1560         */
    1561        public Paint getLabelLinkPaint() {
    1562            return this.labelLinkPaint;
    1563        }
    1564
    1565        /**
    1566         * Sets the paint used for the lines that connect pie sections to their
    1567         * corresponding labels, and sends a {@link PlotChangeEvent} to all
    1568         * registered listeners.
    1569         *
    1570         * @param paint  the paint (<code>null</code> not permitted).
    1571         *
    1572         * @see #getLabelLinkPaint()
    1573         */
    1574        public void setLabelLinkPaint(Paint paint) {
    1575            if (paint == null) {
    1576                throw new IllegalArgumentException("Null 'paint' argument.");
    1577            }
    1578            this.labelLinkPaint = paint;
    1579            notifyListeners(new PlotChangeEvent(this));
    1580        }
    1581
    1582        /**
    1583         * Returns the stroke used for the label linking lines.
    1584         *
    1585         * @return The stroke.
    1586         *
    1587         * @see #setLabelLinkStroke(Stroke)
    1588         */
    1589        public Stroke getLabelLinkStroke() {
    1590            return this.labelLinkStroke;
    1591        }
    1592
    1593        /**
    1594         * Sets the link stroke and sends a {@link PlotChangeEvent} to all
    1595         * registered listeners.
    1596         *
    1597         * @param stroke  the stroke.
    1598         *
    1599         * @see #getLabelLinkStroke()
    1600         */
    1601        public void setLabelLinkStroke(Stroke stroke) {
    1602            if (stroke == null) {
    1603                throw new IllegalArgumentException("Null 'stroke' argument.");
    1604            }
    1605            this.labelLinkStroke = stroke;
    1606            notifyListeners(new PlotChangeEvent(this));
    1607        }
    1608
    1609        /**
    1610         * Returns the section label font.
    1611         *
    1612         * @return The font (never <code>null</code>).
    1613         *
    1614         * @see #setLabelFont(Font)
    1615         */
    1616        public Font getLabelFont() {
    1617            return this.labelFont;
    1618        }
    1619
    1620        /**
    1621         * Sets the section label font and sends a {@link PlotChangeEvent} to all
    1622         * registered listeners.
    1623         *
    1624         * @param font  the font (<code>null</code> not permitted).
    1625         *
    1626         * @see #getLabelFont()
    1627         */
    1628        public void setLabelFont(Font font) {
    1629            if (font == null) {
    1630                throw new IllegalArgumentException("Null 'font' argument.");
    1631            }
    1632            this.labelFont = font;
    1633            notifyListeners(new PlotChangeEvent(this));
    1634        }
    1635
    1636        /**
    1637         * Returns the section label paint.
    1638         *
    1639         * @return The paint (never <code>null</code>).
    1640         *
    1641         * @see #setLabelPaint(Paint)
    1642         */
    1643        public Paint getLabelPaint() {
    1644            return this.labelPaint;
    1645        }
    1646
    1647        /**
    1648         * Sets the section label paint and sends a {@link PlotChangeEvent} to all
    1649         * registered listeners.
    1650         *
    1651         * @param paint  the paint (<code>null</code> not permitted).
    1652         *
    1653         * @see #getLabelPaint()
    1654         */
    1655        public void setLabelPaint(Paint paint) {
    1656            if (paint == null) {
    1657                throw new IllegalArgumentException("Null 'paint' argument.");
    1658            }
    1659            this.labelPaint = paint;
    1660            notifyListeners(new PlotChangeEvent(this));
    1661        }
    1662
    1663        /**
    1664         * Returns the section label background paint.
    1665         *
    1666         * @return The paint (possibly <code>null</code>).
    1667         *
    1668         * @see #setLabelBackgroundPaint(Paint)
    1669         */
    1670        public Paint getLabelBackgroundPaint() {
    1671            return this.labelBackgroundPaint;
    1672        }
    1673
    1674        /**
    1675         * Sets the section label background paint and sends a
    1676         * {@link PlotChangeEvent} to all registered listeners.
    1677         *
    1678         * @param paint  the paint (<code>null</code> permitted).
    1679         *
    1680         * @see #getLabelBackgroundPaint()
    1681         */
    1682        public void setLabelBackgroundPaint(Paint paint) {
    1683            this.labelBackgroundPaint = paint;
    1684            notifyListeners(new PlotChangeEvent(this));
    1685        }
    1686
    1687        /**
    1688         * Returns the section label outline paint.
    1689         *
    1690         * @return The paint (possibly <code>null</code>).
    1691         *
    1692         * @see #setLabelOutlinePaint(Paint)
    1693         */
    1694        public Paint getLabelOutlinePaint() {
    1695            return this.labelOutlinePaint;
    1696        }
    1697
    1698        /**
    1699         * Sets the section label outline paint and sends a
    1700         * {@link PlotChangeEvent} to all registered listeners.
    1701         *
    1702         * @param paint  the paint (<code>null</code> permitted).
    1703         *
    1704         * @see #getLabelOutlinePaint()
    1705         */
    1706        public void setLabelOutlinePaint(Paint paint) {
    1707            this.labelOutlinePaint = paint;
    1708            notifyListeners(new PlotChangeEvent(this));
    1709        }
    1710
    1711        /**
    1712         * Returns the section label outline stroke.
    1713         *
    1714         * @return The stroke (possibly <code>null</code>).
    1715         *
    1716         * @see #setLabelOutlineStroke(Stroke)
    1717         */
    1718        public Stroke getLabelOutlineStroke() {
    1719            return this.labelOutlineStroke;
    1720        }
    1721
    1722        /**
    1723         * Sets the section label outline stroke and sends a
    1724         * {@link PlotChangeEvent} to all registered listeners.
    1725         *
    1726         * @param stroke  the stroke (<code>null</code> permitted).
    1727         *
    1728         * @see #getLabelOutlineStroke()
    1729         */
    1730        public void setLabelOutlineStroke(Stroke stroke) {
    1731            this.labelOutlineStroke = stroke;
    1732            notifyListeners(new PlotChangeEvent(this));
    1733        }
    1734
    1735        /**
    1736         * Returns the section label shadow paint.
    1737         *
    1738         * @return The paint (possibly <code>null</code>).
    1739         *
    1740         * @see #setLabelShadowPaint(Paint)
    1741         */
    1742        public Paint getLabelShadowPaint() {
    1743            return this.labelShadowPaint;
    1744        }
    1745
    1746        /**
    1747         * Sets the section label shadow paint and sends a {@link PlotChangeEvent}
    1748         * to all registered listeners.
    1749         *
    1750         * @param paint  the paint (<code>null</code> permitted).
    1751         *
    1752         * @see #getLabelShadowPaint()
    1753         */
    1754        public void setLabelShadowPaint(Paint paint) {
    1755            this.labelShadowPaint = paint;
    1756            notifyListeners(new PlotChangeEvent(this));
    1757        }
    1758
    1759        /**
    1760         * Returns the tool tip generator, an object that is responsible for
    1761         * generating the text items used for tool tips by the plot.  If the
    1762         * generator is <code>null</code>, no tool tips will be created.
    1763         *
    1764         * @return The generator (possibly <code>null</code>).
    1765         *
    1766         * @see #setToolTipGenerator(PieToolTipGenerator)
    1767         */
    1768        public PieToolTipGenerator getToolTipGenerator() {
    1769            return this.toolTipGenerator;
    1770        }
    1771
    1772        /**
    1773         * Sets the tool tip generator and sends a {@link PlotChangeEvent} to all
    1774         * registered listeners.  Set the generator to <code>null</code> if you
    1775         * don't want any tool tips.
    1776         *
    1777         * @param generator  the generator (<code>null</code> permitted).
    1778         *
    1779         * @see #getToolTipGenerator()
    1780         */
    1781        public void setToolTipGenerator(PieToolTipGenerator generator) {
    1782            this.toolTipGenerator = generator;
    1783            notifyListeners(new PlotChangeEvent(this));
    1784        }
    1785
    1786        /**
    1787         * Returns the URL generator.
    1788         *
    1789         * @return The generator (possibly <code>null</code>).
    1790         *
    1791         * @see #setURLGenerator(PieURLGenerator)
    1792         */
    1793        public PieURLGenerator getURLGenerator() {
    1794            return this.urlGenerator;
    1795        }
    1796
    1797        /**
    1798         * Sets the URL generator and sends a {@link PlotChangeEvent} to all
    1799         * registered listeners.
    1800         *
    1801         * @param generator  the generator (<code>null</code> permitted).
    1802         *
    1803         * @see #getURLGenerator()
    1804         */
    1805        public void setURLGenerator(PieURLGenerator generator) {
    1806            this.urlGenerator = generator;
    1807            notifyListeners(new PlotChangeEvent(this));
    1808        }
    1809
    1810        /**
    1811         * Returns the minimum arc angle that will be drawn.  Pie sections for an
    1812         * angle smaller than this are not drawn, to avoid a JDK bug.
    1813         *
    1814         * @return The minimum angle.
    1815         *
    1816         * @see #setMinimumArcAngleToDraw(double)
    1817         */
    1818        public double getMinimumArcAngleToDraw() {
    1819            return this.minimumArcAngleToDraw;
    1820        }
    1821
    1822        /**
    1823         * Sets the minimum arc angle that will be drawn.  Pie sections for an
    1824         * angle smaller than this are not drawn, to avoid a JDK bug.  See this
    1825         * link for details:
    1826         * <br><br>
    1827         * <a href="http://www.jfree.org/phpBB2/viewtopic.php?t=2707">
    1828         * http://www.jfree.org/phpBB2/viewtopic.php?t=2707</a>
    1829         * <br><br>
    1830         * ...and this bug report in the Java Bug Parade:
    1831         * <br><br>
    1832         * <a href=
    1833         * "http://developer.java.sun.com/developer/bugParade/bugs/4836495.html">
    1834         * http://developer.java.sun.com/developer/bugParade/bugs/4836495.html</a>
    1835         *
    1836         * @param angle  the minimum angle.
    1837         *
    1838         * @see #getMinimumArcAngleToDraw()
    1839         */
    1840        public void setMinimumArcAngleToDraw(double angle) {
    1841            this.minimumArcAngleToDraw = angle;
    1842        }
    1843
    1844        /**
    1845         * Returns the shape used for legend items.
    1846         *
    1847         * @return The shape (never <code>null</code>).
    1848         *
    1849         * @see #setLegendItemShape(Shape)
    1850         */
    1851        public Shape getLegendItemShape() {
    1852            return this.legendItemShape;
    1853        }
    1854
    1855        /**
    1856         * Sets the shape used for legend items and sends a {@link PlotChangeEvent}
    1857         * to all registered listeners.
    1858         *
    1859         * @param shape  the shape (<code>null</code> not permitted).
    1860         *
    1861         * @see #getLegendItemShape()
    1862         */
    1863        public void setLegendItemShape(Shape shape) {
    1864            if (shape == null) {
    1865                throw new IllegalArgumentException("Null 'shape' argument.");
    1866            }
    1867            this.legendItemShape = shape;
    1868            notifyListeners(new PlotChangeEvent(this));
    1869        }
    1870
    1871        /**
    1872         * Returns the legend label generator.
    1873         *
    1874         * @return The legend label generator (never <code>null</code>).
    1875         *
    1876         * @see #setLegendLabelGenerator(PieSectionLabelGenerator)
    1877         */
    1878        public PieSectionLabelGenerator getLegendLabelGenerator() {
    1879            return this.legendLabelGenerator;
    1880        }
    1881
    1882        /**
    1883         * Sets the legend label generator and sends a {@link PlotChangeEvent} to
    1884         * all registered listeners.
    1885         *
    1886         * @param generator  the generator (<code>null</code> not permitted).
    1887         *
    1888         * @see #getLegendLabelGenerator()
    1889         */
    1890        public void setLegendLabelGenerator(PieSectionLabelGenerator generator) {
    1891            if (generator == null) {
    1892                throw new IllegalArgumentException("Null 'generator' argument.");
    1893            }
    1894            this.legendLabelGenerator = generator;
    1895            notifyListeners(new PlotChangeEvent(this));
    1896        }
    1897
    1898        /**
    1899         * Returns the legend label tool tip generator.
    1900         *
    1901         * @return The legend label tool tip generator (possibly <code>null</code>).
    1902         *
    1903         * @see #setLegendLabelToolTipGenerator(PieSectionLabelGenerator)
    1904         */
    1905        public PieSectionLabelGenerator getLegendLabelToolTipGenerator() {
    1906            return this.legendLabelToolTipGenerator;
    1907        }
    1908
    1909        /**
    1910         * Sets the legend label tool tip generator and sends a
    1911         * {@link PlotChangeEvent} to all registered listeners.
    1912         *
    1913         * @param generator  the generator (<code>null</code> permitted).
    1914         *
    1915         * @see #getLegendLabelToolTipGenerator()
    1916         */
    1917        public void setLegendLabelToolTipGenerator(
    1918                PieSectionLabelGenerator generator) {
    1919            this.legendLabelToolTipGenerator = generator;
    1920            notifyListeners(new PlotChangeEvent(this));
    1921        }
    1922
    1923        /**
    1924         * Returns the legend label URL generator.
    1925         *
    1926         * @return The legend label URL generator (possibly <code>null</code>).
    1927         *
    1928         * @see #setLegendLabelURLGenerator(PieURLGenerator)
    1929         *
    1930         * @since 1.0.4
    1931         */
    1932        public PieURLGenerator getLegendLabelURLGenerator() {
    1933            return this.legendLabelURLGenerator;
    1934        }
    1935
    1936        /**
    1937         * Sets the legend label URL generator and sends a
    1938         * {@link PlotChangeEvent} to all registered listeners.
    1939         *
    1940         * @param generator  the generator (<code>null</code> permitted).
    1941         *
    1942         * @see #getLegendLabelURLGenerator()
    1943         *
    1944         * @since 1.0.4
    1945         */
    1946        public void setLegendLabelURLGenerator(PieURLGenerator generator) {
    1947            this.legendLabelURLGenerator = generator;
    1948            notifyListeners(new PlotChangeEvent(this));
    1949        }
    1950
    1951        /**
    1952         * Initialises the drawing procedure.  This method will be called before
    1953         * the first item is rendered, giving the plot an opportunity to initialise
    1954         * any state information it wants to maintain.
    1955         *
    1956         * @param g2  the graphics device.
    1957         * @param plotArea  the plot area (<code>null</code> not permitted).
    1958         * @param plot  the plot.
    1959         * @param index  the secondary index (<code>null</code> for primary
    1960         *               renderer).
    1961         * @param info  collects chart rendering information for return to caller.
    1962         *
    1963         * @return A state object (maintains state information relevant to one
    1964         *         chart drawing).
    1965         */
    1966        public PiePlotState initialise(Graphics2D g2, Rectangle2D plotArea,
    1967                PiePlot plot, Integer index, PlotRenderingInfo info) {
    1968
    1969            PiePlotState state = new PiePlotState(info);
    1970            state.setPassesRequired(2);
    1971            state.setTotal(DatasetUtilities.calculatePieDatasetTotal(
    1972                    plot.getDataset()));
    1973            state.setLatestAngle(plot.getStartAngle());
    1974            return state;
    1975
    1976        }
    1977
    1978        /**
    1979         * Draws the plot on a Java 2D graphics device (such as the screen or a
    1980         * printer).
    1981         *
    1982         * @param g2  the graphics device.
    1983         * @param area  the area within which the plot should be drawn.
    1984         * @param anchor  the anchor point (<code>null</code> permitted).
    1985         * @param parentState  the state from the parent plot, if there is one.
    1986         * @param info  collects info about the drawing
    1987         *              (<code>null</code> permitted).
    1988         */
    1989        public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor,
    1990                         PlotState parentState, PlotRenderingInfo info) {
    1991
    1992            // adjust for insets...
    1993            RectangleInsets insets = getInsets();
    1994            insets.trim(area);
    1995
    1996            if (info != null) {
    1997                info.setPlotArea(area);
    1998                info.setDataArea(area);
    1999            }
    2000
    2001            drawBackground(g2, area);
    2002            drawOutline(g2, area);
    2003
    2004            Shape savedClip = g2.getClip();
    2005            g2.clip(area);
    2006
    2007            Composite originalComposite = g2.getComposite();
    2008            g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
    2009                    getForegroundAlpha()));
    2010
    2011            if (!DatasetUtilities.isEmptyOrNull(this.dataset)) {
    2012                drawPie(g2, area, info);
    2013            }
    2014            else {
    2015                drawNoDataMessage(g2, area);
    2016            }
    2017
    2018            g2.setClip(savedClip);
    2019            g2.setComposite(originalComposite);
    2020
    2021            drawOutline(g2, area);
    2022
    2023        }
    2024
    2025        /**
    2026         * Draws the pie.
    2027         *
    2028         * @param g2  the graphics device.
    2029         * @param plotArea  the plot area.
    2030         * @param info  chart rendering info.
    2031         */
    2032        protected void drawPie(Graphics2D g2, Rectangle2D plotArea,
    2033                               PlotRenderingInfo info) {
    2034
    2035            PiePlotState state = initialise(g2, plotArea, this, null, info);
    2036
    2037            // adjust the plot area for interior spacing and labels...
    2038            double labelWidth = 0.0;
    2039            if (this.labelGenerator != null) {
    2040                labelWidth = this.labelGap + this.maximumLabelWidth
    2041                             + this.labelLinkMargin;
    2042            }
    2043            double gapHorizontal
    2044                = plotArea.getWidth() * (this.interiorGap + labelWidth);
    2045            double gapVertical = plotArea.getHeight() * this.interiorGap;
    2046
    2047            double linkX = plotArea.getX() + gapHorizontal / 2;
    2048            double linkY = plotArea.getY() + gapVertical / 2;
    2049            double linkW = plotArea.getWidth() - gapHorizontal;
    2050            double linkH = plotArea.getHeight() - gapVertical;
    2051
    2052            // make the link area a square if the pie chart is to be circular...
    2053            if (this.circular) {
    2054                double min = Math.min(linkW, linkH) / 2;
    2055                linkX = (linkX + linkX + linkW) / 2 - min;
    2056                linkY = (linkY + linkY + linkH) / 2 - min;
    2057                linkW = 2 * min;
    2058                linkH = 2 * min;
    2059            }
    2060
    2061            // the link area defines the dog leg points for the linking lines to
    2062            // the labels
    2063            Rectangle2D linkArea = new Rectangle2D.Double(linkX, linkY, linkW,
    2064                    linkH);
    2065            state.setLinkArea(linkArea);
    2066
    2067            // the explode area defines the max circle/ellipse for the exploded
    2068            // pie sections.  it is defined by shrinking the linkArea by the
    2069            // linkMargin factor.
    2070            double hh = linkArea.getWidth() * this.labelLinkMargin;
    2071            double vv = linkArea.getHeight() * this.labelLinkMargin;
    2072            Rectangle2D explodeArea = new Rectangle2D.Double(linkX + hh / 2.0,
    2073                    linkY + vv / 2.0, linkW - hh, linkH - vv);
    2074
    2075            state.setExplodedPieArea(explodeArea);
    2076
    2077            // the pie area defines the circle/ellipse for regular pie sections.
    2078            // it is defined by shrinking the explodeArea by the explodeMargin
    2079            // factor.
    2080            double maximumExplodePercent = getMaximumExplodePercent();
    2081            double percent = maximumExplodePercent / (1.0 + maximumExplodePercent);
    2082
    2083            double h1 = explodeArea.getWidth() * percent;
    2084            double v1 = explodeArea.getHeight() * percent;
    2085            Rectangle2D pieArea = new Rectangle2D.Double(explodeArea.getX()
    2086                    + h1 / 2.0, explodeArea.getY() + v1 / 2.0,
    2087                    explodeArea.getWidth() - h1, explodeArea.getHeight() - v1);
    2088
    2089            state.setPieArea(pieArea);
    2090            state.setPieCenterX(pieArea.getCenterX());
    2091            state.setPieCenterY(pieArea.getCenterY());
    2092            state.setPieWRadius(pieArea.getWidth() / 2.0);
    2093            state.setPieHRadius(pieArea.getHeight() / 2.0);
    2094            // plot the data (unless the dataset is null)...
    2095            if ((this.dataset != null) && (this.dataset.getKeys().size() > 0)) {
    2096
    2097                List keys = this.dataset.getKeys();
    2098                double totalValue
    2099                    = DatasetUtilities.calculatePieDatasetTotal(this.dataset);
    2100
    2101                int passesRequired = state.getPassesRequired();
    2102                for (int pass = 0; pass < passesRequired; pass++) {
    2103                    double runningTotal = 0.0;
    2104                    for (int section = 0; section < keys.size(); section++) {
    2105                        Number n = this.dataset.getValue(section);
    2106                        if (n != null) {
    2107                            double value = n.doubleValue();
    2108                            if (value > 0.0) {
    2109                                runningTotal += value;
    2110                                drawItem(g2, section, explodeArea, state, pass);
    2111                            }
    2112                        }
    2113                    }
    2114                }
    2115
    2116                drawLabels(g2, keys, totalValue, plotArea, linkArea, state);
    2117
    2118            }
    2119            else {
    2120                drawNoDataMessage(g2, plotArea);
    2121            }
    2122        }
    2123
    2124        /**
    2125         * Draws a single data item.
    2126         *
    2127         * @param g2  the graphics device (<code>null</code> not permitted).
    2128         * @param section  the section index.
    2129         * @param dataArea  the data plot area.
    2130         * @param state  state information for one chart.
    2131         * @param currentPass  the current pass index.
    2132         */
    2133        protected void drawItem(Graphics2D g2, int section, Rectangle2D dataArea,
    2134                                PiePlotState state, int currentPass) {
    2135
    2136            Number n = this.dataset.getValue(section);
    2137            if (n == null) {
    2138                return;
    2139            }
    2140            double value = n.doubleValue();
    2141            double angle1 = 0.0;
    2142            double angle2 = 0.0;
    2143
    2144            if (this.direction == Rotation.CLOCKWISE) {
    2145                angle1 = state.getLatestAngle();
    2146                angle2 = angle1 - value / state.getTotal() * 360.0;
    2147            }
    2148            else if (this.direction == Rotation.ANTICLOCKWISE) {
    2149                angle1 = state.getLatestAngle();
    2150                angle2 = angle1 + value / state.getTotal() * 360.0;
    2151            }
    2152            else {
    2153                throw new IllegalStateException("Rotation type not recognised.");
    2154            }
    2155
    2156            double angle = (angle2 - angle1);
    2157            if (Math.abs(angle) > getMinimumArcAngleToDraw()) {
    2158                double ep = 0.0;
    2159                double mep = getMaximumExplodePercent();
    2160                if (mep > 0.0) {
    2161                    ep = getExplodePercent(section) / mep;
    2162                }
    2163                Rectangle2D arcBounds = getArcBounds(state.getPieArea(),
    2164                        state.getExplodedPieArea(), angle1, angle, ep);
    2165                Arc2D.Double arc = new Arc2D.Double(arcBounds, angle1, angle,
    2166                        Arc2D.PIE);
    2167
    2168                if (currentPass == 0) {
    2169                    if (this.shadowPaint != null) {
    2170                        Shape shadowArc = ShapeUtilities.createTranslatedShape(
    2171                                arc, (float) this.shadowXOffset,
    2172                                (float) this.shadowYOffset);
    2173                        g2.setPaint(this.shadowPaint);
    2174                        g2.fill(shadowArc);
    2175                    }
    2176                }
    2177                else if (currentPass == 1) {
    2178                    Comparable key = getSectionKey(section);
    2179                    Paint paint = lookupSectionPaint(key, true);
    2180                    g2.setPaint(paint);
    2181                    g2.fill(arc);
    2182
    2183                    Paint outlinePaint = lookupSectionOutlinePaint(key);
    2184                    Stroke outlineStroke = lookupSectionOutlineStroke(key);
    2185                    if (this.sectionOutlinesVisible) {
    2186                        g2.setPaint(outlinePaint);
    2187                        g2.setStroke(outlineStroke);
    2188                        g2.draw(arc);
    2189                    }
    2190
    2191                    // update the linking line target for later
    2192                    // add an entity for the pie section
    2193                    if (state.getInfo() != null) {
    2194                        EntityCollection entities = state.getEntityCollection();
    2195                        if (entities != null) {
    2196                            String tip = null;
    2197                            if (this.toolTipGenerator != null) {
    2198                                tip = this.toolTipGenerator.generateToolTip(
    2199                                        this.dataset, key);
    2200                            }
    2201                            String url = null;
    2202                            if (this.urlGenerator != null) {
    2203                                url = this.urlGenerator.generateURL(this.dataset,
    2204                                        key, this.pieIndex);
    2205                            }
    2206                            PieSectionEntity entity = new PieSectionEntity(
    2207                                    arc, this.dataset, this.pieIndex, section, key,
    2208                                    tip, url);
    2209                            entities.add(entity);
    2210                        }
    2211                    }
    2212                }
    2213            }
    2214            state.setLatestAngle(angle2);
    2215        }
    2216
    2217        /**
    2218         * Draws the labels for the pie sections.
    2219         *
    2220         * @param g2  the graphics device.
    2221         * @param keys  the keys.
    2222         * @param totalValue  the total value.
    2223         * @param plotArea  the plot area.
    2224         * @param linkArea  the link area.
    2225         * @param state  the state.
    2226         */
    2227        protected void drawLabels(Graphics2D g2, List keys, double totalValue,
    2228                                  Rectangle2D plotArea, Rectangle2D linkArea,
    2229                                  PiePlotState state) {
    2230
    2231            Composite originalComposite = g2.getComposite();
    2232            g2.setComposite(
    2233                    AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f));
    2234
    2235            // classify the keys according to which side the label will appear...
    2236            DefaultKeyedValues leftKeys = new DefaultKeyedValues();
    2237            DefaultKeyedValues rightKeys = new DefaultKeyedValues();
    2238
    2239            double runningTotal1 = 0.0;
    2240            Iterator iterator1 = keys.iterator();
    2241            while (iterator1.hasNext()) {
    2242                Comparable key = (Comparable) iterator1.next();
    2243                boolean include = true;
    2244                double v = 0.0;
    2245                Number n = this.dataset.getValue(key);
    2246                if (n == null) {
    2247                    include = !this.ignoreNullValues;
    2248                }
    2249                else {
    2250                    v = n.doubleValue();
    2251                    include = this.ignoreZeroValues ? v > 0.0 : v >= 0.0;
    2252                }
    2253
    2254                if (include) {
    2255                    runningTotal1 = runningTotal1 + v;
    2256                    // work out the mid angle (0 - 90 and 270 - 360) = right,
    2257                    // otherwise left
    2258                    double mid = this.startAngle + (this.direction.getFactor()
    2259                        * ((runningTotal1 - v / 2.0) * 360) / totalValue);
    2260                    if (Math.cos(Math.toRadians(mid)) < 0.0) {
    2261                        leftKeys.addValue(key, new Double(mid));
    2262                    }
    2263                    else {
    2264                        rightKeys.addValue(key, new Double(mid));
    2265                    }
    2266                }
    2267            }
    2268
    2269            g2.setFont(getLabelFont());
    2270            float maxLabelWidth
    2271                = (float) (getMaximumLabelWidth() * plotArea.getWidth());
    2272
    2273            // draw the labels...
    2274            if (this.labelGenerator != null) {
    2275                drawLeftLabels(leftKeys, g2, plotArea, linkArea, maxLabelWidth,
    2276                        state);
    2277                drawRightLabels(rightKeys, g2, plotArea, linkArea, maxLabelWidth,
    2278                        state);
    2279            }
    2280            g2.setComposite(originalComposite);
    2281
    2282        }
    2283
    2284        /**
    2285         * Draws the left labels.
    2286         *
    2287         * @param leftKeys  the keys.
    2288         * @param g2  the graphics device.
    2289         * @param plotArea  the plot area.
    2290         * @param linkArea  the link area.
    2291         * @param maxLabelWidth  the maximum label width.
    2292         * @param state  the state.
    2293         */
    2294        protected void drawLeftLabels(KeyedValues leftKeys, Graphics2D g2,
    2295                                      Rectangle2D plotArea, Rectangle2D linkArea,
    2296                                      float maxLabelWidth, PiePlotState state) {
    2297
    2298            PieLabelDistributor distributor1 = new PieLabelDistributor(
    2299                leftKeys.getItemCount()
    2300            );
    2301            double lGap = plotArea.getWidth() * this.labelGap;
    2302            double verticalLinkRadius = state.getLinkArea().getHeight() / 2.0;
    2303            for (int i = 0; i < leftKeys.getItemCount(); i++) {
    2304                String label = this.labelGenerator.generateSectionLabel(
    2305                        this.dataset, leftKeys.getKey(i));
    2306                if (label != null) {
    2307                    TextBlock block = TextUtilities.createTextBlock(label,
    2308                            this.labelFont, this.labelPaint, maxLabelWidth,
    2309                            new G2TextMeasurer(g2));
    2310                    TextBox labelBox = new TextBox(block);
    2311                    labelBox.setBackgroundPaint(this.labelBackgroundPaint);
    2312                    labelBox.setOutlinePaint(this.labelOutlinePaint);
    2313                    labelBox.setOutlineStroke(this.labelOutlineStroke);
    2314                    labelBox.setShadowPaint(this.labelShadowPaint);
    2315                    double theta = Math.toRadians(
    2316                            leftKeys.getValue(i).doubleValue());
    2317                    double baseY = state.getPieCenterY() - Math.sin(theta)
    2318                                   * verticalLinkRadius;
    2319                    double hh = labelBox.getHeight(g2);
    2320
    2321                    distributor1.addPieLabelRecord(new PieLabelRecord(
    2322                            leftKeys.getKey(i), theta, baseY, labelBox, hh,
    2323                            lGap / 2.0 + lGap / 2.0 * -Math.cos(theta), 0.9
    2324                            + getExplodePercent(this.dataset.getIndex(
    2325                                    leftKeys.getKey(i)))));
    2326                }
    2327            }
    2328            distributor1.distributeLabels(plotArea.getMinY(), plotArea.getHeight());
    2329            for (int i = 0; i < distributor1.getItemCount(); i++) {
    2330                drawLeftLabel(g2, state, distributor1.getPieLabelRecord(i));
    2331            }
    2332        }
    2333
    2334        /**
    2335         * Draws the right labels.
    2336         *
    2337         * @param keys  the keys.
    2338         * @param g2  the graphics device.
    2339         * @param plotArea  the plot area.
    2340         * @param linkArea  the link area.
    2341         * @param maxLabelWidth  the maximum label width.
    2342         * @param state  the state.
    2343         */
    2344        protected void drawRightLabels(KeyedValues keys, Graphics2D g2,
    2345                                       Rectangle2D plotArea, Rectangle2D linkArea,
    2346                                       float maxLabelWidth, PiePlotState state) {
    2347
    2348            // draw the right labels...
    2349            PieLabelDistributor distributor2
    2350                = new PieLabelDistributor(keys.getItemCount());
    2351            double lGap = plotArea.getWidth() * this.labelGap;
    2352            double verticalLinkRadius = state.getLinkArea().getHeight() / 2.0;
    2353
    2354            for (int i = 0; i < keys.getItemCount(); i++) {
    2355                String label = this.labelGenerator.generateSectionLabel(
    2356                        this.dataset, keys.getKey(i));
    2357
    2358                if (label != null) {
    2359                    TextBlock block = TextUtilities.createTextBlock(label,
    2360                            this.labelFont, this.labelPaint, maxLabelWidth,
    2361                            new G2TextMeasurer(g2));
    2362                    TextBox labelBox = new TextBox(block);
    2363                    labelBox.setBackgroundPaint(this.labelBackgroundPaint);
    2364                    labelBox.setOutlinePaint(this.labelOutlinePaint);
    2365                    labelBox.setOutlineStroke(this.labelOutlineStroke);
    2366                    labelBox.setShadowPaint(this.labelShadowPaint);
    2367                    double theta = Math.toRadians(keys.getValue(i).doubleValue());
    2368                    double baseY = state.getPieCenterY()
    2369                                  - Math.sin(theta) * verticalLinkRadius;
    2370                    double hh = labelBox.getHeight(g2);
    2371                    distributor2.addPieLabelRecord(new PieLabelRecord(
    2372                            keys.getKey(i), theta, baseY, labelBox, hh,
    2373                            lGap / 2.0 + lGap / 2.0 * Math.cos(theta),
    2374                            0.9 + getExplodePercent(this.dataset.getIndex(
    2375                                    keys.getKey(i)))));
    2376                }
    2377            }
    2378            distributor2.distributeLabels(plotArea.getMinY(), plotArea.getHeight());
    2379            for (int i = 0; i < distributor2.getItemCount(); i++) {
    2380                drawRightLabel(g2, state, distributor2.getPieLabelRecord(i));
    2381            }
    2382
    2383        }
    2384
    2385        /**
    2386         * Returns a collection of legend items for the pie chart.
    2387         *
    2388         * @return The legend items (never <code>null</code>).
    2389         */
    2390        public LegendItemCollection getLegendItems() {
    2391
    2392            LegendItemCollection result = new LegendItemCollection();
    2393            if (this.dataset == null) {
    2394                return result;
    2395            }
    2396            List keys = this.dataset.getKeys();
    2397            int section = 0;
    2398            Shape shape = getLegendItemShape();
    2399            Iterator iterator = keys.iterator();
    2400            while (iterator.hasNext()) {
    2401                Comparable key = (Comparable) iterator.next();
    2402                Number n = this.dataset.getValue(key);
    2403                boolean include = true;
    2404                if (n == null) {
    2405                    include = !this.ignoreNullValues;
    2406                }
    2407                else {
    2408                    double v = n.doubleValue();
    2409                    if (v == 0.0) {
    2410                        include = !this.ignoreZeroValues;
    2411                    }
    2412                    else {
    2413                        include = v > 0.0;
    2414                    }
    2415                }
    2416                if (include) {
    2417                    String label = this.legendLabelGenerator.generateSectionLabel(
    2418                            this.dataset, key);
    2419                    String description = label;
    2420                    String toolTipText = null;
    2421                    if (this.legendLabelToolTipGenerator != null) {
    2422                        toolTipText
    2423                            = this.legendLabelToolTipGenerator.generateSectionLabel(
    2424                                    this.dataset, key);
    2425                    }
    2426                    String urlText = null;
    2427                    if (this.legendLabelURLGenerator != null) {
    2428                        urlText = this.legendLabelURLGenerator.generateURL(
    2429                                this.dataset, key, this.pieIndex);
    2430                    }
    2431                    Paint paint = lookupSectionPaint(key, true);
    2432                    Paint outlinePaint = lookupSectionOutlinePaint(key);
    2433                    Stroke outlineStroke = lookupSectionOutlineStroke(key);
    2434
    2435                    LegendItem item = new LegendItem(label, description,
    2436                            toolTipText, urlText, true, shape, true, paint,
    2437                            true, outlinePaint, outlineStroke,
    2438                            false,          // line not visible
    2439                            new Line2D.Float(), new BasicStroke(), Color.black);
    2440                    result.add(item);
    2441                    section++;
    2442                }
    2443                else {
    2444                    section++;
    2445                }
    2446            }
    2447            return result;
    2448        }
    2449
    2450        /**
    2451         * Returns a short string describing the type of plot.
    2452         *
    2453         * @return The plot type.
    2454         */
    2455        public String getPlotType() {
    2456            return localizationResources.getString("Pie_Plot");
    2457        }
    2458
    2459        /**
    2460         * A zoom method that does nothing.
    2461         * <p>
    2462         * Plots are required to support the zoom operation.  In the case of a pie
    2463         * chart, it doesn't make sense to zoom in or out, so the method is empty.
    2464         *
    2465         * @param percent  the zoom percentage.
    2466         */
    2467        public void zoom(double percent) {
    2468            // no zooming for pie plots
    2469        }
    2470
    2471        /**
    2472         * Returns a rectangle that can be used to create a pie section (taking
    2473         * into account the amount by which the pie section is 'exploded').
    2474         *
    2475         * @param unexploded  the area inside which the unexploded pie sections are
    2476         *                    drawn.
    2477         * @param exploded  the area inside which the exploded pie sections are
    2478         *                  drawn.
    2479         * @param angle  the start angle.
    2480         * @param extent  the extent of the arc.
    2481         * @param explodePercent  the amount by which the pie section is exploded.
    2482         *
    2483         * @return A rectangle that can be used to create a pie section.
    2484         */
    2485        protected Rectangle2D getArcBounds(Rectangle2D unexploded,
    2486                                           Rectangle2D exploded,
    2487                                           double angle, double extent,
    2488                                           double explodePercent) {
    2489
    2490            if (explodePercent == 0.0) {
    2491                return unexploded;
    2492            }
    2493            else {
    2494                Arc2D arc1 = new Arc2D.Double(unexploded, angle, extent / 2,
    2495                        Arc2D.OPEN);
    2496                Point2D point1 = arc1.getEndPoint();
    2497                Arc2D.Double arc2 = new Arc2D.Double(exploded, angle, extent / 2,
    2498                        Arc2D.OPEN);
    2499                Point2D point2 = arc2.getEndPoint();
    2500                double deltaX = (point1.getX() - point2.getX()) * explodePercent;
    2501                double deltaY = (point1.getY() - point2.getY()) * explodePercent;
    2502                return new Rectangle2D.Double(unexploded.getX() - deltaX,
    2503                        unexploded.getY() - deltaY, unexploded.getWidth(),
    2504                        unexploded.getHeight());
    2505            }
    2506        }
    2507
    2508        /**
    2509         * Draws a section label on the left side of the pie chart.
    2510         *
    2511         * @param g2  the graphics device.
    2512         * @param state  the state.
    2513         * @param record  the label record.
    2514         */
    2515        protected void drawLeftLabel(Graphics2D g2, PiePlotState state,
    2516                                     PieLabelRecord record) {
    2517
    2518            double anchorX = state.getLinkArea().getMinX();
    2519            double targetX = anchorX - record.getGap();
    2520            double targetY = record.getAllocatedY();
    2521
    2522            if (this.labelLinksVisible) {
    2523                double theta = record.getAngle();
    2524                double linkX = state.getPieCenterX() + Math.cos(theta)
    2525                    * state.getPieWRadius() * record.getLinkPercent();
    2526                double linkY = state.getPieCenterY() - Math.sin(theta)
    2527                    * state.getPieHRadius() * record.getLinkPercent();
    2528                double elbowX = state.getPieCenterX() + Math.cos(theta)
    2529                    * state.getLinkArea().getWidth() / 2.0;
    2530                double elbowY = state.getPieCenterY() - Math.sin(theta)
    2531                    * state.getLinkArea().getHeight() / 2.0;
    2532                double anchorY = elbowY;
    2533                g2.setPaint(this.labelLinkPaint);
    2534                g2.setStroke(this.labelLinkStroke);
    2535                g2.draw(new Line2D.Double(linkX, linkY, elbowX, elbowY));
    2536                g2.draw(new Line2D.Double(anchorX, anchorY, elbowX, elbowY));
    2537                g2.draw(new Line2D.Double(anchorX, anchorY, targetX, targetY));
    2538            }
    2539            TextBox tb = record.getLabel();
    2540            tb.draw(g2, (float) targetX, (float) targetY, RectangleAnchor.RIGHT);
    2541
    2542        }
    2543
    2544        /**
    2545         * Draws a section label on the right side of the pie chart.
    2546         *
    2547         * @param g2  the graphics device.
    2548         * @param state  the state.
    2549         * @param record  the label record.
    2550         */
    2551        protected void drawRightLabel(Graphics2D g2, PiePlotState state,
    2552                                      PieLabelRecord record) {
    2553
    2554            double anchorX = state.getLinkArea().getMaxX();
    2555            double targetX = anchorX + record.getGap();
    2556            double targetY = record.getAllocatedY();
    2557
    2558            if (this.labelLinksVisible) {
    2559                double theta = record.getAngle();
    2560                double linkX = state.getPieCenterX() + Math.cos(theta)
    2561                    * state.getPieWRadius() * record.getLinkPercent();
    2562                double linkY = state.getPieCenterY() - Math.sin(theta)
    2563                    * state.getPieHRadius() * record.getLinkPercent();
    2564                double elbowX = state.getPieCenterX() + Math.cos(theta)
    2565                    * state.getLinkArea().getWidth() / 2.0;
    2566                double elbowY = state.getPieCenterY() - Math.sin(theta)
    2567                    * state.getLinkArea().getHeight() / 2.0;
    2568                double anchorY = elbowY;
    2569                g2.setPaint(this.labelLinkPaint);
    2570                g2.setStroke(this.labelLinkStroke);
    2571                g2.draw(new Line2D.Double(linkX, linkY, elbowX, elbowY));
    2572                g2.draw(new Line2D.Double(anchorX, anchorY, elbowX, elbowY));
    2573                g2.draw(new Line2D.Double(anchorX, anchorY, targetX, targetY));
    2574            }
    2575
    2576            TextBox tb = record.getLabel();
    2577            tb.draw(g2, (float) targetX, (float) targetY, RectangleAnchor.LEFT);
    2578
    2579        }
    2580
    2581        /**
    2582         * Tests this plot for equality with an arbitrary object.  Note that the
    2583         * plot's dataset is NOT included in the test for equality.
    2584         *
    2585         * @param obj  the object to test against (<code>null</code> permitted).
    2586         *
    2587         * @return <code>true</code> or <code>false</code>.
    2588         */
    2589        public boolean equals(Object obj) {
    2590            if (obj == this) {
    2591                return true;
    2592            }
    2593            if (!(obj instanceof PiePlot)) {
    2594                return false;
    2595            }
    2596            if (!super.equals(obj)) {
    2597                return false;
    2598            }
    2599            PiePlot that = (PiePlot) obj;
    2600            if (this.pieIndex != that.pieIndex) {
    2601                return false;
    2602            }
    2603            if (this.interiorGap != that.interiorGap) {
    2604                return false;
    2605            }
    2606            if (this.circular != that.circular) {
    2607                return false;
    2608            }
    2609            if (this.startAngle != that.startAngle) {
    2610                return false;
    2611            }
    2612            if (this.direction != that.direction) {
    2613                return false;
    2614            }
    2615            if (this.ignoreZeroValues != that.ignoreZeroValues) {
    2616                return false;
    2617            }
    2618            if (this.ignoreNullValues != that.ignoreNullValues) {
    2619                return false;
    2620            }
    2621            if (!PaintUtilities.equal(this.sectionPaint, that.sectionPaint)) {
    2622                return false;
    2623            }
    2624            if (!ObjectUtilities.equal(this.sectionPaintMap,
    2625                    that.sectionPaintMap)) {
    2626                return false;
    2627            }
    2628            if (!PaintUtilities.equal(this.baseSectionPaint,
    2629                    that.baseSectionPaint)) {
    2630                return false;
    2631            }
    2632            if (this.sectionOutlinesVisible != that.sectionOutlinesVisible) {
    2633                return false;
    2634            }
    2635            if (!PaintUtilities.equal(this.sectionOutlinePaint,
    2636                    that.sectionOutlinePaint)) {
    2637                return false;
    2638            }
    2639            if (!ObjectUtilities.equal(this.sectionOutlinePaintMap,
    2640                    that.sectionOutlinePaintMap)) {
    2641                return false;
    2642            }
    2643            if (!PaintUtilities.equal(
    2644                this.baseSectionOutlinePaint, that.baseSectionOutlinePaint
    2645            )) {
    2646                return false;
    2647            }
    2648            if (!ObjectUtilities.equal(this.sectionOutlineStroke,
    2649                    that.sectionOutlineStroke)) {
    2650                return false;
    2651            }
    2652            if (!ObjectUtilities.equal(this.sectionOutlineStrokeMap,
    2653                    that.sectionOutlineStrokeMap)) {
    2654                return false;
    2655            }
    2656            if (!ObjectUtilities.equal(
    2657                this.baseSectionOutlineStroke, that.baseSectionOutlineStroke
    2658            )) {
    2659                return false;
    2660            }
    2661            if (!PaintUtilities.equal(this.shadowPaint, that.shadowPaint)) {
    2662                return false;
    2663            }
    2664            if (!(this.shadowXOffset == that.shadowXOffset)) {
    2665                return false;
    2666            }
    2667            if (!(this.shadowYOffset == that.shadowYOffset)) {
    2668                return false;
    2669            }
    2670            if (!ObjectUtilities.equal(this.explodePercentages,
    2671                    that.explodePercentages)) {
    2672                return false;
    2673            }
    2674            if (!ObjectUtilities.equal(this.labelGenerator,
    2675                    that.labelGenerator)) {
    2676                return false;
    2677            }
    2678            if (!ObjectUtilities.equal(this.labelFont, that.labelFont)) {
    2679                return false;
    2680            }
    2681            if (!PaintUtilities.equal(this.labelPaint, that.labelPaint)) {
    2682                return false;
    2683            }
    2684            if (!PaintUtilities.equal(this.labelBackgroundPaint,
    2685                    that.labelBackgroundPaint)) {
    2686                return false;
    2687            }
    2688            if (!PaintUtilities.equal(this.labelOutlinePaint,
    2689                    that.labelOutlinePaint)) {
    2690                return false;
    2691            }
    2692            if (!ObjectUtilities.equal(this.labelOutlineStroke,
    2693                    that.labelOutlineStroke)) {
    2694                return false;
    2695            }
    2696            if (!PaintUtilities.equal(this.labelShadowPaint,
    2697                    that.labelShadowPaint)) {
    2698                return false;
    2699            }
    2700            if (!(this.maximumLabelWidth == that.maximumLabelWidth)) {
    2701                return false;
    2702            }
    2703            if (!(this.labelGap == that.labelGap)) {
    2704                return false;
    2705            }
    2706            if (!(this.labelLinkMargin == that.labelLinkMargin)) {
    2707                return false;
    2708            }
    2709            if (this.labelLinksVisible != that.labelLinksVisible) {
    2710                return false;
    2711            }
    2712            if (!PaintUtilities.equal(this.labelLinkPaint, that.labelLinkPaint)) {
    2713                return false;
    2714            }
    2715            if (!ObjectUtilities.equal(this.labelLinkStroke,
    2716                    that.labelLinkStroke)) {
    2717                return false;
    2718            }
    2719            if (!ObjectUtilities.equal(this.toolTipGenerator,
    2720                    that.toolTipGenerator)) {
    2721                return false;
    2722            }
    2723            if (!ObjectUtilities.equal(this.urlGenerator, that.urlGenerator)) {
    2724                return false;
    2725            }
    2726            if (!(this.minimumArcAngleToDraw == that.minimumArcAngleToDraw)) {
    2727                return false;
    2728            }
    2729            if (!ShapeUtilities.equal(this.legendItemShape, that.legendItemShape)) {
    2730                return false;
    2731            }
    2732            if (!ObjectUtilities.equal(this.legendLabelGenerator,
    2733                    that.legendLabelGenerator)) {
    2734                return false;
    2735            }
    2736            if (!ObjectUtilities.equal(this.legendLabelToolTipGenerator,
    2737                    that.legendLabelToolTipGenerator)) {
    2738                return false;
    2739            }
    2740            if (!ObjectUtilities.equal(this.legendLabelURLGenerator,
    2741                    that.legendLabelURLGenerator)) {
    2742                return false;
    2743            }
    2744            // can't find any difference...
    2745            return true;
    2746        }
    2747
    2748        /**
    2749         * Returns a clone of the plot.
    2750         *
    2751         * @return A clone.
    2752         *
    2753         * @throws CloneNotSupportedException if some component of the plot does
    2754         *         not support cloning.
    2755         */
    2756        public Object clone() throws CloneNotSupportedException {
    2757            PiePlot clone = (PiePlot) super.clone();
    2758            if (clone.dataset != null) {
    2759                clone.dataset.addChangeListener(clone);
    2760            }
    2761            if (this.urlGenerator instanceof PublicCloneable) {
    2762                clone.urlGenerator = (PieURLGenerator) ObjectUtilities.clone(
    2763                        this.urlGenerator);
    2764            }
    2765            clone.legendItemShape = ShapeUtilities.clone(this.legendItemShape);
    2766            if (this.legendLabelGenerator != null) {
    2767                clone.legendLabelGenerator = (PieSectionLabelGenerator)
    2768                        ObjectUtilities.clone(this.legendLabelGenerator);
    2769            }
    2770            if (this.legendLabelToolTipGenerator != null) {
    2771                clone.legendLabelToolTipGenerator = (PieSectionLabelGenerator)
    2772                        ObjectUtilities.clone(this.legendLabelToolTipGenerator);
    2773            }
    2774            if (this.legendLabelURLGenerator instanceof PublicCloneable) {
    2775                clone.legendLabelURLGenerator = (PieURLGenerator)
    2776                        ObjectUtilities.clone(this.legendLabelURLGenerator);
    2777            }
    2778            return clone;
    2779        }
    2780
    2781        /**
    2782         * Provides serialization support.
    2783         *
    2784         * @param stream  the output stream.
    2785         *
    2786         * @throws IOException  if there is an I/O error.
    2787         */
    2788        private void writeObject(ObjectOutputStream stream) throws IOException {
    2789            stream.defaultWriteObject();
    2790            SerialUtilities.writePaint(this.sectionPaint, stream);
    2791            SerialUtilities.writePaint(this.baseSectionPaint, stream);
    2792            SerialUtilities.writePaint(this.sectionOutlinePaint, stream);
    2793            SerialUtilities.writePaint(this.baseSectionOutlinePaint, stream);
    2794            SerialUtilities.writeStroke(this.sectionOutlineStroke, stream);
    2795            SerialUtilities.writeStroke(this.baseSectionOutlineStroke, stream);
    2796            SerialUtilities.writePaint(this.shadowPaint, stream);
    2797            SerialUtilities.writePaint(this.labelPaint, stream);
    2798            SerialUtilities.writePaint(this.labelBackgroundPaint, stream);
    2799            SerialUtilities.writePaint(this.labelOutlinePaint, stream);
    2800            SerialUtilities.writeStroke(this.labelOutlineStroke, stream);
    2801            SerialUtilities.writePaint(this.labelShadowPaint, stream);
    2802            SerialUtilities.writePaint(this.labelLinkPaint, stream);
    2803            SerialUtilities.writeStroke(this.labelLinkStroke, stream);
    2804            SerialUtilities.writeShape(this.legendItemShape, stream);
    2805        }
    2806
    2807        /**
    2808         * Provides serialization support.
    2809         *
    2810         * @param stream  the input stream.
    2811         *
    2812         * @throws IOException  if there is an I/O error.
    2813         * @throws ClassNotFoundException  if there is a classpath problem.
    2814         */
    2815        private void readObject(ObjectInputStream stream)
    2816            throws IOException, ClassNotFoundException {
    2817            stream.defaultReadObject();
    2818            this.sectionPaint = SerialUtilities.readPaint(stream);
    2819            this.baseSectionPaint = SerialUtilities.readPaint(stream);
    2820            this.sectionOutlinePaint = SerialUtilities.readPaint(stream);
    2821            this.baseSectionOutlinePaint = SerialUtilities.readPaint(stream);
    2822            this.sectionOutlineStroke = SerialUtilities.readStroke(stream);
    2823            this.baseSectionOutlineStroke = SerialUtilities.readStroke(stream);
    2824            this.shadowPaint = SerialUtilities.readPaint(stream);
    2825            this.labelPaint = SerialUtilities.readPaint(stream);
    2826            this.labelBackgroundPaint = SerialUtilities.readPaint(stream);
    2827            this.labelOutlinePaint = SerialUtilities.readPaint(stream);
    2828            this.labelOutlineStroke = SerialUtilities.readStroke(stream);
    2829            this.labelShadowPaint = SerialUtilities.readPaint(stream);
    2830            this.labelLinkPaint = SerialUtilities.readPaint(stream);
    2831            this.labelLinkStroke = SerialUtilities.readStroke(stream);
    2832            this.legendItemShape = SerialUtilities.readShape(stream);
    2833        }
    2834
    2835        // DEPRECATED METHODS...
    2836
    2837        /**
    2838         * Returns the paint for the specified section.
    2839         *
    2840         * @param section  the section index (zero-based).
    2841         *
    2842         * @return The paint (never <code>null</code>).
    2843         *
    2844         * @deprecated Use {@link #getSectionPaint(Comparable)} instead.
    2845         */
    2846        public Paint getSectionPaint(int section) {
    2847            Comparable key = getSectionKey(section);
    2848            return getSectionPaint(key);
    2849        }
    2850
    2851        /**
    2852         * Sets the paint used to fill a section of the pie and sends a
    2853         * {@link PlotChangeEvent} to all registered listeners.
    2854         *
    2855         * @param section  the section index (zero-based).
    2856         * @param paint  the paint (<code>null</code> permitted).
    2857         *
    2858         * @deprecated Use {@link #setSectionPaint(Comparable, Paint)} instead.
    2859         */
    2860        public void setSectionPaint(int section, Paint paint) {
    2861            Comparable key = getSectionKey(section);
    2862            setSectionPaint(key, paint);
    2863        }
    2864
    2865        /**
    2866         * Returns the paint for the specified section.
    2867         *
    2868         * @param section  the section index (zero-based).
    2869         *
    2870         * @return The paint (possibly <code>null</code>).
    2871         *
    2872         * @deprecated Use {@link #getSectionOutlinePaint(Comparable)} instead.
    2873         */
    2874        public Paint getSectionOutlinePaint(int section) {
    2875            Comparable key = getSectionKey(section);
    2876            return getSectionOutlinePaint(key);
    2877        }
    2878
    2879        /**
    2880         * Sets the paint used to fill a section of the pie and sends a
    2881         * {@link PlotChangeEvent} to all registered listeners.
    2882         *
    2883         * @param section  the section index (zero-based).
    2884         * @param paint  the paint (<code>null</code> permitted).
    2885         *
    2886         * @deprecated Use {@link #setSectionOutlinePaint(Comparable, Paint)}
    2887         *     instead.
    2888         */
    2889        public void setSectionOutlinePaint(int section, Paint paint) {
    2890            Comparable key = getSectionKey(section);
    2891            setSectionOutlinePaint(key, paint);
    2892        }
    2893
    2894        /**
    2895         * Returns the stroke for the specified section.
    2896         *
    2897         * @param section  the section index (zero-based).
    2898         *
    2899         * @return The stroke (possibly <code>null</code>).
    2900         *
    2901         * @deprecated Use {@link #getSectionOutlineStroke(Comparable)} instead.
    2902         */
    2903        public Stroke getSectionOutlineStroke(int section) {
    2904            Comparable key = getSectionKey(section);
    2905            return getSectionOutlineStroke(key);
    2906        }
    2907
    2908        /**
    2909         * Sets the stroke used to fill a section of the pie and sends a
    2910         * {@link PlotChangeEvent} to all registered listeners.
    2911         *
    2912         * @param section  the section index (zero-based).
    2913         * @param stroke  the stroke (<code>null</code> permitted).
    2914         *
    2915         * @deprecated Use {@link #setSectionOutlineStroke(Comparable, Stroke)}
    2916         *     instead.
    2917         */
    2918        public void setSectionOutlineStroke(int section, Stroke stroke) {
    2919            Comparable key = getSectionKey(section);
    2920            setSectionOutlineStroke(key, stroke);
    2921        }
    2922
    2923        /**
    2924         * Returns the amount that a section should be 'exploded'.
    2925         *
    2926         * @param section  the section number.
    2927         *
    2928         * @return The amount that a section should be 'exploded'.
    2929         *
    2930         * @deprecated Use {@link #getExplodePercent(Comparable)} instead.
    2931         */
    2932        public double getExplodePercent(int section) {
    2933            Comparable key = getSectionKey(section);
    2934            return getExplodePercent(key);
    2935        }
    2936
    2937        /**
    2938         * Sets the amount that a pie section should be exploded and sends a
    2939         * {@link PlotChangeEvent} to all registered listeners.
    2940         *
    2941         * @param section  the section index.
    2942         * @param percent  the explode percentage (0.30 = 30 percent).
    2943         *
    2944         * @deprecated Use {@link #setExplodePercent(Comparable, double)} instead.
    2945         */
    2946        public void setExplodePercent(int section, double percent) {
    2947            Comparable key = getSectionKey(section);
    2948            setExplodePercent(key, percent);
    2949        }
    2950
    2951
    2952    }