    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2008, 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-2008, 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     *                   Martin Hilpert (patch 1891849);
    039     *                   Andreas Schroeder (very minor);
    040     *                   Christoph Beck (bug 2121818);
    041     *
    042     * Changes
    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     * 17-Apr-2007 : Check for null label in legend items (DG);
    146     * 19-Apr-2007 : Deprecated override settings (DG);
    147     * 18-May-2007 : Set dataset for LegendItem (DG);
    148     * 14-Jun-2007 : Added label distributor attribute (DG);
    149     * 18-Jul-2007 : Added simple label option (DG);
    150     * 21-Nov-2007 : Fixed labelling bugs, added debug code, restored default
    151     *               white background (DG);
    152     * 19-Mar-2008 : Fixed IllegalArgumentException when drawing with null
    153     *               dataset (DG);
    154     * 31-Mar-2008 : Adjust the label area for the interiorGap (DG);
    155     * 31-Mar-2008 : Added quad and cubic curve label link lines - see patch
    156     *               1891849 by Martin Hilpert (DG);
    157     * 02-Jul-2008 : Added autoPopulate flags (DG);
    158     * 15-Aug-2008 : Added methods to clear section attributes (DG);
    159     * 15-Aug-2008 : Fixed bug 2051168 - problem with LegendItemEntity
    160     *               generation (DG);
    161     * 23-Sep-2008 : Added getLabelLinkDepth() method - see bug 2121818 reported
    162     *               by Christoph Beck (DG);
    163     * 18-Dec-2008 : Use ResourceBundleWrapper - see patch 1607918 by
    164     *               Jess Thrysoee (DG);
    165     *
    166     */
    167
    168    package org.jfree.chart.plot;
    169
    170    import java.awt.AlphaComposite;
    171    import java.awt.BasicStroke;
    172    import java.awt.Color;
    173    import java.awt.Composite;
    174    import java.awt.Font;
    175    import java.awt.FontMetrics;
    176    import java.awt.Graphics2D;
    177    import java.awt.Paint;
    178    import java.awt.Shape;
    179    import java.awt.Stroke;
    180    import java.awt.geom.Arc2D;
    181    import java.awt.geom.CubicCurve2D;
    182    import java.awt.geom.Ellipse2D;
    183    import java.awt.geom.Line2D;
    184    import java.awt.geom.Point2D;
    185    import java.awt.geom.QuadCurve2D;
    186    import java.awt.geom.Rectangle2D;
    187    import java.io.IOException;
    188    import java.io.ObjectInputStream;
    189    import java.io.ObjectOutputStream;
    190    import java.io.Serializable;
    191    import java.util.Iterator;
    192    import java.util.List;
    193    import java.util.Map;
    194    import java.util.ResourceBundle;
    195    import java.util.TreeMap;
    196
    197    import org.jfree.chart.LegendItem;
    198    import org.jfree.chart.LegendItemCollection;
    199    import org.jfree.chart.PaintMap;
    200    import org.jfree.chart.StrokeMap;
    201    import org.jfree.chart.entity.EntityCollection;
    202    import org.jfree.chart.entity.PieSectionEntity;
    203    import org.jfree.chart.event.PlotChangeEvent;
    204    import org.jfree.chart.labels.PieSectionLabelGenerator;
    205    import org.jfree.chart.labels.PieToolTipGenerator;
    206    import org.jfree.chart.labels.StandardPieSectionLabelGenerator;
    207    import org.jfree.chart.urls.PieURLGenerator;
    208    import org.jfree.chart.util.ResourceBundleWrapper;
    209    import org.jfree.data.DefaultKeyedValues;
    210    import org.jfree.data.KeyedValues;
    211    import org.jfree.data.general.DatasetChangeEvent;
    212    import org.jfree.data.general.DatasetUtilities;
    213    import org.jfree.data.general.PieDataset;
    214    import org.jfree.io.SerialUtilities;
    215    import org.jfree.text.G2TextMeasurer;
    216    import org.jfree.text.TextBlock;
    217    import org.jfree.text.TextBox;
    218    import org.jfree.text.TextUtilities;
    219    import org.jfree.ui.RectangleAnchor;
    220    import org.jfree.ui.RectangleInsets;
    221    import org.jfree.ui.TextAnchor;
    222    import org.jfree.util.ObjectUtilities;
    223    import org.jfree.util.PaintUtilities;
    224    import org.jfree.util.PublicCloneable;
    225    import org.jfree.util.Rotation;
    226    import org.jfree.util.ShapeUtilities;
    227    import org.jfree.util.UnitType;
    228
    229    /**
    230     * A plot that displays data in the form of a pie chart, using data from any
    231     * class that implements the {@link PieDataset} interface.
    232     * The example shown here is generated by the <code>PieChartDemo2.java</code>
    233     * program included in the JFreeChart Demo Collection:
    234     * <br><br>
    235     * <img src="../../../../images/PiePlotSample.png"
    236     * alt="PiePlotSample.png" />
    237     * <P>
    238     * Special notes:
    239     * <ol>
    240     * <li>the default starting point is 12 o'clock and the pie sections proceed
    241     * in a clockwise direction, but these settings can be changed;</li>
    242     * <li>negative values in the dataset are ignored;</li>
    243     * <li>there are utility methods for creating a {@link PieDataset} from a
    244     * {@link org.jfree.data.category.CategoryDataset};</li>
    245     * </ol>
    246     *
    247     * @see Plot
    248     * @see PieDataset
    249     */
    250    public class PiePlot extends Plot implements Cloneable, Serializable {
    251
    252        /** For serialization. */
    253        private static final long serialVersionUID = -795612466005590431L;
    254
    255        /** The default interior gap. */
    256        public static final double DEFAULT_INTERIOR_GAP = 0.08;
    257
    258        /** The maximum interior gap (currently 40%). */
    259        public static final double MAX_INTERIOR_GAP = 0.40;
    260
    261        /** The default starting angle for the pie chart. */
    262        public static final double DEFAULT_START_ANGLE = 90.0;
    263
    264        /** The default section label font. */
    265        public static final Font DEFAULT_LABEL_FONT = new Font("SansSerif",
    266                Font.PLAIN, 10);
    267
    268        /** The default section label paint. */
    269        public static final Paint DEFAULT_LABEL_PAINT = Color.black;
    270
    271        /** The default section label background paint. */
    272        public static final Paint DEFAULT_LABEL_BACKGROUND_PAINT = new Color(255,
    273                255, 192);
    274
    275        /** The default section label outline paint. */
    276        public static final Paint DEFAULT_LABEL_OUTLINE_PAINT = Color.black;
    277
    278        /** The default section label outline stroke. */
    279        public static final Stroke DEFAULT_LABEL_OUTLINE_STROKE = new BasicStroke(
    280                0.5f);
    281
    282        /** The default section label shadow paint. */
    283        public static final Paint DEFAULT_LABEL_SHADOW_PAINT = new Color(151, 151,
    284                151, 128);
    285
    286        /** The default minimum arc angle to draw. */
    287        public static final double DEFAULT_MINIMUM_ARC_ANGLE_TO_DRAW = 0.00001;
    288
    289        /** The dataset for the pie chart. */
    290        private PieDataset dataset;
    291
    292        /** The pie index (used by the {@link MultiplePiePlot} class). */
    293        private int pieIndex;
    294
    295        /**
    296         * The amount of space left around the outside of the pie plot, expressed
    297         * as a percentage of the plot area width and height.
    298         */
    299        private double interiorGap;
    300
    301        /** Flag determining whether to draw an ellipse or a perfect circle. */
    302        private boolean circular;
    303
    304        /** The starting angle. */
    305        private double startAngle;
    306
    307        /** The direction for the pie segments. */
    308        private Rotation direction;
    309
    310        /** The section paint map. */
    311        private PaintMap sectionPaintMap;
    312
    313        /** The base section paint (fallback). */
    314        private transient Paint baseSectionPaint;
    315
    316        /**
    317         * A flag that controls whether or not the section paint is auto-populated
    318         * from the drawing supplier.
    319         *
    320         * @since 1.0.11
    321         */
    322        private boolean autoPopulateSectionPaint;
    323
    324        /**
    325         * A flag that controls whether or not an outline is drawn for each
    326         * section in the plot.
    327         */
    328        private boolean sectionOutlinesVisible;
    329
    330        /** The section outline paint map. */
    331        private PaintMap sectionOutlinePaintMap;
    332
    333        /** The base section outline paint (fallback). */
    334        private transient Paint baseSectionOutlinePaint;
    335
    336        /**
    337         * A flag that controls whether or not the section outline paint is
    338         * auto-populated from the drawing supplier.
    339         *
    340         * @since 1.0.11
    341         */
    342        private boolean autoPopulateSectionOutlinePaint;
    343
    344        /** The section outline stroke map. */
    345        private StrokeMap sectionOutlineStrokeMap;
    346
    347        /** The base section outline stroke (fallback). */
    348        private transient Stroke baseSectionOutlineStroke;
    349
    350        /**
    351         * A flag that controls whether or not the section outline stroke is
    352         * auto-populated from the drawing supplier.
    353         *
    354         * @since 1.0.11
    355         */
    356        private boolean autoPopulateSectionOutlineStroke;
    357
    358        /** The shadow paint. */
    359        private transient Paint shadowPaint = Color.gray;
    360
    361        /** The x-offset for the shadow effect. */
    362        private double shadowXOffset = 4.0f;
    363
    364        /** The y-offset for the shadow effect. */
    365        private double shadowYOffset = 4.0f;
    366
    367        /** The percentage amount to explode each pie section. */
    368        private Map explodePercentages;
    369
    370        /** The section label generator. */
    371        private PieSectionLabelGenerator labelGenerator;
    372
    373        /** The font used to display the section labels. */
    374        private Font labelFont;
    375
    376        /** The color used to draw the section labels. */
    377        private transient Paint labelPaint;
    378
    379        /**
    380         * The color used to draw the background of the section labels.  If this
    381         * is <code>null</code>, the background is not filled.
    382         */
    383        private transient Paint labelBackgroundPaint;
    384
    385        /**
    386         * The paint used to draw the outline of the section labels
    387         * (<code>null</code> permitted).
    388         */
    389        private transient Paint labelOutlinePaint;
    390
    391        /**
    392         * The stroke used to draw the outline of the section labels
    393         * (<code>null</code> permitted).
    394         */
    395        private transient Stroke labelOutlineStroke;
    396
    397        /**
    398         * The paint used to draw the shadow for the section labels
    399         * (<code>null</code> permitted).
    400         */
    401        private transient Paint labelShadowPaint;
    402
    403        /**
    404         * A flag that controls whether simple or extended labels are used.
    405         *
    406         * @since 1.0.7
    407         */
    408        private boolean simpleLabels = true;
    409
    410        /**
    411         * The padding between the labels and the label outlines.  This is not
    412         * allowed to be <code>null</code>.
    413         *
    414         * @since 1.0.7
    415         */
    416        private RectangleInsets labelPadding;
    417
    418        /**
    419         * The simple label offset.
    420         *
    421         * @since 1.0.7
    422         */
    423        private RectangleInsets simpleLabelOffset;
    424
    425        /** The maximum label width as a percentage of the plot width. */
    426        private double maximumLabelWidth = 0.14;
    427
    428        /**
    429         * The gap between the labels and the link corner, as a percentage of the
    430         * plot width.
    431         */
    432        private double labelGap = 0.025;
    433
    434        /** A flag that controls whether or not the label links are drawn. */
    435        private boolean labelLinksVisible;
    436
    437        /**
    438         * The label link style.
    439         *
    440         * @since 1.0.10
    441         */
    442        private PieLabelLinkStyle labelLinkStyle = PieLabelLinkStyle.STANDARD;
    443
    444        /** The link margin. */
    445        private double labelLinkMargin = 0.025;
    446
    447        /** The paint used for the label linking lines. */
    448        private transient Paint labelLinkPaint = Color.black;
    449
    450        /** The stroke used for the label linking lines. */
    451        private transient Stroke labelLinkStroke = new BasicStroke(0.5f);
    452
    453        /**
    454         * The pie section label distributor.
    455         *
    456         * @since 1.0.6
    457         */
    458        private AbstractPieLabelDistributor labelDistributor;
    459
    460        /** The tooltip generator. */
    461        private PieToolTipGenerator toolTipGenerator;
    462
    463        /** The URL generator. */
    464        private PieURLGenerator urlGenerator;
    465
    466        /** The legend label generator. */
    467        private PieSectionLabelGenerator legendLabelGenerator;
    468
    469        /** A tool tip generator for the legend. */
    470        private PieSectionLabelGenerator legendLabelToolTipGenerator;
    471
    472        /**
    473         * A URL generator for the legend items (optional).
    474         *
    475         * @since 1.0.4.
    476         */
    477        private PieURLGenerator legendLabelURLGenerator;
    478
    479        /**
    480         * A flag that controls whether <code>null</code> values are ignored.
    481         */
    482        private boolean ignoreNullValues;
    483
    484        /**
    485         * A flag that controls whether zero values are ignored.
    486         */
    487        private boolean ignoreZeroValues;
    488
    489        /** The legend item shape. */
    490        private transient Shape legendItemShape;
    491
    492        /**
    493         * The smallest arc angle that will get drawn (this is to avoid a bug in
    494         * various Java implementations that causes the JVM to crash).  See this
    495         * link for details:
    496         *
    497         * http://www.jfree.org/phpBB2/viewtopic.php?t=2707
    498         *
    499         * ...and this bug report in the Java Bug Parade:
    500         *
    501         * http://developer.java.sun.com/developer/bugParade/bugs/4836495.html
    502         */
    503        private double minimumArcAngleToDraw;
    504
    505        /** The resourceBundle for the localization. */
    506        protected static ResourceBundle localizationResources
    507                = ResourceBundleWrapper.getBundle(
    508                        "org.jfree.chart.plot.LocalizationBundle");
    509
    510        /**
    511         * This debug flag controls whether or not an outline is drawn showing the
    512         * interior of the plot region.  This is drawn as a lightGray rectangle
    513         * showing the padding provided by the 'interiorGap' setting.
    514         */
    515        static final boolean DEBUG_DRAW_INTERIOR = false;
    516
    517        /**
    518         * This debug flag controls whether or not an outline is drawn showing the
    519         * link area (in blue) and link ellipse (in yellow).  This controls where
    520         * the label links have 'elbow' points.
    521         */
    522        static final boolean DEBUG_DRAW_LINK_AREA = false;
    523
    524        /**
    525         * This debug flag controls whether or not an outline is drawn showing
    526         * the pie area (in green).
    527         */
    528        static final boolean DEBUG_DRAW_PIE_AREA = false;
    529
    530        /**
    531         * Creates a new plot.  The dataset is initially set to <code>null</code>.
    532         */
    533        public PiePlot() {
    534            this(null);
    535        }
    536
    537        /**
    538         * Creates a plot that will draw a pie chart for the specified dataset.
    539         *
    540         * @param dataset  the dataset (<code>null</code> permitted).
    541         */
    542        public PiePlot(PieDataset dataset) {
    543            super();
    544            this.dataset = dataset;
    545            if (dataset != null) {
    546                dataset.addChangeListener(this);
    547            }
    548            this.pieIndex = 0;
    549
    550            this.interiorGap = DEFAULT_INTERIOR_GAP;
    551            this.circular = true;
    552            this.startAngle = DEFAULT_START_ANGLE;
    553            this.direction = Rotation.CLOCKWISE;
    554            this.minimumArcAngleToDraw = DEFAULT_MINIMUM_ARC_ANGLE_TO_DRAW;
    555
    556            this.sectionPaint = null;
    557            this.sectionPaintMap = new PaintMap();
    558            this.baseSectionPaint = Color.gray;
    559            this.autoPopulateSectionPaint = true;
    560
    561            this.sectionOutlinesVisible = true;
    562            this.sectionOutlinePaint = null;
    563            this.sectionOutlinePaintMap = new PaintMap();
    564            this.baseSectionOutlinePaint = DEFAULT_OUTLINE_PAINT;
    565            this.autoPopulateSectionOutlinePaint = false;
    566
    567            this.sectionOutlineStroke = null;
    568            this.sectionOutlineStrokeMap = new StrokeMap();
    569            this.baseSectionOutlineStroke = DEFAULT_OUTLINE_STROKE;
    570            this.autoPopulateSectionOutlineStroke = false;
    571
    572            this.explodePercentages = new TreeMap();
    573
    574            this.labelGenerator = new StandardPieSectionLabelGenerator();
    575            this.labelFont = DEFAULT_LABEL_FONT;
    576            this.labelPaint = DEFAULT_LABEL_PAINT;
    577            this.labelBackgroundPaint = DEFAULT_LABEL_BACKGROUND_PAINT;
    578            this.labelOutlinePaint = DEFAULT_LABEL_OUTLINE_PAINT;
    579            this.labelOutlineStroke = DEFAULT_LABEL_OUTLINE_STROKE;
    580            this.labelShadowPaint = DEFAULT_LABEL_SHADOW_PAINT;
    581            this.labelLinksVisible = true;
    582            this.labelDistributor = new PieLabelDistributor(0);
    583
    584            this.simpleLabels = false;
    585            this.simpleLabelOffset = new RectangleInsets(UnitType.RELATIVE, 0.18,
    586                    0.18, 0.18, 0.18);
    587            this.labelPadding = new RectangleInsets(2, 2, 2, 2);
    588
    589            this.toolTipGenerator = null;
    590            this.urlGenerator = null;
    591            this.legendLabelGenerator = new StandardPieSectionLabelGenerator();
    592            this.legendLabelToolTipGenerator = null;
    593            this.legendLabelURLGenerator = null;
    594            this.legendItemShape = Plot.DEFAULT_LEGEND_ITEM_CIRCLE;
    595
    596            this.ignoreNullValues = false;
    597            this.ignoreZeroValues = false;
    598        }
    599
    600        /**
    601         * Returns the dataset.
    602         *
    603         * @return The dataset (possibly <code>null</code>).
    604         *
    605         * @see #setDataset(PieDataset)
    606         */
    607        public PieDataset getDataset() {
    608            return this.dataset;
    609        }
    610
    611        /**
    612         * Sets the dataset and sends a {@link DatasetChangeEvent} to 'this'.
    613         *
    614         * @param dataset  the dataset (<code>null</code> permitted).
    615         *
    616         * @see #getDataset()
    617         */
    618        public void setDataset(PieDataset dataset) {
    619            // if there is an existing dataset, remove the plot from the list of
    620            // change listeners...
    621            PieDataset existing = this.dataset;
    622            if (existing != null) {
    623                existing.removeChangeListener(this);
    624            }
    625
    626            // set the new dataset, and register the chart as a change listener...
    627            this.dataset = dataset;
    628            if (dataset != null) {
    629                setDatasetGroup(dataset.getGroup());
    630                dataset.addChangeListener(this);
    631            }
    632
    633            // send a dataset change event to self...
    634            DatasetChangeEvent event = new DatasetChangeEvent(this, dataset);
    635            datasetChanged(event);
    636        }
    637
    638        /**
    639         * Returns the pie index (this is used by the {@link MultiplePiePlot} class
    640         * to track subplots).
    641         *
    642         * @return The pie index.
    643         *
    644         * @see #setPieIndex(int)
    645         */
    646        public int getPieIndex() {
    647            return this.pieIndex;
    648        }
    649
    650        /**
    651         * Sets the pie index (this is used by the {@link MultiplePiePlot} class to
    652         * track subplots).
    653         *
    654         * @param index  the index.
    655         *
    656         * @see #getPieIndex()
    657         */
    658        public void setPieIndex(int index) {
    659            this.pieIndex = index;
    660        }
    661
    662        /**
    663         * Returns the start angle for the first pie section.  This is measured in
    664         * degrees starting from 3 o'clock and measuring anti-clockwise.
    665         *
    666         * @return The start angle.
    667         *
    668         * @see #setStartAngle(double)
    669         */
    670        public double getStartAngle() {
    671            return this.startAngle;
    672        }
    673
    674        /**
    675         * Sets the starting angle and sends a {@link PlotChangeEvent} to all
    676         * registered listeners.  The initial default value is 90 degrees, which
    677         * corresponds to 12 o'clock.  A value of zero corresponds to 3 o'clock...
    678         * this is the encoding used by Java's Arc2D class.
    679         *
    680         * @param angle  the angle (in degrees).
    681         *
    682         * @see #getStartAngle()
    683         */
    684        public void setStartAngle(double angle) {
    685            this.startAngle = angle;
    686            fireChangeEvent();
    687        }
    688
    689        /**
    690         * Returns the direction in which the pie sections are drawn (clockwise or
    691         * anti-clockwise).
    692         *
    693         * @return The direction (never <code>null</code>).
    694         *
    695         * @see #setDirection(Rotation)
    696         */
    697        public Rotation getDirection() {
    698            return this.direction;
    699        }
    700
    701        /**
    702         * Sets the direction in which the pie sections are drawn and sends a
    703         * {@link PlotChangeEvent} to all registered listeners.
    704         *
    705         * @param direction  the direction (<code>null</code> not permitted).
    706         *
    707         * @see #getDirection()
    708         */
    709        public void setDirection(Rotation direction) {
    710            if (direction == null) {
    711                throw new IllegalArgumentException("Null 'direction' argument.");
    712            }
    713            this.direction = direction;
    714            fireChangeEvent();
    715
    716        }
    717
    718        /**
    719         * Returns the interior gap, measured as a percentage of the available
    720         * drawing space.
    721         *
    722         * @return The gap (as a percentage of the available drawing space).
    723         *
    724         * @see #setInteriorGap(double)
    725         */
    726        public double getInteriorGap() {
    727            return this.interiorGap;
    728        }
    729
    730        /**
    731         * Sets the interior gap and sends a {@link PlotChangeEvent} to all
    732         * registered listeners.  This controls the space between the edges of the
    733         * pie plot and the plot area itself (the region where the section labels
    734         * appear).
    735         *
    736         * @param percent  the gap (as a percentage of the available drawing space).
    737         *
    738         * @see #getInteriorGap()
    739         */
    740        public void setInteriorGap(double percent) {
    741
    742            if ((percent < 0.0) || (percent > MAX_INTERIOR_GAP)) {
    743                throw new IllegalArgumentException(
    744                    "Invalid 'percent' (" + percent + ") argument.");
    745            }
    746
    747            if (this.interiorGap != percent) {
    748                this.interiorGap = percent;
    749                fireChangeEvent();
    750            }
    751
    752        }
    753
    754        /**
    755         * Returns a flag indicating whether the pie chart is circular, or
    756         * stretched into an elliptical shape.
    757         *
    758         * @return A flag indicating whether the pie chart is circular.
    759         *
    760         * @see #setCircular(boolean)
    761         */
    762        public boolean isCircular() {
    763            return this.circular;
    764        }
    765
    766        /**
    767         * A flag indicating whether the pie chart is circular, or stretched into
    768         * an elliptical shape.
    769         *
    770         * @param flag  the new value.
    771         *
    772         * @see #isCircular()
    773         */
    774        public void setCircular(boolean flag) {
    775            setCircular(flag, true);
    776        }
    777
    778        /**
    779         * Sets the circular attribute and, if requested, sends a
    780         * {@link PlotChangeEvent} to all registered listeners.
    781         *
    782         * @param circular  the new value of the flag.
    783         * @param notify  notify listeners?
    784         *
    785         * @see #isCircular()
    786         */
    787        public void setCircular(boolean circular, boolean notify) {
    788            this.circular = circular;
    789            if (notify) {
    790                fireChangeEvent();
    791            }
    792        }
    793
    794        /**
    795         * Returns the flag that controls whether <code>null</code> values in the
    796         * dataset are ignored.
    797         *
    798         * @return A boolean.
    799         *
    800         * @see #setIgnoreNullValues(boolean)
    801         */
    802        public boolean getIgnoreNullValues() {
    803            return this.ignoreNullValues;
    804        }
    805
    806        /**
    807         * Sets a flag that controls whether <code>null</code> values are ignored,
    808         * and sends a {@link PlotChangeEvent} to all registered listeners.  At
    809         * present, this only affects whether or not the key is presented in the
    810         * legend.
    811         *
    812         * @param flag  the flag.
    813         *
    814         * @see #getIgnoreNullValues()
    815         * @see #setIgnoreZeroValues(boolean)
    816         */
    817        public void setIgnoreNullValues(boolean flag) {
    818            this.ignoreNullValues = flag;
    819            fireChangeEvent();
    820        }
    821
    822        /**
    823         * Returns the flag that controls whether zero values in the
    824         * dataset are ignored.
    825         *
    826         * @return A boolean.
    827         *
    828         * @see #setIgnoreZeroValues(boolean)
    829         */
    830        public boolean getIgnoreZeroValues() {
    831            return this.ignoreZeroValues;
    832        }
    833
    834        /**
    835         * Sets a flag that controls whether zero values are ignored,
    836         * and sends a {@link PlotChangeEvent} to all registered listeners.  This
    837         * only affects whether or not a label appears for the non-visible
    838         * pie section.
    839         *
    840         * @param flag  the flag.
    841         *
    842         * @see #getIgnoreZeroValues()
    843         * @see #setIgnoreNullValues(boolean)
    844         */
    845        public void setIgnoreZeroValues(boolean flag) {
    846            this.ignoreZeroValues = flag;
    847            fireChangeEvent();
    848        }
    849
    850        //// SECTION PAINT ////////////////////////////////////////////////////////
    851
    852        /**
    853         * Returns the paint for the specified section.  This is equivalent to
    854         * <code>lookupSectionPaint(section, getAutoPopulateSectionPaint())</code>.
    855         *
    856         * @param key  the section key.
    857         *
    858         * @return The paint for the specified section.
    859         *
    860         * @since 1.0.3
    861         *
    862         * @see #lookupSectionPaint(Comparable, boolean)
    863         */
    864        protected Paint lookupSectionPaint(Comparable key) {
    865            return lookupSectionPaint(key, getAutoPopulateSectionPaint());
    866        }
    867
    868        /**
    869         * Returns the paint for the specified section.  The lookup involves these
    870         * steps:
    871         * <ul>
    872         * <li>if {@link #getSectionPaint()} is non-<code>null</code>, return
    873         *         it;</li>
    874         * <li>if {@link #getSectionPaint(int)} is non-<code>null</code> return
    875         *         it;</li>
    876         * <li>if {@link #getSectionPaint(int)} is <code>null</code> but
    877         *         <code>autoPopulate</code> is <code>true</code>, attempt to fetch
    878         *         a new paint from the drawing supplier
    879         *         ({@link #getDrawingSupplier()});
    880         * <li>if all else fails, return {@link #getBaseSectionPaint()}.
    881         * </ul>
    882         *
    883         * @param key  the section key.
    884         * @param autoPopulate  a flag that controls whether the drawing supplier
    885         *     is used to auto-populate the section paint settings.
    886         *
    887         * @return The paint.
    888         *
    889         * @since 1.0.3
    890         */
    891        protected Paint lookupSectionPaint(Comparable key, boolean autoPopulate) {
    892
    893            // is there an override?
    894            Paint result = getSectionPaint();
    895            if (result != null) {
    896                return result;
    897            }
    898
    899            // if not, check if there is a paint defined for the specified key
    900            result = this.sectionPaintMap.getPaint(key);
    901            if (result != null) {
    902                return result;
    903            }
    904
    905            // nothing defined - do we autoPopulate?
    906            if (autoPopulate) {
    907                DrawingSupplier ds = getDrawingSupplier();
    908                if (ds != null) {
    909                    result = ds.getNextPaint();
    910                    this.sectionPaintMap.put(key, result);
    911                }
    912                else {
    913                    result = this.baseSectionPaint;
    914                }
    915            }
    916            else {
    917                result = this.baseSectionPaint;
    918            }
    919            return result;
    920        }
    921
    922        /**
    923         * Returns the paint for ALL sections in the plot.
    924         *
    925         * @return The paint (possibly <code>null</code>).
    926         *
    927         * @see #setSectionPaint(Paint)
    928         *
    929         * @deprecated Use {@link #getSectionPaint(Comparable)} and
    930         *     {@link #getBaseSectionPaint()}.  Deprecated as of version 1.0.6.
    931         */
    932        public Paint getSectionPaint() {
    933            return this.sectionPaint;
    934        }
    935
    936        /**
    937         * Sets the paint for ALL sections in the plot.  If this is set to
    938         * </code>null</code>, then a list of paints is used instead (to allow
    939         * different colors to be used for each section).
    940         *
    941         * @param paint  the paint (<code>null</code> permitted).
    942         *
    943         * @see #getSectionPaint()
    944         *
    945         * @deprecated Use {@link #setSectionPaint(Comparable, Paint)} and
    946         *     {@link #setBaseSectionPaint(Paint)}.  Deprecated as of version 1.0.6.
    947         */
    948        public void setSectionPaint(Paint paint) {
    949            this.sectionPaint = paint;
    950            fireChangeEvent();
    951        }
    952
    953        /**
    954         * Returns a key for the specified section.  If there is no such section
    955         * in the dataset, we generate a key.  This is to provide some backward
    956         * compatibility for the (now deprecated) methods that get/set attributes
    957         * based on section indices.  The preferred way of doing this now is to
    958         * link the attributes directly to the section key (there are new methods
    959         * for this, starting from version 1.0.3).
    960         *
    961         * @param section  the section index.
    962         *
    963         * @return The key.
    964         *
    965         * @since 1.0.3
    966         */
    967        protected Comparable getSectionKey(int section) {
    968            Comparable key = null;
    969            if (this.dataset != null) {
    970                if (section >= 0 && section < this.dataset.getItemCount()) {
    971                    key = this.dataset.getKey(section);
    972                }
    973            }
    974            if (key == null) {
    975                key = new Integer(section);
    976            }
    977            return key;
    978        }
    979
    980        /**
    981         * Returns the paint associated with the specified key, or
    982         * <code>null</code> if there is no paint associated with the key.
    983         *
    984         * @param key  the key (<code>null</code> not permitted).
    985         *
    986         * @return The paint associated with the specified key, or
    987         *     <code>null</code>.
    988         *
    989         * @throws IllegalArgumentException if <code>key</code> is
    990         *     <code>null</code>.
    991         *
    992         * @see #setSectionPaint(Comparable, Paint)
    993         *
    994         * @since 1.0.3
    995         */
    996        public Paint getSectionPaint(Comparable key) {
    997            // null argument check delegated...
    998            return this.sectionPaintMap.getPaint(key);
    999        }
    1000
    1001        /**
    1002         * Sets the paint associated with the specified key, and sends a
    1003         * {@link PlotChangeEvent} to all registered listeners.
    1004         *
    1005         * @param key  the key (<code>null</code> not permitted).
    1006         * @param paint  the paint.
    1007         *
    1008         * @throws IllegalArgumentException if <code>key</code> is
    1009         *     <code>null</code>.
    1010         *
    1011         * @see #getSectionPaint(Comparable)
    1012         *
    1013         * @since 1.0.3
    1014         */
    1015        public void setSectionPaint(Comparable key, Paint paint) {
    1016            // null argument check delegated...
    1017            this.sectionPaintMap.put(key, paint);
    1018            fireChangeEvent();
    1019        }
    1020
    1021        /**
    1022         * Clears the section paint settings for this plot and, if requested, sends
    1023         * a {@link PlotChangeEvent} to all registered listeners.  Be aware that
    1024         * if the <code>autoPopulateSectionPaint</code> flag is set, the section
    1025         * paints may be repopulated using the same colours as before.
    1026         *
    1027         * @param notify  notify listeners?
    1028         *
    1029         * @since 1.0.11
    1030         *
    1031         * @see #autoPopulateSectionPaint
    1032         */
    1033        public void clearSectionPaints(boolean notify) {
    1034            this.sectionPaintMap.clear();
    1035            if (notify) {
    1036                fireChangeEvent();
    1037            }
    1038        }
    1039
    1040        /**
    1041         * Returns the base section paint.  This is used when no other paint is
    1042         * defined, which is rare.  The default value is <code>Color.gray</code>.
    1043         *
    1044         * @return The paint (never <code>null</code>).
    1045         *
    1046         * @see #setBaseSectionPaint(Paint)
    1047         */
    1048        public Paint getBaseSectionPaint() {
    1049            return this.baseSectionPaint;
    1050        }
    1051
    1052        /**
    1053         * Sets the base section paint and sends a {@link PlotChangeEvent} to all
    1054         * registered listeners.
    1055         *
    1056         * @param paint  the paint (<code>null</code> not permitted).
    1057         *
    1058         * @see #getBaseSectionPaint()
    1059         */
    1060        public void setBaseSectionPaint(Paint paint) {
    1061            if (paint == null) {
    1062                throw new IllegalArgumentException("Null 'paint' argument.");
    1063            }
    1064            this.baseSectionPaint = paint;
    1065            fireChangeEvent();
    1066        }
    1067
    1068        /**
    1069         * Returns the flag that controls whether or not the section paint is
    1070         * auto-populated by the {@link #lookupSectionPaint(Comparable)} method.
    1071         *
    1072         * @return A boolean.
    1073         *
    1074         * @since 1.0.11
    1075         */
    1076        public boolean getAutoPopulateSectionPaint() {
    1077            return this.autoPopulateSectionPaint;
    1078        }
    1079
    1080        /**
    1081         * Sets the flag that controls whether or not the section paint is
    1082         * auto-populated by the {@link #lookupSectionPaint(Comparable)} method,
    1083         * and sends a {@link PlotChangeEvent} to all registered listeners.
    1084         *
    1085         * @param auto  auto-populate?
    1086         *
    1087         * @since 1.0.11
    1088         */
    1089        public void setAutoPopulateSectionPaint(boolean auto) {
    1090            this.autoPopulateSectionPaint = auto;
    1091            fireChangeEvent();
    1092        }
    1093
    1094        //// SECTION OUTLINE PAINT ////////////////////////////////////////////////
    1095
    1096        /**
    1097         * Returns the flag that controls whether or not the outline is drawn for
    1098         * each pie section.
    1099         *
    1100         * @return The flag that controls whether or not the outline is drawn for
    1101         *         each pie section.
    1102         *
    1103         * @see #setSectionOutlinesVisible(boolean)
    1104         */
    1105        public boolean getSectionOutlinesVisible() {
    1106            return this.sectionOutlinesVisible;
    1107        }
    1108
    1109        /**
    1110         * Sets the flag that controls whether or not the outline is drawn for
    1111         * each pie section, and sends a {@link PlotChangeEvent} to all registered
    1112         * listeners.
    1113         *
    1114         * @param visible  the flag.
    1115         *
    1116         * @see #getSectionOutlinesVisible()
    1117         */
    1118        public void setSectionOutlinesVisible(boolean visible) {
    1119            this.sectionOutlinesVisible = visible;
    1120            fireChangeEvent();
    1121        }
    1122
    1123        /**
    1124         * Returns the outline paint for the specified section.  This is equivalent
    1125         * to <code>lookupSectionPaint(section,
    1126         * getAutoPopulateSectionOutlinePaint())</code>.
    1127         *
    1128         * @param key  the section key.
    1129         *
    1130         * @return The paint for the specified section.
    1131         *
    1132         * @since 1.0.3
    1133         *
    1134         * @see #lookupSectionOutlinePaint(Comparable, boolean)
    1135         */
    1136        protected Paint lookupSectionOutlinePaint(Comparable key) {
    1137            return lookupSectionOutlinePaint(key,
    1138                    getAutoPopulateSectionOutlinePaint());
    1139        }
    1140
    1141        /**
    1142         * Returns the outline paint for the specified section.  The lookup
    1143         * involves these steps:
    1144         * <ul>
    1145         * <li>if {@link #getSectionOutlinePaint()} is non-<code>null</code>,
    1146         *         return it;</li>
    1147         * <li>otherwise, if {@link #getSectionOutlinePaint(int)} is
    1148         *         non-<code>null</code> return it;</li>
    1149         * <li>if {@link #getSectionOutlinePaint(int)} is <code>null</code> but
    1150         *         <code>autoPopulate</code> is <code>true</code>, attempt to fetch
    1151         *         a new outline paint from the drawing supplier
    1152         *         ({@link #getDrawingSupplier()});
    1153         * <li>if all else fails, return {@link #getBaseSectionOutlinePaint()}.
    1154         * </ul>
    1155         *
    1156         * @param key  the section key.
    1157         * @param autoPopulate  a flag that controls whether the drawing supplier
    1158         *     is used to auto-populate the section outline paint settings.
    1159         *
    1160         * @return The paint.
    1161         *
    1162         * @since 1.0.3
    1163         */
    1164        protected Paint lookupSectionOutlinePaint(Comparable key,
    1165                boolean autoPopulate) {
    1166
    1167            // is there an override?
    1168            Paint result = getSectionOutlinePaint();
    1169            if (result != null) {
    1170                return result;
    1171            }
    1172
    1173            // if not, check if there is a paint defined for the specified key
    1174            result = this.sectionOutlinePaintMap.getPaint(key);
    1175            if (result != null) {
    1176                return result;
    1177            }
    1178
    1179            // nothing defined - do we autoPopulate?
    1180            if (autoPopulate) {
    1181                DrawingSupplier ds = getDrawingSupplier();
    1182                if (ds != null) {
    1183                    result = ds.getNextOutlinePaint();
    1184                    this.sectionOutlinePaintMap.put(key, result);
    1185                }
    1186                else {
    1187                    result = this.baseSectionOutlinePaint;
    1188                }
    1189            }
    1190            else {
    1191                result = this.baseSectionOutlinePaint;
    1192            }
    1193            return result;
    1194        }
    1195
    1196        /**
    1197         * Returns the outline paint associated with the specified key, or
    1198         * <code>null</code> if there is no paint associated with the key.
    1199         *
    1200         * @param key  the key (<code>null</code> not permitted).
    1201         *
    1202         * @return The paint associated with the specified key, or
    1203         *     <code>null</code>.
    1204         *
    1205         * @throws IllegalArgumentException if <code>key</code> is
    1206         *     <code>null</code>.
    1207         *
    1208         * @see #setSectionOutlinePaint(Comparable, Paint)
    1209         *
    1210         * @since 1.0.3
    1211         */
    1212        public Paint getSectionOutlinePaint(Comparable key) {
    1213            // null argument check delegated...
    1214            return this.sectionOutlinePaintMap.getPaint(key);
    1215        }
    1216
    1217        /**
    1218         * Sets the outline paint associated with the specified key, and sends a
    1219         * {@link PlotChangeEvent} to all registered listeners.
    1220         *
    1221         * @param key  the key (<code>null</code> not permitted).
    1222         * @param paint  the paint.
    1223         *
    1224         * @throws IllegalArgumentException if <code>key</code> is
    1225         *     <code>null</code>.
    1226         *
    1227         * @see #getSectionOutlinePaint(Comparable)
    1228         *
    1229         * @since 1.0.3
    1230         */
    1231        public void setSectionOutlinePaint(Comparable key, Paint paint) {
    1232            // null argument check delegated...
    1233            this.sectionOutlinePaintMap.put(key, paint);
    1234            fireChangeEvent();
    1235        }
    1236
    1237        /**
    1238         * Clears the section outline paint settings for this plot and, if
    1239         * requested, sends a {@link PlotChangeEvent} to all registered listeners.
    1240         * Be aware that if the <code>autoPopulateSectionPaint</code> flag is set,
    1241         * the section paints may be repopulated using the same colours as before.
    1242         *
    1243         * @param notify  notify listeners?
    1244         *
    1245         * @since 1.0.11
    1246         *
    1247         * @see #autoPopulateSectionOutlinePaint
    1248         */
    1249        public void clearSectionOutlinePaints(boolean notify) {
    1250            this.sectionOutlinePaintMap.clear();
    1251            if (notify) {
    1252                fireChangeEvent();
    1253            }
    1254        }
    1255
    1256        /**
    1257         * Returns the base section paint.  This is used when no other paint is
    1258         * available.
    1259         *
    1260         * @return The paint (never <code>null</code>).
    1261         *
    1262         * @see #setBaseSectionOutlinePaint(Paint)
    1263         */
    1264        public Paint getBaseSectionOutlinePaint() {
    1265            return this.baseSectionOutlinePaint;
    1266        }
    1267
    1268        /**
    1269         * Sets the base section paint.
    1270         *
    1271         * @param paint  the paint (<code>null</code> not permitted).
    1272         *
    1273         * @see #getBaseSectionOutlinePaint()
    1274         */
    1275        public void setBaseSectionOutlinePaint(Paint paint) {
    1276            if (paint == null) {
    1277                throw new IllegalArgumentException("Null 'paint' argument.");
    1278            }
    1279            this.baseSectionOutlinePaint = paint;
    1280            fireChangeEvent();
    1281        }
    1282
    1283        /**
    1284         * Returns the flag that controls whether or not the section outline paint
    1285         * is auto-populated by the {@link #lookupSectionOutlinePaint(Comparable)}
    1286         * method.
    1287         *
    1288         * @return A boolean.
    1289         *
    1290         * @since 1.0.11
    1291         */
    1292        public boolean getAutoPopulateSectionOutlinePaint() {
    1293            return this.autoPopulateSectionOutlinePaint;
    1294        }
    1295
    1296        /**
    1297         * Sets the flag that controls whether or not the section outline paint is
    1298         * auto-populated by the {@link #lookupSectionOutlinePaint(Comparable)}
    1299         * method, and sends a {@link PlotChangeEvent} to all registered listeners.
    1300         *
    1301         * @param auto  auto-populate?
    1302         *
    1303         * @since 1.0.11
    1304         */
    1305        public void setAutoPopulateSectionOutlinePaint(boolean auto) {
    1306            this.autoPopulateSectionOutlinePaint = auto;
    1307            fireChangeEvent();
    1308        }
    1309
    1310        //// SECTION OUTLINE STROKE ///////////////////////////////////////////////
    1311
    1312        /**
    1313         * Returns the outline stroke for the specified section.  This is
    1314         * equivalent to <code>lookupSectionOutlineStroke(section,
    1315         * getAutoPopulateSectionOutlineStroke())</code>.
    1316         *
    1317         * @param key  the section key.
    1318         *
    1319         * @return The stroke for the specified section.
    1320         *
    1321         * @since 1.0.3
    1322         *
    1323         * @see #lookupSectionOutlineStroke(Comparable, boolean)
    1324         */
    1325        protected Stroke lookupSectionOutlineStroke(Comparable key) {
    1326            return lookupSectionOutlineStroke(key,
    1327                    getAutoPopulateSectionOutlineStroke());
    1328        }
    1329
    1330        /**
    1331         * Returns the outline stroke for the specified section.  The lookup
    1332         * involves these steps:
    1333         * <ul>
    1334         * <li>if {@link #getSectionOutlineStroke()} is non-<code>null</code>,
    1335         *         return it;</li>
    1336         * <li>otherwise, if {@link #getSectionOutlineStroke(int)} is
    1337         *         non-<code>null</code> return it;</li>
    1338         * <li>if {@link #getSectionOutlineStroke(int)} is <code>null</code> but
    1339         *         <code>autoPopulate</code> is <code>true</code>, attempt to fetch
    1340         *         a new outline stroke from the drawing supplier
    1341         *         ({@link #getDrawingSupplier()});
    1342         * <li>if all else fails, return {@link #getBaseSectionOutlineStroke()}.
    1343         * </ul>
    1344         *
    1345         * @param key  the section key.
    1346         * @param autoPopulate  a flag that controls whether the drawing supplier
    1347         *     is used to auto-populate the section outline stroke settings.
    1348         *
    1349         * @return The stroke.
    1350         *
    1351         * @since 1.0.3
    1352         */
    1353        protected Stroke lookupSectionOutlineStroke(Comparable key,
    1354                boolean autoPopulate) {
    1355
    1356            // is there an override?
    1357            Stroke result = getSectionOutlineStroke();
    1358            if (result != null) {
    1359                return result;
    1360            }
    1361
    1362            // if not, check if there is a stroke defined for the specified key
    1363            result = this.sectionOutlineStrokeMap.getStroke(key);
    1364            if (result != null) {
    1365                return result;
    1366            }
    1367
    1368            // nothing defined - do we autoPopulate?
    1369            if (autoPopulate) {
    1370                DrawingSupplier ds = getDrawingSupplier();
    1371                if (ds != null) {
    1372                    result = ds.getNextOutlineStroke();
    1373                    this.sectionOutlineStrokeMap.put(key, result);
    1374                }
    1375                else {
    1376                    result = this.baseSectionOutlineStroke;
    1377                }
    1378            }
    1379            else {
    1380                result = this.baseSectionOutlineStroke;
    1381            }
    1382            return result;
    1383        }
    1384
    1385        /**
    1386         * Returns the outline stroke associated with the specified key, or
    1387         * <code>null</code> if there is no stroke associated with the key.
    1388         *
    1389         * @param key  the key (<code>null</code> not permitted).
    1390         *
    1391         * @return The stroke associated with the specified key, or
    1392         *     <code>null</code>.
    1393         *
    1394         * @throws IllegalArgumentException if <code>key</code> is
    1395         *     <code>null</code>.
    1396         *
    1397         * @see #setSectionOutlineStroke(Comparable, Stroke)
    1398         *
    1399         * @since 1.0.3
    1400         */
    1401        public Stroke getSectionOutlineStroke(Comparable key) {
    1402            // null argument check delegated...
    1403            return this.sectionOutlineStrokeMap.getStroke(key);
    1404        }
    1405
    1406        /**
    1407         * Sets the outline stroke associated with the specified key, and sends a
    1408         * {@link PlotChangeEvent} to all registered listeners.
    1409         *
    1410         * @param key  the key (<code>null</code> not permitted).
    1411         * @param stroke  the stroke.
    1412         *
    1413         * @throws IllegalArgumentException if <code>key</code> is
    1414         *     <code>null</code>.
    1415         *
    1416         * @see #getSectionOutlineStroke(Comparable)
    1417         *
    1418         * @since 1.0.3
    1419         */
    1420        public void setSectionOutlineStroke(Comparable key, Stroke stroke) {
    1421            // null argument check delegated...
    1422            this.sectionOutlineStrokeMap.put(key, stroke);
    1423            fireChangeEvent();
    1424        }
    1425
    1426        /**
    1427         * Clears the section outline stroke settings for this plot and, if
    1428         * requested, sends a {@link PlotChangeEvent} to all registered listeners.
    1429         * Be aware that if the <code>autoPopulateSectionPaint</code> flag is set,
    1430         * the section paints may be repopulated using the same colours as before.
    1431         *
    1432         * @param notify  notify listeners?
    1433         *
    1434         * @since 1.0.11
    1435         *
    1436         * @see #autoPopulateSectionOutlineStroke
    1437         */
    1438        public void clearSectionOutlineStrokes(boolean notify) {
    1439            this.sectionOutlineStrokeMap.clear();
    1440            if (notify) {
    1441                fireChangeEvent();
    1442            }
    1443        }
    1444
    1445        /**
    1446         * Returns the base section stroke.  This is used when no other stroke is
    1447         * available.
    1448         *
    1449         * @return The stroke (never <code>null</code>).
    1450         *
    1451         * @see #setBaseSectionOutlineStroke(Stroke)
    1452         */
    1453        public Stroke getBaseSectionOutlineStroke() {
    1454            return this.baseSectionOutlineStroke;
    1455        }
    1456
    1457        /**
    1458         * Sets the base section stroke.
    1459         *
    1460         * @param stroke  the stroke (<code>null</code> not permitted).
    1461         *
    1462         * @see #getBaseSectionOutlineStroke()
    1463         */
    1464        public void setBaseSectionOutlineStroke(Stroke stroke) {
    1465            if (stroke == null) {
    1466                throw new IllegalArgumentException("Null 'stroke' argument.");
    1467            }
    1468            this.baseSectionOutlineStroke = stroke;
    1469            fireChangeEvent();
    1470        }
    1471
    1472        /**
    1473         * Returns the flag that controls whether or not the section outline stroke
    1474         * is auto-populated by the {@link #lookupSectionOutlinePaint(Comparable)}
    1475         * method.
    1476         *
    1477         * @return A boolean.
    1478         *
    1479         * @since 1.0.11
    1480         */
    1481        public boolean getAutoPopulateSectionOutlineStroke() {
    1482            return this.autoPopulateSectionOutlineStroke;
    1483        }
    1484
    1485        /**
    1486         * Sets the flag that controls whether or not the section outline stroke is
    1487         * auto-populated by the {@link #lookupSectionOutlineStroke(Comparable)}
    1488         * method, and sends a {@link PlotChangeEvent} to all registered listeners.
    1489         *
    1490         * @param auto  auto-populate?
    1491         *
    1492         * @since 1.0.11
    1493         */
    1494        public void setAutoPopulateSectionOutlineStroke(boolean auto) {
    1495            this.autoPopulateSectionOutlineStroke = auto;
    1496            fireChangeEvent();
    1497        }
    1498
    1499        /**
    1500         * Returns the shadow paint.
    1501         *
    1502         * @return The paint (possibly <code>null</code>).
    1503         *
    1504         * @see #setShadowPaint(Paint)
    1505         */
    1506        public Paint getShadowPaint() {
    1507            return this.shadowPaint;
    1508        }
    1509
    1510        /**
    1511         * Sets the shadow paint and sends a {@link PlotChangeEvent} to all
    1512         * registered listeners.
    1513         *
    1514         * @param paint  the paint (<code>null</code> permitted).
    1515         *
    1516         * @see #getShadowPaint()
    1517         */
    1518        public void setShadowPaint(Paint paint) {
    1519            this.shadowPaint = paint;
    1520            fireChangeEvent();
    1521        }
    1522
    1523        /**
    1524         * Returns the x-offset for the shadow effect.
    1525         *
    1526         * @return The offset (in Java2D units).
    1527         *
    1528         * @see #setShadowXOffset(double)
    1529         */
    1530        public double getShadowXOffset() {
    1531            return this.shadowXOffset;
    1532        }
    1533
    1534        /**
    1535         * Sets the x-offset for the shadow effect and sends a
    1536         * {@link PlotChangeEvent} to all registered listeners.
    1537         *
    1538         * @param offset  the offset (in Java2D units).
    1539         *
    1540         * @see #getShadowXOffset()
    1541         */
    1542        public void setShadowXOffset(double offset) {
    1543            this.shadowXOffset = offset;
    1544            fireChangeEvent();
    1545        }
    1546
    1547        /**
    1548         * Returns the y-offset for the shadow effect.
    1549         *
    1550         * @return The offset (in Java2D units).
    1551         *
    1552         * @see #setShadowYOffset(double)
    1553         */
    1554        public double getShadowYOffset() {
    1555            return this.shadowYOffset;
    1556        }
    1557
    1558        /**
    1559         * Sets the y-offset for the shadow effect and sends a
    1560         * {@link PlotChangeEvent} to all registered listeners.
    1561         *
    1562         * @param offset  the offset (in Java2D units).
    1563         *
    1564         * @see #getShadowYOffset()
    1565         */
    1566        public void setShadowYOffset(double offset) {
    1567            this.shadowYOffset = offset;
    1568            fireChangeEvent();
    1569        }
    1570
    1571        /**
    1572         * Returns the amount that the section with the specified key should be
    1573         * exploded.
    1574         *
    1575         * @param key  the key (<code>null</code> not permitted).
    1576         *
    1577         * @return The amount that the section with the specified key should be
    1578         *     exploded.
    1579         *
    1580         * @throws IllegalArgumentException if <code>key</code> is
    1581         *     <code>null</code>.
    1582         *
    1583         * @since 1.0.3
    1584         *
    1585         * @see #setExplodePercent(Comparable, double)
    1586         */
    1587        public double getExplodePercent(Comparable key) {
    1588            double result = 0.0;
    1589            if (this.explodePercentages != null) {
    1590                Number percent = (Number) this.explodePercentages.get(key);
    1591                if (percent != null) {
    1592                    result = percent.doubleValue();
    1593                }
    1594            }
    1595            return result;
    1596        }
    1597
    1598        /**
    1599         * Sets the amount that a pie section should be exploded and sends a
    1600         * {@link PlotChangeEvent} to all registered listeners.
    1601         *
    1602         * @param key  the section key (<code>null</code> not permitted).
    1603         * @param percent  the explode percentage (0.30 = 30 percent).
    1604         *
    1605         * @since 1.0.3
    1606         *
    1607         * @see #getExplodePercent(Comparable)
    1608         */
    1609        public void setExplodePercent(Comparable key, double percent) {
    1610            if (key == null) {
    1611                throw new IllegalArgumentException("Null 'key' argument.");
    1612            }
    1613            if (this.explodePercentages == null) {
    1614                this.explodePercentages = new TreeMap();
    1615            }
    1616            this.explodePercentages.put(key, new Double(percent));
    1617            fireChangeEvent();
    1618        }
    1619
    1620        /**
    1621         * Returns the maximum explode percent.
    1622         *
    1623         * @return The percent.
    1624         */
    1625        public double getMaximumExplodePercent() {
    1626            if (this.dataset == null) {
    1627                return 0.0;
    1628            }
    1629            double result = 0.0;
    1630            Iterator iterator = this.dataset.getKeys().iterator();
    1631            while (iterator.hasNext()) {
    1632                Comparable key = (Comparable) iterator.next();
    1633                Number explode = (Number) this.explodePercentages.get(key);
    1634                if (explode != null) {
    1635                    result = Math.max(result, explode.doubleValue());
    1636                }
    1637            }
    1638            return result;
    1639        }
    1640
    1641        /**
    1642         * Returns the section label generator.
    1643         *
    1644         * @return The generator (possibly <code>null</code>).
    1645         *
    1646         * @see #setLabelGenerator(PieSectionLabelGenerator)
    1647         */
    1648        public PieSectionLabelGenerator getLabelGenerator() {
    1649            return this.labelGenerator;
    1650        }
    1651
    1652        /**
    1653         * Sets the section label generator and sends a {@link PlotChangeEvent} to
    1654         * all registered listeners.
    1655         *
    1656         * @param generator  the generator (<code>null</code> permitted).
    1657         *
    1658         * @see #getLabelGenerator()
    1659         */
    1660        public void setLabelGenerator(PieSectionLabelGenerator generator) {
    1661            this.labelGenerator = generator;
    1662            fireChangeEvent();
    1663        }
    1664
    1665        /**
    1666         * Returns the gap between the edge of the pie and the labels, expressed as
    1667         * a percentage of the plot width.
    1668         *
    1669         * @return The gap (a percentage, where 0.05 = five percent).
    1670         *
    1671         * @see #setLabelGap(double)
    1672         */
    1673        public double getLabelGap() {
    1674            return this.labelGap;
    1675        }
    1676
    1677        /**
    1678         * Sets the gap between the edge of the pie and the labels (expressed as a
    1679         * percentage of the plot width) and sends a {@link PlotChangeEvent} to all
    1680         * registered listeners.
    1681         *
    1682         * @param gap  the gap (a percentage, where 0.05 = five percent).
    1683         *
    1684         * @see #getLabelGap()
    1685         */
    1686        public void setLabelGap(double gap) {
    1687            this.labelGap = gap;
    1688            fireChangeEvent();
    1689        }
    1690
    1691        /**
    1692         * Returns the maximum label width as a percentage of the plot width.
    1693         *
    1694         * @return The width (a percentage, where 0.20 = 20 percent).
    1695         *
    1696         * @see #setMaximumLabelWidth(double)
    1697         */
    1698        public double getMaximumLabelWidth() {
    1699            return this.maximumLabelWidth;
    1700        }
    1701
    1702        /**
    1703         * Sets the maximum label width as a percentage of the plot width and sends
    1704         * a {@link PlotChangeEvent} to all registered listeners.
    1705         *
    1706         * @param width  the width (a percentage, where 0.20 = 20 percent).
    1707         *
    1708         * @see #getMaximumLabelWidth()
    1709         */
    1710        public void setMaximumLabelWidth(double width) {
    1711            this.maximumLabelWidth = width;
    1712            fireChangeEvent();
    1713        }
    1714
    1715        /**
    1716         * Returns the flag that controls whether or not label linking lines are
    1717         * visible.
    1718         *
    1719         * @return A boolean.
    1720         *
    1721         * @see #setLabelLinksVisible(boolean)
    1722         */
    1723        public boolean getLabelLinksVisible() {
    1724            return this.labelLinksVisible;
    1725        }
    1726
    1727        /**
    1728         * Sets the flag that controls whether or not label linking lines are
    1729         * visible and sends a {@link PlotChangeEvent} to all registered listeners.
    1730         * Please take care when hiding the linking lines - depending on the data
    1731         * values, the labels can be displayed some distance away from the
    1732         * corresponding pie section.
    1733         *
    1734         * @param visible  the flag.
    1735         *
    1736         * @see #getLabelLinksVisible()
    1737         */
    1738        public void setLabelLinksVisible(boolean visible) {
    1739            this.labelLinksVisible = visible;
    1740            fireChangeEvent();
    1741        }
    1742
    1743        /**
    1744         * Returns the label link style.
    1745         *
    1746         * @return The label link style (never <code>null</code>).
    1747         *
    1748         * @see #setLabelLinkStyle(PieLabelLinkStyle)
    1749         *
    1750         * @since 1.0.10
    1751         */
    1752        public PieLabelLinkStyle getLabelLinkStyle() {
    1753            return this.labelLinkStyle;
    1754        }
    1755
    1756        /**
    1757         * Sets the label link style and sends a {@link PlotChangeEvent} to all
    1758         * registered listeners.
    1759         *
    1760         * @param style  the new style (<code>null</code> not permitted).
    1761         *
    1762         * @see #getLabelLinkStyle()
    1763         *
    1764         * @since 1.0.10
    1765         */
    1766        public void setLabelLinkStyle(PieLabelLinkStyle style) {
    1767            if (style == null) {
    1768                throw new IllegalArgumentException("Null 'style' argument.");
    1769            }
    1770            this.labelLinkStyle = style;
    1771            fireChangeEvent();
    1772        }
    1773
    1774        /**
    1775         * Returns the margin (expressed as a percentage of the width or height)
    1776         * between the edge of the pie and the link point.
    1777         *
    1778         * @return The link margin (as a percentage, where 0.05 is five percent).
    1779         *
    1780         * @see #setLabelLinkMargin(double)
    1781         */
    1782        public double getLabelLinkMargin() {
    1783            return this.labelLinkMargin;
    1784        }
    1785
    1786        /**
    1787         * Sets the link margin and sends a {@link PlotChangeEvent} to all
    1788         * registered listeners.
    1789         *
    1790         * @param margin  the margin.
    1791         *
    1792         * @see #getLabelLinkMargin()
    1793         */
    1794        public void setLabelLinkMargin(double margin) {
    1795            this.labelLinkMargin = margin;
    1796            fireChangeEvent();
    1797        }
    1798
    1799        /**
    1800         * Returns the paint used for the lines that connect pie sections to their
    1801         * corresponding labels.
    1802         *
    1803         * @return The paint (never <code>null</code>).
    1804         *
    1805         * @see #setLabelLinkPaint(Paint)
    1806         */
    1807        public Paint getLabelLinkPaint() {
    1808            return this.labelLinkPaint;
    1809        }
    1810
    1811        /**
    1812         * Sets the paint used for the lines that connect pie sections to their
    1813         * corresponding labels, and sends a {@link PlotChangeEvent} to all
    1814         * registered listeners.
    1815         *
    1816         * @param paint  the paint (<code>null</code> not permitted).
    1817         *
    1818         * @see #getLabelLinkPaint()
    1819         */
    1820        public void setLabelLinkPaint(Paint paint) {
    1821            if (paint == null) {
    1822                throw new IllegalArgumentException("Null 'paint' argument.");
    1823            }
    1824            this.labelLinkPaint = paint;
    1825            fireChangeEvent();
    1826        }
    1827
    1828        /**
    1829         * Returns the stroke used for the label linking lines.
    1830         *
    1831         * @return The stroke.
    1832         *
    1833         * @see #setLabelLinkStroke(Stroke)
    1834         */
    1835        public Stroke getLabelLinkStroke() {
    1836            return this.labelLinkStroke;
    1837        }
    1838
    1839        /**
    1840         * Sets the link stroke and sends a {@link PlotChangeEvent} to all
    1841         * registered listeners.
    1842         *
    1843         * @param stroke  the stroke.
    1844         *
    1845         * @see #getLabelLinkStroke()
    1846         */
    1847        public void setLabelLinkStroke(Stroke stroke) {
    1848            if (stroke == null) {
    1849                throw new IllegalArgumentException("Null 'stroke' argument.");
    1850            }
    1851            this.labelLinkStroke = stroke;
    1852            fireChangeEvent();
    1853        }
    1854
    1855        /**
    1856         * Returns the distance that the end of the label link is embedded into
    1857         * the plot, expressed as a percentage of the plot's radius.
    1858         * <br><br>
    1859         * This method is overridden in the {@link RingPlot} class to resolve
    1860         * bug 2121818.
    1861         *
    1862         * @return <code>0.10</code>.
    1863         *
    1864         * @since 1.0.12
    1865         */
    1866        protected double getLabelLinkDepth() {
    1867            return 0.1;
    1868        }
    1869
    1870        /**
    1871         * Returns the section label font.
    1872         *
    1873         * @return The font (never <code>null</code>).
    1874         *
    1875         * @see #setLabelFont(Font)
    1876         */
    1877        public Font getLabelFont() {
    1878            return this.labelFont;
    1879        }
    1880
    1881        /**
    1882         * Sets the section label font and sends a {@link PlotChangeEvent} to all
    1883         * registered listeners.
    1884         *
    1885         * @param font  the font (<code>null</code> not permitted).
    1886         *
    1887         * @see #getLabelFont()
    1888         */
    1889        public void setLabelFont(Font font) {
    1890            if (font == null) {
    1891                throw new IllegalArgumentException("Null 'font' argument.");
    1892            }
    1893            this.labelFont = font;
    1894            fireChangeEvent();
    1895        }
    1896
    1897        /**
    1898         * Returns the section label paint.
    1899         *
    1900         * @return The paint (never <code>null</code>).
    1901         *
    1902         * @see #setLabelPaint(Paint)
    1903         */
    1904        public Paint getLabelPaint() {
    1905            return this.labelPaint;
    1906        }
    1907
    1908        /**
    1909         * Sets the section label paint and sends a {@link PlotChangeEvent} to all
    1910         * registered listeners.
    1911         *
    1912         * @param paint  the paint (<code>null</code> not permitted).
    1913         *
    1914         * @see #getLabelPaint()
    1915         */
    1916        public void setLabelPaint(Paint paint) {
    1917            if (paint == null) {
    1918                throw new IllegalArgumentException("Null 'paint' argument.");
    1919            }
    1920            this.labelPaint = paint;
    1921            fireChangeEvent();
    1922        }
    1923
    1924        /**
    1925         * Returns the section label background paint.
    1926         *
    1927         * @return The paint (possibly <code>null</code>).
    1928         *
    1929         * @see #setLabelBackgroundPaint(Paint)
    1930         */
    1931        public Paint getLabelBackgroundPaint() {
    1932            return this.labelBackgroundPaint;
    1933        }
    1934
    1935        /**
    1936         * Sets the section label background paint and sends a
    1937         * {@link PlotChangeEvent} to all registered listeners.
    1938         *
    1939         * @param paint  the paint (<code>null</code> permitted).
    1940         *
    1941         * @see #getLabelBackgroundPaint()
    1942         */
    1943        public void setLabelBackgroundPaint(Paint paint) {
    1944            this.labelBackgroundPaint = paint;
    1945            fireChangeEvent();
    1946        }
    1947
    1948        /**
    1949         * Returns the section label outline paint.
    1950         *
    1951         * @return The paint (possibly <code>null</code>).
    1952         *
    1953         * @see #setLabelOutlinePaint(Paint)
    1954         */
    1955        public Paint getLabelOutlinePaint() {
    1956            return this.labelOutlinePaint;
    1957        }
    1958
    1959        /**
    1960         * Sets the section label outline paint and sends a
    1961         * {@link PlotChangeEvent} to all registered listeners.
    1962         *
    1963         * @param paint  the paint (<code>null</code> permitted).
    1964         *
    1965         * @see #getLabelOutlinePaint()
    1966         */
    1967        public void setLabelOutlinePaint(Paint paint) {
    1968            this.labelOutlinePaint = paint;
    1969            fireChangeEvent();
    1970        }
    1971
    1972        /**
    1973         * Returns the section label outline stroke.
    1974         *
    1975         * @return The stroke (possibly <code>null</code>).
    1976         *
    1977         * @see #setLabelOutlineStroke(Stroke)
    1978         */
    1979        public Stroke getLabelOutlineStroke() {
    1980            return this.labelOutlineStroke;
    1981        }
    1982
    1983        /**
    1984         * Sets the section label outline stroke and sends a
    1985         * {@link PlotChangeEvent} to all registered listeners.
    1986         *
    1987         * @param stroke  the stroke (<code>null</code> permitted).
    1988         *
    1989         * @see #getLabelOutlineStroke()
    1990         */
    1991        public void setLabelOutlineStroke(Stroke stroke) {
    1992            this.labelOutlineStroke = stroke;
    1993            fireChangeEvent();
    1994        }
    1995
    1996        /**
    1997         * Returns the section label shadow paint.
    1998         *
    1999         * @return The paint (possibly <code>null</code>).
    2000         *
    2001         * @see #setLabelShadowPaint(Paint)
    2002         */
    2003        public Paint getLabelShadowPaint() {
    2004            return this.labelShadowPaint;
    2005        }
    2006
    2007        /**
    2008         * Sets the section label shadow paint and sends a {@link PlotChangeEvent}
    2009         * to all registered listeners.
    2010         *
    2011         * @param paint  the paint (<code>null</code> permitted).
    2012         *
    2013         * @see #getLabelShadowPaint()
    2014         */
    2015        public void setLabelShadowPaint(Paint paint) {
    2016            this.labelShadowPaint = paint;
    2017            fireChangeEvent();
    2018        }
    2019
    2020        /**
    2021         * Returns the label padding.
    2022         *
    2023         * @return The label padding (never <code>null</code>).
    2024         *
    2025         * @since 1.0.7
    2026         *
    2027         * @see #setLabelPadding(RectangleInsets)
    2028         */
    2029        public RectangleInsets getLabelPadding() {
    2030            return this.labelPadding;
    2031        }
    2032
    2033        /**
    2034         * Sets the padding between each label and its outline and sends a
    2035         * {@link PlotChangeEvent} to all registered listeners.
    2036         *
    2037         * @param padding  the padding (<code>null</code> not permitted).
    2038         *
    2039         * @since 1.0.7
    2040         *
    2041         * @see #getLabelPadding()
    2042         */
    2043        public void setLabelPadding(RectangleInsets padding) {
    2044            if (padding == null) {
    2045                throw new IllegalArgumentException("Null 'padding' argument.");
    2046            }
    2047            this.labelPadding = padding;
    2048            fireChangeEvent();
    2049        }
    2050
    2051        /**
    2052         * Returns the flag that controls whether simple or extended labels are
    2053         * displayed on the plot.
    2054         *
    2055         * @return A boolean.
    2056         *
    2057         * @since 1.0.7
    2058         */
    2059        public boolean getSimpleLabels() {
    2060            return this.simpleLabels;
    2061        }
    2062
    2063        /**
    2064         * Sets the flag that controls whether simple or extended labels are
    2065         * displayed on the plot, and sends a {@link PlotChangeEvent} to all
    2066         * registered listeners.
    2067         *
    2068         * @param simple  the new flag value.
    2069         *
    2070         * @since 1.0.7
    2071         */
    2072        public void setSimpleLabels(boolean simple) {
    2073            this.simpleLabels = simple;
    2074            fireChangeEvent();
    2075        }
    2076
    2077        /**
    2078         * Returns the offset used for the simple labels, if they are displayed.
    2079         *
    2080         * @return The offset (never <code>null</code>).
    2081         *
    2082         * @since 1.0.7
    2083         *
    2084         * @see #setSimpleLabelOffset(RectangleInsets)
    2085         */
    2086        public RectangleInsets getSimpleLabelOffset() {
    2087            return this.simpleLabelOffset;
    2088        }
    2089
    2090        /**
    2091         * Sets the offset for the simple labels and sends a
    2092         * {@link PlotChangeEvent} to all registered listeners.
    2093         *
    2094         * @param offset  the offset (<code>null</code> not permitted).
    2095         *
    2096         * @since 1.0.7
    2097         *
    2098         * @see #getSimpleLabelOffset()
    2099         */
    2100        public void setSimpleLabelOffset(RectangleInsets offset) {
    2101            if (offset == null) {
    2102                throw new IllegalArgumentException("Null 'offset' argument.");
    2103            }
    2104            this.simpleLabelOffset = offset;
    2105            fireChangeEvent();
    2106        }
    2107
    2108        /**
    2109         * Returns the object responsible for the vertical layout of the pie
    2110         * section labels.
    2111         *
    2112         * @return The label distributor (never <code>null</code>).
    2113         *
    2114         * @since 1.0.6
    2115         */
    2116        public AbstractPieLabelDistributor getLabelDistributor() {
    2117            return this.labelDistributor;
    2118        }
    2119
    2120        /**
    2121         * Sets the label distributor and sends a {@link PlotChangeEvent} to all
    2122         * registered listeners.
    2123         *
    2124         * @param distributor  the distributor (<code>null</code> not permitted).
    2125         *
    2126         * @since 1.0.6
    2127         */
    2128        public void setLabelDistributor(AbstractPieLabelDistributor distributor) {
    2129            if (distributor == null) {
    2130                throw new IllegalArgumentException("Null 'distributor' argument.");
    2131            }
    2132            this.labelDistributor = distributor;
    2133            fireChangeEvent();
    2134        }
    2135
    2136        /**
    2137         * Returns the tool tip generator, an object that is responsible for
    2138         * generating the text items used for tool tips by the plot.  If the
    2139         * generator is <code>null</code>, no tool tips will be created.
    2140         *
    2141         * @return The generator (possibly <code>null</code>).
    2142         *
    2143         * @see #setToolTipGenerator(PieToolTipGenerator)
    2144         */
    2145        public PieToolTipGenerator getToolTipGenerator() {
    2146            return this.toolTipGenerator;
    2147        }
    2148
    2149        /**
    2150         * Sets the tool tip generator and sends a {@link PlotChangeEvent} to all
    2151         * registered listeners.  Set the generator to <code>null</code> if you
    2152         * don't want any tool tips.
    2153         *
    2154         * @param generator  the generator (<code>null</code> permitted).
    2155         *
    2156         * @see #getToolTipGenerator()
    2157         */
    2158        public void setToolTipGenerator(PieToolTipGenerator generator) {
    2159            this.toolTipGenerator = generator;
    2160            fireChangeEvent();
    2161        }
    2162
    2163        /**
    2164         * Returns the URL generator.
    2165         *
    2166         * @return The generator (possibly <code>null</code>).
    2167         *
    2168         * @see #setURLGenerator(PieURLGenerator)
    2169         */
    2170        public PieURLGenerator getURLGenerator() {
    2171            return this.urlGenerator;
    2172        }
    2173
    2174        /**
    2175         * Sets the URL generator and sends a {@link PlotChangeEvent} to all
    2176         * registered listeners.
    2177         *
    2178         * @param generator  the generator (<code>null</code> permitted).
    2179         *
    2180         * @see #getURLGenerator()
    2181         */
    2182        public void setURLGenerator(PieURLGenerator generator) {
    2183            this.urlGenerator = generator;
    2184            fireChangeEvent();
    2185        }
    2186
    2187        /**
    2188         * Returns the minimum arc angle that will be drawn.  Pie sections for an
    2189         * angle smaller than this are not drawn, to avoid a JDK bug.
    2190         *
    2191         * @return The minimum angle.
    2192         *
    2193         * @see #setMinimumArcAngleToDraw(double)
    2194         */
    2195        public double getMinimumArcAngleToDraw() {
    2196            return this.minimumArcAngleToDraw;
    2197        }
    2198
    2199        /**
    2200         * Sets the minimum arc angle that will be drawn.  Pie sections for an
    2201         * angle smaller than this are not drawn, to avoid a JDK bug.  See this
    2202         * link for details:
    2203         * <br><br>
    2204         * <a href="http://www.jfree.org/phpBB2/viewtopic.php?t=2707">
    2205         * http://www.jfree.org/phpBB2/viewtopic.php?t=2707</a>
    2206         * <br><br>
    2207         * ...and this bug report in the Java Bug Parade:
    2208         * <br><br>
    2209         * <a href=
    2210         * "http://developer.java.sun.com/developer/bugParade/bugs/4836495.html">
    2211         * http://developer.java.sun.com/developer/bugParade/bugs/4836495.html</a>
    2212         *
    2213         * @param angle  the minimum angle.
    2214         *
    2215         * @see #getMinimumArcAngleToDraw()
    2216         */
    2217        public void setMinimumArcAngleToDraw(double angle) {
    2218            this.minimumArcAngleToDraw = angle;
    2219        }
    2220
    2221        /**
    2222         * Returns the shape used for legend items.
    2223         *
    2224         * @return The shape (never <code>null</code>).
    2225         *
    2226         * @see #setLegendItemShape(Shape)
    2227         */
    2228        public Shape getLegendItemShape() {
    2229            return this.legendItemShape;
    2230        }
    2231
    2232        /**
    2233         * Sets the shape used for legend items and sends a {@link PlotChangeEvent}
    2234         * to all registered listeners.
    2235         *
    2236         * @param shape  the shape (<code>null</code> not permitted).
    2237         *
    2238         * @see #getLegendItemShape()
    2239         */
    2240        public void setLegendItemShape(Shape shape) {
    2241            if (shape == null) {
    2242                throw new IllegalArgumentException("Null 'shape' argument.");
    2243            }
    2244            this.legendItemShape = shape;
    2245            fireChangeEvent();
    2246        }
    2247
    2248        /**
    2249         * Returns the legend label generator.
    2250         *
    2251         * @return The legend label generator (never <code>null</code>).
    2252         *
    2253         * @see #setLegendLabelGenerator(PieSectionLabelGenerator)
    2254         */
    2255        public PieSectionLabelGenerator getLegendLabelGenerator() {
    2256            return this.legendLabelGenerator;
    2257        }
    2258
    2259        /**
    2260         * Sets the legend label generator and sends a {@link PlotChangeEvent} to
    2261         * all registered listeners.
    2262         *
    2263         * @param generator  the generator (<code>null</code> not permitted).
    2264         *
    2265         * @see #getLegendLabelGenerator()
    2266         */
    2267        public void setLegendLabelGenerator(PieSectionLabelGenerator generator) {
    2268            if (generator == null) {
    2269                throw new IllegalArgumentException("Null 'generator' argument.");
    2270            }
    2271            this.legendLabelGenerator = generator;
    2272            fireChangeEvent();
    2273        }
    2274
    2275        /**
    2276         * Returns the legend label tool tip generator.
    2277         *
    2278         * @return The legend label tool tip generator (possibly <code>null</code>).
    2279         *
    2280         * @see #setLegendLabelToolTipGenerator(PieSectionLabelGenerator)
    2281         */
    2282        public PieSectionLabelGenerator getLegendLabelToolTipGenerator() {
    2283            return this.legendLabelToolTipGenerator;
    2284        }
    2285
    2286        /**
    2287         * Sets the legend label tool tip generator and sends a
    2288         * {@link PlotChangeEvent} to all registered listeners.
    2289         *
    2290         * @param generator  the generator (<code>null</code> permitted).
    2291         *
    2292         * @see #getLegendLabelToolTipGenerator()
    2293         */
    2294        public void setLegendLabelToolTipGenerator(
    2295                PieSectionLabelGenerator generator) {
    2296            this.legendLabelToolTipGenerator = generator;
    2297            fireChangeEvent();
    2298        }
    2299
    2300        /**
    2301         * Returns the legend label URL generator.
    2302         *
    2303         * @return The legend label URL generator (possibly <code>null</code>).
    2304         *
    2305         * @see #setLegendLabelURLGenerator(PieURLGenerator)
    2306         *
    2307         * @since 1.0.4
    2308         */
    2309        public PieURLGenerator getLegendLabelURLGenerator() {
    2310            return this.legendLabelURLGenerator;
    2311        }
    2312
    2313        /**
    2314         * Sets the legend label URL generator and sends a
    2315         * {@link PlotChangeEvent} to all registered listeners.
    2316         *
    2317         * @param generator  the generator (<code>null</code> permitted).
    2318         *
    2319         * @see #getLegendLabelURLGenerator()
    2320         *
    2321         * @since 1.0.4
    2322         */
    2323        public void setLegendLabelURLGenerator(PieURLGenerator generator) {
    2324            this.legendLabelURLGenerator = generator;
    2325            fireChangeEvent();
    2326        }
    2327
    2328        /**
    2329         * Initialises the drawing procedure.  This method will be called before
    2330         * the first item is rendered, giving the plot an opportunity to initialise
    2331         * any state information it wants to maintain.
    2332         *
    2333         * @param g2  the graphics device.
    2334         * @param plotArea  the plot area (<code>null</code> not permitted).
    2335         * @param plot  the plot.
    2336         * @param index  the secondary index (<code>null</code> for primary
    2337         *               renderer).
    2338         * @param info  collects chart rendering information for return to caller.
    2339         *
    2340         * @return A state object (maintains state information relevant to one
    2341         *         chart drawing).
    2342         */
    2343        public PiePlotState initialise(Graphics2D g2, Rectangle2D plotArea,
    2344                PiePlot plot, Integer index, PlotRenderingInfo info) {
    2345
    2346            PiePlotState state = new PiePlotState(info);
    2347            state.setPassesRequired(2);
    2348            if (this.dataset != null) {
    2349                state.setTotal(DatasetUtilities.calculatePieDatasetTotal(
    2350                        plot.getDataset()));
    2351            }
    2352            state.setLatestAngle(plot.getStartAngle());
    2353            return state;
    2354
    2355        }
    2356
    2357        /**
    2358         * Draws the plot on a Java 2D graphics device (such as the screen or a
    2359         * printer).
    2360         *
    2361         * @param g2  the graphics device.
    2362         * @param area  the area within which the plot should be drawn.
    2363         * @param anchor  the anchor point (<code>null</code> permitted).
    2364         * @param parentState  the state from the parent plot, if there is one.
    2365         * @param info  collects info about the drawing
    2366         *              (<code>null</code> permitted).
    2367         */
    2368        public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor,
    2369                         PlotState parentState, PlotRenderingInfo info) {
    2370
    2371            // adjust for insets...
    2372            RectangleInsets insets = getInsets();
    2373            insets.trim(area);
    2374
    2375            if (info != null) {
    2376                info.setPlotArea(area);
    2377                info.setDataArea(area);
    2378            }
    2379
    2380            drawBackground(g2, area);
    2381            drawOutline(g2, area);
    2382
    2383            Shape savedClip = g2.getClip();
    2384            g2.clip(area);
    2385
    2386            Composite originalComposite = g2.getComposite();
    2387            g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
    2388                    getForegroundAlpha()));
    2389
    2390            if (!DatasetUtilities.isEmptyOrNull(this.dataset)) {
    2391                drawPie(g2, area, info);
    2392            }
    2393            else {
    2394                drawNoDataMessage(g2, area);
    2395            }
    2396
    2397            g2.setClip(savedClip);
    2398            g2.setComposite(originalComposite);
    2399
    2400            drawOutline(g2, area);
    2401
    2402        }
    2403
    2404        /**
    2405         * Draws the pie.
    2406         *
    2407         * @param g2  the graphics device.
    2408         * @param plotArea  the plot area.
    2409         * @param info  chart rendering info.
    2410         */
    2411        protected void drawPie(Graphics2D g2, Rectangle2D plotArea,
    2412                               PlotRenderingInfo info) {
    2413
    2414            PiePlotState state = initialise(g2, plotArea, this, null, info);
    2415
    2416            // adjust the plot area for interior spacing and labels...
    2417            double labelReserve = 0.0;
    2418            if (this.labelGenerator != null && !this.simpleLabels) {
    2419                labelReserve = this.labelGap + this.maximumLabelWidth;
    2420            }
    2421            double gapHorizontal = plotArea.getWidth() * (this.interiorGap
    2422                    + labelReserve) * 2.0;
    2423            double gapVertical = plotArea.getHeight() * this.interiorGap * 2.0;
    2424
    2425
    2426            if (DEBUG_DRAW_INTERIOR) {
    2427                double hGap = plotArea.getWidth() * this.interiorGap;
    2428                double vGap = plotArea.getHeight() * this.interiorGap;
    2429
    2430                double igx1 = plotArea.getX() + hGap;
    2431                double igx2 = plotArea.getMaxX() - hGap;
    2432                double igy1 = plotArea.getY() + vGap;
    2433                double igy2 = plotArea.getMaxY() - vGap;
    2434                g2.setPaint(Color.gray);
    2435                g2.draw(new Rectangle2D.Double(igx1, igy1, igx2 - igx1,
    2436                        igy2 - igy1));
    2437            }
    2438
    2439            double linkX = plotArea.getX() + gapHorizontal / 2;
    2440            double linkY = plotArea.getY() + gapVertical / 2;
    2441            double linkW = plotArea.getWidth() - gapHorizontal;
    2442            double linkH = plotArea.getHeight() - gapVertical;
    2443
    2444            // make the link area a square if the pie chart is to be circular...
    2445            if (this.circular) {
    2446                double min = Math.min(linkW, linkH) / 2;
    2447                linkX = (linkX + linkX + linkW) / 2 - min;
    2448                linkY = (linkY + linkY + linkH) / 2 - min;
    2449                linkW = 2 * min;
    2450                linkH = 2 * min;
    2451            }
    2452
    2453            // the link area defines the dog leg points for the linking lines to
    2454            // the labels
    2455            Rectangle2D linkArea = new Rectangle2D.Double(linkX, linkY, linkW,
    2456                    linkH);
    2457            state.setLinkArea(linkArea);
    2458
    2459            if (DEBUG_DRAW_LINK_AREA) {
    2460                g2.setPaint(Color.blue);
    2461                g2.draw(linkArea);
    2462                g2.setPaint(Color.yellow);
    2463                g2.draw(new Ellipse2D.Double(linkArea.getX(), linkArea.getY(),
    2464                        linkArea.getWidth(), linkArea.getHeight()));
    2465            }
    2466
    2467            // the explode area defines the max circle/ellipse for the exploded
    2468            // pie sections.  it is defined by shrinking the linkArea by the
    2469            // linkMargin factor.
    2470            double lm = 0.0;
    2471            if (!this.simpleLabels) {
    2472                lm = this.labelLinkMargin;
    2473            }
    2474            double hh = linkArea.getWidth() * lm * 2.0;
    2475            double vv = linkArea.getHeight() * lm * 2.0;
    2476            Rectangle2D explodeArea = new Rectangle2D.Double(linkX + hh / 2.0,
    2477                    linkY + vv / 2.0, linkW - hh, linkH - vv);
    2478
    2479            state.setExplodedPieArea(explodeArea);
    2480
    2481            // the pie area defines the circle/ellipse for regular pie sections.
    2482            // it is defined by shrinking the explodeArea by the explodeMargin
    2483            // factor.
    2484            double maximumExplodePercent = getMaximumExplodePercent();
    2485            double percent = maximumExplodePercent / (1.0 + maximumExplodePercent);
    2486
    2487            double h1 = explodeArea.getWidth() * percent;
    2488            double v1 = explodeArea.getHeight() * percent;
    2489            Rectangle2D pieArea = new Rectangle2D.Double(explodeArea.getX()
    2490                    + h1 / 2.0, explodeArea.getY() + v1 / 2.0,
    2491                    explodeArea.getWidth() - h1, explodeArea.getHeight() - v1);
    2492
    2493            if (DEBUG_DRAW_PIE_AREA) {
    2494                g2.setPaint(Color.green);
    2495                g2.draw(pieArea);
    2496            }
    2497            state.setPieArea(pieArea);
    2498            state.setPieCenterX(pieArea.getCenterX());
    2499            state.setPieCenterY(pieArea.getCenterY());
    2500            state.setPieWRadius(pieArea.getWidth() / 2.0);
    2501            state.setPieHRadius(pieArea.getHeight() / 2.0);
    2502
    2503            // plot the data (unless the dataset is null)...
    2504            if ((this.dataset != null) && (this.dataset.getKeys().size() > 0)) {
    2505
    2506                List keys = this.dataset.getKeys();
    2507                double totalValue = DatasetUtilities.calculatePieDatasetTotal(
    2508                        this.dataset);
    2509
    2510                int passesRequired = state.getPassesRequired();
    2511                for (int pass = 0; pass < passesRequired; pass++) {
    2512                    double runningTotal = 0.0;
    2513                    for (int section = 0; section < keys.size(); section++) {
    2514                        Number n = this.dataset.getValue(section);
    2515                        if (n != null) {
    2516                            double value = n.doubleValue();
    2517                            if (value > 0.0) {
    2518                                runningTotal += value;
    2519                                drawItem(g2, section, explodeArea, state, pass);
    2520                            }
    2521                        }
    2522                    }
    2523                }
    2524                if (this.simpleLabels) {
    2525                    drawSimpleLabels(g2, keys, totalValue, plotArea, linkArea,
    2526                            state);
    2527                }
    2528                else {
    2529                    drawLabels(g2, keys, totalValue, plotArea, linkArea, state);
    2530                }
    2531
    2532            }
    2533            else {
    2534                drawNoDataMessage(g2, plotArea);
    2535            }
    2536        }
    2537
    2538        /**
    2539         * Draws a single data item.
    2540         *
    2541         * @param g2  the graphics device (<code>null</code> not permitted).
    2542         * @param section  the section index.
    2543         * @param dataArea  the data plot area.
    2544         * @param state  state information for one chart.
    2545         * @param currentPass  the current pass index.
    2546         */
    2547        protected void drawItem(Graphics2D g2, int section, Rectangle2D dataArea,
    2548                                PiePlotState state, int currentPass) {
    2549
    2550            Number n = this.dataset.getValue(section);
    2551            if (n == null) {
    2552                return;
    2553            }
    2554            double value = n.doubleValue();
    2555            double angle1 = 0.0;
    2556            double angle2 = 0.0;
    2557
    2558            if (this.direction == Rotation.CLOCKWISE) {
    2559                angle1 = state.getLatestAngle();
    2560                angle2 = angle1 - value / state.getTotal() * 360.0;
    2561            }
    2562            else if (this.direction == Rotation.ANTICLOCKWISE) {
    2563                angle1 = state.getLatestAngle();
    2564                angle2 = angle1 + value / state.getTotal() * 360.0;
    2565            }
    2566            else {
    2567                throw new IllegalStateException("Rotation type not recognised.");
    2568            }
    2569
    2570            double angle = (angle2 - angle1);
    2571            if (Math.abs(angle) > getMinimumArcAngleToDraw()) {
    2572                double ep = 0.0;
    2573                double mep = getMaximumExplodePercent();
    2574                if (mep > 0.0) {
    2575                    ep = getExplodePercent(section) / mep;
    2576                }
    2577                Rectangle2D arcBounds = getArcBounds(state.getPieArea(),
    2578                        state.getExplodedPieArea(), angle1, angle, ep);
    2579                Arc2D.Double arc = new Arc2D.Double(arcBounds, angle1, angle,
    2580                        Arc2D.PIE);
    2581
    2582                if (currentPass == 0) {
    2583                    if (this.shadowPaint != null) {
    2584                        Shape shadowArc = ShapeUtilities.createTranslatedShape(
    2585                                arc, (float) this.shadowXOffset,
    2586                                (float) this.shadowYOffset);
    2587                        g2.setPaint(this.shadowPaint);
    2588                        g2.fill(shadowArc);
    2589                    }
    2590                }
    2591                else if (currentPass == 1) {
    2592                    Comparable key = getSectionKey(section);
    2593                    Paint paint = lookupSectionPaint(key);
    2594                    g2.setPaint(paint);
    2595                    g2.fill(arc);
    2596
    2597                    Paint outlinePaint = lookupSectionOutlinePaint(key);
    2598                    Stroke outlineStroke = lookupSectionOutlineStroke(key);
    2599                    if (this.sectionOutlinesVisible) {
    2600                        g2.setPaint(outlinePaint);
    2601                        g2.setStroke(outlineStroke);
    2602                        g2.draw(arc);
    2603                    }
    2604
    2605                    // update the linking line target for later
    2606                    // add an entity for the pie section
    2607                    if (state.getInfo() != null) {
    2608                        EntityCollection entities = state.getEntityCollection();
    2609                        if (entities != null) {
    2610                            String tip = null;
    2611                            if (this.toolTipGenerator != null) {
    2612                                tip = this.toolTipGenerator.generateToolTip(
    2613                                        this.dataset, key);
    2614                            }
    2615                            String url = null;
    2616                            if (this.urlGenerator != null) {
    2617                                url = this.urlGenerator.generateURL(this.dataset,
    2618                                        key, this.pieIndex);
    2619                            }
    2620                            PieSectionEntity entity = new PieSectionEntity(
    2621                                    arc, this.dataset, this.pieIndex, section, key,
    2622                                    tip, url);
    2623                            entities.add(entity);
    2624                        }
    2625                    }
    2626                }
    2627            }
    2628            state.setLatestAngle(angle2);
    2629        }
    2630
    2631        /**
    2632         * Draws the pie section labels in the simple form.
    2633         *
    2634         * @param g2  the graphics device.
    2635         * @param keys  the section keys.
    2636         * @param totalValue  the total value for all sections in the pie.
    2637         * @param plotArea  the plot area.
    2638         * @param pieArea  the area containing the pie.
    2639         * @param state  the plot state.
    2640         *
    2641         * @since 1.0.7
    2642         */
    2643        protected void drawSimpleLabels(Graphics2D g2, List keys,
    2644                double totalValue, Rectangle2D plotArea, Rectangle2D pieArea,
    2645                PiePlotState state) {
    2646
    2647            Composite originalComposite = g2.getComposite();
    2648            g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
    2649                    1.0f));
    2650
    2651            RectangleInsets labelInsets = new RectangleInsets(UnitType.RELATIVE,
    2652                    0.18, 0.18, 0.18, 0.18);
    2653            Rectangle2D labelsArea = labelInsets.createInsetRectangle(pieArea);
    2654            double runningTotal = 0.0;
    2655            Iterator iterator = keys.iterator();
    2656            while (iterator.hasNext()) {
    2657                Comparable key = (Comparable) iterator.next();
    2658                boolean include = true;
    2659                double v = 0.0;
    2660                Number n = getDataset().getValue(key);
    2661                if (n == null) {
    2662                    include = !getIgnoreNullValues();
    2663                }
    2664                else {
    2665                    v = n.doubleValue();
    2666                    include = getIgnoreZeroValues() ? v > 0.0 : v >= 0.0;
    2667                }
    2668
    2669                if (include) {
    2670                    runningTotal = runningTotal + v;
    2671                    // work out the mid angle (0 - 90 and 270 - 360) = right,
    2672                    // otherwise left
    2673                    double mid = getStartAngle() + (getDirection().getFactor()
    2674                            * ((runningTotal - v / 2.0) * 360) / totalValue);
    2675
    2676                    Arc2D arc = new Arc2D.Double(labelsArea, getStartAngle(),
    2677                            mid - getStartAngle(), Arc2D.OPEN);
    2678                    int x = (int) arc.getEndPoint().getX();
    2679                    int y = (int) arc.getEndPoint().getY();
    2680
    2681                    PieSectionLabelGenerator labelGenerator = getLabelGenerator();
    2682                    if (labelGenerator == null) {
    2683                        continue;
    2684                    }
    2685                    String label = labelGenerator.generateSectionLabel(
    2686                            this.dataset, key);
    2687                    if (label == null) {
    2688                        continue;
    2689                    }
    2690                    g2.setFont(this.labelFont);
    2691                    FontMetrics fm = g2.getFontMetrics();
    2692                    Rectangle2D bounds = TextUtilities.getTextBounds(label, g2, fm);
    2693                    Rectangle2D out = this.labelPadding.createOutsetRectangle(
    2694                            bounds);
    2695                    Shape bg = ShapeUtilities.createTranslatedShape(out,
    2696                            x - bounds.getCenterX(), y - bounds.getCenterY());
    2697                    if (this.labelShadowPaint != null) {
    2698                        Shape shadow = ShapeUtilities.createTranslatedShape(bg,
    2699                                this.shadowXOffset, this.shadowYOffset);
    2700                        g2.setPaint(this.labelShadowPaint);
    2701                        g2.fill(shadow);
    2702                    }
    2703                    if (this.labelBackgroundPaint != null) {
    2704                        g2.setPaint(this.labelBackgroundPaint);
    2705                        g2.fill(bg);
    2706                    }
    2707                    if (this.labelOutlinePaint != null
    2708                            && this.labelOutlineStroke != null) {
    2709                        g2.setPaint(this.labelOutlinePaint);
    2710                        g2.setStroke(this.labelOutlineStroke);
    2711                        g2.draw(bg);
    2712                    }
    2713
    2714                    g2.setPaint(this.labelPaint);
    2715                    g2.setFont(this.labelFont);
    2716                    TextUtilities.drawAlignedString(getLabelGenerator()
    2717                            .generateSectionLabel(getDataset(), key), g2, x, y,
    2718                            TextAnchor.CENTER);
    2719
    2720                }
    2721            }
    2722
    2723            g2.setComposite(originalComposite);
    2724
    2725        }
    2726
    2727        /**
    2728         * Draws the labels for the pie sections.
    2729         *
    2730         * @param g2  the graphics device.
    2731         * @param keys  the keys.
    2732         * @param totalValue  the total value.
    2733         * @param plotArea  the plot area.
    2734         * @param linkArea  the link area.
    2735         * @param state  the state.
    2736         */
    2737        protected void drawLabels(Graphics2D g2, List keys, double totalValue,
    2738                                  Rectangle2D plotArea, Rectangle2D linkArea,
    2739                                  PiePlotState state) {
    2740
    2741            Composite originalComposite = g2.getComposite();
    2742            g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
    2743                    1.0f));
    2744
    2745            // classify the keys according to which side the label will appear...
    2746            DefaultKeyedValues leftKeys = new DefaultKeyedValues();
    2747            DefaultKeyedValues rightKeys = new DefaultKeyedValues();
    2748
    2749            double runningTotal = 0.0;
    2750            Iterator iterator = keys.iterator();
    2751            while (iterator.hasNext()) {
    2752                Comparable key = (Comparable) iterator.next();
    2753                boolean include = true;
    2754                double v = 0.0;
    2755                Number n = this.dataset.getValue(key);
    2756                if (n == null) {
    2757                    include = !this.ignoreNullValues;
    2758                }
    2759                else {
    2760                    v = n.doubleValue();
    2761                    include = this.ignoreZeroValues ? v > 0.0 : v >= 0.0;
    2762                }
    2763
    2764                if (include) {
    2765                    runningTotal = runningTotal + v;
    2766                    // work out the mid angle (0 - 90 and 270 - 360) = right,
    2767                    // otherwise left
    2768                    double mid = this.startAngle + (this.direction.getFactor()
    2769                            * ((runningTotal - v / 2.0) * 360) / totalValue);
    2770                    if (Math.cos(Math.toRadians(mid)) < 0.0) {
    2771                        leftKeys.addValue(key, new Double(mid));
    2772                    }
    2773                    else {
    2774                        rightKeys.addValue(key, new Double(mid));
    2775                    }
    2776                }
    2777            }
    2778
    2779            g2.setFont(getLabelFont());
    2780
    2781            // calculate the max label width from the plot dimensions, because
    2782            // a circular pie can leave a lot more room for labels...
    2783            double marginX = plotArea.getX() + this.interiorGap
    2784                    * plotArea.getWidth();
    2785            double gap = plotArea.getWidth() * this.labelGap;
    2786            double ww = linkArea.getX() - gap - marginX;
    2787            float labelWidth = (float) this.labelPadding.trimWidth(ww);
    2788
    2789            // draw the labels...
    2790            if (this.labelGenerator != null) {
    2791                drawLeftLabels(leftKeys, g2, plotArea, linkArea, labelWidth,
    2792                        state);
    2793                drawRightLabels(rightKeys, g2, plotArea, linkArea, labelWidth,
    2794                        state);
    2795            }
    2796            g2.setComposite(originalComposite);
    2797
    2798        }
    2799
    2800        /**
    2801         * Draws the left labels.
    2802         *
    2803         * @param leftKeys  a collection of keys and angles (to the middle of the
    2804         *         section, in degrees) for the sections on the left side of the
    2805         *         plot.
    2806         * @param g2  the graphics device.
    2807         * @param plotArea  the plot area.
    2808         * @param linkArea  the link area.
    2809         * @param maxLabelWidth  the maximum label width.
    2810         * @param state  the state.
    2811         */
    2812        protected void drawLeftLabels(KeyedValues leftKeys, Graphics2D g2,
    2813                                      Rectangle2D plotArea, Rectangle2D linkArea,
    2814                                      float maxLabelWidth, PiePlotState state) {
    2815
    2816            this.labelDistributor.clear();
    2817            double lGap = plotArea.getWidth() * this.labelGap;
    2818            double verticalLinkRadius = state.getLinkArea().getHeight() / 2.0;
    2819            for (int i = 0; i < leftKeys.getItemCount(); i++) {
    2820                String label = this.labelGenerator.generateSectionLabel(
    2821                        this.dataset, leftKeys.getKey(i));
    2822                if (label != null) {
    2823                    TextBlock block = TextUtilities.createTextBlock(label,
    2824                            this.labelFont, this.labelPaint, maxLabelWidth,
    2825                            new G2TextMeasurer(g2));
    2826                    TextBox labelBox = new TextBox(block);
    2827                    labelBox.setBackgroundPaint(this.labelBackgroundPaint);
    2828                    labelBox.setOutlinePaint(this.labelOutlinePaint);
    2829                    labelBox.setOutlineStroke(this.labelOutlineStroke);
    2830                    labelBox.setShadowPaint(this.labelShadowPaint);
    2831                    labelBox.setInteriorGap(this.labelPadding);
    2832                    double theta = Math.toRadians(
    2833                            leftKeys.getValue(i).doubleValue());
    2834                    double baseY = state.getPieCenterY() - Math.sin(theta)
    2835                                   * verticalLinkRadius;
    2836                    double hh = labelBox.getHeight(g2);
    2837
    2838                    this.labelDistributor.addPieLabelRecord(new PieLabelRecord(
    2839                            leftKeys.getKey(i), theta, baseY, labelBox, hh,
    2840                            lGap / 2.0 + lGap / 2.0 * -Math.cos(theta), 1.0
    2841                            - getLabelLinkDepth()
    2842                            + getExplodePercent(leftKeys.getKey(i))));
    2843                }
    2844            }
    2845            double hh = plotArea.getHeight();
    2846            double gap = hh * getInteriorGap();
    2847            this.labelDistributor.distributeLabels(plotArea.getMinY() + gap,
    2848                    hh - 2 * gap);
    2849            for (int i = 0; i < this.labelDistributor.getItemCount(); i++) {
    2850                drawLeftLabel(g2, state,
    2851                        this.labelDistributor.getPieLabelRecord(i));
    2852            }
    2853        }
    2854
    2855        /**
    2856         * Draws the right labels.
    2857         *
    2858         * @param keys  the keys.
    2859         * @param g2  the graphics device.
    2860         * @param plotArea  the plot area.
    2861         * @param linkArea  the link area.
    2862         * @param maxLabelWidth  the maximum label width.
    2863         * @param state  the state.
    2864         */
    2865        protected void drawRightLabels(KeyedValues keys, Graphics2D g2,
    2866                                       Rectangle2D plotArea, Rectangle2D linkArea,
    2867                                       float maxLabelWidth, PiePlotState state) {
    2868
    2869            // draw the right labels...
    2870            this.labelDistributor.clear();
    2871            double lGap = plotArea.getWidth() * this.labelGap;
    2872            double verticalLinkRadius = state.getLinkArea().getHeight() / 2.0;
    2873
    2874            for (int i = 0; i < keys.getItemCount(); i++) {
    2875                String label = this.labelGenerator.generateSectionLabel(
    2876                        this.dataset, keys.getKey(i));
    2877
    2878                if (label != null) {
    2879                    TextBlock block = TextUtilities.createTextBlock(label,
    2880                            this.labelFont, this.labelPaint, maxLabelWidth,
    2881                            new G2TextMeasurer(g2));
    2882                    TextBox labelBox = new TextBox(block);
    2883                    labelBox.setBackgroundPaint(this.labelBackgroundPaint);
    2884                    labelBox.setOutlinePaint(this.labelOutlinePaint);
    2885                    labelBox.setOutlineStroke(this.labelOutlineStroke);
    2886                    labelBox.setShadowPaint(this.labelShadowPaint);
    2887                    labelBox.setInteriorGap(this.labelPadding);
    2888                    double theta = Math.toRadians(keys.getValue(i).doubleValue());
    2889                    double baseY = state.getPieCenterY()
    2890                                  - Math.sin(theta) * verticalLinkRadius;
    2891                    double hh = labelBox.getHeight(g2);
    2892                    this.labelDistributor.addPieLabelRecord(new PieLabelRecord(
    2893                            keys.getKey(i), theta, baseY, labelBox, hh,
    2894                            lGap / 2.0 + lGap / 2.0 * Math.cos(theta),
    2895                            1.0 - getLabelLinkDepth()
    2896                            + getExplodePercent(keys.getKey(i))));
    2897                }
    2898            }
    2899            double hh = plotArea.getHeight();
    2900            double gap = hh * getInteriorGap();
    2901            this.labelDistributor.distributeLabels(plotArea.getMinY() + gap,
    2902                    hh - 2 * gap);
    2903            for (int i = 0; i < this.labelDistributor.getItemCount(); i++) {
    2904                drawRightLabel(g2, state,
    2905                        this.labelDistributor.getPieLabelRecord(i));
    2906            }
    2907
    2908        }
    2909
    2910        /**
    2911         * Returns a collection of legend items for the pie chart.
    2912         *
    2913         * @return The legend items (never <code>null</code>).
    2914         */
    2915        public LegendItemCollection getLegendItems() {
    2916
    2917            LegendItemCollection result = new LegendItemCollection();
    2918            if (this.dataset == null) {
    2919                return result;
    2920            }
    2921            List keys = this.dataset.getKeys();
    2922            int section = 0;
    2923            Shape shape = getLegendItemShape();
    2924            Iterator iterator = keys.iterator();
    2925            while (iterator.hasNext()) {
    2926                Comparable key = (Comparable) iterator.next();
    2927                Number n = this.dataset.getValue(key);
    2928                boolean include = true;
    2929                if (n == null) {
    2930                    include = !this.ignoreNullValues;
    2931                }
    2932                else {
    2933                    double v = n.doubleValue();
    2934                    if (v == 0.0) {
    2935                        include = !this.ignoreZeroValues;
    2936                    }
    2937                    else {
    2938                        include = v > 0.0;
    2939                    }
    2940                }
    2941                if (include) {
    2942                    String label = this.legendLabelGenerator.generateSectionLabel(
    2943                            this.dataset, key);
    2944                    if (label != null) {
    2945                        String description = label;
    2946                        String toolTipText = null;
    2947                        if (this.legendLabelToolTipGenerator != null) {
    2948                            toolTipText = this.legendLabelToolTipGenerator
    2949                                    .generateSectionLabel(this.dataset, key);
    2950                        }
    2951                        String urlText = null;
    2952                        if (this.legendLabelURLGenerator != null) {
    2953                            urlText = this.legendLabelURLGenerator.generateURL(
    2954                                    this.dataset, key, this.pieIndex);
    2955                        }
    2956                        Paint paint = lookupSectionPaint(key);
    2957                        Paint outlinePaint = lookupSectionOutlinePaint(key);
    2958                        Stroke outlineStroke = lookupSectionOutlineStroke(key);
    2959                        LegendItem item = new LegendItem(label, description,
    2960                                toolTipText, urlText, true, shape, true, paint,
    2961                                true, outlinePaint, outlineStroke,
    2962                                false,          // line not visible
    2963                                new Line2D.Float(), new BasicStroke(), Color.black);
    2964                        item.setDataset(getDataset());
    2965                        item.setSeriesIndex(this.dataset.getIndex(key));
    2966                        item.setSeriesKey(key);
    2967                        result.add(item);
    2968                    }
    2969                    section++;
    2970                }
    2971                else {
    2972                    section++;
    2973                }
    2974            }
    2975            return result;
    2976        }
    2977
    2978        /**
    2979         * Returns a short string describing the type of plot.
    2980         *
    2981         * @return The plot type.
    2982         */
    2983        public String getPlotType() {
    2984            return localizationResources.getString("Pie_Plot");
    2985        }
    2986
    2987        /**
    2988         * Returns a rectangle that can be used to create a pie section (taking
    2989         * into account the amount by which the pie section is 'exploded').
    2990         *
    2991         * @param unexploded  the area inside which the unexploded pie sections are
    2992         *                    drawn.
    2993         * @param exploded  the area inside which the exploded pie sections are
    2994         *                  drawn.
    2995         * @param angle  the start angle.
    2996         * @param extent  the extent of the arc.
    2997         * @param explodePercent  the amount by which the pie section is exploded.
    2998         *
    2999         * @return A rectangle that can be used to create a pie section.
    3000         */
    3001        protected Rectangle2D getArcBounds(Rectangle2D unexploded,
    3002                                           Rectangle2D exploded,
    3003                                           double angle, double extent,
    3004                                           double explodePercent) {
    3005
    3006            if (explodePercent == 0.0) {
    3007                return unexploded;
    3008            }
    3009            else {
    3010                Arc2D arc1 = new Arc2D.Double(unexploded, angle, extent / 2,
    3011                        Arc2D.OPEN);
    3012                Point2D point1 = arc1.getEndPoint();
    3013                Arc2D.Double arc2 = new Arc2D.Double(exploded, angle, extent / 2,
    3014                        Arc2D.OPEN);
    3015                Point2D point2 = arc2.getEndPoint();
    3016                double deltaX = (point1.getX() - point2.getX()) * explodePercent;
    3017                double deltaY = (point1.getY() - point2.getY()) * explodePercent;
    3018                return new Rectangle2D.Double(unexploded.getX() - deltaX,
    3019                        unexploded.getY() - deltaY, unexploded.getWidth(),
    3020                        unexploded.getHeight());
    3021            }
    3022        }
    3023
    3024        /**
    3025         * Draws a section label on the left side of the pie chart.
    3026         *
    3027         * @param g2  the graphics device.
    3028         * @param state  the state.
    3029         * @param record  the label record.
    3030         */
    3031        protected void drawLeftLabel(Graphics2D g2, PiePlotState state,
    3032                                     PieLabelRecord record) {
    3033
    3034            double anchorX = state.getLinkArea().getMinX();
    3035            double targetX = anchorX - record.getGap();
    3036            double targetY = record.getAllocatedY();
    3037
    3038            if (this.labelLinksVisible) {
    3039                double theta = record.getAngle();
    3040                double linkX = state.getPieCenterX() + Math.cos(theta)
    3041                        * state.getPieWRadius() * record.getLinkPercent();
    3042                double linkY = state.getPieCenterY() - Math.sin(theta)
    3043                        * state.getPieHRadius() * record.getLinkPercent();
    3044                double elbowX = state.getPieCenterX() + Math.cos(theta)
    3045                        * state.getLinkArea().getWidth() / 2.0;
    3046                double elbowY = state.getPieCenterY() - Math.sin(theta)
    3047                        * state.getLinkArea().getHeight() / 2.0;
    3048                double anchorY = elbowY;
    3049                g2.setPaint(this.labelLinkPaint);
    3050                g2.setStroke(this.labelLinkStroke);
    3051                PieLabelLinkStyle style = getLabelLinkStyle();
    3052                if (style.equals(PieLabelLinkStyle.STANDARD)) {
    3053                    g2.draw(new Line2D.Double(linkX, linkY, elbowX, elbowY));
    3054                    g2.draw(new Line2D.Double(anchorX, anchorY, elbowX, elbowY));
    3055                    g2.draw(new Line2D.Double(anchorX, anchorY, targetX, targetY));
    3056                }
    3057                else if (style.equals(PieLabelLinkStyle.QUAD_CURVE)) {
    3058                    QuadCurve2D q = new QuadCurve2D.Float();
    3059                    q.setCurve(targetX, targetY, anchorX, anchorY, elbowX, elbowY);
    3060                    g2.draw(q);
    3061                    g2.draw(new Line2D.Double(elbowX, elbowY, linkX, linkY));
    3062                }
    3063                else if (style.equals(PieLabelLinkStyle.CUBIC_CURVE)) {
    3064                    CubicCurve2D c = new CubicCurve2D .Float();
    3065                    c.setCurve(targetX, targetY, anchorX, anchorY, elbowX, elbowY,
    3066                            linkX, linkY);
    3067                    g2.draw(c);
    3068                }
    3069            }
    3070            TextBox tb = record.getLabel();
    3071            tb.draw(g2, (float) targetX, (float) targetY, RectangleAnchor.RIGHT);
    3072
    3073        }
    3074
    3075        /**
    3076         * Draws a section label on the right side of the pie chart.
    3077         *
    3078         * @param g2  the graphics device.
    3079         * @param state  the state.
    3080         * @param record  the label record.
    3081         */
    3082        protected void drawRightLabel(Graphics2D g2, PiePlotState state,
    3083                                      PieLabelRecord record) {
    3084
    3085            double anchorX = state.getLinkArea().getMaxX();
    3086            double targetX = anchorX + record.getGap();
    3087            double targetY = record.getAllocatedY();
    3088
    3089            if (this.labelLinksVisible) {
    3090                double theta = record.getAngle();
    3091                double linkX = state.getPieCenterX() + Math.cos(theta)
    3092                        * state.getPieWRadius() * record.getLinkPercent();
    3093                double linkY = state.getPieCenterY() - Math.sin(theta)
    3094                        * state.getPieHRadius() * record.getLinkPercent();
    3095                double elbowX = state.getPieCenterX() + Math.cos(theta)
    3096                        * state.getLinkArea().getWidth() / 2.0;
    3097                double elbowY = state.getPieCenterY() - Math.sin(theta)
    3098                        * state.getLinkArea().getHeight() / 2.0;
    3099                double anchorY = elbowY;
    3100                g2.setPaint(this.labelLinkPaint);
    3101                g2.setStroke(this.labelLinkStroke);
    3102                PieLabelLinkStyle style = getLabelLinkStyle();
    3103                if (style.equals(PieLabelLinkStyle.STANDARD)) {
    3104                    g2.draw(new Line2D.Double(linkX, linkY, elbowX, elbowY));
    3105                    g2.draw(new Line2D.Double(anchorX, anchorY, elbowX, elbowY));
    3106                    g2.draw(new Line2D.Double(anchorX, anchorY, targetX, targetY));
    3107                }
    3108                else if (style.equals(PieLabelLinkStyle.QUAD_CURVE)) {
    3109                    QuadCurve2D q = new QuadCurve2D.Float();
    3110                    q.setCurve(targetX, targetY, anchorX, anchorY, elbowX, elbowY);
    3111                    g2.draw(q);
    3112                    g2.draw(new Line2D.Double(elbowX, elbowY, linkX, linkY));
    3113                }
    3114                else if (style.equals(PieLabelLinkStyle.CUBIC_CURVE)) {
    3115                    CubicCurve2D c = new CubicCurve2D .Float();
    3116                    c.setCurve(targetX, targetY, anchorX, anchorY, elbowX, elbowY,
    3117                            linkX, linkY);
    3118                    g2.draw(c);
    3119                }
    3120            }
    3121
    3122            TextBox tb = record.getLabel();
    3123            tb.draw(g2, (float) targetX, (float) targetY, RectangleAnchor.LEFT);
    3124
    3125        }
    3126
    3127        /**
    3128         * Tests this plot for equality with an arbitrary object.  Note that the
    3129         * plot's dataset is NOT included in the test for equality.
    3130         *
    3131         * @param obj  the object to test against (<code>null</code> permitted).
    3132         *
    3133         * @return <code>true</code> or <code>false</code>.
    3134         */
    3135        public boolean equals(Object obj) {
    3136            if (obj == this) {
    3137                return true;
    3138            }
    3139            if (!(obj instanceof PiePlot)) {
    3140                return false;
    3141            }
    3142            if (!super.equals(obj)) {
    3143                return false;
    3144            }
    3145            PiePlot that = (PiePlot) obj;
    3146            if (this.pieIndex != that.pieIndex) {
    3147                return false;
    3148            }
    3149            if (this.interiorGap != that.interiorGap) {
    3150                return false;
    3151            }
    3152            if (this.circular != that.circular) {
    3153                return false;
    3154            }
    3155            if (this.startAngle != that.startAngle) {
    3156                return false;
    3157            }
    3158            if (this.direction != that.direction) {
    3159                return false;
    3160            }
    3161            if (this.ignoreZeroValues != that.ignoreZeroValues) {
    3162                return false;
    3163            }
    3164            if (this.ignoreNullValues != that.ignoreNullValues) {
    3165                return false;
    3166            }
    3167            if (!PaintUtilities.equal(this.sectionPaint, that.sectionPaint)) {
    3168                return false;
    3169            }
    3170            if (!ObjectUtilities.equal(this.sectionPaintMap,
    3171                    that.sectionPaintMap)) {
    3172                return false;
    3173            }
    3174            if (!PaintUtilities.equal(this.baseSectionPaint,
    3175                    that.baseSectionPaint)) {
    3176                return false;
    3177            }
    3178            if (this.sectionOutlinesVisible != that.sectionOutlinesVisible) {
    3179                return false;
    3180            }
    3181            if (!PaintUtilities.equal(this.sectionOutlinePaint,
    3182                    that.sectionOutlinePaint)) {
    3183                return false;
    3184            }
    3185            if (!ObjectUtilities.equal(this.sectionOutlinePaintMap,
    3186                    that.sectionOutlinePaintMap)) {
    3187                return false;
    3188            }
    3189            if (!PaintUtilities.equal(
    3190                this.baseSectionOutlinePaint, that.baseSectionOutlinePaint
    3191            )) {
    3192                return false;
    3193            }
    3194            if (!ObjectUtilities.equal(this.sectionOutlineStroke,
    3195                    that.sectionOutlineStroke)) {
    3196                return false;
    3197            }
    3198            if (!ObjectUtilities.equal(this.sectionOutlineStrokeMap,
    3199                    that.sectionOutlineStrokeMap)) {
    3200                return false;
    3201            }
    3202            if (!ObjectUtilities.equal(
    3203                this.baseSectionOutlineStroke, that.baseSectionOutlineStroke
    3204            )) {
    3205                return false;
    3206            }
    3207            if (!PaintUtilities.equal(this.shadowPaint, that.shadowPaint)) {
    3208                return false;
    3209            }
    3210            if (!(this.shadowXOffset == that.shadowXOffset)) {
    3211                return false;
    3212            }
    3213            if (!(this.shadowYOffset == that.shadowYOffset)) {
    3214                return false;
    3215            }
    3216            if (!ObjectUtilities.equal(this.explodePercentages,
    3217                    that.explodePercentages)) {
    3218                return false;
    3219            }
    3220            if (!ObjectUtilities.equal(this.labelGenerator,
    3221                    that.labelGenerator)) {
    3222                return false;
    3223            }
    3224            if (!ObjectUtilities.equal(this.labelFont, that.labelFont)) {
    3225                return false;
    3226            }
    3227            if (!PaintUtilities.equal(this.labelPaint, that.labelPaint)) {
    3228                return false;
    3229            }
    3230            if (!PaintUtilities.equal(this.labelBackgroundPaint,
    3231                    that.labelBackgroundPaint)) {
    3232                return false;
    3233            }
    3234            if (!PaintUtilities.equal(this.labelOutlinePaint,
    3235                    that.labelOutlinePaint)) {
    3236                return false;
    3237            }
    3238            if (!ObjectUtilities.equal(this.labelOutlineStroke,
    3239                    that.labelOutlineStroke)) {
    3240                return false;
    3241            }
    3242            if (!PaintUtilities.equal(this.labelShadowPaint,
    3243                    that.labelShadowPaint)) {
    3244                return false;
    3245            }
    3246            if (this.simpleLabels != that.simpleLabels) {
    3247                return false;
    3248            }
    3249            if (!this.simpleLabelOffset.equals(that.simpleLabelOffset)) {
    3250                return false;
    3251            }
    3252            if (!this.labelPadding.equals(that.labelPadding)) {
    3253                return false;
    3254            }
    3255            if (!(this.maximumLabelWidth == that.maximumLabelWidth)) {
    3256                return false;
    3257            }
    3258            if (!(this.labelGap == that.labelGap)) {
    3259                return false;
    3260            }
    3261            if (!(this.labelLinkMargin == that.labelLinkMargin)) {
    3262                return false;
    3263            }
    3264            if (this.labelLinksVisible != that.labelLinksVisible) {
    3265                return false;
    3266            }
    3267            if (!this.labelLinkStyle.equals(that.labelLinkStyle)) {
    3268                return false;
    3269            }
    3270            if (!PaintUtilities.equal(this.labelLinkPaint, that.labelLinkPaint)) {
    3271                return false;
    3272            }
    3273            if (!ObjectUtilities.equal(this.labelLinkStroke,
    3274                    that.labelLinkStroke)) {
    3275                return false;
    3276            }
    3277            if (!ObjectUtilities.equal(this.toolTipGenerator,
    3278                    that.toolTipGenerator)) {
    3279                return false;
    3280            }
    3281            if (!ObjectUtilities.equal(this.urlGenerator, that.urlGenerator)) {
    3282                return false;
    3283            }
    3284            if (!(this.minimumArcAngleToDraw == that.minimumArcAngleToDraw)) {
    3285                return false;
    3286            }
    3287            if (!ShapeUtilities.equal(this.legendItemShape, that.legendItemShape)) {
    3288                return false;
    3289            }
    3290            if (!ObjectUtilities.equal(this.legendLabelGenerator,
    3291                    that.legendLabelGenerator)) {
    3292                return false;
    3293            }
    3294            if (!ObjectUtilities.equal(this.legendLabelToolTipGenerator,
    3295                    that.legendLabelToolTipGenerator)) {
    3296                return false;
    3297            }
    3298            if (!ObjectUtilities.equal(this.legendLabelURLGenerator,
    3299                    that.legendLabelURLGenerator)) {
    3300                return false;
    3301            }
    3302            if (this.autoPopulateSectionPaint != that.autoPopulateSectionPaint) {
    3303                return false;
    3304            }
    3305            if (this.autoPopulateSectionOutlinePaint
    3306                    != that.autoPopulateSectionOutlinePaint) {
    3307                return false;
    3308            }
    3309            if (this.autoPopulateSectionOutlineStroke
    3310                    != that.autoPopulateSectionOutlineStroke) {
    3311                return false;
    3312            }
    3313            // can't find any difference...
    3314            return true;
    3315        }
    3316
    3317        /**
    3318         * Returns a clone of the plot.
    3319         *
    3320         * @return A clone.
    3321         *
    3322         * @throws CloneNotSupportedException if some component of the plot does
    3323         *         not support cloning.
    3324         */
    3325        public Object clone() throws CloneNotSupportedException {
    3326            PiePlot clone = (PiePlot) super.clone();
    3327            if (clone.dataset != null) {
    3328                clone.dataset.addChangeListener(clone);
    3329            }
    3330            if (this.urlGenerator instanceof PublicCloneable) {
    3331                clone.urlGenerator = (PieURLGenerator) ObjectUtilities.clone(
    3332                        this.urlGenerator);
    3333            }
    3334            clone.legendItemShape = ShapeUtilities.clone(this.legendItemShape);
    3335            if (this.legendLabelGenerator != null) {
    3336                clone.legendLabelGenerator = (PieSectionLabelGenerator)
    3337                        ObjectUtilities.clone(this.legendLabelGenerator);
    3338            }
    3339            if (this.legendLabelToolTipGenerator != null) {
    3340                clone.legendLabelToolTipGenerator = (PieSectionLabelGenerator)
    3341                        ObjectUtilities.clone(this.legendLabelToolTipGenerator);
    3342            }
    3343            if (this.legendLabelURLGenerator instanceof PublicCloneable) {
    3344                clone.legendLabelURLGenerator = (PieURLGenerator)
    3345                        ObjectUtilities.clone(this.legendLabelURLGenerator);
    3346            }
    3347            return clone;
    3348        }
    3349
    3350        /**
    3351         * Provides serialization support.
    3352         *
    3353         * @param stream  the output stream.
    3354         *
    3355         * @throws IOException  if there is an I/O error.
    3356         */
    3357        private void writeObject(ObjectOutputStream stream) throws IOException {
    3358            stream.defaultWriteObject();
    3359            SerialUtilities.writePaint(this.sectionPaint, stream);
    3360            SerialUtilities.writePaint(this.baseSectionPaint, stream);
    3361            SerialUtilities.writePaint(this.sectionOutlinePaint, stream);
    3362            SerialUtilities.writePaint(this.baseSectionOutlinePaint, stream);
    3363            SerialUtilities.writeStroke(this.sectionOutlineStroke, stream);
    3364            SerialUtilities.writeStroke(this.baseSectionOutlineStroke, stream);
    3365            SerialUtilities.writePaint(this.shadowPaint, stream);
    3366            SerialUtilities.writePaint(this.labelPaint, stream);
    3367            SerialUtilities.writePaint(this.labelBackgroundPaint, stream);
    3368            SerialUtilities.writePaint(this.labelOutlinePaint, stream);
    3369            SerialUtilities.writeStroke(this.labelOutlineStroke, stream);
    3370            SerialUtilities.writePaint(this.labelShadowPaint, stream);
    3371            SerialUtilities.writePaint(this.labelLinkPaint, stream);
    3372            SerialUtilities.writeStroke(this.labelLinkStroke, stream);
    3373            SerialUtilities.writeShape(this.legendItemShape, stream);
    3374        }
    3375
    3376        /**
    3377         * Provides serialization support.
    3378         *
    3379         * @param stream  the input stream.
    3380         *
    3381         * @throws IOException  if there is an I/O error.
    3382         * @throws ClassNotFoundException  if there is a classpath problem.
    3383         */
    3384        private void readObject(ObjectInputStream stream)
    3385            throws IOException, ClassNotFoundException {
    3386            stream.defaultReadObject();
    3387            this.sectionPaint = SerialUtilities.readPaint(stream);
    3388            this.baseSectionPaint = SerialUtilities.readPaint(stream);
    3389            this.sectionOutlinePaint = SerialUtilities.readPaint(stream);
    3390            this.baseSectionOutlinePaint = SerialUtilities.readPaint(stream);
    3391            this.sectionOutlineStroke = SerialUtilities.readStroke(stream);
    3392            this.baseSectionOutlineStroke = SerialUtilities.readStroke(stream);
    3393            this.shadowPaint = SerialUtilities.readPaint(stream);
    3394            this.labelPaint = SerialUtilities.readPaint(stream);
    3395            this.labelBackgroundPaint = SerialUtilities.readPaint(stream);
    3396            this.labelOutlinePaint = SerialUtilities.readPaint(stream);
    3397            this.labelOutlineStroke = SerialUtilities.readStroke(stream);
    3398            this.labelShadowPaint = SerialUtilities.readPaint(stream);
    3399            this.labelLinkPaint = SerialUtilities.readPaint(stream);
    3400            this.labelLinkStroke = SerialUtilities.readStroke(stream);
    3401            this.legendItemShape = SerialUtilities.readShape(stream);
    3402        }
    3403
    3404        // DEPRECATED FIELDS AND METHODS...
    3405
    3406        /**
    3407         * The paint for ALL sections (overrides list).
    3408         *
    3409         * @deprecated This field is redundant, it is sufficient to use
    3410         *     sectionPaintMap and baseSectionPaint.  Deprecated as of version
    3411         *     1.0.6.
    3412         */
    3413        private transient Paint sectionPaint;
    3414
    3415        /**
    3416         * The outline paint for ALL sections (overrides list).
    3417         *
    3418         * @deprecated This field is redundant, it is sufficient to use
    3419         *     sectionOutlinePaintMap and baseSectionOutlinePaint.  Deprecated as
    3420         *     of version 1.0.6.
    3421         */
    3422        private transient Paint sectionOutlinePaint;
    3423
    3424        /**
    3425         * The outline stroke for ALL sections (overrides list).
    3426         *
    3427         * @deprecated This field is redundant, it is sufficient to use
    3428         *     sectionOutlineStrokeMap and baseSectionOutlineStroke.  Deprecated as
    3429         *     of version 1.0.6.
    3430         */
    3431        private transient Stroke sectionOutlineStroke;
    3432
    3433        /**
    3434         * Returns the paint for the specified section.
    3435         *
    3436         * @param section  the section index (zero-based).
    3437         *
    3438         * @return The paint (never <code>null</code>).
    3439         *
    3440         * @deprecated Use {@link #getSectionPaint(Comparable)} instead.
    3441         */
    3442        public Paint getSectionPaint(int section) {
    3443            Comparable key = getSectionKey(section);
    3444            return getSectionPaint(key);
    3445        }
    3446
    3447        /**
    3448         * Sets the paint used to fill a section of the pie and sends a
    3449         * {@link PlotChangeEvent} to all registered listeners.
    3450         *
    3451         * @param section  the section index (zero-based).
    3452         * @param paint  the paint (<code>null</code> permitted).
    3453         *
    3454         * @deprecated Use {@link #setSectionPaint(Comparable, Paint)} instead.
    3455         */
    3456        public void setSectionPaint(int section, Paint paint) {
    3457            Comparable key = getSectionKey(section);
    3458            setSectionPaint(key, paint);
    3459        }
    3460
    3461        /**
    3462         * Returns the outline paint for ALL sections in the plot.
    3463         *
    3464         * @return The paint (possibly <code>null</code>).
    3465         *
    3466         * @see #setSectionOutlinePaint(Paint)
    3467         *
    3468         * @deprecated Use {@link #getSectionOutlinePaint(Comparable)} and
    3469         *     {@link #getBaseSectionOutlinePaint()}.  Deprecated as of version
    3470         *     1.0.6.
    3471         */
    3472        public Paint getSectionOutlinePaint() {
    3473            return this.sectionOutlinePaint;
    3474        }
    3475
    3476        /**
    3477         * Sets the outline paint for ALL sections in the plot.  If this is set to
    3478         * </code>null</code>, then a list of paints is used instead (to allow
    3479         * different colors to be used for each section).
    3480         *
    3481         * @param paint  the paint (<code>null</code> permitted).
    3482         *
    3483         * @see #getSectionOutlinePaint()
    3484         *
    3485         * @deprecated Use {@link #setSectionOutlinePaint(Comparable, Paint)} and
    3486         *     {@link #setBaseSectionOutlinePaint(Paint)}.  Deprecated as of
    3487         *     version 1.0.6.
    3488         */
    3489        public void setSectionOutlinePaint(Paint paint) {
    3490            this.sectionOutlinePaint = paint;
    3491            fireChangeEvent();
    3492        }
    3493
    3494        /**
    3495         * Returns the paint for the specified section.
    3496         *
    3497         * @param section  the section index (zero-based).
    3498         *
    3499         * @return The paint (possibly <code>null</code>).
    3500         *
    3501         * @deprecated Use {@link #getSectionOutlinePaint(Comparable)} instead.
    3502         */
    3503        public Paint getSectionOutlinePaint(int section) {
    3504            Comparable key = getSectionKey(section);
    3505            return getSectionOutlinePaint(key);
    3506        }
    3507
    3508        /**
    3509         * Sets the paint used to fill a section of the pie and sends a
    3510         * {@link PlotChangeEvent} to all registered listeners.
    3511         *
    3512         * @param section  the section index (zero-based).
    3513         * @param paint  the paint (<code>null</code> permitted).
    3514         *
    3515         * @deprecated Use {@link #setSectionOutlinePaint(Comparable, Paint)}
    3516         *     instead.
    3517         */
    3518        public void setSectionOutlinePaint(int section, Paint paint) {
    3519            Comparable key = getSectionKey(section);
    3520            setSectionOutlinePaint(key, paint);
    3521        }
    3522
    3523        /**
    3524         * Returns the outline stroke for ALL sections in the plot.
    3525         *
    3526         * @return The stroke (possibly <code>null</code>).
    3527         *
    3528         * @see #setSectionOutlineStroke(Stroke)
    3529         *
    3530         * @deprecated Use {@link #getSectionOutlineStroke(Comparable)} and
    3531         *     {@link #getBaseSectionOutlineStroke()}.  Deprecated as of version
    3532         *     1.0.6.
    3533         */
    3534        public Stroke getSectionOutlineStroke() {
    3535            return this.sectionOutlineStroke;
    3536        }
    3537
    3538        /**
    3539         * Sets the outline stroke for ALL sections in the plot.  If this is set to
    3540         * </code>null</code>, then a list of paints is used instead (to allow
    3541         * different colors to be used for each section).
    3542         *
    3543         * @param stroke  the stroke (<code>null</code> permitted).
    3544         *
    3545         * @see #getSectionOutlineStroke()
    3546         *
    3547         * @deprecated Use {@link #setSectionOutlineStroke(Comparable, Stroke)} and
    3548         *     {@link #setBaseSectionOutlineStroke(Stroke)}.  Deprecated as of
    3549         *     version 1.0.6.
    3550         */
    3551        public void setSectionOutlineStroke(Stroke stroke) {
    3552            this.sectionOutlineStroke = stroke;
    3553            fireChangeEvent();
    3554        }
    3555
    3556        /**
    3557         * Returns the stroke for the specified section.
    3558         *
    3559         * @param section  the section index (zero-based).
    3560         *
    3561         * @return The stroke (possibly <code>null</code>).
    3562         *
    3563         * @deprecated Use {@link #getSectionOutlineStroke(Comparable)} instead.
    3564         */
    3565        public Stroke getSectionOutlineStroke(int section) {
    3566            Comparable key = getSectionKey(section);
    3567            return getSectionOutlineStroke(key);
    3568        }
    3569
    3570        /**
    3571         * Sets the stroke used to fill a section of the pie and sends a
    3572         * {@link PlotChangeEvent} to all registered listeners.
    3573         *
    3574         * @param section  the section index (zero-based).
    3575         * @param stroke  the stroke (<code>null</code> permitted).
    3576         *
    3577         * @deprecated Use {@link #setSectionOutlineStroke(Comparable, Stroke)}
    3578         *     instead.
    3579         */
    3580        public void setSectionOutlineStroke(int section, Stroke stroke) {
    3581            Comparable key = getSectionKey(section);
    3582            setSectionOutlineStroke(key, stroke);
    3583        }
    3584
    3585        /**
    3586         * Returns the amount that a section should be 'exploded'.
    3587         *
    3588         * @param section  the section number.
    3589         *
    3590         * @return The amount that a section should be 'exploded'.
    3591         *
    3592         * @deprecated Use {@link #getExplodePercent(Comparable)} instead.
    3593         */
    3594        public double getExplodePercent(int section) {
    3595            Comparable key = getSectionKey(section);
    3596            return getExplodePercent(key);
    3597        }
    3598
    3599        /**
    3600         * Sets the amount that a pie section should be exploded and sends a
    3601         * {@link PlotChangeEvent} to all registered listeners.
    3602         *
    3603         * @param section  the section index.
    3604         * @param percent  the explode percentage (0.30 = 30 percent).
    3605         *
    3606         * @deprecated Use {@link #setExplodePercent(Comparable, double)} instead.
    3607         */
    3608        public void setExplodePercent(int section, double percent) {
    3609            Comparable key = getSectionKey(section);
    3610            setExplodePercent(key, percent);
    3611        }
    3612
    3613    }