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
168package org.jfree.chart.plot;
169
170import java.awt.AlphaComposite;
171import java.awt.BasicStroke;
172import java.awt.Color;
173import java.awt.Composite;
174import java.awt.Font;
175import java.awt.FontMetrics;
176import java.awt.Graphics2D;
177import java.awt.Paint;
178import java.awt.Shape;
179import java.awt.Stroke;
180import java.awt.geom.Arc2D;
181import java.awt.geom.CubicCurve2D;
182import java.awt.geom.Ellipse2D;
183import java.awt.geom.Line2D;
184import java.awt.geom.Point2D;
185import java.awt.geom.QuadCurve2D;
186import java.awt.geom.Rectangle2D;
187import java.io.IOException;
188import java.io.ObjectInputStream;
189import java.io.ObjectOutputStream;
190import java.io.Serializable;
191import java.util.Iterator;
192import java.util.List;
193import java.util.Map;
194import java.util.ResourceBundle;
195import java.util.TreeMap;
196
197import org.jfree.chart.LegendItem;
198import org.jfree.chart.LegendItemCollection;
199import org.jfree.chart.PaintMap;
200import org.jfree.chart.StrokeMap;
201import org.jfree.chart.entity.EntityCollection;
202import org.jfree.chart.entity.PieSectionEntity;
203import org.jfree.chart.event.PlotChangeEvent;
204import org.jfree.chart.labels.PieSectionLabelGenerator;
205import org.jfree.chart.labels.PieToolTipGenerator;
206import org.jfree.chart.labels.StandardPieSectionLabelGenerator;
207import org.jfree.chart.urls.PieURLGenerator;
208import org.jfree.chart.util.ResourceBundleWrapper;
209import org.jfree.data.DefaultKeyedValues;
210import org.jfree.data.KeyedValues;
211import org.jfree.data.general.DatasetChangeEvent;
212import org.jfree.data.general.DatasetUtilities;
213import org.jfree.data.general.PieDataset;
214import org.jfree.io.SerialUtilities;
215import org.jfree.text.G2TextMeasurer;
216import org.jfree.text.TextBlock;
217import org.jfree.text.TextBox;
218import org.jfree.text.TextUtilities;
219import org.jfree.ui.RectangleAnchor;
220import org.jfree.ui.RectangleInsets;
221import org.jfree.ui.TextAnchor;
222import org.jfree.util.ObjectUtilities;
223import org.jfree.util.PaintUtilities;
224import org.jfree.util.PublicCloneable;
225import org.jfree.util.Rotation;
226import org.jfree.util.ShapeUtilities;
227import 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 */
250public 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}