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