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