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