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