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