Source for org.jfree.chart.plot.PiePlot

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