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