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