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