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