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