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