All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jfree.chart.plot.PiePlot Maven / Gradle / Ivy

/* ===========================================================
 * JFreeChart : a free chart library for the Java(tm) platform
 * ===========================================================
 *
 * (C) Copyright 2000-2020, by Object Refinery Limited and Contributors.
 *
 * Project Info:  http://www.jfree.org/jfreechart/index.html
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
 * USA.
 *
 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
 * Other names may be trademarks of their respective owners.]
 *
 * ------------
 * PiePlot.java
 * ------------
 * (C) Copyright 2000-2020, by Andrzej Porebski and Contributors.
 *
 * Original Author:  Andrzej Porebski;
 * Contributor(s):   David Gilbert (for Object Refinery Limited);
 *                   Martin Cordova (percentages in labels);
 *                   Richard Atkinson (URL support for image maps);
 *                   Christian W. Zuckschwerdt;
 *                   Arnaud Lelievre;
 *                   Martin Hilpert (patch 1891849);
 *                   Andreas Schroeder (very minor);
 *                   Christoph Beck (bug 2121818);
 *                   Tracy Hiltbrand (Added generics for bug fix);
 * 
 */

package org.jfree.chart.plot;

import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.RadialGradientPaint;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.Arc2D;
import java.awt.geom.CubicCurve2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.QuadCurve2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.ResourceBundle;
import java.util.TreeMap;
import org.jfree.chart.JFreeChart;

import org.jfree.chart.LegendItem;
import org.jfree.chart.LegendItemCollection;
import org.jfree.chart.PaintMap;
import org.jfree.chart.StrokeMap;
import org.jfree.chart.entity.EntityCollection;
import org.jfree.chart.entity.PieSectionEntity;
import org.jfree.chart.event.PlotChangeEvent;
import org.jfree.chart.labels.PieSectionLabelGenerator;
import org.jfree.chart.labels.PieToolTipGenerator;
import org.jfree.chart.labels.StandardPieSectionLabelGenerator;
import org.jfree.chart.text.G2TextMeasurer;
import org.jfree.chart.text.TextBlock;
import org.jfree.chart.text.TextBox;
import org.jfree.chart.text.TextUtils;
import org.jfree.chart.ui.RectangleAnchor;
import org.jfree.chart.ui.RectangleInsets;
import org.jfree.chart.ui.TextAnchor;
import org.jfree.chart.urls.PieURLGenerator;
import org.jfree.chart.util.ObjectUtils;
import org.jfree.chart.util.PaintUtils;
import org.jfree.chart.util.Args;
import org.jfree.chart.util.PublicCloneable;
import org.jfree.chart.util.ResourceBundleWrapper;
import org.jfree.chart.util.Rotation;
import org.jfree.chart.util.SerialUtils;
import org.jfree.chart.util.ShadowGenerator;
import org.jfree.chart.util.ShapeUtils;
import org.jfree.chart.util.UnitType;
import org.jfree.data.DefaultKeyedValues;
import org.jfree.data.KeyedValues;
import org.jfree.data.general.DatasetChangeEvent;
import org.jfree.data.general.DatasetUtils;
import org.jfree.data.general.PieDataset;

/**
 * A plot that displays data in the form of a pie chart, using data from any
 * class that implements the {@link PieDataset} interface.
 * The example shown here is generated by the {@code PieChartDemo2.java}
 * program included in the JFreeChart Demo Collection:
 * 

* PieChartDemo2.svg *

* Special notes: *

    *
  1. the default starting point is 12 o'clock and the pie sections proceed * in a clockwise direction, but these settings can be changed;
  2. *
  3. negative values in the dataset are ignored;
  4. *
  5. there are utility methods for creating a {@link PieDataset} from a * {@link org.jfree.data.category.CategoryDataset};
  6. *
* * @param Key type for PieDataset * * @see Plot * @see PieDataset */ public class PiePlot> extends Plot implements Cloneable, Serializable { /** For serialization. */ private static final long serialVersionUID = -795612466005590431L; /** The default interior gap. */ public static final double DEFAULT_INTERIOR_GAP = 0.08; /** The maximum interior gap (currently 40%). */ public static final double MAX_INTERIOR_GAP = 0.40; /** The default starting angle for the pie chart. */ public static final double DEFAULT_START_ANGLE = 90.0; /** The default section label font. */ public static final Font DEFAULT_LABEL_FONT = new Font("SansSerif", Font.PLAIN, 10); /** The default section label paint. */ public static final Paint DEFAULT_LABEL_PAINT = Color.BLACK; /** The default section label background paint. */ public static final Paint DEFAULT_LABEL_BACKGROUND_PAINT = new Color(255, 255, 192); /** The default section label outline paint. */ public static final Paint DEFAULT_LABEL_OUTLINE_PAINT = Color.BLACK; /** The default section label outline stroke. */ public static final Stroke DEFAULT_LABEL_OUTLINE_STROKE = new BasicStroke( 0.5f); /** The default section label shadow paint. */ public static final Paint DEFAULT_LABEL_SHADOW_PAINT = new Color(151, 151, 151, 128); /** The default minimum arc angle to draw. */ public static final double DEFAULT_MINIMUM_ARC_ANGLE_TO_DRAW = 0.00001; /** The dataset for the pie chart. */ private PieDataset dataset; /** The pie index (used by the {@link MultiplePiePlot} class). */ private int pieIndex; /** * The amount of space left around the outside of the pie plot, expressed * as a percentage of the plot area width and height. */ private double interiorGap; /** Flag determining whether to draw an ellipse or a perfect circle. */ private boolean circular; /** The starting angle. */ private double startAngle; /** The direction for the pie segments. */ private Rotation direction; /** The section paint map. */ private PaintMap sectionPaintMap; /** The default section paint (fallback). */ private transient Paint defaultSectionPaint; /** * A flag that controls whether or not the section paint is auto-populated * from the drawing supplier. * * @since 1.0.11 */ private boolean autoPopulateSectionPaint; /** * A flag that controls whether or not an outline is drawn for each * section in the plot. */ private boolean sectionOutlinesVisible; /** The section outline paint map. */ private PaintMap sectionOutlinePaintMap; /** The default section outline paint (fallback). */ private transient Paint defaultSectionOutlinePaint; /** * A flag that controls whether or not the section outline paint is * auto-populated from the drawing supplier. * * @since 1.0.11 */ private boolean autoPopulateSectionOutlinePaint; /** The section outline stroke map. */ private StrokeMap sectionOutlineStrokeMap; /** The default section outline stroke (fallback). */ private transient Stroke defaultSectionOutlineStroke; /** * A flag that controls whether or not the section outline stroke is * auto-populated from the drawing supplier. * * @since 1.0.11 */ private boolean autoPopulateSectionOutlineStroke; /** The shadow paint. */ private transient Paint shadowPaint = Color.GRAY; /** The x-offset for the shadow effect. */ private double shadowXOffset = 4.0f; /** The y-offset for the shadow effect. */ private double shadowYOffset = 4.0f; /** The percentage amount to explode each pie section. */ private Map explodePercentages; /** The section label generator. */ private PieSectionLabelGenerator labelGenerator; /** The font used to display the section labels. */ private Font labelFont; /** The color used to draw the section labels. */ private transient Paint labelPaint; /** * The color used to draw the background of the section labels. If this * is {@code null}, the background is not filled. */ private transient Paint labelBackgroundPaint; /** * The paint used to draw the outline of the section labels * ({@code null} permitted). */ private transient Paint labelOutlinePaint; /** * The stroke used to draw the outline of the section labels * ({@code null} permitted). */ private transient Stroke labelOutlineStroke; /** * The paint used to draw the shadow for the section labels * ({@code null} permitted). */ private transient Paint labelShadowPaint; /** * A flag that controls whether simple or extended labels are used. * * @since 1.0.7 */ private boolean simpleLabels = true; /** * The padding between the labels and the label outlines. This is not * allowed to be {@code null}. * * @since 1.0.7 */ private RectangleInsets labelPadding; /** * The simple label offset. * * @since 1.0.7 */ private RectangleInsets simpleLabelOffset; /** The maximum label width as a percentage of the plot width. */ private double maximumLabelWidth = 0.14; /** * The gap between the labels and the link corner, as a percentage of the * plot width. */ private double labelGap = 0.025; /** A flag that controls whether or not the label links are drawn. */ private boolean labelLinksVisible; /** * The label link style. * * @since 1.0.10 */ private PieLabelLinkStyle labelLinkStyle = PieLabelLinkStyle.STANDARD; /** The link margin. */ private double labelLinkMargin = 0.025; /** The paint used for the label linking lines. */ private transient Paint labelLinkPaint = Color.BLACK; /** The stroke used for the label linking lines. */ private transient Stroke labelLinkStroke = new BasicStroke(0.5f); /** * The pie section label distributor. * * @since 1.0.6 */ private AbstractPieLabelDistributor labelDistributor; /** The tooltip generator. */ private PieToolTipGenerator toolTipGenerator; /** The URL generator. */ private PieURLGenerator urlGenerator; /** The legend label generator. */ private PieSectionLabelGenerator legendLabelGenerator; /** A tool tip generator for the legend. */ private PieSectionLabelGenerator legendLabelToolTipGenerator; /** * A URL generator for the legend items (optional). * * @since 1.0.4. */ private PieURLGenerator legendLabelURLGenerator; /** * A flag that controls whether {@code null} values are ignored. */ private boolean ignoreNullValues; /** * A flag that controls whether zero values are ignored. */ private boolean ignoreZeroValues; /** The legend item shape. */ private transient Shape legendItemShape; /** * The smallest arc angle that will get drawn (this is to avoid a bug in * various Java implementations that causes the JVM to crash). See this * link for details: * * http://www.jfree.org/phpBB2/viewtopic.php?t=2707 * * ...and this bug report in the Java Bug Parade: * * http://developer.java.sun.com/developer/bugParade/bugs/4836495.html */ private double minimumArcAngleToDraw; /** * The shadow generator for the plot ({@code null} permitted). * * @since 1.0.14 */ private ShadowGenerator shadowGenerator; /** The resourceBundle for the localization. */ protected static ResourceBundle localizationResources = ResourceBundleWrapper.getBundle( "org.jfree.chart.plot.LocalizationBundle"); /** * This debug flag controls whether or not an outline is drawn showing the * interior of the plot region. This is drawn as a lightGray rectangle * showing the padding provided by the 'interiorGap' setting. */ static final boolean DEBUG_DRAW_INTERIOR = false; /** * This debug flag controls whether or not an outline is drawn showing the * link area (in blue) and link ellipse (in yellow). This controls where * the label links have 'elbow' points. */ static final boolean DEBUG_DRAW_LINK_AREA = false; /** * This debug flag controls whether or not an outline is drawn showing * the pie area (in green). */ static final boolean DEBUG_DRAW_PIE_AREA = false; /** * Creates a new plot. The dataset is initially set to {@code null}. */ public PiePlot() { this(null); } /** * Creates a plot that will draw a pie chart for the specified dataset. * * @param dataset the dataset ({@code null} permitted). */ public PiePlot(PieDataset dataset) { super(); this.dataset = dataset; if (dataset != null) { dataset.addChangeListener(this); } this.pieIndex = 0; this.interiorGap = DEFAULT_INTERIOR_GAP; this.circular = true; this.startAngle = DEFAULT_START_ANGLE; this.direction = Rotation.CLOCKWISE; this.minimumArcAngleToDraw = DEFAULT_MINIMUM_ARC_ANGLE_TO_DRAW; this.sectionPaintMap = new PaintMap(); this.defaultSectionPaint = Color.GRAY; this.autoPopulateSectionPaint = true; this.sectionOutlinesVisible = true; this.sectionOutlinePaintMap = new PaintMap(); this.defaultSectionOutlinePaint = DEFAULT_OUTLINE_PAINT; this.autoPopulateSectionOutlinePaint = false; this.sectionOutlineStrokeMap = new StrokeMap(); this.defaultSectionOutlineStroke = DEFAULT_OUTLINE_STROKE; this.autoPopulateSectionOutlineStroke = false; this.explodePercentages = new TreeMap<>(); this.labelGenerator = new StandardPieSectionLabelGenerator(); this.labelFont = DEFAULT_LABEL_FONT; this.labelPaint = DEFAULT_LABEL_PAINT; this.labelBackgroundPaint = DEFAULT_LABEL_BACKGROUND_PAINT; this.labelOutlinePaint = DEFAULT_LABEL_OUTLINE_PAINT; this.labelOutlineStroke = DEFAULT_LABEL_OUTLINE_STROKE; this.labelShadowPaint = DEFAULT_LABEL_SHADOW_PAINT; this.labelLinksVisible = true; this.labelDistributor = new PieLabelDistributor(0); this.simpleLabels = false; this.simpleLabelOffset = new RectangleInsets(UnitType.RELATIVE, 0.18, 0.18, 0.18, 0.18); this.labelPadding = new RectangleInsets(2, 2, 2, 2); this.toolTipGenerator = null; this.urlGenerator = null; this.legendLabelGenerator = new StandardPieSectionLabelGenerator(); this.legendLabelToolTipGenerator = null; this.legendLabelURLGenerator = null; this.legendItemShape = Plot.DEFAULT_LEGEND_ITEM_CIRCLE; this.ignoreNullValues = false; this.ignoreZeroValues = false; this.shadowGenerator = null; } /** * Returns the dataset. * * @return The dataset (possibly {@code null}). * * @see #setDataset(PieDataset) */ public PieDataset getDataset() { return this.dataset; } /** * Sets the dataset and sends a {@link DatasetChangeEvent} to 'this'. * * @param dataset the dataset ({@code null} permitted). * * @see #getDataset() */ public void setDataset(PieDataset dataset) { // if there is an existing dataset, remove the plot from the list of // change listeners... PieDataset existing = this.dataset; if (existing != null) { existing.removeChangeListener(this); } // set the new dataset, and register the chart as a change listener... this.dataset = dataset; if (dataset != null) { setDatasetGroup(dataset.getGroup()); dataset.addChangeListener(this); } // send a dataset change event to self... DatasetChangeEvent event = new DatasetChangeEvent(this, dataset); datasetChanged(event); } /** * Returns the pie index (this is used by the {@link MultiplePiePlot} class * to track subplots). * * @return The pie index. * * @see #setPieIndex(int) */ public int getPieIndex() { return this.pieIndex; } /** * Sets the pie index (this is used by the {@link MultiplePiePlot} class to * track subplots). * * @param index the index. * * @see #getPieIndex() */ public void setPieIndex(int index) { this.pieIndex = index; } /** * Returns the start angle for the first pie section. This is measured in * degrees starting from 3 o'clock and measuring anti-clockwise. * * @return The start angle. * * @see #setStartAngle(double) */ public double getStartAngle() { return this.startAngle; } /** * Sets the starting angle and sends a {@link PlotChangeEvent} to all * registered listeners. The initial default value is 90 degrees, which * corresponds to 12 o'clock. A value of zero corresponds to 3 o'clock... * this is the encoding used by Java's Arc2D class. * * @param angle the angle (in degrees). * * @see #getStartAngle() */ public void setStartAngle(double angle) { this.startAngle = angle; fireChangeEvent(); } /** * Returns the direction in which the pie sections are drawn (clockwise or * anti-clockwise). * * @return The direction (never {@code null}). * * @see #setDirection(Rotation) */ public Rotation getDirection() { return this.direction; } /** * Sets the direction in which the pie sections are drawn and sends a * {@link PlotChangeEvent} to all registered listeners. * * @param direction the direction ({@code null} not permitted). * * @see #getDirection() */ public void setDirection(Rotation direction) { Args.nullNotPermitted(direction, "direction"); this.direction = direction; fireChangeEvent(); } /** * Returns the interior gap, measured as a percentage of the available * drawing space. * * @return The gap (as a percentage of the available drawing space). * * @see #setInteriorGap(double) */ public double getInteriorGap() { return this.interiorGap; } /** * Sets the interior gap and sends a {@link PlotChangeEvent} to all * registered listeners. This controls the space between the edges of the * pie plot and the plot area itself (the region where the section labels * appear). * * @param percent the gap (as a percentage of the available drawing space). * * @see #getInteriorGap() */ public void setInteriorGap(double percent) { if ((percent < 0.0) || (percent > MAX_INTERIOR_GAP)) { throw new IllegalArgumentException( "Invalid 'percent' (" + percent + ") argument."); } if (this.interiorGap != percent) { this.interiorGap = percent; fireChangeEvent(); } } /** * Returns a flag indicating whether the pie chart is circular, or * stretched into an elliptical shape. * * @return A flag indicating whether the pie chart is circular. * * @see #setCircular(boolean) */ public boolean isCircular() { return this.circular; } /** * A flag indicating whether the pie chart is circular, or stretched into * an elliptical shape. * * @param flag the new value. * * @see #isCircular() */ public void setCircular(boolean flag) { setCircular(flag, true); } /** * Sets the circular attribute and, if requested, sends a * {@link PlotChangeEvent} to all registered listeners. * * @param circular the new value of the flag. * @param notify notify listeners? * * @see #isCircular() */ public void setCircular(boolean circular, boolean notify) { this.circular = circular; if (notify) { fireChangeEvent(); } } /** * Returns the flag that controls whether {@code null} values in the * dataset are ignored. * * @return A boolean. * * @see #setIgnoreNullValues(boolean) */ public boolean getIgnoreNullValues() { return this.ignoreNullValues; } /** * Sets a flag that controls whether {@code null} values are ignored, * and sends a {@link PlotChangeEvent} to all registered listeners. At * present, this only affects whether or not the key is presented in the * legend. * * @param flag the flag. * * @see #getIgnoreNullValues() * @see #setIgnoreZeroValues(boolean) */ public void setIgnoreNullValues(boolean flag) { this.ignoreNullValues = flag; fireChangeEvent(); } /** * Returns the flag that controls whether zero values in the * dataset are ignored. * * @return A boolean. * * @see #setIgnoreZeroValues(boolean) */ public boolean getIgnoreZeroValues() { return this.ignoreZeroValues; } /** * Sets a flag that controls whether zero values are ignored, * and sends a {@link PlotChangeEvent} to all registered listeners. This * only affects whether or not a label appears for the non-visible * pie section. * * @param flag the flag. * * @see #getIgnoreZeroValues() * @see #setIgnoreNullValues(boolean) */ public void setIgnoreZeroValues(boolean flag) { this.ignoreZeroValues = flag; fireChangeEvent(); } //// SECTION PAINT //////////////////////////////////////////////////////// /** * Returns the paint for the specified section. This is equivalent to * {@code lookupSectionPaint(section, getAutoPopulateSectionPaint())}. * * @param key the section key. * * @return The paint for the specified section. * * @since 1.0.3 * * @see #lookupSectionPaint(Comparable, boolean) */ protected Paint lookupSectionPaint(Comparable key) { return lookupSectionPaint(key, getAutoPopulateSectionPaint()); } /** * Returns the paint for the specified section. The lookup involves these * steps: *
    *
  • if {@link #getSectionPaint(Comparable)} is non-{@code null} return * it;
  • *
  • if {@link #getSectionPaint(Comparable)} is {@code null} but * {@code autoPopulate} is {@code true}, attempt to fetch * a new paint from the drawing supplier * ({@link #getDrawingSupplier()}); *
  • if all else fails, return {@link #getDefaultSectionPaint()}. *
* * @param key the section key. * @param autoPopulate a flag that controls whether the drawing supplier * is used to auto-populate the section paint settings. * * @return The paint. * * @since 1.0.3 */ protected Paint lookupSectionPaint(Comparable key, boolean autoPopulate) { // if not, check if there is a paint defined for the specified key Paint result = this.sectionPaintMap.getPaint(key); if (result != null) { return result; } // nothing defined - do we autoPopulate? if (autoPopulate) { DrawingSupplier ds = getDrawingSupplier(); if (ds != null) { result = ds.getNextPaint(); this.sectionPaintMap.put(key, result); } else { result = this.defaultSectionPaint; } } else { result = this.defaultSectionPaint; } return result; } /** * Returns a key for the specified section. The preferred way of doing this * now is to link the attributes directly to the section key (there are new * methods for this, starting from version 1.0.3). * * @param section the section index. * * @return The key. * * @since 1.0.3 */ protected K getSectionKey(int section) { K key = null; if (this.dataset != null) { if (section >= 0 && section < this.dataset.getItemCount()) { key = this.dataset.getKey(section); } } return key; } /** * Returns the paint associated with the specified key, or * {@code null} if there is no paint associated with the key. * * @param key the key ({@code null} not permitted). * * @return The paint associated with the specified key, or * {@code null}. * * @throws IllegalArgumentException if {@code key} is * {@code null}. * * @see #setSectionPaint(Comparable, Paint) * * @since 1.0.3 */ public Paint getSectionPaint(Comparable key) { // null argument check delegated... return this.sectionPaintMap.getPaint(key); } /** * Sets the paint associated with the specified key, and sends a * {@link PlotChangeEvent} to all registered listeners. * * @param key the key ({@code null} not permitted). * @param paint the paint. * * @throws IllegalArgumentException if {@code key} is * {@code null}. * * @see #getSectionPaint(Comparable) * * @since 1.0.3 */ public void setSectionPaint(Comparable key, Paint paint) { // null argument check delegated... this.sectionPaintMap.put(key, paint); fireChangeEvent(); } /** * Clears the section paint settings for this plot and, if requested, sends * a {@link PlotChangeEvent} to all registered listeners. Be aware that * if the {@code autoPopulateSectionPaint} flag is set, the section * paints may be repopulated using the same colours as before. * * @param notify notify listeners? * * @since 1.0.11 * * @see #autoPopulateSectionPaint */ public void clearSectionPaints(boolean notify) { this.sectionPaintMap.clear(); if (notify) { fireChangeEvent(); } } /** * Returns the default section paint. This is used when no other paint is * defined, which is rare. The default value is {@code Color.GRAY}. * * @return The paint (never {@code null}). * * @see #setDefaultSectionPaint(Paint) */ public Paint getDefaultSectionPaint() { return this.defaultSectionPaint; } /** * Sets the default section paint and sends a {@link PlotChangeEvent} to all * registered listeners. * * @param paint the paint ({@code null} not permitted). * * @see #getDefaultSectionPaint() */ public void setDefaultSectionPaint(Paint paint) { Args.nullNotPermitted(paint, "paint"); this.defaultSectionPaint = paint; fireChangeEvent(); } /** * Returns the flag that controls whether or not the section paint is * auto-populated by the {@link #lookupSectionPaint(Comparable)} method. * * @return A boolean. * * @since 1.0.11 */ public boolean getAutoPopulateSectionPaint() { return this.autoPopulateSectionPaint; } /** * Sets the flag that controls whether or not the section paint is * auto-populated by the {@link #lookupSectionPaint(Comparable)} method, * and sends a {@link PlotChangeEvent} to all registered listeners. * * @param auto auto-populate? * * @since 1.0.11 */ public void setAutoPopulateSectionPaint(boolean auto) { this.autoPopulateSectionPaint = auto; fireChangeEvent(); } //// SECTION OUTLINE PAINT //////////////////////////////////////////////// /** * Returns the flag that controls whether or not the outline is drawn for * each pie section. * * @return The flag that controls whether or not the outline is drawn for * each pie section. * * @see #setSectionOutlinesVisible(boolean) */ public boolean getSectionOutlinesVisible() { return this.sectionOutlinesVisible; } /** * Sets the flag that controls whether or not the outline is drawn for * each pie section, and sends a {@link PlotChangeEvent} to all registered * listeners. * * @param visible the flag. * * @see #getSectionOutlinesVisible() */ public void setSectionOutlinesVisible(boolean visible) { this.sectionOutlinesVisible = visible; fireChangeEvent(); } /** * Returns the outline paint for the specified section. This is equivalent * to {@code lookupSectionPaint(section, * getAutoPopulateSectionOutlinePaint())}. * * @param key the section key. * * @return The paint for the specified section. * * @since 1.0.3 * * @see #lookupSectionOutlinePaint(Comparable, boolean) */ protected Paint lookupSectionOutlinePaint(Comparable key) { return lookupSectionOutlinePaint(key, getAutoPopulateSectionOutlinePaint()); } /** * Returns the outline paint for the specified section. The lookup * involves these steps: *
    *
  • if {@link #getSectionOutlinePaint(Comparable)} is * non-{@code null} return it;
  • *
  • if {@link #getSectionOutlinePaint(Comparable)} is {@code null} but * {@code autoPopulate} is {@code true}, attempt to fetch * a new outline paint from the drawing supplier * ({@link #getDrawingSupplier()}); *
  • if all else fails, return {@link #getDefaultSectionOutlinePaint()}. *
* * @param key the section key. * @param autoPopulate a flag that controls whether the drawing supplier * is used to auto-populate the section outline paint settings. * * @return The paint. * * @since 1.0.3 */ protected Paint lookupSectionOutlinePaint(Comparable key, boolean autoPopulate) { // if not, check if there is a paint defined for the specified key Paint result = this.sectionOutlinePaintMap.getPaint(key); if (result != null) { return result; } // nothing defined - do we autoPopulate? if (autoPopulate) { DrawingSupplier ds = getDrawingSupplier(); if (ds != null) { result = ds.getNextOutlinePaint(); this.sectionOutlinePaintMap.put(key, result); } else { result = this.defaultSectionOutlinePaint; } } else { result = this.defaultSectionOutlinePaint; } return result; } /** * Returns the outline paint associated with the specified key, or * {@code null} if there is no paint associated with the key. * * @param key the key ({@code null} not permitted). * * @return The paint associated with the specified key, or * {@code null}. * * @throws IllegalArgumentException if {@code key} is * {@code null}. * * @see #setSectionOutlinePaint(Comparable, Paint) * * @since 1.0.3 */ public Paint getSectionOutlinePaint(Comparable key) { // null argument check delegated... return this.sectionOutlinePaintMap.getPaint(key); } /** * Sets the outline paint associated with the specified key, and sends a * {@link PlotChangeEvent} to all registered listeners. * * @param key the key ({@code null} not permitted). * @param paint the paint. * * @throws IllegalArgumentException if {@code key} is * {@code null}. * * @see #getSectionOutlinePaint(Comparable) * * @since 1.0.3 */ public void setSectionOutlinePaint(Comparable key, Paint paint) { // null argument check delegated... this.sectionOutlinePaintMap.put(key, paint); fireChangeEvent(); } /** * Clears the section outline paint settings for this plot and, if * requested, sends a {@link PlotChangeEvent} to all registered listeners. * Be aware that if the {@code autoPopulateSectionPaint} flag is set, * the section paints may be repopulated using the same colours as before. * * @param notify notify listeners? * * @since 1.0.11 * * @see #autoPopulateSectionOutlinePaint */ public void clearSectionOutlinePaints(boolean notify) { this.sectionOutlinePaintMap.clear(); if (notify) { fireChangeEvent(); } } /** * Returns the default section paint. This is used when no other paint is * available. * * @return The paint (never {@code null}). * * @see #setDefaultSectionOutlinePaint(Paint) */ public Paint getDefaultSectionOutlinePaint() { return this.defaultSectionOutlinePaint; } /** * Sets the default section paint. * * @param paint the paint ({@code null} not permitted). * * @see #getDefaultSectionOutlinePaint() */ public void setDefaultSectionOutlinePaint(Paint paint) { Args.nullNotPermitted(paint, "paint"); this.defaultSectionOutlinePaint = paint; fireChangeEvent(); } /** * Returns the flag that controls whether or not the section outline paint * is auto-populated by the {@link #lookupSectionOutlinePaint(Comparable)} * method. * * @return A boolean. * * @since 1.0.11 */ public boolean getAutoPopulateSectionOutlinePaint() { return this.autoPopulateSectionOutlinePaint; } /** * Sets the flag that controls whether or not the section outline paint is * auto-populated by the {@link #lookupSectionOutlinePaint(Comparable)} * method, and sends a {@link PlotChangeEvent} to all registered listeners. * * @param auto auto-populate? * * @since 1.0.11 */ public void setAutoPopulateSectionOutlinePaint(boolean auto) { this.autoPopulateSectionOutlinePaint = auto; fireChangeEvent(); } //// SECTION OUTLINE STROKE /////////////////////////////////////////////// /** * Returns the outline stroke for the specified section. This is * equivalent to {@code lookupSectionOutlineStroke(section, * getAutoPopulateSectionOutlineStroke())}. * * @param key the section key. * * @return The stroke for the specified section. * * @since 1.0.3 * * @see #lookupSectionOutlineStroke(Comparable, boolean) */ protected Stroke lookupSectionOutlineStroke(Comparable key) { return lookupSectionOutlineStroke(key, getAutoPopulateSectionOutlineStroke()); } /** * Returns the outline stroke for the specified section. The lookup * involves these steps: *
    *
  • if {@link #getSectionOutlineStroke(Comparable)} is * non-{@code null} return it;
  • *
  • if {@link #getSectionOutlineStroke(Comparable)} is {@code null} but * {@code autoPopulate} is {@code true}, attempt to fetch * a new outline stroke from the drawing supplier * ({@link #getDrawingSupplier()}); *
  • if all else fails, return {@link #getDefaultSectionOutlineStroke()}. *
* * @param key the section key. * @param autoPopulate a flag that controls whether the drawing supplier * is used to auto-populate the section outline stroke settings. * * @return The stroke. * * @since 1.0.3 */ protected Stroke lookupSectionOutlineStroke(Comparable key, boolean autoPopulate) { // if not, check if there is a stroke defined for the specified key Stroke result = this.sectionOutlineStrokeMap.getStroke(key); if (result != null) { return result; } // nothing defined - do we autoPopulate? if (autoPopulate) { DrawingSupplier ds = getDrawingSupplier(); if (ds != null) { result = ds.getNextOutlineStroke(); this.sectionOutlineStrokeMap.put(key, result); } else { result = this.defaultSectionOutlineStroke; } } else { result = this.defaultSectionOutlineStroke; } return result; } /** * Returns the outline stroke associated with the specified key, or * {@code null} if there is no stroke associated with the key. * * @param key the key ({@code null} not permitted). * * @return The stroke associated with the specified key, or * {@code null}. * * @throws IllegalArgumentException if {@code key} is * {@code null}. * * @see #setSectionOutlineStroke(Comparable, Stroke) * * @since 1.0.3 */ public Stroke getSectionOutlineStroke(Comparable key) { // null argument check delegated... return this.sectionOutlineStrokeMap.getStroke(key); } /** * Sets the outline stroke associated with the specified key, and sends a * {@link PlotChangeEvent} to all registered listeners. * * @param key the key ({@code null} not permitted). * @param stroke the stroke. * * @throws IllegalArgumentException if {@code key} is * {@code null}. * * @see #getSectionOutlineStroke(Comparable) * * @since 1.0.3 */ public void setSectionOutlineStroke(Comparable key, Stroke stroke) { // null argument check delegated... this.sectionOutlineStrokeMap.put(key, stroke); fireChangeEvent(); } /** * Clears the section outline stroke settings for this plot and, if * requested, sends a {@link PlotChangeEvent} to all registered listeners. * Be aware that if the {@code autoPopulateSectionPaint} flag is set, * the section paints may be repopulated using the same colours as before. * * @param notify notify listeners? * * @since 1.0.11 * * @see #autoPopulateSectionOutlineStroke */ public void clearSectionOutlineStrokes(boolean notify) { this.sectionOutlineStrokeMap.clear(); if (notify) { fireChangeEvent(); } } /** * Returns the default section stroke. This is used when no other stroke is * available. * * @return The stroke (never {@code null}). * * @see #setDefaultSectionOutlineStroke(Stroke) */ public Stroke getDefaultSectionOutlineStroke() { return this.defaultSectionOutlineStroke; } /** * Sets the default section stroke. * * @param stroke the stroke ({@code null} not permitted). * * @see #getDefaultSectionOutlineStroke() */ public void setDefaultSectionOutlineStroke(Stroke stroke) { Args.nullNotPermitted(stroke, "stroke"); this.defaultSectionOutlineStroke = stroke; fireChangeEvent(); } /** * Returns the flag that controls whether or not the section outline stroke * is auto-populated by the {@link #lookupSectionOutlinePaint(Comparable)} * method. * * @return A boolean. * * @since 1.0.11 */ public boolean getAutoPopulateSectionOutlineStroke() { return this.autoPopulateSectionOutlineStroke; } /** * Sets the flag that controls whether or not the section outline stroke is * auto-populated by the {@link #lookupSectionOutlineStroke(Comparable)} * method, and sends a {@link PlotChangeEvent} to all registered listeners. * * @param auto auto-populate? * * @since 1.0.11 */ public void setAutoPopulateSectionOutlineStroke(boolean auto) { this.autoPopulateSectionOutlineStroke = auto; fireChangeEvent(); } /** * Returns the shadow paint. * * @return The paint (possibly {@code null}). * * @see #setShadowPaint(Paint) */ public Paint getShadowPaint() { return this.shadowPaint; } /** * Sets the shadow paint and sends a {@link PlotChangeEvent} to all * registered listeners. * * @param paint the paint ({@code null} permitted). * * @see #getShadowPaint() */ public void setShadowPaint(Paint paint) { this.shadowPaint = paint; fireChangeEvent(); } /** * Returns the x-offset for the shadow effect. * * @return The offset (in Java2D units). * * @see #setShadowXOffset(double) */ public double getShadowXOffset() { return this.shadowXOffset; } /** * Sets the x-offset for the shadow effect and sends a * {@link PlotChangeEvent} to all registered listeners. * * @param offset the offset (in Java2D units). * * @see #getShadowXOffset() */ public void setShadowXOffset(double offset) { this.shadowXOffset = offset; fireChangeEvent(); } /** * Returns the y-offset for the shadow effect. * * @return The offset (in Java2D units). * * @see #setShadowYOffset(double) */ public double getShadowYOffset() { return this.shadowYOffset; } /** * Sets the y-offset for the shadow effect and sends a * {@link PlotChangeEvent} to all registered listeners. * * @param offset the offset (in Java2D units). * * @see #getShadowYOffset() */ public void setShadowYOffset(double offset) { this.shadowYOffset = offset; fireChangeEvent(); } /** * Returns the amount that the section with the specified key should be * exploded. * * @param key the key ({@code null} not permitted). * * @return The amount that the section with the specified key should be * exploded. * * @throws IllegalArgumentException if {@code key} is * {@code null}. * * @since 1.0.3 * * @see #setExplodePercent(Comparable, double) */ public double getExplodePercent(K key) { double result = 0.0; if (this.explodePercentages != null) { Number percent = (Number) this.explodePercentages.get(key); if (percent != null) { result = percent.doubleValue(); } } return result; } /** * Sets the amount that a pie section should be exploded and sends a * {@link PlotChangeEvent} to all registered listeners. * * @param key the section key ({@code null} not permitted). * @param percent the explode percentage (0.30 = 30 percent). * * @since 1.0.3 * * @see #getExplodePercent(Comparable) */ public void setExplodePercent(K key, double percent) { Args.nullNotPermitted(key, "key"); if (this.explodePercentages == null) { this.explodePercentages = new TreeMap<>(); } this.explodePercentages.put(key, percent); fireChangeEvent(); } /** * Returns the maximum explode percent. * * @return The percent. */ public double getMaximumExplodePercent() { if (this.dataset == null) { return 0.0; } double result = 0.0; for (K key : this.dataset.getKeys()) { Double explode = this.explodePercentages.get(key); if (explode != null) { result = Math.max(result, explode); } } return result; } /** * Returns the section label generator. * * @return The generator (possibly {@code null}). * * @see #setLabelGenerator(PieSectionLabelGenerator) */ public PieSectionLabelGenerator getLabelGenerator() { return this.labelGenerator; } /** * Sets the section label generator and sends a {@link PlotChangeEvent} to * all registered listeners. * * @param generator the generator ({@code null} permitted). * * @see #getLabelGenerator() */ public void setLabelGenerator(PieSectionLabelGenerator generator) { this.labelGenerator = generator; fireChangeEvent(); } /** * Returns the gap between the edge of the pie and the labels, expressed as * a percentage of the plot width. * * @return The gap (a percentage, where 0.05 = five percent). * * @see #setLabelGap(double) */ public double getLabelGap() { return this.labelGap; } /** * Sets the gap between the edge of the pie and the labels (expressed as a * percentage of the plot width) and sends a {@link PlotChangeEvent} to all * registered listeners. * * @param gap the gap (a percentage, where 0.05 = five percent). * * @see #getLabelGap() */ public void setLabelGap(double gap) { this.labelGap = gap; fireChangeEvent(); } /** * Returns the maximum label width as a percentage of the plot width. * * @return The width (a percentage, where 0.20 = 20 percent). * * @see #setMaximumLabelWidth(double) */ public double getMaximumLabelWidth() { return this.maximumLabelWidth; } /** * Sets the maximum label width as a percentage of the plot width and sends * a {@link PlotChangeEvent} to all registered listeners. * * @param width the width (a percentage, where 0.20 = 20 percent). * * @see #getMaximumLabelWidth() */ public void setMaximumLabelWidth(double width) { this.maximumLabelWidth = width; fireChangeEvent(); } /** * Returns the flag that controls whether or not label linking lines are * visible. * * @return A boolean. * * @see #setLabelLinksVisible(boolean) */ public boolean getLabelLinksVisible() { return this.labelLinksVisible; } /** * Sets the flag that controls whether or not label linking lines are * visible and sends a {@link PlotChangeEvent} to all registered listeners. * Please take care when hiding the linking lines - depending on the data * values, the labels can be displayed some distance away from the * corresponding pie section. * * @param visible the flag. * * @see #getLabelLinksVisible() */ public void setLabelLinksVisible(boolean visible) { this.labelLinksVisible = visible; fireChangeEvent(); } /** * Returns the label link style. * * @return The label link style (never {@code null}). * * @see #setLabelLinkStyle(PieLabelLinkStyle) * * @since 1.0.10 */ public PieLabelLinkStyle getLabelLinkStyle() { return this.labelLinkStyle; } /** * Sets the label link style and sends a {@link PlotChangeEvent} to all * registered listeners. * * @param style the new style ({@code null} not permitted). * * @see #getLabelLinkStyle() * * @since 1.0.10 */ public void setLabelLinkStyle(PieLabelLinkStyle style) { Args.nullNotPermitted(style, "style"); this.labelLinkStyle = style; fireChangeEvent(); } /** * Returns the margin (expressed as a percentage of the width or height) * between the edge of the pie and the link point. * * @return The link margin (as a percentage, where 0.05 is five percent). * * @see #setLabelLinkMargin(double) */ public double getLabelLinkMargin() { return this.labelLinkMargin; } /** * Sets the link margin and sends a {@link PlotChangeEvent} to all * registered listeners. * * @param margin the margin. * * @see #getLabelLinkMargin() */ public void setLabelLinkMargin(double margin) { this.labelLinkMargin = margin; fireChangeEvent(); } /** * Returns the paint used for the lines that connect pie sections to their * corresponding labels. * * @return The paint (never {@code null}). * * @see #setLabelLinkPaint(Paint) */ public Paint getLabelLinkPaint() { return this.labelLinkPaint; } /** * Sets the paint used for the lines that connect pie sections to their * corresponding labels, and sends a {@link PlotChangeEvent} to all * registered listeners. * * @param paint the paint ({@code null} not permitted). * * @see #getLabelLinkPaint() */ public void setLabelLinkPaint(Paint paint) { Args.nullNotPermitted(paint, "paint"); this.labelLinkPaint = paint; fireChangeEvent(); } /** * Returns the stroke used for the label linking lines. * * @return The stroke. * * @see #setLabelLinkStroke(Stroke) */ public Stroke getLabelLinkStroke() { return this.labelLinkStroke; } /** * Sets the link stroke and sends a {@link PlotChangeEvent} to all * registered listeners. * * @param stroke the stroke. * * @see #getLabelLinkStroke() */ public void setLabelLinkStroke(Stroke stroke) { Args.nullNotPermitted(stroke, "stroke"); this.labelLinkStroke = stroke; fireChangeEvent(); } /** * Returns the distance that the end of the label link is embedded into * the plot, expressed as a percentage of the plot's radius. *

* This method is overridden in the {@link RingPlot} class to resolve * bug 2121818. * * @return {@code 0.10}. * * @since 1.0.12 */ protected double getLabelLinkDepth() { return 0.1; } /** * Returns the section label font. * * @return The font (never {@code null}). * * @see #setLabelFont(Font) */ public Font getLabelFont() { return this.labelFont; } /** * Sets the section label font and sends a {@link PlotChangeEvent} to all * registered listeners. * * @param font the font ({@code null} not permitted). * * @see #getLabelFont() */ public void setLabelFont(Font font) { Args.nullNotPermitted(font, "font"); this.labelFont = font; fireChangeEvent(); } /** * Returns the section label paint. * * @return The paint (never {@code null}). * * @see #setLabelPaint(Paint) */ public Paint getLabelPaint() { return this.labelPaint; } /** * Sets the section label paint and sends a {@link PlotChangeEvent} to all * registered listeners. * * @param paint the paint ({@code null} not permitted). * * @see #getLabelPaint() */ public void setLabelPaint(Paint paint) { Args.nullNotPermitted(paint, "paint"); this.labelPaint = paint; fireChangeEvent(); } /** * Returns the section label background paint. * * @return The paint (possibly {@code null}). * * @see #setLabelBackgroundPaint(Paint) */ public Paint getLabelBackgroundPaint() { return this.labelBackgroundPaint; } /** * Sets the section label background paint and sends a * {@link PlotChangeEvent} to all registered listeners. * * @param paint the paint ({@code null} permitted). * * @see #getLabelBackgroundPaint() */ public void setLabelBackgroundPaint(Paint paint) { this.labelBackgroundPaint = paint; fireChangeEvent(); } /** * Returns the section label outline paint. * * @return The paint (possibly {@code null}). * * @see #setLabelOutlinePaint(Paint) */ public Paint getLabelOutlinePaint() { return this.labelOutlinePaint; } /** * Sets the section label outline paint and sends a * {@link PlotChangeEvent} to all registered listeners. * * @param paint the paint ({@code null} permitted). * * @see #getLabelOutlinePaint() */ public void setLabelOutlinePaint(Paint paint) { this.labelOutlinePaint = paint; fireChangeEvent(); } /** * Returns the section label outline stroke. * * @return The stroke (possibly {@code null}). * * @see #setLabelOutlineStroke(Stroke) */ public Stroke getLabelOutlineStroke() { return this.labelOutlineStroke; } /** * Sets the section label outline stroke and sends a * {@link PlotChangeEvent} to all registered listeners. * * @param stroke the stroke ({@code null} permitted). * * @see #getLabelOutlineStroke() */ public void setLabelOutlineStroke(Stroke stroke) { this.labelOutlineStroke = stroke; fireChangeEvent(); } /** * Returns the section label shadow paint. * * @return The paint (possibly {@code null}). * * @see #setLabelShadowPaint(Paint) */ public Paint getLabelShadowPaint() { return this.labelShadowPaint; } /** * Sets the section label shadow paint and sends a {@link PlotChangeEvent} * to all registered listeners. * * @param paint the paint ({@code null} permitted). * * @see #getLabelShadowPaint() */ public void setLabelShadowPaint(Paint paint) { this.labelShadowPaint = paint; fireChangeEvent(); } /** * Returns the label padding. * * @return The label padding (never {@code null}). * * @since 1.0.7 * * @see #setLabelPadding(RectangleInsets) */ public RectangleInsets getLabelPadding() { return this.labelPadding; } /** * Sets the padding between each label and its outline and sends a * {@link PlotChangeEvent} to all registered listeners. * * @param padding the padding ({@code null} not permitted). * * @since 1.0.7 * * @see #getLabelPadding() */ public void setLabelPadding(RectangleInsets padding) { Args.nullNotPermitted(padding, "padding"); this.labelPadding = padding; fireChangeEvent(); } /** * Returns the flag that controls whether simple or extended labels are * displayed on the plot. * * @return A boolean. * * @since 1.0.7 */ public boolean getSimpleLabels() { return this.simpleLabels; } /** * Sets the flag that controls whether simple or extended labels are * displayed on the plot, and sends a {@link PlotChangeEvent} to all * registered listeners. * * @param simple the new flag value. * * @since 1.0.7 */ public void setSimpleLabels(boolean simple) { this.simpleLabels = simple; fireChangeEvent(); } /** * Returns the offset used for the simple labels, if they are displayed. * * @return The offset (never {@code null}). * * @since 1.0.7 * * @see #setSimpleLabelOffset(RectangleInsets) */ public RectangleInsets getSimpleLabelOffset() { return this.simpleLabelOffset; } /** * Sets the offset for the simple labels and sends a * {@link PlotChangeEvent} to all registered listeners. * * @param offset the offset ({@code null} not permitted). * * @since 1.0.7 * * @see #getSimpleLabelOffset() */ public void setSimpleLabelOffset(RectangleInsets offset) { Args.nullNotPermitted(offset, "offset"); this.simpleLabelOffset = offset; fireChangeEvent(); } /** * Returns the object responsible for the vertical layout of the pie * section labels. * * @return The label distributor (never {@code null}). * * @since 1.0.6 */ public AbstractPieLabelDistributor getLabelDistributor() { return this.labelDistributor; } /** * Sets the label distributor and sends a {@link PlotChangeEvent} to all * registered listeners. * * @param distributor the distributor ({@code null} not permitted). * * @since 1.0.6 */ public void setLabelDistributor(AbstractPieLabelDistributor distributor) { Args.nullNotPermitted(distributor, "distributor"); this.labelDistributor = distributor; fireChangeEvent(); } /** * Returns the tool tip generator, an object that is responsible for * generating the text items used for tool tips by the plot. If the * generator is {@code null}, no tool tips will be created. * * @return The generator (possibly {@code null}). * * @see #setToolTipGenerator(PieToolTipGenerator) */ public PieToolTipGenerator getToolTipGenerator() { return this.toolTipGenerator; } /** * Sets the tool tip generator and sends a {@link PlotChangeEvent} to all * registered listeners. Set the generator to {@code null} if you * don't want any tool tips. * * @param generator the generator ({@code null} permitted). * * @see #getToolTipGenerator() */ public void setToolTipGenerator(PieToolTipGenerator generator) { this.toolTipGenerator = generator; fireChangeEvent(); } /** * Returns the URL generator. * * @return The generator (possibly {@code null}). * * @see #setURLGenerator(PieURLGenerator) */ public PieURLGenerator getURLGenerator() { return this.urlGenerator; } /** * Sets the URL generator and sends a {@link PlotChangeEvent} to all * registered listeners. * * @param generator the generator ({@code null} permitted). * * @see #getURLGenerator() */ public void setURLGenerator(PieURLGenerator generator) { this.urlGenerator = generator; fireChangeEvent(); } /** * Returns the minimum arc angle that will be drawn. Pie sections for an * angle smaller than this are not drawn, to avoid a JDK bug. * * @return The minimum angle. * * @see #setMinimumArcAngleToDraw(double) */ public double getMinimumArcAngleToDraw() { return this.minimumArcAngleToDraw; } /** * Sets the minimum arc angle that will be drawn. Pie sections for an * angle smaller than this are not drawn, to avoid a JDK bug. See this * link for details: *

* * http://www.jfree.org/phpBB2/viewtopic.php?t=2707 *

* ...and this bug report in the Java Bug Parade: *

* * http://developer.java.sun.com/developer/bugParade/bugs/4836495.html * * @param angle the minimum angle. * * @see #getMinimumArcAngleToDraw() */ public void setMinimumArcAngleToDraw(double angle) { this.minimumArcAngleToDraw = angle; } /** * Returns the shape used for legend items. * * @return The shape (never {@code null}). * * @see #setLegendItemShape(Shape) */ public Shape getLegendItemShape() { return this.legendItemShape; } /** * Sets the shape used for legend items and sends a {@link PlotChangeEvent} * to all registered listeners. * * @param shape the shape ({@code null} not permitted). * * @see #getLegendItemShape() */ public void setLegendItemShape(Shape shape) { Args.nullNotPermitted(shape, "shape"); this.legendItemShape = shape; fireChangeEvent(); } /** * Returns the legend label generator. * * @return The legend label generator (never {@code null}). * * @see #setLegendLabelGenerator(PieSectionLabelGenerator) */ public PieSectionLabelGenerator getLegendLabelGenerator() { return this.legendLabelGenerator; } /** * Sets the legend label generator and sends a {@link PlotChangeEvent} to * all registered listeners. * * @param generator the generator ({@code null} not permitted). * * @see #getLegendLabelGenerator() */ public void setLegendLabelGenerator(PieSectionLabelGenerator generator) { Args.nullNotPermitted(generator, "generator"); this.legendLabelGenerator = generator; fireChangeEvent(); } /** * Returns the legend label tool tip generator. * * @return The legend label tool tip generator (possibly {@code null}). * * @see #setLegendLabelToolTipGenerator(PieSectionLabelGenerator) */ public PieSectionLabelGenerator getLegendLabelToolTipGenerator() { return this.legendLabelToolTipGenerator; } /** * Sets the legend label tool tip generator and sends a * {@link PlotChangeEvent} to all registered listeners. * * @param generator the generator ({@code null} permitted). * * @see #getLegendLabelToolTipGenerator() */ public void setLegendLabelToolTipGenerator( PieSectionLabelGenerator generator) { this.legendLabelToolTipGenerator = generator; fireChangeEvent(); } /** * Returns the legend label URL generator. * * @return The legend label URL generator (possibly {@code null}). * * @see #setLegendLabelURLGenerator(PieURLGenerator) * * @since 1.0.4 */ public PieURLGenerator getLegendLabelURLGenerator() { return this.legendLabelURLGenerator; } /** * Sets the legend label URL generator and sends a * {@link PlotChangeEvent} to all registered listeners. * * @param generator the generator ({@code null} permitted). * * @see #getLegendLabelURLGenerator() * * @since 1.0.4 */ public void setLegendLabelURLGenerator(PieURLGenerator generator) { this.legendLabelURLGenerator = generator; fireChangeEvent(); } /** * Returns the shadow generator for the plot, if any. * * @return The shadow generator (possibly {@code null}). * * @since 1.0.14 */ public ShadowGenerator getShadowGenerator() { return this.shadowGenerator; } /** * Sets the shadow generator for the plot and sends a * {@link PlotChangeEvent} to all registered listeners. Note that this is * a bitmap drop-shadow generation facility and is separate from the * vector based show option that is controlled via the * {@link #setShadowPaint(java.awt.Paint)} method. * * @param generator the generator ({@code null} permitted). * * @since 1.0.14 */ public void setShadowGenerator(ShadowGenerator generator) { this.shadowGenerator = generator; fireChangeEvent(); } /** * Handles a mouse wheel rotation (this method is intended for use by the * {@code MouseWheelHandler} class). * * @param rotateClicks the number of rotate clicks on the the mouse wheel. * * @since 1.0.14 */ public void handleMouseWheelRotation(int rotateClicks) { setStartAngle(this.startAngle + rotateClicks * 4.0); } /** * Initialises the drawing procedure. This method will be called before * the first item is rendered, giving the plot an opportunity to initialise * any state information it wants to maintain. * * @param g2 the graphics device. * @param plotArea the plot area ({@code null} not permitted). * @param plot the plot. * @param index the secondary index ({@code null} for primary * renderer). * @param info collects chart rendering information for return to caller. * * @return A state object (maintains state information relevant to one * chart drawing). */ public PiePlotState initialise(Graphics2D g2, Rectangle2D plotArea, PiePlot plot, Integer index, PlotRenderingInfo info) { PiePlotState state = new PiePlotState(info); state.setPassesRequired(2); if (this.dataset != null) { state.setTotal(DatasetUtils.calculatePieDatasetTotal( plot.getDataset())); } state.setLatestAngle(plot.getStartAngle()); return state; } /** * Draws the plot on a Java 2D graphics device (such as the screen or a * printer). * * @param g2 the graphics device. * @param area the area within which the plot should be drawn. * @param anchor the anchor point ({@code null} permitted). * @param parentState the state from the parent plot, if there is one. * @param info collects info about the drawing * ({@code null} permitted). */ @Override public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor, PlotState parentState, PlotRenderingInfo info) { // adjust for insets... RectangleInsets insets = getInsets(); insets.trim(area); if (info != null) { info.setPlotArea(area); info.setDataArea(area); } drawBackground(g2, area); drawOutline(g2, area); Shape savedClip = g2.getClip(); g2.clip(area); Composite originalComposite = g2.getComposite(); g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, getForegroundAlpha())); if (!DatasetUtils.isEmptyOrNull(this.dataset)) { Graphics2D savedG2 = g2; boolean suppressShadow = Boolean.TRUE.equals(g2.getRenderingHint( JFreeChart.KEY_SUPPRESS_SHADOW_GENERATION)); BufferedImage dataImage = null; if (this.shadowGenerator != null && !suppressShadow) { dataImage = new BufferedImage((int) area.getWidth(), (int) area.getHeight(), BufferedImage.TYPE_INT_ARGB); g2 = dataImage.createGraphics(); g2.translate(-area.getX(), -area.getY()); g2.setRenderingHints(savedG2.getRenderingHints()); } drawPie(g2, area, info); if (this.shadowGenerator != null && !suppressShadow) { BufferedImage shadowImage = this.shadowGenerator.createDropShadow(dataImage); g2 = savedG2; g2.drawImage(shadowImage, (int) area.getX() + this.shadowGenerator.calculateOffsetX(), (int) area.getY() + this.shadowGenerator.calculateOffsetY(), null); g2.drawImage(dataImage, (int) area.getX(), (int) area.getY(), null); } } else { drawNoDataMessage(g2, area); } g2.setClip(savedClip); g2.setComposite(originalComposite); drawOutline(g2, area); } /** * Draws the pie. * * @param g2 the graphics device. * @param plotArea the plot area. * @param info chart rendering info. */ protected void drawPie(Graphics2D g2, Rectangle2D plotArea, PlotRenderingInfo info) { PiePlotState state = initialise(g2, plotArea, this, null, info); // adjust the plot area for interior spacing and labels... double labelReserve = 0.0; if (this.labelGenerator != null && !this.simpleLabels) { labelReserve = this.labelGap + this.maximumLabelWidth; } double gapHorizontal = plotArea.getWidth() * labelReserve * 2.0; double gapVertical = plotArea.getHeight() * this.interiorGap * 2.0; if (DEBUG_DRAW_INTERIOR) { double hGap = plotArea.getWidth() * this.interiorGap; double vGap = plotArea.getHeight() * this.interiorGap; double igx1 = plotArea.getX() + hGap; double igx2 = plotArea.getMaxX() - hGap; double igy1 = plotArea.getY() + vGap; double igy2 = plotArea.getMaxY() - vGap; g2.setPaint(Color.GRAY); g2.draw(new Rectangle2D.Double(igx1, igy1, igx2 - igx1, igy2 - igy1)); } double linkX = plotArea.getX() + gapHorizontal / 2; double linkY = plotArea.getY() + gapVertical / 2; double linkW = plotArea.getWidth() - gapHorizontal; double linkH = plotArea.getHeight() - gapVertical; // make the link area a square if the pie chart is to be circular... if (this.circular) { double min = Math.min(linkW, linkH) / 2; linkX = (linkX + linkX + linkW) / 2 - min; linkY = (linkY + linkY + linkH) / 2 - min; linkW = 2 * min; linkH = 2 * min; } // the link area defines the dog leg points for the linking lines to // the labels Rectangle2D linkArea = new Rectangle2D.Double(linkX, linkY, linkW, linkH); state.setLinkArea(linkArea); if (DEBUG_DRAW_LINK_AREA) { g2.setPaint(Color.BLUE); g2.draw(linkArea); g2.setPaint(Color.YELLOW); g2.draw(new Ellipse2D.Double(linkArea.getX(), linkArea.getY(), linkArea.getWidth(), linkArea.getHeight())); } // the explode area defines the max circle/ellipse for the exploded // pie sections. it is defined by shrinking the linkArea by the // linkMargin factor. double lm = 0.0; if (!this.simpleLabels) { lm = this.labelLinkMargin; } double hh = linkArea.getWidth() * lm * 2.0; double vv = linkArea.getHeight() * lm * 2.0; Rectangle2D explodeArea = new Rectangle2D.Double(linkX + hh / 2.0, linkY + vv / 2.0, linkW - hh, linkH - vv); state.setExplodedPieArea(explodeArea); // the pie area defines the circle/ellipse for regular pie sections. // it is defined by shrinking the explodeArea by the explodeMargin // factor. double maximumExplodePercent = getMaximumExplodePercent(); double percent = maximumExplodePercent / (1.0 + maximumExplodePercent); double h1 = explodeArea.getWidth() * percent; double v1 = explodeArea.getHeight() * percent; Rectangle2D pieArea = new Rectangle2D.Double(explodeArea.getX() + h1 / 2.0, explodeArea.getY() + v1 / 2.0, explodeArea.getWidth() - h1, explodeArea.getHeight() - v1); if (DEBUG_DRAW_PIE_AREA) { g2.setPaint(Color.GREEN); g2.draw(pieArea); } state.setPieArea(pieArea); state.setPieCenterX(pieArea.getCenterX()); state.setPieCenterY(pieArea.getCenterY()); state.setPieWRadius(pieArea.getWidth() / 2.0); state.setPieHRadius(pieArea.getHeight() / 2.0); // plot the data (unless the dataset is null)... if ((this.dataset != null) && (this.dataset.getKeys().size() > 0)) { List keys = this.dataset.getKeys(); double totalValue = DatasetUtils.calculatePieDatasetTotal( this.dataset); int passesRequired = state.getPassesRequired(); for (int pass = 0; pass < passesRequired; pass++) { double runningTotal = 0.0; for (int section = 0; section < keys.size(); section++) { Number n = this.dataset.getValue(section); if (n != null) { double value = n.doubleValue(); if (value > 0.0) { runningTotal += value; drawItem(g2, section, explodeArea, state, pass); } } } } if (this.simpleLabels) { drawSimpleLabels(g2, keys, totalValue, plotArea, linkArea, state); } else { drawLabels(g2, keys, totalValue, plotArea, linkArea, state); } } else { drawNoDataMessage(g2, plotArea); } } /** * Draws a single data item. * * @param g2 the graphics device ({@code null} not permitted). * @param section the section index. * @param dataArea the data plot area. * @param state state information for one chart. * @param currentPass the current pass index. */ protected void drawItem(Graphics2D g2, int section, Rectangle2D dataArea, PiePlotState state, int currentPass) { Number n = this.dataset.getValue(section); if (n == null) { return; } double value = n.doubleValue(); double angle1 = 0.0; double angle2 = 0.0; if (this.direction == Rotation.CLOCKWISE) { angle1 = state.getLatestAngle(); angle2 = angle1 - value / state.getTotal() * 360.0; } else if (this.direction == Rotation.ANTICLOCKWISE) { angle1 = state.getLatestAngle(); angle2 = angle1 + value / state.getTotal() * 360.0; } else { throw new IllegalStateException("Rotation type not recognised."); } double angle = (angle2 - angle1); if (Math.abs(angle) > getMinimumArcAngleToDraw()) { double ep = 0.0; double mep = getMaximumExplodePercent(); if (mep > 0.0) { ep = getExplodePercent(dataset.getKey(section)) / mep; } Rectangle2D arcBounds = getArcBounds(state.getPieArea(), state.getExplodedPieArea(), angle1, angle, ep); Arc2D.Double arc = new Arc2D.Double(arcBounds, angle1, angle, Arc2D.PIE); if (currentPass == 0) { if (this.shadowPaint != null && this.shadowGenerator == null) { Shape shadowArc = ShapeUtils.createTranslatedShape( arc, (float) this.shadowXOffset, (float) this.shadowYOffset); g2.setPaint(this.shadowPaint); g2.fill(shadowArc); } } else if (currentPass == 1) { K key = getSectionKey(section); Paint paint = lookupSectionPaint(key, state); g2.setPaint(paint); g2.fill(arc); Paint outlinePaint = lookupSectionOutlinePaint(key); Stroke outlineStroke = lookupSectionOutlineStroke(key); if (this.sectionOutlinesVisible) { g2.setPaint(outlinePaint); g2.setStroke(outlineStroke); g2.draw(arc); } // update the linking line target for later // add an entity for the pie section if (state.getInfo() != null) { EntityCollection entities = state.getEntityCollection(); if (entities != null) { String tip = null; if (this.toolTipGenerator != null) { tip = this.toolTipGenerator.generateToolTip( this.dataset, key); } String url = null; if (this.urlGenerator != null) { url = this.urlGenerator.generateURL(this.dataset, key, this.pieIndex); } PieSectionEntity entity = new PieSectionEntity( arc, this.dataset, this.pieIndex, section, key, tip, url); entities.add(entity); } } } } state.setLatestAngle(angle2); } /** * Draws the pie section labels in the simple form. * * @param g2 the graphics device. * @param keys the section keys. * @param totalValue the total value for all sections in the pie. * @param plotArea the plot area. * @param pieArea the area containing the pie. * @param state the plot state. * * @since 1.0.7 */ protected void drawSimpleLabels(Graphics2D g2, List keys, double totalValue, Rectangle2D plotArea, Rectangle2D pieArea, PiePlotState state) { Composite originalComposite = g2.getComposite(); g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f)); Rectangle2D labelsArea = this.simpleLabelOffset.createInsetRectangle( pieArea); double runningTotal = 0.0; for (K key : keys) { boolean include; double v = 0.0; Number n = getDataset().getValue(key); if (n == null) { include = !getIgnoreNullValues(); } else { v = n.doubleValue(); include = getIgnoreZeroValues() ? v > 0.0 : v >= 0.0; } if (include) { runningTotal = runningTotal + v; // work out the mid angle (0 - 90 and 270 - 360) = right, // otherwise left double mid = getStartAngle() + (getDirection().getFactor() * ((runningTotal - v / 2.0) * 360) / totalValue); Arc2D arc = new Arc2D.Double(labelsArea, getStartAngle(), mid - getStartAngle(), Arc2D.OPEN); int x = (int) arc.getEndPoint().getX(); int y = (int) arc.getEndPoint().getY(); PieSectionLabelGenerator myLabelGenerator = getLabelGenerator(); if (myLabelGenerator == null) { continue; } String label = myLabelGenerator.generateSectionLabel( this.dataset, key); if (label == null) { continue; } g2.setFont(this.labelFont); FontMetrics fm = g2.getFontMetrics(); Rectangle2D bounds = TextUtils.getTextBounds(label, g2, fm); Rectangle2D out = this.labelPadding.createOutsetRectangle( bounds); Shape bg = ShapeUtils.createTranslatedShape(out, x - bounds.getCenterX(), y - bounds.getCenterY()); if (this.labelShadowPaint != null && this.shadowGenerator == null) { Shape shadow = ShapeUtils.createTranslatedShape(bg, this.shadowXOffset, this.shadowYOffset); g2.setPaint(this.labelShadowPaint); g2.fill(shadow); } if (this.labelBackgroundPaint != null) { g2.setPaint(this.labelBackgroundPaint); g2.fill(bg); } if (this.labelOutlinePaint != null && this.labelOutlineStroke != null) { g2.setPaint(this.labelOutlinePaint); g2.setStroke(this.labelOutlineStroke); g2.draw(bg); } g2.setPaint(this.labelPaint); g2.setFont(this.labelFont); TextUtils.drawAlignedString(label, g2, x, y, TextAnchor.CENTER); } } g2.setComposite(originalComposite); } /** * Draws the labels for the pie sections. * * @param g2 the graphics device. * @param keys the keys. * @param totalValue the total value. * @param plotArea the plot area. * @param linkArea the link area. * @param state the state. */ protected void drawLabels(Graphics2D g2, List keys, double totalValue, Rectangle2D plotArea, Rectangle2D linkArea, PiePlotState state) { Composite originalComposite = g2.getComposite(); g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f)); // classify the keys according to which side the label will appear... DefaultKeyedValues leftKeys = new DefaultKeyedValues(); DefaultKeyedValues rightKeys = new DefaultKeyedValues(); double runningTotal = 0.0; for (K key : keys) { boolean include; double v = 0.0; Number n = this.dataset.getValue(key); if (n == null) { include = !this.ignoreNullValues; } else { v = n.doubleValue(); include = this.ignoreZeroValues ? v > 0.0 : v >= 0.0; } if (include) { runningTotal = runningTotal + v; // work out the mid angle (0 - 90 and 270 - 360) = right, // otherwise left double mid = this.startAngle + (this.direction.getFactor() * ((runningTotal - v / 2.0) * 360) / totalValue); if (Math.cos(Math.toRadians(mid)) < 0.0) { leftKeys.addValue(key, mid); } else { rightKeys.addValue(key, mid); } } } g2.setFont(getLabelFont()); // calculate the max label width from the plot dimensions, because // a circular pie can leave a lot more room for labels... double marginX = plotArea.getX(); double gap = plotArea.getWidth() * this.labelGap; double ww = linkArea.getX() - gap - marginX; float labelWidth = (float) this.labelPadding.trimWidth(ww); // draw the labels... if (this.labelGenerator != null) { drawLeftLabels(leftKeys, g2, plotArea, linkArea, labelWidth, state); drawRightLabels(rightKeys, g2, plotArea, linkArea, labelWidth, state); } g2.setComposite(originalComposite); } /** * Draws the left labels. * * @param leftKeys a collection of keys and angles (to the middle of the * section, in degrees) for the sections on the left side of the * plot. * @param g2 the graphics device. * @param plotArea the plot area. * @param linkArea the link area. * @param maxLabelWidth the maximum label width. * @param state the state. */ protected void drawLeftLabels(KeyedValues leftKeys, Graphics2D g2, Rectangle2D plotArea, Rectangle2D linkArea, float maxLabelWidth, PiePlotState state) { this.labelDistributor.clear(); double lGap = plotArea.getWidth() * this.labelGap; double verticalLinkRadius = state.getLinkArea().getHeight() / 2.0; for (int i = 0; i < leftKeys.getItemCount(); i++) { String label = this.labelGenerator.generateSectionLabel( this.dataset, leftKeys.getKey(i)); if (label != null) { TextBlock block = TextUtils.createTextBlock(label, this.labelFont, this.labelPaint, maxLabelWidth, new G2TextMeasurer(g2)); TextBox labelBox = new TextBox(block); labelBox.setBackgroundPaint(this.labelBackgroundPaint); labelBox.setOutlinePaint(this.labelOutlinePaint); labelBox.setOutlineStroke(this.labelOutlineStroke); if (this.shadowGenerator == null) { labelBox.setShadowPaint(this.labelShadowPaint); } else { labelBox.setShadowPaint(null); } labelBox.setInteriorGap(this.labelPadding); double theta = Math.toRadians( leftKeys.getValue(i).doubleValue()); double baseY = state.getPieCenterY() - Math.sin(theta) * verticalLinkRadius; double hh = labelBox.getHeight(g2); this.labelDistributor.addPieLabelRecord(new PieLabelRecord( leftKeys.getKey(i), theta, baseY, labelBox, hh, lGap / 2.0 + lGap / 2.0 * -Math.cos(theta), 1.0 - getLabelLinkDepth() + getExplodePercent(leftKeys.getKey(i)))); } } double hh = plotArea.getHeight(); double gap = hh * getInteriorGap(); this.labelDistributor.distributeLabels(plotArea.getMinY() + gap, hh - 2 * gap); for (int i = 0; i < this.labelDistributor.getItemCount(); i++) { drawLeftLabel(g2, state, this.labelDistributor.getPieLabelRecord(i)); } } /** * Draws the right labels. * * @param keys the keys. * @param g2 the graphics device. * @param plotArea the plot area. * @param linkArea the link area. * @param maxLabelWidth the maximum label width. * @param state the state. */ protected void drawRightLabels(KeyedValues keys, Graphics2D g2, Rectangle2D plotArea, Rectangle2D linkArea, float maxLabelWidth, PiePlotState state) { // draw the right labels... this.labelDistributor.clear(); double lGap = plotArea.getWidth() * this.labelGap; double verticalLinkRadius = state.getLinkArea().getHeight() / 2.0; for (int i = 0; i < keys.getItemCount(); i++) { String label = this.labelGenerator.generateSectionLabel( this.dataset, keys.getKey(i)); if (label != null) { TextBlock block = TextUtils.createTextBlock(label, this.labelFont, this.labelPaint, maxLabelWidth, new G2TextMeasurer(g2)); TextBox labelBox = new TextBox(block); labelBox.setBackgroundPaint(this.labelBackgroundPaint); labelBox.setOutlinePaint(this.labelOutlinePaint); labelBox.setOutlineStroke(this.labelOutlineStroke); if (this.shadowGenerator == null) { labelBox.setShadowPaint(this.labelShadowPaint); } else { labelBox.setShadowPaint(null); } labelBox.setInteriorGap(this.labelPadding); double theta = Math.toRadians(keys.getValue(i).doubleValue()); double baseY = state.getPieCenterY() - Math.sin(theta) * verticalLinkRadius; double hh = labelBox.getHeight(g2); this.labelDistributor.addPieLabelRecord(new PieLabelRecord( keys.getKey(i), theta, baseY, labelBox, hh, lGap / 2.0 + lGap / 2.0 * Math.cos(theta), 1.0 - getLabelLinkDepth() + getExplodePercent(keys.getKey(i)))); } } double hh = plotArea.getHeight(); double gap = 0.00; //hh * getInteriorGap(); this.labelDistributor.distributeLabels(plotArea.getMinY() + gap, hh - 2 * gap); for (int i = 0; i < this.labelDistributor.getItemCount(); i++) { drawRightLabel(g2, state, this.labelDistributor.getPieLabelRecord(i)); } } /** * Returns a collection of legend items for the pie chart. * * @return The legend items (never {@code null}). */ @Override public LegendItemCollection getLegendItems() { LegendItemCollection result = new LegendItemCollection(); if (this.dataset == null) { return result; } List keys = this.dataset.getKeys(); int section = 0; Shape shape = getLegendItemShape(); for (K key : keys) { Number n = this.dataset.getValue(key); boolean include; if (n == null) { include = !this.ignoreNullValues; } else { double v = n.doubleValue(); if (v == 0.0) { include = !this.ignoreZeroValues; } else { include = v > 0.0; } } if (include) { String label = this.legendLabelGenerator.generateSectionLabel( this.dataset, key); if (label != null) { String description = label; String toolTipText = null; if (this.legendLabelToolTipGenerator != null) { toolTipText = this.legendLabelToolTipGenerator .generateSectionLabel(this.dataset, key); } String urlText = null; if (this.legendLabelURLGenerator != null) { urlText = this.legendLabelURLGenerator.generateURL( this.dataset, key, this.pieIndex); } Paint paint = lookupSectionPaint(key); Paint outlinePaint = lookupSectionOutlinePaint(key); Stroke outlineStroke = lookupSectionOutlineStroke(key); LegendItem item = new LegendItem(label, description, toolTipText, urlText, true, shape, true, paint, true, outlinePaint, outlineStroke, false, // line not visible new Line2D.Float(), new BasicStroke(), Color.BLACK); item.setDataset(getDataset()); item.setSeriesIndex(this.dataset.getIndex(key)); item.setSeriesKey(key); result.add(item); } section++; } else { section++; } } return result; } /** * Returns a short string describing the type of plot. * * @return The plot type. */ @Override public String getPlotType() { return localizationResources.getString("Pie_Plot"); } /** * Returns a rectangle that can be used to create a pie section (taking * into account the amount by which the pie section is 'exploded'). * * @param unexploded the area inside which the unexploded pie sections are * drawn. * @param exploded the area inside which the exploded pie sections are * drawn. * @param angle the start angle. * @param extent the extent of the arc. * @param explodePercent the amount by which the pie section is exploded. * * @return A rectangle that can be used to create a pie section. */ protected Rectangle2D getArcBounds(Rectangle2D unexploded, Rectangle2D exploded, double angle, double extent, double explodePercent) { if (explodePercent == 0.0) { return unexploded; } Arc2D arc1 = new Arc2D.Double(unexploded, angle, extent / 2, Arc2D.OPEN); Point2D point1 = arc1.getEndPoint(); Arc2D.Double arc2 = new Arc2D.Double(exploded, angle, extent / 2, Arc2D.OPEN); Point2D point2 = arc2.getEndPoint(); double deltaX = (point1.getX() - point2.getX()) * explodePercent; double deltaY = (point1.getY() - point2.getY()) * explodePercent; return new Rectangle2D.Double(unexploded.getX() - deltaX, unexploded.getY() - deltaY, unexploded.getWidth(), unexploded.getHeight()); } /** * Draws a section label on the left side of the pie chart. * * @param g2 the graphics device. * @param state the state. * @param record the label record. */ protected void drawLeftLabel(Graphics2D g2, PiePlotState state, PieLabelRecord record) { double anchorX = state.getLinkArea().getMinX(); double targetX = anchorX - record.getGap(); double targetY = record.getAllocatedY(); if (this.labelLinksVisible) { double theta = record.getAngle(); double linkX = state.getPieCenterX() + Math.cos(theta) * state.getPieWRadius() * record.getLinkPercent(); double linkY = state.getPieCenterY() - Math.sin(theta) * state.getPieHRadius() * record.getLinkPercent(); double elbowX = state.getPieCenterX() + Math.cos(theta) * state.getLinkArea().getWidth() / 2.0; double elbowY = state.getPieCenterY() - Math.sin(theta) * state.getLinkArea().getHeight() / 2.0; double anchorY = elbowY; g2.setPaint(this.labelLinkPaint); g2.setStroke(this.labelLinkStroke); PieLabelLinkStyle style = getLabelLinkStyle(); if (style.equals(PieLabelLinkStyle.STANDARD)) { g2.draw(new Line2D.Double(linkX, linkY, elbowX, elbowY)); g2.draw(new Line2D.Double(anchorX, anchorY, elbowX, elbowY)); g2.draw(new Line2D.Double(anchorX, anchorY, targetX, targetY)); } else if (style.equals(PieLabelLinkStyle.QUAD_CURVE)) { QuadCurve2D q = new QuadCurve2D.Float(); q.setCurve(targetX, targetY, anchorX, anchorY, elbowX, elbowY); g2.draw(q); g2.draw(new Line2D.Double(elbowX, elbowY, linkX, linkY)); } else if (style.equals(PieLabelLinkStyle.CUBIC_CURVE)) { CubicCurve2D c = new CubicCurve2D .Float(); c.setCurve(targetX, targetY, anchorX, anchorY, elbowX, elbowY, linkX, linkY); g2.draw(c); } } TextBox tb = record.getLabel(); tb.draw(g2, (float) targetX, (float) targetY, RectangleAnchor.RIGHT); } /** * Draws a section label on the right side of the pie chart. * * @param g2 the graphics device. * @param state the state. * @param record the label record. */ protected void drawRightLabel(Graphics2D g2, PiePlotState state, PieLabelRecord record) { double anchorX = state.getLinkArea().getMaxX(); double targetX = anchorX + record.getGap(); double targetY = record.getAllocatedY(); if (this.labelLinksVisible) { double theta = record.getAngle(); double linkX = state.getPieCenterX() + Math.cos(theta) * state.getPieWRadius() * record.getLinkPercent(); double linkY = state.getPieCenterY() - Math.sin(theta) * state.getPieHRadius() * record.getLinkPercent(); double elbowX = state.getPieCenterX() + Math.cos(theta) * state.getLinkArea().getWidth() / 2.0; double elbowY = state.getPieCenterY() - Math.sin(theta) * state.getLinkArea().getHeight() / 2.0; double anchorY = elbowY; g2.setPaint(this.labelLinkPaint); g2.setStroke(this.labelLinkStroke); PieLabelLinkStyle style = getLabelLinkStyle(); if (style.equals(PieLabelLinkStyle.STANDARD)) { g2.draw(new Line2D.Double(linkX, linkY, elbowX, elbowY)); g2.draw(new Line2D.Double(anchorX, anchorY, elbowX, elbowY)); g2.draw(new Line2D.Double(anchorX, anchorY, targetX, targetY)); } else if (style.equals(PieLabelLinkStyle.QUAD_CURVE)) { QuadCurve2D q = new QuadCurve2D.Float(); q.setCurve(targetX, targetY, anchorX, anchorY, elbowX, elbowY); g2.draw(q); g2.draw(new Line2D.Double(elbowX, elbowY, linkX, linkY)); } else if (style.equals(PieLabelLinkStyle.CUBIC_CURVE)) { CubicCurve2D c = new CubicCurve2D .Float(); c.setCurve(targetX, targetY, anchorX, anchorY, elbowX, elbowY, linkX, linkY); g2.draw(c); } } TextBox tb = record.getLabel(); tb.draw(g2, (float) targetX, (float) targetY, RectangleAnchor.LEFT); } /** * Returns the center for the specified section. * Checks to see if the section is exploded and recalculates the * new center if so. * * @param state PiePlotState * @param key section key. * * @return The center for the specified section. * * @since 1.0.14 */ protected Point2D getArcCenter(PiePlotState state, K key) { Point2D center = new Point2D.Double(state.getPieCenterX(), state .getPieCenterY()); double ep = getExplodePercent(key); double mep = getMaximumExplodePercent(); if (mep > 0.0) { ep = ep / mep; } if (ep != 0) { Rectangle2D pieArea = state.getPieArea(); Rectangle2D expPieArea = state.getExplodedPieArea(); double angle1, angle2; Number n = this.dataset.getValue(key); double value = n.doubleValue(); if (this.direction == Rotation.CLOCKWISE) { angle1 = state.getLatestAngle(); angle2 = angle1 - value / state.getTotal() * 360.0; } else if (this.direction == Rotation.ANTICLOCKWISE) { angle1 = state.getLatestAngle(); angle2 = angle1 + value / state.getTotal() * 360.0; } else { throw new IllegalStateException("Rotation type not recognised."); } double angle = (angle2 - angle1); Arc2D arc1 = new Arc2D.Double(pieArea, angle1, angle / 2, Arc2D.OPEN); Point2D point1 = arc1.getEndPoint(); Arc2D.Double arc2 = new Arc2D.Double(expPieArea, angle1, angle / 2, Arc2D.OPEN); Point2D point2 = arc2.getEndPoint(); double deltaX = (point1.getX() - point2.getX()) * ep; double deltaY = (point1.getY() - point2.getY()) * ep; center = new Point2D.Double(state.getPieCenterX() - deltaX, state.getPieCenterY() - deltaY); } return center; } /** * Returns the paint for the specified section. This is equivalent to * {@code lookupSectionPaint(section)}. Checks to see if the user set the * {@code Paint} to be of type {@code RadialGradientPaint} and if so it * adjusts the center and radius to match the Pie. * * @param key the section key. * @param state PiePlotState. * * @return The paint for the specified section. * * @since 1.0.14 */ protected Paint lookupSectionPaint(K key, PiePlotState state) { Paint paint = lookupSectionPaint(key, getAutoPopulateSectionPaint()); // for a RadialGradientPaint we adjust the center and radius to match // the current pie segment... if (paint instanceof RadialGradientPaint) { RadialGradientPaint rgp = (RadialGradientPaint) paint; Point2D center = getArcCenter(state, key); float radius = (float) Math.max(state.getPieHRadius(), state.getPieWRadius()); float[] fractions = rgp.getFractions(); Color[] colors = rgp.getColors(); paint = new RadialGradientPaint(center, radius, fractions, colors); } return paint; } /** * Tests this plot for equality with an arbitrary object. Note that the * plot's dataset is NOT included in the test for equality. * * @param obj the object to test against ({@code null} permitted). * * @return {@code true} or {@code false}. */ @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof PiePlot)) { return false; } if (!super.equals(obj)) { return false; } PiePlot that = (PiePlot) obj; if (this.pieIndex != that.pieIndex) { return false; } if (this.interiorGap != that.interiorGap) { return false; } if (this.circular != that.circular) { return false; } if (this.startAngle != that.startAngle) { return false; } if (this.direction != that.direction) { return false; } if (this.ignoreZeroValues != that.ignoreZeroValues) { return false; } if (this.ignoreNullValues != that.ignoreNullValues) { return false; } if (!ObjectUtils.equal(this.sectionPaintMap, that.sectionPaintMap)) { return false; } if (!PaintUtils.equal(this.defaultSectionPaint, that.defaultSectionPaint)) { return false; } if (this.sectionOutlinesVisible != that.sectionOutlinesVisible) { return false; } if (!ObjectUtils.equal(this.sectionOutlinePaintMap, that.sectionOutlinePaintMap)) { return false; } if (!PaintUtils.equal(this.defaultSectionOutlinePaint, that.defaultSectionOutlinePaint)) { return false; } if (!ObjectUtils.equal(this.sectionOutlineStrokeMap, that.sectionOutlineStrokeMap)) { return false; } if (!ObjectUtils.equal(this.defaultSectionOutlineStroke, that.defaultSectionOutlineStroke)) { return false; } if (!PaintUtils.equal(this.shadowPaint, that.shadowPaint)) { return false; } if (!(this.shadowXOffset == that.shadowXOffset)) { return false; } if (!(this.shadowYOffset == that.shadowYOffset)) { return false; } if (!ObjectUtils.equal(this.explodePercentages, that.explodePercentages)) { return false; } if (!ObjectUtils.equal(this.labelGenerator, that.labelGenerator)) { return false; } if (!ObjectUtils.equal(this.labelFont, that.labelFont)) { return false; } if (!PaintUtils.equal(this.labelPaint, that.labelPaint)) { return false; } if (!PaintUtils.equal(this.labelBackgroundPaint, that.labelBackgroundPaint)) { return false; } if (!PaintUtils.equal(this.labelOutlinePaint, that.labelOutlinePaint)) { return false; } if (!ObjectUtils.equal(this.labelOutlineStroke, that.labelOutlineStroke)) { return false; } if (!PaintUtils.equal(this.labelShadowPaint, that.labelShadowPaint)) { return false; } if (this.simpleLabels != that.simpleLabels) { return false; } if (!this.simpleLabelOffset.equals(that.simpleLabelOffset)) { return false; } if (!this.labelPadding.equals(that.labelPadding)) { return false; } if (!(this.maximumLabelWidth == that.maximumLabelWidth)) { return false; } if (!(this.labelGap == that.labelGap)) { return false; } if (!(this.labelLinkMargin == that.labelLinkMargin)) { return false; } if (this.labelLinksVisible != that.labelLinksVisible) { return false; } if (!this.labelLinkStyle.equals(that.labelLinkStyle)) { return false; } if (!PaintUtils.equal(this.labelLinkPaint, that.labelLinkPaint)) { return false; } if (!ObjectUtils.equal(this.labelLinkStroke, that.labelLinkStroke)) { return false; } if (!ObjectUtils.equal(this.toolTipGenerator, that.toolTipGenerator)) { return false; } if (!ObjectUtils.equal(this.urlGenerator, that.urlGenerator)) { return false; } if (!(this.minimumArcAngleToDraw == that.minimumArcAngleToDraw)) { return false; } if (!ShapeUtils.equal(this.legendItemShape, that.legendItemShape)) { return false; } if (!ObjectUtils.equal(this.legendLabelGenerator, that.legendLabelGenerator)) { return false; } if (!ObjectUtils.equal(this.legendLabelToolTipGenerator, that.legendLabelToolTipGenerator)) { return false; } if (!ObjectUtils.equal(this.legendLabelURLGenerator, that.legendLabelURLGenerator)) { return false; } if (this.autoPopulateSectionPaint != that.autoPopulateSectionPaint) { return false; } if (this.autoPopulateSectionOutlinePaint != that.autoPopulateSectionOutlinePaint) { return false; } if (this.autoPopulateSectionOutlineStroke != that.autoPopulateSectionOutlineStroke) { return false; } if (!ObjectUtils.equal(this.shadowGenerator, that.shadowGenerator)) { return false; } // can't find any difference... return true; } /** * Generates a hashcode. Note that, as with the equals method, the dataset * is NOT included in the hashcode. * * @return the hashcode */ @Override public int hashCode() { int hash = 7; hash = 73 * hash + this.pieIndex; hash = 73 * hash + (int) (Double.doubleToLongBits(this.interiorGap) ^ (Double.doubleToLongBits(this.interiorGap) >>> 32)); hash = 73 * hash + (this.circular ? 1 : 0); hash = 73 * hash + (int) (Double.doubleToLongBits(this.startAngle) ^ (Double.doubleToLongBits(this.startAngle) >>> 32)); hash = 73 * hash + Objects.hashCode(this.direction); hash = 73 * hash + Objects.hashCode(this.sectionPaintMap); hash = 73 * hash + Objects.hashCode(this.defaultSectionPaint); hash = 73 * hash + (this.autoPopulateSectionPaint ? 1 : 0); hash = 73 * hash + (this.sectionOutlinesVisible ? 1 : 0); hash = 73 * hash + Objects.hashCode(this.sectionOutlinePaintMap); hash = 73 * hash + Objects.hashCode(this.defaultSectionOutlinePaint); hash = 73 * hash + (this.autoPopulateSectionOutlinePaint ? 1 : 0); hash = 73 * hash + Objects.hashCode(this.sectionOutlineStrokeMap); hash = 73 * hash + Objects.hashCode(this.defaultSectionOutlineStroke); hash = 73 * hash + (this.autoPopulateSectionOutlineStroke ? 1 : 0); hash = 73 * hash + Objects.hashCode(this.shadowPaint); hash = 73 * hash + (int) (Double.doubleToLongBits(this.shadowXOffset) ^ (Double.doubleToLongBits(this.shadowXOffset) >>> 32)); hash = 73 * hash + (int) (Double.doubleToLongBits(this.shadowYOffset) ^ (Double.doubleToLongBits(this.shadowYOffset) >>> 32)); hash = 73 * hash + Objects.hashCode(this.explodePercentages); hash = 73 * hash + Objects.hashCode(this.labelGenerator); hash = 73 * hash + Objects.hashCode(this.labelFont); hash = 73 * hash + Objects.hashCode(this.labelPaint); hash = 73 * hash + Objects.hashCode(this.labelBackgroundPaint); hash = 73 * hash + Objects.hashCode(this.labelOutlinePaint); hash = 73 * hash + Objects.hashCode(this.labelOutlineStroke); hash = 73 * hash + Objects.hashCode(this.labelShadowPaint); hash = 73 * hash + (this.simpleLabels ? 1 : 0); hash = 73 * hash + Objects.hashCode(this.labelPadding); hash = 73 * hash + Objects.hashCode(this.simpleLabelOffset); hash = 73 * hash + (int) (Double.doubleToLongBits(this.maximumLabelWidth) ^ (Double.doubleToLongBits(this.maximumLabelWidth) >>> 32)); hash = 73 * hash + (int) (Double.doubleToLongBits(this.labelGap) ^ (Double.doubleToLongBits(this.labelGap) >>> 32)); hash = 73 * hash + (this.labelLinksVisible ? 1 : 0); hash = 73 * hash + Objects.hashCode(this.labelLinkStyle); hash = 73 * hash + (int) (Double.doubleToLongBits(this.labelLinkMargin) ^ (Double.doubleToLongBits(this.labelLinkMargin) >>> 32)); hash = 73 * hash + Objects.hashCode(this.labelLinkPaint); hash = 73 * hash + Objects.hashCode(this.labelLinkStroke); hash = 73 * hash + Objects.hashCode(this.toolTipGenerator); hash = 73 * hash + Objects.hashCode(this.urlGenerator); hash = 73 * hash + Objects.hashCode(this.legendLabelGenerator); hash = 73 * hash + Objects.hashCode(this.legendLabelToolTipGenerator); hash = 73 * hash + Objects.hashCode(this.legendLabelURLGenerator); hash = 73 * hash + (this.ignoreNullValues ? 1 : 0); hash = 73 * hash + (this.ignoreZeroValues ? 1 : 0); hash = 73 * hash + Objects.hashCode(this.legendItemShape); hash = 73 * hash + (int) (Double.doubleToLongBits(this.minimumArcAngleToDraw) ^ (Double.doubleToLongBits(this.minimumArcAngleToDraw) >>> 32)); hash = 73 * hash + Objects.hashCode(this.shadowGenerator); return hash; } /** * Returns a clone of the plot. * * @return A clone. * * @throws CloneNotSupportedException if some component of the plot does * not support cloning. */ @Override public Object clone() throws CloneNotSupportedException { PiePlot clone = (PiePlot) super.clone(); clone.sectionPaintMap = (PaintMap) this.sectionPaintMap.clone(); clone.sectionOutlinePaintMap = (PaintMap) this.sectionOutlinePaintMap.clone(); clone.sectionOutlineStrokeMap = (StrokeMap) this.sectionOutlineStrokeMap.clone(); clone.explodePercentages = new TreeMap<>(this.explodePercentages); if (this.labelGenerator != null) { clone.labelGenerator = (PieSectionLabelGenerator) ObjectUtils.clone(this.labelGenerator); } if (clone.dataset != null) { clone.dataset.addChangeListener(clone); } if (this.urlGenerator instanceof PublicCloneable) { clone.urlGenerator = (PieURLGenerator) ObjectUtils.clone( this.urlGenerator); } clone.legendItemShape = ShapeUtils.clone(this.legendItemShape); if (this.legendLabelGenerator != null) { clone.legendLabelGenerator = (PieSectionLabelGenerator) ObjectUtils.clone(this.legendLabelGenerator); } if (this.legendLabelToolTipGenerator != null) { clone.legendLabelToolTipGenerator = (PieSectionLabelGenerator) ObjectUtils.clone(this.legendLabelToolTipGenerator); } if (this.legendLabelURLGenerator instanceof PublicCloneable) { clone.legendLabelURLGenerator = (PieURLGenerator) ObjectUtils.clone(this.legendLabelURLGenerator); } return clone; } /** * Provides serialization support. * * @param stream the output stream. * * @throws IOException if there is an I/O error. */ private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); SerialUtils.writePaint(this.defaultSectionPaint, stream); SerialUtils.writePaint(this.defaultSectionOutlinePaint, stream); SerialUtils.writeStroke(this.defaultSectionOutlineStroke, stream); SerialUtils.writePaint(this.shadowPaint, stream); SerialUtils.writePaint(this.labelPaint, stream); SerialUtils.writePaint(this.labelBackgroundPaint, stream); SerialUtils.writePaint(this.labelOutlinePaint, stream); SerialUtils.writeStroke(this.labelOutlineStroke, stream); SerialUtils.writePaint(this.labelShadowPaint, stream); SerialUtils.writePaint(this.labelLinkPaint, stream); SerialUtils.writeStroke(this.labelLinkStroke, stream); SerialUtils.writeShape(this.legendItemShape, stream); } /** * Provides serialization support. * * @param stream the input stream. * * @throws IOException if there is an I/O error. * @throws ClassNotFoundException if there is a classpath problem. */ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); this.defaultSectionPaint = SerialUtils.readPaint(stream); this.defaultSectionOutlinePaint = SerialUtils.readPaint(stream); this.defaultSectionOutlineStroke = SerialUtils.readStroke(stream); this.shadowPaint = SerialUtils.readPaint(stream); this.labelPaint = SerialUtils.readPaint(stream); this.labelBackgroundPaint = SerialUtils.readPaint(stream); this.labelOutlinePaint = SerialUtils.readPaint(stream); this.labelOutlineStroke = SerialUtils.readStroke(stream); this.labelShadowPaint = SerialUtils.readPaint(stream); this.labelLinkPaint = SerialUtils.readPaint(stream); this.labelLinkStroke = SerialUtils.readStroke(stream); this.legendItemShape = SerialUtils.readShape(stream); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy