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