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