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