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