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