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