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