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