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