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