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