![JAR search and dependency download from the Maven repository](/logo.png)
org.jfree.chart.plot.PiePlot Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jfreechart Show documentation
Show all versions of jfreechart Show documentation
JFreeChart is a class library, written in Java, for generating charts.
Utilising the Java2D API, it supports a wide range of chart types including
bar charts, pie charts, line charts, XY-plots, time series plots, Sankey charts
and more.
/* ===========================================================
* JFreeChart : a free chart library for the Java(tm) platform
* ===========================================================
*
* (C) Copyright 2000-present, by David Gilbert 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-present, by Andrzej Porebski and Contributors.
*
* Original Author: Andrzej Porebski;
* Contributor(s): David Gilbert;
* 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:
*
*
*
* Special notes:
*
* - the default starting point is 12 o'clock and the pie sections proceed
* in a clockwise direction, but these settings can be changed;
* - negative values in the dataset are ignored;
* - there are utility methods for creating a {@link PieDataset} from a
* {@link org.jfree.data.category.CategoryDataset};
*
*
* @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.
*/
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.
*/
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.
*/
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.
*/
private boolean simpleLabels = true;
/**
* The padding between the labels and the label outlines. This is not
* allowed to be {@code null}.
*/
private RectangleInsets labelPadding;
/**
* The simple label offset.
*/
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.
*/
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.
*/
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).
*/
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).
*/
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.
*
* @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.
*/
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.
*/
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)
*/
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)
*/
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?
*
* @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.
*/
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?
*/
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.
*
* @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.
*/
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)
*/
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)
*/
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?
*
* @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.
*/
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?
*/
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.
*
* @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.
*/
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)
*/
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)
*/
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?
*
* @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.
*/
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?
*/
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}.
*
* @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).
*
* @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)
*/
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()
*/
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}.
*/
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}).
*
* @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).
*
* @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.
*/
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.
*/
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}).
*
* @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).
*
* @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}).
*/
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).
*/
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)
*/
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()
*/
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}).
*/
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).
*/
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.
*/
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.
*/
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.
*/
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.
*/
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 (!Objects.equals(this.sectionPaintMap,
that.sectionPaintMap)) {
return false;
}
if (!PaintUtils.equal(this.defaultSectionPaint,
that.defaultSectionPaint)) {
return false;
}
if (this.sectionOutlinesVisible != that.sectionOutlinesVisible) {
return false;
}
if (!Objects.equals(this.sectionOutlinePaintMap,
that.sectionOutlinePaintMap)) {
return false;
}
if (!PaintUtils.equal(this.defaultSectionOutlinePaint,
that.defaultSectionOutlinePaint)) {
return false;
}
if (!Objects.equals(this.sectionOutlineStrokeMap,
that.sectionOutlineStrokeMap)) {
return false;
}
if (!Objects.equals(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 (!Objects.equals(this.explodePercentages,
that.explodePercentages)) {
return false;
}
if (!Objects.equals(this.labelGenerator,
that.labelGenerator)) {
return false;
}
if (!Objects.equals(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 (!Objects.equals(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 (!Objects.equals(this.labelLinkStroke,
that.labelLinkStroke)) {
return false;
}
if (!Objects.equals(this.toolTipGenerator,
that.toolTipGenerator)) {
return false;
}
if (!Objects.equals(this.urlGenerator, that.urlGenerator)) {
return false;
}
if (!(this.minimumArcAngleToDraw == that.minimumArcAngleToDraw)) {
return false;
}
if (!ShapeUtils.equal(this.legendItemShape, that.legendItemShape)) {
return false;
}
if (!Objects.equals(this.legendLabelGenerator,
that.legendLabelGenerator)) {
return false;
}
if (!Objects.equals(this.legendLabelToolTipGenerator,
that.legendLabelToolTipGenerator)) {
return false;
}
if (!Objects.equals(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 (!Objects.equals(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 - 2025 Weber Informatics LLC | Privacy Policy