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