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