001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2005, 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-2005, 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.8 2005/12/02 11:53: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     * 
134     */
135    
136    package org.jfree.chart.plot;
137    
138    import java.awt.AlphaComposite;
139    import java.awt.BasicStroke;
140    import java.awt.Color;
141    import java.awt.Composite;
142    import java.awt.Font;
143    import java.awt.Graphics2D;
144    import java.awt.Paint;
145    import java.awt.Shape;
146    import java.awt.Stroke;
147    import java.awt.geom.Arc2D;
148    import java.awt.geom.Line2D;
149    import java.awt.geom.Point2D;
150    import java.awt.geom.Rectangle2D;
151    import java.io.IOException;
152    import java.io.ObjectInputStream;
153    import java.io.ObjectOutputStream;
154    import java.io.Serializable;
155    import java.util.Iterator;
156    import java.util.List;
157    import java.util.ResourceBundle;
158    
159    import org.jfree.chart.LegendItem;
160    import org.jfree.chart.LegendItemCollection;
161    import org.jfree.chart.entity.EntityCollection;
162    import org.jfree.chart.entity.PieSectionEntity;
163    import org.jfree.chart.event.PlotChangeEvent;
164    import org.jfree.chart.labels.PieSectionLabelGenerator;
165    import org.jfree.chart.labels.PieToolTipGenerator;
166    import org.jfree.chart.labels.StandardPieSectionLabelGenerator;
167    import org.jfree.chart.urls.PieURLGenerator;
168    import org.jfree.data.DefaultKeyedValues;
169    import org.jfree.data.KeyedValues;
170    import org.jfree.data.general.DatasetChangeEvent;
171    import org.jfree.data.general.DatasetUtilities;
172    import org.jfree.data.general.PieDataset;
173    import org.jfree.io.SerialUtilities;
174    import org.jfree.text.G2TextMeasurer;
175    import org.jfree.text.TextBlock;
176    import org.jfree.text.TextBox;
177    import org.jfree.text.TextUtilities;
178    import org.jfree.ui.RectangleAnchor;
179    import org.jfree.ui.RectangleInsets;
180    import org.jfree.util.ObjectList;
181    import org.jfree.util.ObjectUtilities;
182    import org.jfree.util.PaintList;
183    import org.jfree.util.PaintUtilities;
184    import org.jfree.util.Rotation;
185    import org.jfree.util.ShapeUtilities;
186    import org.jfree.util.StrokeList;
187    
188    /**
189     * A plot that displays data in the form of a pie chart, using data from any 
190     * class that implements the {@link PieDataset} interface.
191     * <P>
192     * Special notes:
193     * <ol>
194     * <li>the default starting point is 12 o'clock and the pie sections proceed
195     * in a clockwise direction, but these settings can be changed;</li>
196     * <li>negative values in the dataset are ignored;</li>
197     * <li>there are utility methods for creating a {@link PieDataset} from a
198     * {@link org.jfree.data.category.CategoryDataset};</li>
199     * </ol>
200     *
201     * @see Plot
202     * @see PieDataset
203     */
204    public class PiePlot extends Plot implements Cloneable, Serializable {
205        
206        /** For serialization. */
207        private static final long serialVersionUID = -795612466005590431L;
208        
209        /** The default interior gap. */
210        public static final double DEFAULT_INTERIOR_GAP = 0.25;
211    
212        /** The maximum interior gap (currently 40%). */
213        public static final double MAX_INTERIOR_GAP = 0.40;
214    
215        /** The default starting angle for the pie chart. */
216        public static final double DEFAULT_START_ANGLE = 90.0;
217    
218        /** The default section label font. */
219        public static final Font DEFAULT_LABEL_FONT 
220            = new Font("SansSerif", Font.PLAIN, 10);
221    
222        /** The default section label paint. */
223        public static final Paint DEFAULT_LABEL_PAINT = Color.black;
224        
225        /** The default section label background paint. */
226        public static final Paint DEFAULT_LABEL_BACKGROUND_PAINT 
227            = new Color(255, 255, 192);
228    
229        /** The default section label outline paint. */
230        public static final Paint DEFAULT_LABEL_OUTLINE_PAINT = Color.black;
231        
232        /** The default section label outline stroke. */
233        public static final Stroke DEFAULT_LABEL_OUTLINE_STROKE 
234            = new BasicStroke(0.5f);
235        
236        /** The default section label shadow paint. */
237        public static final Paint DEFAULT_LABEL_SHADOW_PAINT = Color.lightGray;
238        
239        /** The default minimum arc angle to draw. */
240        public static final double DEFAULT_MINIMUM_ARC_ANGLE_TO_DRAW = 0.00001;
241    
242        /** The dataset for the pie chart. */
243        private PieDataset dataset;
244    
245        /** The pie index (used by the {@link MultiplePiePlot} class). */
246        private int pieIndex;
247    
248        /** 
249         * The amount of space left around the outside of the pie plot, expressed 
250         * as a percentage. 
251         */
252        private double interiorGap;
253    
254        /** Flag determining whether to draw an ellipse or a perfect circle. */
255        private boolean circular;
256    
257        /** The starting angle. */
258        private double startAngle;
259    
260        /** The direction for the pie segments. */
261        private Rotation direction;
262    
263        /** The paint for ALL sections (overrides list). */
264        private transient Paint sectionPaint;
265    
266        /** The section paint list. */
267        private PaintList sectionPaintList;
268    
269        /** The base section paint (fallback). */
270        private transient Paint baseSectionPaint;
271    
272        /** 
273         * A flag that controls whether or not an outline is drawn for each
274         * section in the plot.
275         */
276        private boolean sectionOutlinesVisible;
277        
278        /** The outline paint for ALL sections (overrides list). */
279        private transient Paint sectionOutlinePaint;
280    
281        /** The section outline paint list. */
282        private PaintList sectionOutlinePaintList;
283    
284        /** The base section outline paint (fallback). */
285        private transient Paint baseSectionOutlinePaint;
286    
287        /** The outline stroke for ALL sections (overrides list). */
288        private transient Stroke sectionOutlineStroke;
289    
290        /** The section outline stroke list. */
291        private StrokeList sectionOutlineStrokeList;
292    
293        /** The base section outline stroke (fallback). */
294        private transient Stroke baseSectionOutlineStroke;
295    
296        /** The shadow paint. */
297        private transient Paint shadowPaint = Color.gray;
298    
299        /** The x-offset for the shadow effect. */
300        private double shadowXOffset = 4.0f;
301        
302        /** The y-offset for the shadow effect. */
303        private double shadowYOffset = 4.0f;
304        
305        /** The percentage amount to explode each pie section. */
306        private ObjectList explodePercentages;
307        
308        /** The section label generator. */
309        private PieSectionLabelGenerator labelGenerator;
310    
311        /** The font used to display the section labels. */
312        private Font labelFont;
313    
314        /** The color used to draw the section labels. */
315        private transient Paint labelPaint;
316        
317        /** The color used to draw the background of the section labels. */
318        private transient Paint labelBackgroundPaint;
319    
320        /** 
321         * The paint used to draw the outline of the section labels 
322         * (<code>null</code> permitted). 
323         */
324        private transient Paint labelOutlinePaint;
325        
326        /** 
327         * The stroke used to draw the outline of the section labels 
328         * (<code>null</code> permitted). 
329         */
330        private transient Stroke labelOutlineStroke;
331        
332        /** 
333         * The paint used to draw the shadow for the section labels 
334         * (<code>null</code> permitted). 
335         */
336        private transient Paint labelShadowPaint;
337        
338        /** The maximum label width as a percentage of the plot width. */
339        private double maximumLabelWidth = 0.20;
340        
341        /** 
342         * The gap between the labels and the plot as a percentage of the plot 
343         * width. 
344         */
345        private double labelGap = 0.05;
346    
347        /** A flag that controls whether or not the label links are drawn. */
348        private boolean labelLinksVisible;
349        
350        /** The link margin. */
351        private double labelLinkMargin = 0.05;
352        
353        /** The paint used for the label linking lines. */
354        private transient Paint labelLinkPaint = Color.black;
355        
356        /** The stroke used for the label linking lines. */
357        private transient Stroke labelLinkStroke = new BasicStroke(0.5f);
358        
359        /** The tooltip generator. */
360        private PieToolTipGenerator toolTipGenerator;
361    
362        /** The URL generator. */
363        private PieURLGenerator urlGenerator;
364        
365        /** The legend label generator. */
366        private PieSectionLabelGenerator legendLabelGenerator;
367        
368        /** A tool tip generator for the legend. */
369        private PieSectionLabelGenerator legendLabelToolTipGenerator;
370        
371        /** 
372         * A flag that controls whether <code>null</code> values are ignored.  
373         */
374        private boolean ignoreNullValues;
375        
376        /**
377         * A flag that controls whether zero values are ignored.
378         */
379        private boolean ignoreZeroValues;
380    
381        /** The legend item shape. */
382        private transient Shape legendItemShape;
383        
384        /**
385         * The smallest arc angle that will get drawn (this is to avoid a bug in 
386         * various Java implementations that causes the JVM to crash).  See this 
387         * link for details:
388         *
389         * http://www.jfree.org/phpBB2/viewtopic.php?t=2707
390         *
391         * ...and this bug report in the Java Bug Parade:
392         *
393         * http://developer.java.sun.com/developer/bugParade/bugs/4836495.html
394         */
395        private double minimumArcAngleToDraw;
396    
397        /** The resourceBundle for the localization. */
398        protected static ResourceBundle localizationResources =
399            ResourceBundle.getBundle("org.jfree.chart.plot.LocalizationBundle");
400    
401        /**
402         * Creates a new plot.  The dataset is initially set to <code>null</code>.
403         */
404        public PiePlot() {
405            this(null);
406        }
407    
408        /**
409         * Creates a plot that will draw a pie chart for the specified dataset.
410         *
411         * @param dataset  the dataset (<code>null</code> permitted).
412         */
413        public PiePlot(PieDataset dataset) {
414            super();
415            this.dataset = dataset;
416            if (dataset != null) {
417                dataset.addChangeListener(this);
418            }
419            this.pieIndex = 0;
420            
421            this.interiorGap = DEFAULT_INTERIOR_GAP;
422            this.circular = true;
423            this.startAngle = DEFAULT_START_ANGLE;
424            this.direction = Rotation.CLOCKWISE;
425            this.minimumArcAngleToDraw = DEFAULT_MINIMUM_ARC_ANGLE_TO_DRAW;
426            
427            this.sectionPaint = null;
428            this.sectionPaintList = new PaintList();
429            this.baseSectionPaint = null;
430    
431            this.sectionOutlinesVisible = true;
432            this.sectionOutlinePaint = null;
433            this.sectionOutlinePaintList = new PaintList();
434            this.baseSectionOutlinePaint = DEFAULT_OUTLINE_PAINT;
435    
436            this.sectionOutlineStroke = null;
437            this.sectionOutlineStrokeList = new StrokeList();
438            this.baseSectionOutlineStroke = DEFAULT_OUTLINE_STROKE;
439            
440            this.explodePercentages = new ObjectList();
441    
442            this.labelGenerator = new StandardPieSectionLabelGenerator();
443            this.labelFont = DEFAULT_LABEL_FONT;
444            this.labelPaint = DEFAULT_LABEL_PAINT;
445            this.labelBackgroundPaint = DEFAULT_LABEL_BACKGROUND_PAINT;
446            this.labelOutlinePaint = DEFAULT_LABEL_OUTLINE_PAINT;
447            this.labelOutlineStroke = DEFAULT_LABEL_OUTLINE_STROKE;
448            this.labelShadowPaint = DEFAULT_LABEL_SHADOW_PAINT;
449            this.labelLinksVisible = true;
450            
451            this.toolTipGenerator = null;
452            this.urlGenerator = null;
453            this.legendLabelGenerator = new StandardPieSectionLabelGenerator();
454            this.legendLabelToolTipGenerator = null;
455            this.legendItemShape = Plot.DEFAULT_LEGEND_ITEM_CIRCLE;
456            
457            this.ignoreNullValues = false;
458            this.ignoreZeroValues = false;
459        }
460    
461        /**
462         * Returns the dataset.
463         *
464         * @return The dataset (possibly <code>null</code>).
465         */
466        public PieDataset getDataset() {
467            return this.dataset;
468        }
469    
470        /**
471         * Sets the dataset and sends a {@link DatasetChangeEvent} to 'this'.
472         *
473         * @param dataset  the dataset (<code>null</code> permitted).
474         */
475        public void setDataset(PieDataset dataset) {
476            // if there is an existing dataset, remove the plot from the list of 
477            // change listeners...
478            PieDataset existing = this.dataset;
479            if (existing != null) {
480                existing.removeChangeListener(this);
481            }
482    
483            // set the new dataset, and register the chart as a change listener...
484            this.dataset = dataset;
485            if (dataset != null) {
486                setDatasetGroup(dataset.getGroup());
487                dataset.addChangeListener(this);
488            }
489    
490            // send a dataset change event to self...
491            DatasetChangeEvent event = new DatasetChangeEvent(this, dataset);
492            datasetChanged(event);
493        }
494        
495        /**
496         * Returns the pie index (this is used by the {@link MultiplePiePlot} class
497         * to track subplots).
498         * 
499         * @return The pie index.
500         */
501        public int getPieIndex() {
502            return this.pieIndex;
503        }
504        
505        /**
506         * Sets the pie index (this is used by the {@link MultiplePiePlot} class to 
507         * track subplots).
508         * 
509         * @param index  the index.
510         */
511        public void setPieIndex(int index) {
512            this.pieIndex = index;
513        }
514        
515        /**
516         * Returns the start angle for the first pie section.  This is measured in 
517         * degrees starting from 3 o'clock and measuring anti-clockwise.
518         *
519         * @return The start angle.
520         */
521        public double getStartAngle() {
522            return this.startAngle;
523        }
524    
525        /**
526         * Sets the starting angle and sends a {@link PlotChangeEvent} to all 
527         * registered listeners.  The initial default value is 90 degrees, which 
528         * corresponds to 12 o'clock.  A value of zero corresponds to 3 o'clock...
529         * this is the encoding used by Java's Arc2D class.
530         *
531         * @param angle  the angle (in degrees).
532         */
533        public void setStartAngle(double angle) {
534            this.startAngle = angle;
535            notifyListeners(new PlotChangeEvent(this));
536        }
537    
538        /**
539         * Returns the direction in which the pie sections are drawn (clockwise or 
540         * anti-clockwise).
541         *
542         * @return The direction (never <code>null</code>).
543         */
544        public Rotation getDirection() {
545            return this.direction;
546        }
547    
548        /**
549         * Sets the direction in which the pie sections are drawn and sends a 
550         * {@link PlotChangeEvent} to all registered listeners.
551         *
552         * @param direction  the direction (<code>null</code> not permitted).
553         */
554        public void setDirection(Rotation direction) {
555            if (direction == null) {
556                throw new IllegalArgumentException("Null 'direction' argument.");
557            }
558            this.direction = direction;
559            notifyListeners(new PlotChangeEvent(this));
560    
561        }
562    
563        /**
564         * Returns the interior gap, measured as a percentage of the available 
565         * drawing space.
566         *
567         * @return The gap (as a percentage of the available drawing space).
568         */
569        public double getInteriorGap() {
570            return this.interiorGap;
571        }
572    
573        /**
574         * Sets the interior gap and sends a {@link PlotChangeEvent} to all 
575         * registered listeners.  This controls the space between the edges of the 
576         * pie plot and the plot area itself (the region where the section labels 
577         * appear).
578         *
579         * @param percent  the gap (as a percentage of the available drawing space).
580         */
581        public void setInteriorGap(double percent) {
582    
583            // check arguments...
584            if ((percent < 0.0) || (percent > MAX_INTERIOR_GAP)) {
585                throw new IllegalArgumentException(
586                    "Invalid 'percent' (" + percent + ") argument.");
587            }
588    
589            // make the change...
590            if (this.interiorGap != percent) {
591                this.interiorGap = percent;
592                notifyListeners(new PlotChangeEvent(this));
593            }
594    
595        }
596    
597        /**
598         * Returns a flag indicating whether the pie chart is circular, or
599         * stretched into an elliptical shape.
600         *
601         * @return A flag indicating whether the pie chart is circular.
602         */
603        public boolean isCircular() {
604            return this.circular;
605        }
606    
607        /**
608         * A flag indicating whether the pie chart is circular, or stretched into
609         * an elliptical shape.
610         *
611         * @param flag  the new value.
612         */
613        public void setCircular(boolean flag) {
614            setCircular(flag, true);
615        }
616    
617        /**
618         * Sets the circular attribute and, if requested, sends a 
619         * {@link PlotChangeEvent} to all registered listeners.
620         *
621         * @param circular  the new value of the flag.
622         * @param notify  notify listeners?
623         */
624        public void setCircular(boolean circular, boolean notify) {
625            this.circular = circular;
626            if (notify) {
627                notifyListeners(new PlotChangeEvent(this));   
628            }
629        }
630    
631        /**
632         * Returns the flag that controls whether <code>null</code> values in the 
633         * dataset are ignored.  
634         * 
635         * @return A boolean.
636         */
637        public boolean getIgnoreNullValues() {
638            return this.ignoreNullValues;   
639        }
640        
641        /**
642         * Sets a flag that controls whether <code>null</code> values are ignored, 
643         * and sends a {@link PlotChangeEvent} to all registered listeners.  At 
644         * present, this only affects whether or not the key is presented in the 
645         * legend.
646         * 
647         * @param flag  the flag.
648         */
649        public void setIgnoreNullValues(boolean flag) {
650            this.ignoreNullValues = flag;
651            notifyListeners(new PlotChangeEvent(this));
652        }
653        
654        /**
655         * Returns the flag that controls whether zero values in the 
656         * dataset are ignored.  
657         * 
658         * @return A boolean.
659         */
660        public boolean getIgnoreZeroValues() {
661            return this.ignoreZeroValues;   
662        }
663        
664        /**
665         * Sets a flag that controls whether zero values are ignored, 
666         * and sends a {@link PlotChangeEvent} to all registered listeners.  This 
667         * only affects whether or not a label appears for the non-visible
668         * pie section.
669         * 
670         * @param flag  the flag.
671         */
672        public void setIgnoreZeroValues(boolean flag) {
673            this.ignoreZeroValues = flag;
674            notifyListeners(new PlotChangeEvent(this));
675        }
676        
677        //// SECTION PAINT ////////////////////////////////////////////////////////
678    
679        /**
680         * Returns the paint for ALL sections in the plot.
681         *
682         * @return The paint (possibly <code>null</code>).
683         */
684        public Paint getSectionPaint() {
685            return this.sectionPaint;
686        }
687    
688        /**
689         * Sets the paint for ALL sections in the plot.  If this is set to
690         * </code>null</code>, then a list of paints is used instead (to allow
691         * different colors to be used for each section).
692         *
693         * @param paint  the paint (<code>null</code> permitted).
694         */
695        public void setSectionPaint(Paint paint) {
696            this.sectionPaint = paint;
697            notifyListeners(new PlotChangeEvent(this));
698        }
699    
700        /**
701         * Returns the paint for the specified section.
702         * 
703         * @param section  the section index (zero-based).
704         * 
705         * @return The paint (never <code>null</code>).
706         */
707        public Paint getSectionPaint(int section) {
708            
709            // return the override, if there is one...
710            if (this.sectionPaint != null) {
711                return this.sectionPaint;
712            }
713    
714            // otherwise look up the paint list
715            Paint result = this.sectionPaintList.getPaint(section);
716            if (result == null) {
717                DrawingSupplier supplier = getDrawingSupplier();
718                if (supplier != null) {
719                    Paint p = supplier.getNextPaint();
720                    this.sectionPaintList.setPaint(section, p);
721                    result = p;
722                }
723                else {
724                    result = this.baseSectionPaint;
725                }
726            }
727            return result;
728           
729        }
730        
731        /**
732         * Sets the paint used to fill a section of the pie and sends a 
733         * {@link PlotChangeEvent} to all registered listeners.
734         *
735         * @param section  the section index (zero-based).
736         * @param paint  the paint (<code>null</code> permitted).
737         */
738        public void setSectionPaint(int section, Paint paint) {
739            this.sectionPaintList.setPaint(section, paint);
740            notifyListeners(new PlotChangeEvent(this));
741        }
742        
743        /**
744         * Returns the base section paint.  This is used when no other paint is 
745         * available.
746         * 
747         * @return The paint (never <code>null</code>).
748         */
749        public Paint getBaseSectionPaint() {
750            return this.baseSectionPaint;   
751        }
752        
753        /**
754         * Sets the base section paint.
755         * 
756         * @param paint  the paint (<code>null</code> not permitted).
757         */
758        public void setBaseSectionPaint(Paint paint) {
759            if (paint == null) {
760                throw new IllegalArgumentException("Null 'paint' argument.");   
761            }
762            this.baseSectionPaint = paint;
763            notifyListeners(new PlotChangeEvent(this));
764        }
765        
766        //// SECTION OUTLINE PAINT ////////////////////////////////////////////////
767    
768        /**
769         * Returns the flag that controls whether or not the outline is drawn for
770         * each pie section.
771         * 
772         * @return The flag that controls whether or not the outline is drawn for
773         *         each pie section.
774         */
775        public boolean getSectionOutlinesVisible() {
776            return this.sectionOutlinesVisible;
777        }
778        
779        /**
780         * Sets the flag that controls whether or not the outline is drawn for 
781         * each pie section, and sends a {@link PlotChangeEvent} to all registered
782         * listeners.
783         * 
784         * @param visible  the flag.
785         */
786        public void setSectionOutlinesVisible(boolean visible) {
787            this.sectionOutlinesVisible = visible;
788            notifyListeners(new PlotChangeEvent(this));
789        }
790        
791        /**
792         * Returns the outline paint for ALL sections in the plot.
793         *
794         * @return The paint (possibly <code>null</code>).
795         */
796        public Paint getSectionOutlinePaint() {
797            return this.sectionOutlinePaint;
798        }
799    
800        /**
801         * Sets the outline paint for ALL sections in the plot.  If this is set to
802         * </code>null</code>, then a list of paints is used instead (to allow
803         * different colors to be used for each section).
804         *
805         * @param paint  the paint (<code>null</code> permitted).
806         */
807        public void setSectionOutlinePaint(Paint paint) {
808            this.sectionOutlinePaint = paint;
809            notifyListeners(new PlotChangeEvent(this));
810        }
811    
812        /**
813         * Returns the paint for the specified section.
814         * 
815         * @param section  the section index (zero-based).
816         * 
817         * @return The paint (never <code>null</code>).
818         */
819        public Paint getSectionOutlinePaint(int section) {
820            
821            // return the override, if there is one...
822            if (this.sectionOutlinePaint != null) {
823                return this.sectionOutlinePaint;
824            }
825    
826            // otherwise look up the paint list
827            Paint result = this.sectionOutlinePaintList.getPaint(section);
828            if (result == null) {
829                result = this.baseSectionOutlinePaint;
830            }
831            return result;
832           
833        }
834        
835        /**
836         * Sets the paint used to fill a section of the pie and sends a 
837         * {@link PlotChangeEvent} to all registered listeners.
838         *
839         * @param section  the section index (zero-based).
840         * @param paint  the paint (<code>null</code> permitted).
841         */
842        public void setSectionOutlinePaint(int section, Paint paint) {
843            this.sectionOutlinePaintList.setPaint(section, paint);
844            notifyListeners(new PlotChangeEvent(this));
845        }
846        
847        /**
848         * Returns the base section paint.  This is used when no other paint is 
849         * available.
850         * 
851         * @return The paint (never <code>null</code>).
852         */
853        public Paint getBaseSectionOutlinePaint() {
854            return this.baseSectionOutlinePaint;   
855        }
856        
857        /**
858         * Sets the base section paint.
859         * 
860         * @param paint  the paint (<code>null</code> not permitted).
861         */
862        public void setBaseSectionOutlinePaint(Paint paint) {
863            if (paint == null) {
864                throw new IllegalArgumentException("Null 'paint' argument.");   
865            }
866            this.baseSectionOutlinePaint = paint;
867            notifyListeners(new PlotChangeEvent(this));
868        }
869        
870        //// SECTION OUTLINE STROKE ///////////////////////////////////////////////
871    
872        /**
873         * Returns the outline stroke for ALL sections in the plot.
874         *
875         * @return The stroke (possibly <code>null</code>).
876         */
877        public Stroke getSectionOutlineStroke() {
878            return this.sectionOutlineStroke;
879        }
880    
881        /**
882         * Sets the outline stroke for ALL sections in the plot.  If this is set to
883         * </code>null</code>, then a list of paints is used instead (to allow
884         * different colors to be used for each section).
885         *
886         * @param stroke  the stroke (<code>null</code> permitted).
887         */
888        public void setSectionOutlineStroke(Stroke stroke) {
889            this.sectionOutlineStroke = stroke;
890            notifyListeners(new PlotChangeEvent(this));
891        }
892    
893        /**
894         * Returns the stroke for the specified section.
895         * 
896         * @param section  the section index (zero-based).
897         * 
898         * @return The stroke (never <code>null</code>).
899         */
900        public Stroke getSectionOutlineStroke(int section) {
901            
902            // return the override, if there is one...
903            if (this.sectionOutlineStroke != null) {
904                return this.sectionOutlineStroke;
905            }
906    
907            // otherwise look up the paint list
908            Stroke result = this.sectionOutlineStrokeList.getStroke(section);
909            if (result == null) {
910                result = this.baseSectionOutlineStroke;
911            }
912            return result;
913           
914        }
915        
916        /**
917         * Sets the stroke used to fill a section of the pie and sends a 
918         * {@link PlotChangeEvent} to all registered listeners.
919         *
920         * @param section  the section index (zero-based).
921         * @param stroke  the stroke (<code>null</code> permitted).
922         */
923        public void setSectionOutlineStroke(int section, Stroke stroke) {
924            this.sectionOutlineStrokeList.setStroke(section, stroke);
925            notifyListeners(new PlotChangeEvent(this));
926        }
927        
928        /**
929         * Returns the base section stroke.  This is used when no other stroke is 
930         * available.
931         * 
932         * @return The stroke (never <code>null</code>).
933         */
934        public Stroke getBaseSectionOutlineStroke() {
935            return this.baseSectionOutlineStroke;   
936        }
937        
938        /**
939         * Sets the base section stroke.
940         * 
941         * @param stroke  the stroke (<code>null</code> not permitted).
942         */
943        public void setBaseSectionOutlineStroke(Stroke stroke) {
944            if (stroke == null) {
945                throw new IllegalArgumentException("Null 'stroke' argument.");   
946            }
947            this.baseSectionOutlineStroke = stroke;
948            notifyListeners(new PlotChangeEvent(this));
949        }
950    
951        /**
952         * Returns the shadow paint.
953         * 
954         * @return The paint (possibly <code>null</code>).
955         */
956        public Paint getShadowPaint() {
957            return this.shadowPaint;   
958        }
959        
960        /**
961         * Sets the shadow paint and sends a {@link PlotChangeEvent} to all 
962         * registered listeners.
963         * 
964         * @param paint  the paint (<code>null</code> permitted).
965         */
966        public void setShadowPaint(Paint paint) {
967            this.shadowPaint = paint;
968            notifyListeners(new PlotChangeEvent(this));
969        }
970        
971        /**
972         * Returns the x-offset for the shadow effect.
973         * 
974         * @return The offset (in Java2D units).
975         */
976        public double getShadowXOffset() {
977            return this.shadowXOffset;
978        }
979        
980        /**
981         * Sets the x-offset for the shadow effect and sends a 
982         * {@link PlotChangeEvent} to all registered listeners.
983         * 
984         * @param offset  the offset (in Java2D units).
985         */
986        public void setShadowXOffset(double offset) {
987            this.shadowXOffset = offset;   
988            notifyListeners(new PlotChangeEvent(this));
989        }
990        
991        /**
992         * Returns the y-offset for the shadow effect.
993         * 
994         * @return The offset (in Java2D units).
995         */
996        public double getShadowYOffset() {
997            return this.shadowYOffset;
998        }
999        
1000        /**
1001         * Sets the y-offset for the shadow effect and sends a 
1002         * {@link PlotChangeEvent} to all registered listeners.
1003         * 
1004         * @param offset  the offset (in Java2D units).
1005         */
1006        public void setShadowYOffset(double offset) {
1007            this.shadowYOffset = offset;   
1008            notifyListeners(new PlotChangeEvent(this));
1009        }
1010        
1011        /**
1012         * Returns the amount that a section should be 'exploded'.
1013         *
1014         * @param section  the section number.
1015         *
1016         * @return The amount that a section should be 'exploded'.
1017         */
1018        public double getExplodePercent(int section) {
1019            double result = 0.0;
1020            if (this.explodePercentages != null) {
1021                Number percent = (Number) this.explodePercentages.get(section);
1022                if (percent != null) {
1023                    result = percent.doubleValue();
1024                }
1025            }
1026            return result;
1027        }
1028    
1029        /**
1030         * Sets the amount that a pie section should be exploded and sends a 
1031         * {@link PlotChangeEvent} to all registered listeners.
1032         *
1033         * @param section  the section index.
1034         * @param percent  the explode percentage (0.30 = 30 percent).
1035         */
1036        public void setExplodePercent(int section, double percent) {
1037            if (this.explodePercentages == null) {
1038                this.explodePercentages = new ObjectList();
1039            }
1040            this.explodePercentages.set(section, new Double(percent));
1041            notifyListeners(new PlotChangeEvent(this));
1042        }
1043        
1044        /**
1045         * Returns the maximum explode percent.
1046         * 
1047         * @return The percent.
1048         */
1049        public double getMaximumExplodePercent() {
1050            double result = 0.0;
1051            for (int i = 0; i < this.explodePercentages.size(); i++) {
1052                Number explode = (Number) this.explodePercentages.get(i);
1053                if (explode != null) {
1054                    result = Math.max(result, explode.doubleValue());   
1055                }
1056            }
1057            return result;
1058        }
1059        
1060        /**
1061         * Returns the section label generator. 
1062         * 
1063         * @return The generator (possibly <code>null</code>).
1064         */
1065        public PieSectionLabelGenerator getLabelGenerator() {
1066            return this.labelGenerator;   
1067        }
1068        
1069        /**
1070         * Sets the section label generator and sends a {@link PlotChangeEvent} to
1071         * all registered listeners.
1072         * 
1073         * @param generator  the generator (<code>null</code> permitted).
1074         */
1075        public void setLabelGenerator(PieSectionLabelGenerator generator) {
1076            this.labelGenerator = generator;
1077            notifyListeners(new PlotChangeEvent(this));
1078        }
1079        
1080        /**
1081         * Returns the gap between the edge of the pie and the labels, expressed as 
1082         * a percentage of the plot width.
1083         * 
1084         * @return The gap (a percentage, where 0.05 = five percent).
1085         */
1086        public double getLabelGap() {
1087            return this.labelGap;   
1088        }
1089        
1090        /**
1091         * Sets the gap between the edge of the pie and the labels (expressed as a 
1092         * percentage of the plot width) and sends a {@link PlotChangeEvent} to all
1093         * registered listeners.
1094         * 
1095         * @param gap  the gap (a percentage, where 0.05 = five percent).
1096         */
1097        public void setLabelGap(double gap) {
1098            this.labelGap = gap;   
1099            notifyListeners(new PlotChangeEvent(this));
1100        }
1101        
1102        /**
1103         * Returns the maximum label width as a percentage of the plot width.
1104         * 
1105         * @return The width (a percentage, where 0.20 = 20 percent).
1106         */
1107        public double getMaximumLabelWidth() {
1108            return this.maximumLabelWidth;   
1109        }
1110        
1111        /**
1112         * Sets the maximum label width as a percentage of the plot width and sends
1113         * a {@link PlotChangeEvent} to all registered listeners.
1114         * 
1115         * @param width  the width (a percentage, where 0.20 = 20 percent).
1116         */
1117        public void setMaximumLabelWidth(double width) {
1118            this.maximumLabelWidth = width;
1119            notifyListeners(new PlotChangeEvent(this));
1120        }
1121        
1122        /**
1123         * Returns the flag that controls whether or not label linking lines are
1124         * visible.
1125         * 
1126         * @return A boolean.
1127         */
1128        public boolean getLabelLinksVisible() {
1129            return this.labelLinksVisible;
1130        }
1131        
1132        /**
1133         * Sets the flag that controls whether or not label linking lines are 
1134         * visible and sends a {@link PlotChangeEvent} to all registered listeners.
1135         * Please take care when hiding the linking lines - depending on the data 
1136         * values, the labels can be displayed some distance away from the
1137         * corresponding pie section.
1138         * 
1139         * @param visible  the flag.
1140         */
1141        public void setLabelLinksVisible(boolean visible) {
1142            this.labelLinksVisible = visible;
1143            notifyListeners(new PlotChangeEvent(this));
1144        }
1145        
1146        /**
1147         * Returns the margin (expressed as a percentage of the width or height) 
1148         * between the edge of the pie and the link point.
1149         * 
1150         * @return The link margin (as a percentage, where 0.05 is five percent).
1151         */
1152        public double getLabelLinkMargin() {
1153            return this.labelLinkMargin;   
1154        }
1155        
1156        /**
1157         * Sets the link margin and sends a {@link PlotChangeEvent} to all 
1158         * registered listeners.
1159         * 
1160         * @param margin  the margin.
1161         */
1162        public void setLabelLinkMargin(double margin) {
1163            this.labelLinkMargin = margin;
1164            notifyListeners(new PlotChangeEvent(this));
1165        }
1166        
1167        /**
1168         * Returns the paint used for the lines that connect pie sections to their 
1169         * corresponding labels.
1170         * 
1171         * @return The paint (never <code>null</code>).
1172         */
1173        public Paint getLabelLinkPaint() {
1174            return this.labelLinkPaint;   
1175        }
1176        
1177        /**
1178         * Sets the paint used for the lines that connect pie sections to their 
1179         * corresponding labels, and sends a {@link PlotChangeEvent} to all 
1180         * registered listeners.
1181         * 
1182         * @param paint  the paint (<code>null</code> not permitted).
1183         */
1184        public void setLabelLinkPaint(Paint paint) {
1185            if (paint == null) {
1186                throw new IllegalArgumentException("Null 'paint' argument.");
1187            }
1188            this.labelLinkPaint = paint;
1189            notifyListeners(new PlotChangeEvent(this));
1190        }
1191        
1192        /**
1193         * Returns the stroke used for the label linking lines.
1194         * 
1195         * @return The stroke.
1196         */
1197        public Stroke getLabelLinkStroke() {
1198            return this.labelLinkStroke;   
1199        }
1200        
1201        /**
1202         * Sets the link stroke and sends a {@link PlotChangeEvent} to all 
1203         * registered listeners.
1204         * 
1205         * @param stroke  the stroke.
1206         */
1207        public void setLabelLinkStroke(Stroke stroke) {
1208            if (stroke == null) {
1209                throw new IllegalArgumentException("Null 'stroke' argument.");
1210            }
1211            this.labelLinkStroke = stroke;
1212            notifyListeners(new PlotChangeEvent(this));
1213        }
1214        
1215        /**
1216         * Returns the section label font.
1217         *
1218         * @return The font (never <code>null</code>).
1219         */
1220        public Font getLabelFont() {
1221            return this.labelFont;
1222        }
1223    
1224        /**
1225         * Sets the section label font and sends a {@link PlotChangeEvent} to all 
1226         * registered listeners.
1227         *
1228         * @param font  the font (<code>null</code> not permitted).
1229         */
1230        public void setLabelFont(Font font) {
1231            if (font == null) {
1232                throw new IllegalArgumentException("Null 'font' argument.");
1233            }
1234            this.labelFont = font;
1235            notifyListeners(new PlotChangeEvent(this));
1236        }
1237    
1238        /**
1239         * Returns the section label paint.
1240         *
1241         * @return The paint (never <code>null</code>).
1242         */
1243        public Paint getLabelPaint() {
1244            return this.labelPaint;
1245        }
1246    
1247        /**
1248         * Sets the section label paint and sends a {@link PlotChangeEvent} to all 
1249         * registered listeners.
1250         *
1251         * @param paint  the paint (<code>null</code> not permitted).
1252         */
1253        public void setLabelPaint(Paint paint) {
1254            if (paint == null) {
1255                throw new IllegalArgumentException("Null 'paint' argument.");
1256            }
1257            this.labelPaint = paint;
1258            notifyListeners(new PlotChangeEvent(this));
1259        }
1260    
1261        /**
1262         * Returns the section label background paint.
1263         *
1264         * @return The paint (possibly <code>null</code>).
1265         */
1266        public Paint getLabelBackgroundPaint() {
1267            return this.labelBackgroundPaint;
1268        }
1269    
1270        /**
1271         * Sets the section label background paint and sends a 
1272         * {@link PlotChangeEvent} to all registered listeners.
1273         *
1274         * @param paint  the paint (<code>null</code> permitted).
1275         */
1276        public void setLabelBackgroundPaint(Paint paint) {
1277            this.labelBackgroundPaint = paint;
1278            notifyListeners(new PlotChangeEvent(this));
1279        }
1280    
1281        /**
1282         * Returns the section label outline paint.
1283         *
1284         * @return The paint (possibly <code>null</code>).
1285         */
1286        public Paint getLabelOutlinePaint() {
1287            return this.labelOutlinePaint;
1288        }
1289    
1290        /**
1291         * Sets the section label outline paint and sends a 
1292         * {@link PlotChangeEvent} to all registered listeners.
1293         *
1294         * @param paint  the paint (<code>null</code> permitted).
1295         */
1296        public void setLabelOutlinePaint(Paint paint) {
1297            this.labelOutlinePaint = paint;
1298            notifyListeners(new PlotChangeEvent(this));
1299        }
1300    
1301        /**
1302         * Returns the section label outline stroke.
1303         *
1304         * @return The stroke (possibly <code>null</code>).
1305         */
1306        public Stroke getLabelOutlineStroke() {
1307            return this.labelOutlineStroke;
1308        }
1309    
1310        /**
1311         * Sets the section label outline stroke and sends a 
1312         * {@link PlotChangeEvent} to all registered listeners.
1313         *
1314         * @param stroke  the stroke (<code>null</code> permitted).
1315         */
1316        public void setLabelOutlineStroke(Stroke stroke) {
1317            this.labelOutlineStroke = stroke;
1318            notifyListeners(new PlotChangeEvent(this));
1319        }
1320    
1321        /**
1322         * Returns the section label shadow paint.
1323         *
1324         * @return The paint (possibly <code>null</code>).
1325         */
1326        public Paint getLabelShadowPaint() {
1327            return this.labelShadowPaint;
1328        }
1329    
1330        /**
1331         * Sets the section label shadow paint and sends a {@link PlotChangeEvent}
1332         * to all registered listeners.
1333         *
1334         * @param paint  the paint (<code>null</code> permitted).
1335         */
1336        public void setLabelShadowPaint(Paint paint) {
1337            this.labelShadowPaint = paint;
1338            notifyListeners(new PlotChangeEvent(this));
1339        }
1340    
1341        /**
1342         * Returns the tool tip generator, an object that is responsible for 
1343         * generating the text items used for tool tips by the plot.  If the 
1344         * generator is <code>null</code>, no tool tips will be created.
1345         *
1346         * @return The generator (possibly <code>null</code>).
1347         */
1348        public PieToolTipGenerator getToolTipGenerator() {
1349            return this.toolTipGenerator;
1350        }
1351    
1352        /**
1353         * Sets the tool tip generator and sends a {@link PlotChangeEvent} to all 
1354         * registered listeners.  Set the generator to <code>null</code> if you 
1355         * don't want any tool tips.
1356         *
1357         * @param generator  the generator (<code>null</code> permitted).
1358         */
1359        public void setToolTipGenerator(PieToolTipGenerator generator) {
1360            this.toolTipGenerator = generator;
1361            notifyListeners(new PlotChangeEvent(this));
1362        }
1363    
1364        /**
1365         * Returns the URL generator.
1366         *
1367         * @return The generator (possibly <code>null</code>).
1368         */
1369        public PieURLGenerator getURLGenerator() {
1370            return this.urlGenerator;
1371        }
1372    
1373        /**
1374         * Sets the URL generator and sends a {@link PlotChangeEvent} to all 
1375         * registered listeners.
1376         *
1377         * @param generator  the generator (<code>null</code> permitted).
1378         */
1379        public void setURLGenerator(PieURLGenerator generator) {
1380            this.urlGenerator = generator;
1381            notifyListeners(new PlotChangeEvent(this));
1382        }
1383    
1384        /**
1385         * Returns the minimum arc angle that will be drawn.  Pie sections for an 
1386         * angle smaller than this are not drawn, to avoid a JDK bug.
1387         *
1388         * @return The minimum angle.
1389         */
1390        public double getMinimumArcAngleToDraw() {
1391            return this.minimumArcAngleToDraw;
1392        }
1393    
1394        /**
1395         * Sets the minimum arc angle that will be drawn.  Pie sections for an 
1396         * angle smaller than this are not drawn, to avoid a JDK bug.  See this 
1397         * link for details:
1398         * <br><br>
1399         * <a href="http://www.jfree.org/phpBB2/viewtopic.php?t=2707">
1400         * http://www.jfree.org/phpBB2/viewtopic.php?t=2707</a>
1401         * <br><br>
1402         * ...and this bug report in the Java Bug Parade:
1403         * <br><br>
1404         * <a href=
1405         * "http://developer.java.sun.com/developer/bugParade/bugs/4836495.html">
1406         * http://developer.java.sun.com/developer/bugParade/bugs/4836495.html</a>
1407         *
1408         * @param angle  the minimum angle.
1409         */
1410        public void setMinimumArcAngleToDraw(double angle) {
1411            this.minimumArcAngleToDraw = angle;
1412        }
1413        
1414        /**
1415         * Returns the shape used for legend items.
1416         * 
1417         * @return The shape.
1418         */
1419        public Shape getLegendItemShape() {
1420            return this.legendItemShape;
1421        }
1422    
1423        /**
1424         * Sets the shape used for legend items.
1425         * 
1426         * @param shape  the shape (<code>null</code> not permitted).
1427         */
1428        public void setLegendItemShape(Shape shape) {
1429            if (shape == null) {
1430                throw new IllegalArgumentException("Null 'shape' argument.");
1431            }
1432            this.legendItemShape = shape;
1433            notifyListeners(new PlotChangeEvent(this));
1434        }
1435        
1436        /**
1437         * Returns the legend label tool tip generator.
1438         * 
1439         * @return The legend label tool tip generator (possibly <code>null</code>).
1440         */
1441        public PieSectionLabelGenerator getLegendLabelToolTipGenerator() {
1442            return this.legendLabelToolTipGenerator;
1443        }
1444        
1445        /**
1446         * Sets the legend label tool tip generator and sends a 
1447         * {@link PlotChangeEvent} to all registered listeners.
1448         * 
1449         * @param generator  the generator (<code>null</code> permitted).
1450         */
1451        public void setLegendLabelToolTipGenerator(
1452                PieSectionLabelGenerator generator) {
1453            this.legendLabelToolTipGenerator = generator;
1454            notifyListeners(new PlotChangeEvent(this));
1455        }
1456        
1457        /**
1458         * Returns the legend label generator.
1459         * 
1460         * @return The legend label generator (never <code>null</code>).
1461         */
1462        public PieSectionLabelGenerator getLegendLabelGenerator() {
1463            return this.legendLabelGenerator;
1464        }
1465        
1466        /**
1467         * Sets the legend label generator and sends a {@link PlotChangeEvent} to 
1468         * all registered listeners.
1469         * 
1470         * @param generator  the generator (<code>null</code> not permitted).
1471         */
1472        public void setLegendLabelGenerator(PieSectionLabelGenerator generator) {
1473            if (generator == null) {
1474                throw new IllegalArgumentException("Null 'generator' argument.");
1475            }
1476            this.legendLabelGenerator = generator;
1477            notifyListeners(new PlotChangeEvent(this));
1478        }
1479        
1480        /**
1481         * Initialises the drawing procedure.  This method will be called before 
1482         * the first item is rendered, giving the plot an opportunity to initialise
1483         * any state information it wants to maintain.
1484         *
1485         * @param g2  the graphics device.
1486         * @param plotArea  the plot area (<code>null</code> not permitted).
1487         * @param plot  the plot.
1488         * @param index  the secondary index (<code>null</code> for primary 
1489         *               renderer).
1490         * @param info  collects chart rendering information for return to caller.
1491         * 
1492         * @return A state object (maintains state information relevant to one 
1493         *         chart drawing).
1494         */
1495        public PiePlotState initialise(Graphics2D g2,
1496                                       Rectangle2D plotArea,
1497                                       PiePlot plot,
1498                                       Integer index,
1499                                       PlotRenderingInfo info) {
1500         
1501            PiePlotState state = new PiePlotState(info);
1502            state.setPassesRequired(2);
1503            state.setTotal(
1504                DatasetUtilities.calculatePieDatasetTotal(plot.getDataset())
1505            );
1506            state.setLatestAngle(plot.getStartAngle());
1507            return state;
1508            
1509        }
1510        
1511        /**
1512         * Draws the plot on a Java 2D graphics device (such as the screen or a 
1513         * printer).
1514         *
1515         * @param g2  the graphics device.
1516         * @param area  the area within which the plot should be drawn.
1517         * @param anchor  the anchor point (<code>null</code> permitted).
1518         * @param parentState  the state from the parent plot, if there is one.
1519         * @param info  collects info about the drawing 
1520         *              (<code>null</code> permitted).
1521         */
1522        public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor,
1523                         PlotState parentState,
1524                         PlotRenderingInfo info) {
1525    
1526            // adjust for insets...
1527            RectangleInsets insets = getInsets();
1528            insets.trim(area);
1529    
1530            if (info != null) {
1531                info.setPlotArea(area);
1532                info.setDataArea(area);
1533            }
1534    
1535            drawBackground(g2, area);
1536            drawOutline(g2, area);
1537    
1538            Shape savedClip = g2.getClip();
1539            g2.clip(area);
1540    
1541            Composite originalComposite = g2.getComposite();
1542            g2.setComposite(
1543                AlphaComposite.getInstance(
1544                    AlphaComposite.SRC_OVER, getForegroundAlpha()
1545                )
1546            );
1547    
1548            if (!DatasetUtilities.isEmptyOrNull(this.dataset)) {
1549                drawPie(g2, area, info);
1550            }
1551            else {
1552                drawNoDataMessage(g2, area);
1553            }
1554    
1555            g2.setClip(savedClip);
1556            g2.setComposite(originalComposite);
1557    
1558            drawOutline(g2, area);
1559    
1560        }
1561    
1562        /**
1563         * Draws the pie.
1564         *
1565         * @param g2  the graphics device.
1566         * @param plotArea  the plot area.
1567         * @param info  chart rendering info.
1568         */
1569        protected void drawPie(Graphics2D g2, 
1570                               Rectangle2D plotArea, 
1571                               PlotRenderingInfo info) {
1572    
1573            PiePlotState state = initialise(g2, plotArea, this, null, info);
1574    
1575            // adjust the plot area for interior spacing and labels...
1576            double labelWidth = 0.0;
1577            if (this.labelGenerator != null) {
1578                labelWidth = this.labelGap + this.maximumLabelWidth 
1579                             + this.labelLinkMargin;    
1580            }
1581            double gapHorizontal 
1582                = plotArea.getWidth() * (this.interiorGap + labelWidth);
1583            double gapVertical = plotArea.getHeight() * this.interiorGap;
1584    
1585            double linkX = plotArea.getX() + gapHorizontal / 2;
1586            double linkY = plotArea.getY() + gapVertical / 2;
1587            double linkW = plotArea.getWidth() - gapHorizontal;
1588            double linkH = plotArea.getHeight() - gapVertical;
1589            
1590            // make the link area a square if the pie chart is to be circular...
1591            if (this.circular) {
1592                double min = Math.min(linkW, linkH) / 2;
1593                linkX = (linkX + linkX + linkW) / 2 - min;
1594                linkY = (linkY + linkY + linkH) / 2 - min;
1595                linkW = 2 * min;
1596                linkH = 2 * min;
1597            }
1598    
1599            // the link area defines the dog leg points for the linking lines to 
1600            // the labels
1601            Rectangle2D linkArea = new Rectangle2D.Double(
1602                linkX, linkY, linkW, linkH
1603            );
1604            state.setLinkArea(linkArea);
1605            
1606            // the explode area defines the max circle/ellipse for the exploded 
1607            // pie sections.  it is defined by shrinking the linkArea by the 
1608            // linkMargin factor.
1609            double hh = linkArea.getWidth() * this.labelLinkMargin;
1610            double vv = linkArea.getHeight() * this.labelLinkMargin;
1611            Rectangle2D explodeArea = new Rectangle2D.Double(
1612                linkX + hh / 2.0, linkY + vv / 2.0, linkW - hh, linkH - vv
1613            );
1614           
1615            state.setExplodedPieArea(explodeArea);
1616            
1617            // the pie area defines the circle/ellipse for regular pie sections.
1618            // it is defined by shrinking the explodeArea by the explodeMargin 
1619            // factor. 
1620            double maximumExplodePercent = getMaximumExplodePercent();
1621            double percent = maximumExplodePercent / (1.0 + maximumExplodePercent);
1622            
1623            double h1 = explodeArea.getWidth() * percent;
1624            double v1 = explodeArea.getHeight() * percent;
1625            Rectangle2D pieArea = new Rectangle2D.Double(
1626                explodeArea.getX() + h1 / 2.0, explodeArea.getY() + v1 / 2.0,
1627                explodeArea.getWidth() - h1, explodeArea.getHeight() - v1
1628            );
1629    
1630            state.setPieArea(pieArea);
1631            state.setPieCenterX(pieArea.getCenterX());
1632            state.setPieCenterY(pieArea.getCenterY());
1633            state.setPieWRadius(pieArea.getWidth() / 2.0);
1634            state.setPieHRadius(pieArea.getHeight() / 2.0);
1635            // plot the data (unless the dataset is null)...
1636            if ((this.dataset != null) && (this.dataset.getKeys().size() > 0)) {
1637    
1638                List keys = this.dataset.getKeys();
1639                double totalValue 
1640                    = DatasetUtilities.calculatePieDatasetTotal(this.dataset);
1641    
1642                int passesRequired = state.getPassesRequired();
1643                for (int pass = 0; pass < passesRequired; pass++) {
1644                    double runningTotal = 0.0;
1645                    for (int section = 0; section < keys.size(); section++) {
1646                        Number n = this.dataset.getValue(section);
1647                        if (n != null) {
1648                            double value = n.doubleValue();
1649                            if (value > 0.0) {
1650                                runningTotal += value;
1651                                drawItem(g2, section, explodeArea, state, pass);
1652                            }
1653                        } 
1654                    }
1655                }
1656                
1657                drawLabels(g2, keys, totalValue, plotArea, linkArea, state);
1658    
1659            }
1660            else {
1661                drawNoDataMessage(g2, plotArea);
1662            }
1663        }
1664        
1665        /**
1666         * Draws a single data item.
1667         *
1668         * @param g2  the graphics device (<code>null</code> not permitted).
1669         * @param section  the section index.
1670         * @param dataArea  the data plot area.
1671         * @param state  state information for one chart.
1672         * @param currentPass  the current pass index.
1673         */
1674        protected void drawItem(Graphics2D g2,
1675                                int section,
1676                                Rectangle2D dataArea,
1677                                PiePlotState state,
1678                                int currentPass) {
1679        
1680            Number n = this.dataset.getValue(section);
1681            if (n == null) {
1682                return;   
1683            }
1684            double value = n.doubleValue();
1685            double angle1 = 0.0;
1686            double angle2 = 0.0;
1687            
1688            if (this.direction == Rotation.CLOCKWISE) {
1689                angle1 = state.getLatestAngle();
1690                angle2 = angle1 - value / state.getTotal() * 360.0;
1691            }
1692            else if (this.direction == Rotation.ANTICLOCKWISE) {
1693                angle1 = state.getLatestAngle();
1694                angle2 = angle1 + value / state.getTotal() * 360.0;         
1695            }
1696            else {
1697                throw new IllegalStateException("Rotation type not recognised.");   
1698            }
1699            
1700            double angle = (angle2 - angle1);
1701            if (Math.abs(angle) > getMinimumArcAngleToDraw()) {
1702                double ep = 0.0;
1703                double mep = getMaximumExplodePercent();
1704                if (mep > 0.0) {
1705                    ep = getExplodePercent(section) / mep;                
1706                }
1707                Rectangle2D arcBounds = getArcBounds(
1708                    state.getPieArea(), state.getExplodedPieArea(),
1709                    angle1, angle, ep
1710                );
1711                Arc2D.Double arc = new Arc2D.Double(
1712                    arcBounds, angle1, angle, Arc2D.PIE
1713                );
1714                
1715                if (currentPass == 0) {
1716                    if (this.shadowPaint != null) {
1717                        Shape shadowArc = ShapeUtilities.createTranslatedShape(
1718                            arc, (float) this.shadowXOffset, 
1719                            (float) this.shadowYOffset
1720                        );
1721                        g2.setPaint(this.shadowPaint);
1722                        g2.fill(shadowArc);
1723                    }
1724                }
1725                else if (currentPass == 1) {
1726    
1727                    Paint paint = getSectionPaint(section);
1728                    g2.setPaint(paint);
1729                    g2.fill(arc);
1730    
1731                    Paint outlinePaint = getSectionOutlinePaint(section);
1732                    Stroke outlineStroke = getSectionOutlineStroke(section);
1733                    if (this.sectionOutlinesVisible) {
1734                        g2.setPaint(outlinePaint);
1735                        g2.setStroke(outlineStroke);
1736                        g2.draw(arc);
1737                    }
1738                    
1739                    // update the linking line target for later
1740                    // add an entity for the pie section
1741                    if (state.getInfo() != null) {
1742                        EntityCollection entities = state.getEntityCollection();
1743                        if (entities != null) {
1744                            Comparable key = this.dataset.getKey(section);
1745                            String tip = null;
1746                            if (this.toolTipGenerator != null) {
1747                                tip = this.toolTipGenerator.generateToolTip(
1748                                    this.dataset, key
1749                                );
1750                            }
1751                            String url = null;
1752                            if (this.urlGenerator != null) {
1753                                url = this.urlGenerator.generateURL(
1754                                    this.dataset, key, this.pieIndex
1755                                );
1756                            }
1757                            PieSectionEntity entity = new PieSectionEntity(
1758                                arc, this.dataset, this.pieIndex, section, key, 
1759                                tip, url
1760                            );
1761                            entities.add(entity);
1762                        }
1763                    }
1764                }
1765            }    
1766            state.setLatestAngle(angle2);
1767        }
1768        
1769        /**
1770         * Draws the labels for the pie sections.
1771         * 
1772         * @param g2  the graphics device.
1773         * @param keys  the keys.
1774         * @param totalValue  the total value.
1775         * @param plotArea  the plot area.
1776         * @param linkArea  the link area.
1777         * @param state  the state.
1778         */
1779        protected void drawLabels(Graphics2D g2, List keys, double totalValue, 
1780                                  Rectangle2D plotArea, Rectangle2D linkArea, 
1781                                  PiePlotState state) {   
1782    
1783            Composite originalComposite = g2.getComposite();
1784            g2.setComposite(
1785                AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f)
1786            );
1787    
1788            // classify the keys according to which side the label will appear...
1789            DefaultKeyedValues leftKeys = new DefaultKeyedValues();
1790            DefaultKeyedValues rightKeys = new DefaultKeyedValues();
1791           
1792            double runningTotal1 = 0.0;
1793            Iterator iterator1 = keys.iterator();
1794            while (iterator1.hasNext()) {
1795                Comparable key = (Comparable) iterator1.next();
1796                Number n = this.dataset.getValue(key);
1797                if (n != null) {
1798                    double v = n.doubleValue();
1799                    if (this.ignoreZeroValues ? v > 0.0 : v >= 0.0) {
1800                        runningTotal1 = runningTotal1 + v;
1801                        // work out the mid angle (0 - 90 and 270 - 360) = right, 
1802                        // otherwise left
1803                        double mid = this.startAngle + (this.direction.getFactor()
1804                            * ((runningTotal1 - v / 2.0) * 360) / totalValue);
1805                        if (Math.cos(Math.toRadians(mid)) < 0.0) {
1806                            leftKeys.addValue(key, new Double(mid));
1807                        }
1808                        else {
1809                            rightKeys.addValue(key, new Double(mid));
1810                        }
1811                    }
1812                }
1813            }
1814           
1815            g2.setFont(getLabelFont());
1816            float maxLabelWidth 
1817                = (float) (getMaximumLabelWidth() * plotArea.getWidth());
1818            
1819            // draw the left labels...
1820            if (this.labelGenerator != null) {
1821                drawLeftLabels(
1822                    leftKeys, g2, plotArea, linkArea, maxLabelWidth, state
1823                );
1824                drawRightLabels(
1825                    rightKeys, g2, plotArea, linkArea, maxLabelWidth, state
1826                );
1827            }
1828            g2.setComposite(originalComposite);
1829    
1830        }
1831    
1832        /**
1833         * Draws the left labels.
1834         * 
1835         * @param leftKeys  the keys.
1836         * @param g2  the graphics device.
1837         * @param plotArea  the plot area.
1838         * @param linkArea  the link area.
1839         * @param maxLabelWidth  the maximum label width.
1840         * @param state  the state.
1841         */
1842        protected void drawLeftLabels(KeyedValues leftKeys, Graphics2D g2, 
1843                                      Rectangle2D plotArea, Rectangle2D linkArea, 
1844                                      float maxLabelWidth, PiePlotState state) {
1845            
1846            PieLabelDistributor distributor1 = new PieLabelDistributor(
1847                leftKeys.getItemCount()
1848            );
1849            double lGap = plotArea.getWidth() * this.labelGap;
1850            double verticalLinkRadius = state.getLinkArea().getHeight() / 2.0;
1851            for (int i = 0; i < leftKeys.getItemCount(); i++) {   
1852                String label = this.labelGenerator.generateSectionLabel(
1853                    this.dataset, leftKeys.getKey(i)
1854                );
1855                if (label != null) {
1856                    TextBlock block = TextUtilities.createTextBlock(
1857                        label, 
1858                        this.labelFont, this.labelPaint, maxLabelWidth, 
1859                        new G2TextMeasurer(g2)
1860                    );
1861                    TextBox labelBox = new TextBox(block);
1862                    labelBox.setBackgroundPaint(this.labelBackgroundPaint);
1863                    labelBox.setOutlinePaint(this.labelOutlinePaint);
1864                    labelBox.setOutlineStroke(this.labelOutlineStroke);
1865                    labelBox.setShadowPaint(this.labelShadowPaint);
1866                    double theta = Math.toRadians(
1867                        leftKeys.getValue(i).doubleValue()
1868                    );
1869                    double baseY = state.getPieCenterY() - Math.sin(theta) 
1870                                   * verticalLinkRadius;
1871                    double hh = labelBox.getHeight(g2);
1872    
1873                    distributor1.addPieLabelRecord(
1874                        new PieLabelRecord(
1875                            leftKeys.getKey(i), theta, baseY, labelBox, hh,
1876                            lGap / 2.0 + lGap / 2.0 * -Math.cos(theta), 
1877                            0.9 + getExplodePercent(this.dataset.getIndex(
1878                                    leftKeys.getKey(i)))
1879                        )
1880                    );
1881                }
1882            }
1883            distributor1.distributeLabels(plotArea.getMinY(), plotArea.getHeight());
1884            for (int i = 0; i < distributor1.getItemCount(); i++) {
1885                drawLeftLabel(g2, state, distributor1.getPieLabelRecord(i));
1886            }
1887        }
1888        
1889        /**
1890         * Draws the right labels.
1891         * 
1892         * @param keys  the keys.
1893         * @param g2  the graphics device.
1894         * @param plotArea  the plot area.
1895         * @param linkArea  the link area.
1896         * @param maxLabelWidth  the maximum label width.
1897         * @param state  the state.
1898         */
1899        protected void drawRightLabels(KeyedValues keys, Graphics2D g2, 
1900                                       Rectangle2D plotArea, Rectangle2D linkArea, 
1901                                       float maxLabelWidth, PiePlotState state) {
1902    
1903            // draw the right labels...
1904            PieLabelDistributor distributor2 
1905                = new PieLabelDistributor(keys.getItemCount());
1906            double lGap = plotArea.getWidth() * this.labelGap;
1907            double verticalLinkRadius = state.getLinkArea().getHeight() / 2.0;
1908    
1909            for (int i = 0; i < keys.getItemCount(); i++) {
1910                String label = this.labelGenerator.generateSectionLabel(
1911                    this.dataset, keys.getKey(i)
1912                );
1913    
1914                if (label != null) {
1915                    TextBlock block = TextUtilities.createTextBlock(
1916                        label, this.labelFont, this.labelPaint, 
1917                        maxLabelWidth, new G2TextMeasurer(g2)
1918                    );
1919                    TextBox labelBox = new TextBox(block);
1920                    labelBox.setBackgroundPaint(this.labelBackgroundPaint);
1921                    labelBox.setOutlinePaint(this.labelOutlinePaint);
1922                    labelBox.setOutlineStroke(this.labelOutlineStroke);
1923                    labelBox.setShadowPaint(this.labelShadowPaint);
1924                    double theta = Math.toRadians(keys.getValue(i).doubleValue());
1925                    double baseY = state.getPieCenterY() 
1926                                  - Math.sin(theta) * verticalLinkRadius;
1927                    double hh = labelBox.getHeight(g2);
1928                    distributor2.addPieLabelRecord(
1929                        new PieLabelRecord(
1930                            keys.getKey(i), theta, baseY, labelBox, hh,
1931                            lGap / 2.0 + lGap / 2.0 * Math.cos(theta), 
1932                            0.9 + getExplodePercent(this.dataset.getIndex(
1933                                    keys.getKey(i)))
1934                        )
1935                    );
1936                }
1937            }
1938            distributor2.distributeLabels(linkArea.getMinY(), linkArea.getHeight());
1939            for (int i = 0; i < distributor2.getItemCount(); i++) {
1940                drawRightLabel(g2, state, distributor2.getPieLabelRecord(i));
1941            }
1942    
1943        }
1944        
1945        /**
1946         * Returns a collection of legend items for the pie chart.
1947         *
1948         * @return The legend items (never <code>null</code>).
1949         */
1950        public LegendItemCollection getLegendItems() {
1951    
1952            LegendItemCollection result = new LegendItemCollection();
1953            if (this.dataset == null) {
1954                return result;
1955            }
1956            List keys = this.dataset.getKeys();
1957            int section = 0;
1958            Shape shape = getLegendItemShape();
1959            Iterator iterator = keys.iterator();
1960            while (iterator.hasNext()) {
1961                Comparable key = (Comparable) iterator.next();
1962                Number n = this.dataset.getValue(key);
1963                boolean include = true;
1964                if (n == null) {
1965                    include = !this.ignoreNullValues;   
1966                }
1967                else {
1968                    double v = n.doubleValue();
1969                    if (v == 0.0) {
1970                        include = !this.ignoreZeroValues;   
1971                    }
1972                    else {
1973                        include = v > 0.0;   
1974                    }
1975                }
1976                if (include) {
1977                    String label = this.legendLabelGenerator.generateSectionLabel(
1978                        this.dataset, key
1979                    );
1980                    String description = label;
1981                    String toolTipText = null;
1982                    if (this.legendLabelToolTipGenerator != null) {
1983                        toolTipText = this.legendLabelToolTipGenerator
1984                            .generateSectionLabel(
1985                                this.dataset, key
1986                            );
1987                    }
1988                    String urlText = null;
1989                    Paint paint = getSectionPaint(section);
1990                    Paint outlinePaint = getSectionOutlinePaint(section);
1991                    Stroke outlineStroke = getSectionOutlineStroke(section);
1992    
1993                    LegendItem item = new LegendItem(label, description, 
1994                            toolTipText, urlText, true, shape, true, paint, 
1995                            true, outlinePaint, outlineStroke, 
1996                            false,          // line not visible
1997                            new Line2D.Float(), new BasicStroke(), Color.black);
1998                    result.add(item);
1999                    section++;
2000                }
2001            }
2002            return result;
2003        }
2004    
2005        /**
2006         * Returns a short string describing the type of plot.
2007         *
2008         * @return The plot type.
2009         */
2010        public String getPlotType() {
2011            return localizationResources.getString("Pie_Plot");
2012        }
2013    
2014        /**
2015         * A zoom method that does nothing.
2016         * <p>
2017         * Plots are required to support the zoom operation.  In the case of a pie
2018         * chart, it doesn't make sense to zoom in or out, so the method is empty.
2019         *
2020         * @param percent  the zoom percentage.
2021         */
2022        public void zoom(double percent) {
2023            // no zooming for pie plots
2024        }
2025    
2026        /**
2027         * Returns a rectangle that can be used to create a pie section (taking
2028         * into account the amount by which the pie section is 'exploded').
2029         *
2030         * @param unexploded  the area inside which the unexploded pie sections are
2031         *                    drawn.
2032         * @param exploded  the area inside which the exploded pie sections are 
2033         *                  drawn.
2034         * @param angle  the start angle.
2035         * @param extent  the extent of the arc.
2036         * @param explodePercent  the amount by which the pie section is exploded.
2037         *
2038         * @return A rectangle that can be used to create a pie section.
2039         */
2040        protected Rectangle2D getArcBounds(Rectangle2D unexploded, 
2041                                           Rectangle2D exploded,
2042                                           double angle, double extent, 
2043                                           double explodePercent) {
2044    
2045            if (explodePercent == 0.0) {
2046                return unexploded;
2047            }
2048            else {
2049                Arc2D arc1 = new Arc2D.Double(
2050                    unexploded, angle, extent / 2, Arc2D.OPEN
2051                );
2052                Point2D point1 = arc1.getEndPoint();
2053                Arc2D.Double arc2 = new Arc2D.Double(
2054                    exploded, angle, extent / 2, Arc2D.OPEN
2055                );
2056                Point2D point2 = arc2.getEndPoint();
2057                double deltaX = (point1.getX() - point2.getX()) * explodePercent;
2058                double deltaY = (point1.getY() - point2.getY()) * explodePercent;
2059                return new Rectangle2D.Double(
2060                    unexploded.getX() - deltaX, unexploded.getY() - deltaY,
2061                    unexploded.getWidth(), unexploded.getHeight()
2062                );
2063            }
2064        }
2065        
2066        /**
2067         * Draws a section label on the left side of the pie chart.
2068         * 
2069         * @param g2  the graphics device.
2070         * @param state  the state.
2071         * @param record  the label record.
2072         */
2073        protected void drawLeftLabel(Graphics2D g2, PiePlotState state, 
2074                                     PieLabelRecord record) {
2075    
2076            double anchorX = state.getLinkArea().getMinX();
2077            double targetX = anchorX - record.getGap();
2078            double targetY = record.getAllocatedY();
2079            
2080            if (this.labelLinksVisible) {
2081                double theta = record.getAngle();
2082                double linkX = state.getPieCenterX() + Math.cos(theta) 
2083                    * state.getPieWRadius() * record.getLinkPercent();
2084                double linkY = state.getPieCenterY() - Math.sin(theta) 
2085                    * state.getPieHRadius() * record.getLinkPercent();
2086                double elbowX = state.getPieCenterX() + Math.cos(theta) 
2087                    * state.getLinkArea().getWidth() / 2.0;
2088                double elbowY = state.getPieCenterY() - Math.sin(theta) 
2089                    * state.getLinkArea().getHeight() / 2.0;
2090                double anchorY = elbowY;
2091                g2.setPaint(this.labelLinkPaint);
2092                g2.setStroke(this.labelLinkStroke);
2093                g2.draw(new Line2D.Double(linkX, linkY, elbowX, elbowY));
2094                g2.draw(new Line2D.Double(anchorX, anchorY, elbowX, elbowY));
2095                g2.draw(new Line2D.Double(anchorX, anchorY, targetX, targetY));
2096            }
2097            TextBox tb = record.getLabel();
2098            tb.draw(g2, (float) targetX, (float) targetY, RectangleAnchor.RIGHT);
2099            
2100        }
2101    
2102        /**
2103         * Draws a section label on the right side of the pie chart.
2104         * 
2105         * @param g2  the graphics device.
2106         * @param state  the state.
2107         * @param record  the label record.
2108         */
2109        protected void drawRightLabel(Graphics2D g2, PiePlotState state, 
2110                                      PieLabelRecord record) {
2111            
2112            double anchorX = state.getLinkArea().getMaxX();
2113            double targetX = anchorX + record.getGap();
2114            double targetY = record.getAllocatedY();
2115            
2116            if (this.labelLinksVisible) {
2117                double theta = record.getAngle();
2118                double linkX = state.getPieCenterX() + Math.cos(theta) 
2119                    * state.getPieWRadius() * record.getLinkPercent();
2120                double linkY = state.getPieCenterY() - Math.sin(theta) 
2121                    * state.getPieHRadius() * record.getLinkPercent();
2122                double elbowX = state.getPieCenterX() + Math.cos(theta) 
2123                    * state.getLinkArea().getWidth() / 2.0;
2124                double elbowY = state.getPieCenterY() - Math.sin(theta) 
2125                    * state.getLinkArea().getHeight() / 2.0;
2126                double anchorY = elbowY;
2127                g2.setPaint(this.labelLinkPaint);
2128                g2.setStroke(this.labelLinkStroke);
2129                g2.draw(new Line2D.Double(linkX, linkY, elbowX, elbowY));
2130                g2.draw(new Line2D.Double(anchorX, anchorY, elbowX, elbowY));
2131                g2.draw(new Line2D.Double(anchorX, anchorY, targetX, targetY));
2132            }
2133            
2134            TextBox tb = record.getLabel();
2135            tb.draw(g2, (float) targetX, (float) targetY, RectangleAnchor.LEFT);
2136        
2137        }
2138    
2139        /**
2140         * Tests this plot for equality with an arbitrary object.  Note that the 
2141         * plot's dataset is NOT included in the test for equality.
2142         *
2143         * @param obj  the object to test against (<code>null</code> permitted).
2144         *
2145         * @return <code>true</code> or <code>false</code>.
2146         */
2147        public boolean equals(Object obj) {
2148            if (obj == this) {
2149                return true;
2150            }
2151            if (!(obj instanceof PiePlot)) {
2152                return false;
2153            }
2154            if (!super.equals(obj)) {
2155                return false;
2156            }
2157            PiePlot that = (PiePlot) obj;
2158            if (this.pieIndex != that.pieIndex) {
2159                return false;
2160            }
2161            if (this.interiorGap != that.interiorGap) {
2162                return false;
2163            }
2164            if (this.circular != that.circular) {
2165                return false;
2166            }
2167            if (this.startAngle != that.startAngle) {
2168                return false;
2169            }
2170            if (this.direction != that.direction) {
2171                return false;
2172            }
2173            if (this.ignoreZeroValues != that.ignoreZeroValues) {
2174                return false;
2175            }
2176            if (this.ignoreNullValues != that.ignoreNullValues) {
2177                return false;
2178            }
2179            if (!PaintUtilities.equal(this.sectionPaint, that.sectionPaint)) {
2180                return false;
2181            }
2182            if (!ObjectUtilities.equal(this.sectionPaintList, 
2183                    that.sectionPaintList)) {
2184                return false;
2185            }
2186            if (!PaintUtilities.equal(this.baseSectionPaint, 
2187                    that.baseSectionPaint)) {
2188                return false;
2189            }
2190            if (this.sectionOutlinesVisible != that.sectionOutlinesVisible) {
2191                return false;
2192            }
2193            if (!PaintUtilities.equal(this.sectionOutlinePaint, 
2194                    that.sectionOutlinePaint)) {
2195                return false;
2196            }
2197            if (!ObjectUtilities.equal(this.sectionOutlinePaintList, 
2198                    that.sectionOutlinePaintList)) {
2199                return false;
2200            }
2201            if (!PaintUtilities.equal(
2202                this.baseSectionOutlinePaint, that.baseSectionOutlinePaint
2203            )) {
2204                return false;
2205            }
2206            if (!ObjectUtilities.equal(this.sectionOutlineStroke, 
2207                    that.sectionOutlineStroke)) {
2208                return false;
2209            }
2210            if (!ObjectUtilities.equal(
2211                this.sectionOutlineStrokeList, that.sectionOutlineStrokeList
2212            )) {
2213                return false;
2214            }
2215            if (!ObjectUtilities.equal(
2216                this.baseSectionOutlineStroke, that.baseSectionOutlineStroke
2217            )) {
2218                return false;
2219            }
2220            if (!PaintUtilities.equal(this.shadowPaint, that.shadowPaint)) {
2221                return false;
2222            }
2223            if (!(this.shadowXOffset == that.shadowXOffset)) {
2224                return false;
2225            }
2226            if (!(this.shadowYOffset == that.shadowYOffset)) {
2227                return false;
2228            }   
2229            if (!ObjectUtilities.equal(this.explodePercentages, 
2230                    that.explodePercentages)) {
2231                return false;
2232            }
2233            if (!ObjectUtilities.equal(this.labelGenerator, 
2234                    that.labelGenerator)) {
2235                return false;
2236            }
2237            if (!ObjectUtilities.equal(this.labelFont, that.labelFont)) {
2238                return false;
2239            }
2240            if (!PaintUtilities.equal(this.labelPaint, that.labelPaint)) {
2241                return false;
2242            }
2243            if (!PaintUtilities.equal(this.labelBackgroundPaint, 
2244                    that.labelBackgroundPaint)) {
2245                return false;
2246            }
2247            if (!PaintUtilities.equal(this.labelOutlinePaint, 
2248                    that.labelOutlinePaint)) {
2249                return false;
2250            }
2251            if (!ObjectUtilities.equal(this.labelOutlineStroke, 
2252                    that.labelOutlineStroke)) {
2253                return false;
2254            }
2255            if (!PaintUtilities.equal(this.labelShadowPaint, 
2256                    that.labelShadowPaint)) {
2257                return false;
2258            }
2259            if (!(this.maximumLabelWidth == that.maximumLabelWidth)) {
2260                return false;
2261            }
2262            if (!(this.labelGap == that.labelGap)) {
2263                return false;
2264            }
2265            if (!(this.labelLinkMargin == that.labelLinkMargin)) {
2266                return false;
2267            }
2268            if (this.labelLinksVisible != that.labelLinksVisible) {
2269                return false;
2270            }
2271            if (!PaintUtilities.equal(this.labelLinkPaint, that.labelLinkPaint)) {
2272                return false;
2273            }
2274            if (!ObjectUtilities.equal(this.labelLinkStroke, 
2275                    that.labelLinkStroke)) {
2276                return false;
2277            }
2278            if (!ObjectUtilities.equal(this.toolTipGenerator, 
2279                    that.toolTipGenerator)) {
2280                return false;
2281            }
2282            if (!ObjectUtilities.equal(this.urlGenerator, that.urlGenerator)) {
2283                return false;
2284            }
2285            if (!(this.minimumArcAngleToDraw == that.minimumArcAngleToDraw)) {
2286                return false;
2287            }
2288            if (!ShapeUtilities.equal(this.legendItemShape, that.legendItemShape)) {
2289                return false;
2290            }
2291            // can't find any difference...
2292            return true;
2293        }
2294    
2295        /**
2296         * Returns a clone of the plot.
2297         *
2298         * @return A clone.
2299         *
2300         * @throws CloneNotSupportedException if some component of the plot does 
2301         *         not support cloning.
2302         */
2303        public Object clone() throws CloneNotSupportedException {
2304    
2305            PiePlot clone = (PiePlot) super.clone();
2306            if (clone.dataset != null) {
2307                clone.dataset.addChangeListener(clone);
2308            }
2309            return clone;
2310    
2311        }
2312    
2313        /**
2314         * Provides serialization support.
2315         *
2316         * @param stream  the output stream.
2317         *
2318         * @throws IOException  if there is an I/O error.
2319         */
2320        private void writeObject(ObjectOutputStream stream) throws IOException {
2321            stream.defaultWriteObject();
2322            SerialUtilities.writePaint(this.sectionPaint, stream);
2323            SerialUtilities.writePaint(this.baseSectionPaint, stream);
2324            SerialUtilities.writePaint(this.sectionOutlinePaint, stream);
2325            SerialUtilities.writePaint(this.baseSectionOutlinePaint, stream);
2326            SerialUtilities.writeStroke(this.sectionOutlineStroke, stream);
2327            SerialUtilities.writeStroke(this.baseSectionOutlineStroke, stream);
2328            SerialUtilities.writePaint(this.shadowPaint, stream);
2329            SerialUtilities.writePaint(this.labelPaint, stream);
2330            SerialUtilities.writePaint(this.labelBackgroundPaint, stream);
2331            SerialUtilities.writePaint(this.labelOutlinePaint, stream);
2332            SerialUtilities.writeStroke(this.labelOutlineStroke, stream);
2333            SerialUtilities.writePaint(this.labelShadowPaint, stream);
2334            SerialUtilities.writePaint(this.labelLinkPaint, stream);
2335            SerialUtilities.writeStroke(this.labelLinkStroke, stream);
2336            SerialUtilities.writeShape(this.legendItemShape, stream);
2337        }
2338    
2339        /**
2340         * Provides serialization support.
2341         *
2342         * @param stream  the input stream.
2343         *
2344         * @throws IOException  if there is an I/O error.
2345         * @throws ClassNotFoundException  if there is a classpath problem.
2346         */
2347        private void readObject(ObjectInputStream stream) 
2348            throws IOException, ClassNotFoundException {
2349            stream.defaultReadObject();
2350            this.sectionPaint = SerialUtilities.readPaint(stream);
2351            this.baseSectionPaint = SerialUtilities.readPaint(stream);
2352            this.sectionOutlinePaint = SerialUtilities.readPaint(stream);
2353            this.baseSectionOutlinePaint = SerialUtilities.readPaint(stream);
2354            this.sectionOutlineStroke = SerialUtilities.readStroke(stream);
2355            this.baseSectionOutlineStroke = SerialUtilities.readStroke(stream);
2356            this.shadowPaint = SerialUtilities.readPaint(stream);
2357            this.labelPaint = SerialUtilities.readPaint(stream);
2358            this.labelBackgroundPaint = SerialUtilities.readPaint(stream);
2359            this.labelOutlinePaint = SerialUtilities.readPaint(stream);
2360            this.labelOutlineStroke = SerialUtilities.readStroke(stream);
2361            this.labelShadowPaint = SerialUtilities.readPaint(stream);
2362            this.labelLinkPaint = SerialUtilities.readPaint(stream);
2363            this.labelLinkStroke = SerialUtilities.readStroke(stream);
2364            this.legendItemShape = SerialUtilities.readShape(stream);
2365        }
2366    
2367    }