/* =========================================================== * JFreeChart : a free chart library for the Java(tm) platform * =========================================================== * * (C) Copyright 2000-2012, 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.] * * -------------- * PiePlot3D.java * -------------- * (C) Copyright 2000-2012, by Object Refinery and Contributors. * * Original Author: Tomer Peretz; * Contributor(s): Richard Atkinson; * David Gilbert (for Object Refinery Limited); * Xun Kang; * Christian W. Zuckschwerdt; * Arnaud Lelievre; * Dave Crane; * Martin Hoeller; * * Changes * ------- * 21-Jun-2002 : Version 1; * 31-Jul-2002 : Modified to use startAngle and direction, drawing modified so * that charts render with foreground alpha < 1.0 (DG); * 05-Aug-2002 : Small modification to draw method to support URLs for HTML * image maps (RA); * 26-Sep-2002 : Fixed errors reported by Checkstyle (DG); * 18-Oct-2002 : Added drawing bug fix sent in by Xun Kang, and made a couple * of other related fixes (DG); * 30-Oct-2002 : Changed the PieDataset interface. Fixed another drawing * bug (DG); * 12-Nov-2002 : Fixed null pointer exception for zero or negative values (DG); * 07-Mar-2003 : Modified to pass pieIndex on to PieSectionEntity (DG); * 21-Mar-2003 : Added workaround for bug id 620031 (DG); * 26-Mar-2003 : Implemented Serializable (DG); * 30-Jul-2003 : Modified entity constructor (CZ); * 29-Aug-2003 : Small changes for API updates in PiePlot class (DG); * 02-Sep-2003 : Fixed bug where the 'no data' message is not displayed (DG); * 08-Sep-2003 : Added internationalization via use of properties * resourceBundle (RFE 690236) (AL); * 29-Oct-2003 : Added workaround for font alignment in PDF output (DG); * 20-Nov-2003 : Fixed bug 845289 (sides not showing) (DG); * 25-Nov-2003 : Added patch (845095) to fix outline paint issues (DG); * 10-Mar-2004 : Numerous changes to enhance labelling (DG); * 31-Mar-2004 : Adjusted plot area when label generator is null (DG); * 08-Apr-2004 : Added flag to PiePlot class to control the treatment of null * values (DG); * Added pieIndex to PieSectionEntity (DG); * 15-Nov-2004 : Removed creation of default tool tip generator (DG); * 16-Jun-2005 : Added default constructor (DG); * ------------- JFREECHART 1.0.x --------------------------------------------- * 27-Sep-2006 : Updated draw() method for new lookup methods (DG); * 22-Mar-2007 : Added equals() override (DG); * 18-Jun-2007 : Added handling for simple label option (DG); * 04-Oct-2007 : Added option to darken sides of plot - thanks to Alex Moots * (see patch 1805262) (DG); * 21-Nov-2007 : Changed default depth factor, fixed labelling bugs and added * debug code - see debug flags in PiePlot class (DG); * 20-Mar-2008 : Fixed bug 1920854 - multiple redraws of the section * labels (DG); * 19-May-2009 : Fixed FindBugs warnings, patch by Michal Wozniak (DG); * 10-Jul-2009 : Added drop shaow support (DG); * 10-Oct-2011 : Localization fix: bug #3353913 (MH); * 18-Oct-2011 : Fix tooltip offset with shadow generator (DG); * 17-Jun-2012 : Removed JCommon dependencies (DG); * */ package org.jfree.chart.plot; import java.awt.AlphaComposite; 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.Polygon; import java.awt.Shape; import java.awt.Stroke; import java.awt.geom.Arc2D; import java.awt.geom.Area; import java.awt.geom.Ellipse2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import org.jfree.chart.ui.RectangleInsets; import org.jfree.chart.entity.EntityCollection; import org.jfree.chart.entity.PieSectionEntity; import org.jfree.chart.event.PlotChangeEvent; import org.jfree.chart.labels.PieToolTipGenerator; import org.jfree.data.general.DatasetUtilities; import org.jfree.data.general.PieDataset; /** * A plot that displays data in the form of a 3D pie chart, using data from * any class that implements the {@link PieDataset} interface. * <P> * Although this class extends {@link PiePlot}, it does not currently support * exploded sections. */ public class PiePlot3D extends PiePlot implements Serializable { /** For serialization. */ private static final long serialVersionUID = 3408984188945161432L; /** The factor of the depth of the pie from the plot height */ private double depthFactor = 0.12; /** * A flag that controls whether or not the sides of the pie chart * are rendered using a darker colour. * * @since 1.0.7. */ private boolean darkerSides = false; // default preserves previous // behaviour /** * Creates a new instance with no dataset. */ public PiePlot3D() { this(null); } /** * Creates a pie chart with a three dimensional effect using the specified * dataset. * * @param dataset the dataset (<code>null</code> permitted). */ public PiePlot3D(PieDataset dataset) { super(dataset); setCircular(false, false); } /** * Returns the depth factor for the chart. * * @return The depth factor. * * @see #setDepthFactor(double) */ public double getDepthFactor() { return this.depthFactor; } /** * Sets the pie depth as a percentage of the height of the plot area, and * sends a {@link PlotChangeEvent} to all registered listeners. * * @param factor the depth factor (for example, 0.20 is twenty percent). * * @see #getDepthFactor() */ public void setDepthFactor(double factor) { this.depthFactor = factor; fireChangeEvent(); } /** * Returns a flag that controls whether or not the sides of the pie chart * are rendered using a darker colour. This is only applied if the * section colour is an instance of {@link java.awt.Color}. * * @return A boolean. * * @see #setDarkerSides(boolean) * * @since 1.0.7 */ public boolean getDarkerSides() { return this.darkerSides; } /** * Sets a flag that controls whether or not the sides of the pie chart * are rendered using a darker colour, and sends a {@link PlotChangeEvent} * to all registered listeners. This is only applied if the * section colour is an instance of {@link java.awt.Color}. * * @param darker true to darken the sides, false to use the default * behaviour. * * @see #getDarkerSides() * * @since 1.0.7. */ public void setDarkerSides(boolean darker) { this.darkerSides = darker; fireChangeEvent(); } /** * Draws the plot on a Java 2D graphics device (such as the screen or a * printer). This method is called by the * {@link org.jfree.chart.JFreeChart} class, you don't normally need * to call it yourself. * * @param g2 the graphics device. * @param plotArea the area within which the plot should be drawn. * @param anchor the anchor point. * @param parentState the state from the parent plot, if there is one. * @param info collects info about the drawing * (<code>null</code> permitted). */ @Override public void draw(Graphics2D g2, Rectangle2D plotArea, Point2D anchor, PlotState parentState, PlotRenderingInfo info) { // adjust for insets... RectangleInsets insets = getInsets(); insets.trim(plotArea); Rectangle2D originalPlotArea = (Rectangle2D) plotArea.clone(); if (info != null) { info.setPlotArea(plotArea); info.setDataArea(plotArea); } drawBackground(g2, plotArea); Shape savedClip = g2.getClip(); g2.clip(plotArea); Graphics2D savedG2 = g2; BufferedImage dataImage = null; if (getShadowGenerator() != null) { dataImage = new BufferedImage((int) plotArea.getWidth(), (int) plotArea.getHeight(), BufferedImage.TYPE_INT_ARGB); g2 = dataImage.createGraphics(); g2.translate(-plotArea.getX(), -plotArea.getY()); g2.setRenderingHints(savedG2.getRenderingHints()); originalPlotArea = (Rectangle2D) plotArea.clone(); } // adjust the plot area by the interior spacing value double gapPercent = getInteriorGap(); double labelPercent = 0.0; if (getLabelGenerator() != null) { labelPercent = getLabelGap() + getMaximumLabelWidth(); } double gapHorizontal = plotArea.getWidth() * (gapPercent + labelPercent) * 2.0; double gapVertical = plotArea.getHeight() * gapPercent * 2.0; if (DEBUG_DRAW_INTERIOR) { double hGap = plotArea.getWidth() * getInteriorGap(); double vGap = plotArea.getHeight() * getInteriorGap(); double igx1 = plotArea.getX() + hGap; double igx2 = plotArea.getMaxX() - hGap; double igy1 = plotArea.getY() + vGap; double igy2 = plotArea.getMaxY() - vGap; g2.setPaint(Color.LIGHT_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 (isCircular()) { // is 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; } PiePlotState state = initialise(g2, plotArea, this, null, info); // the link area defines the dog leg points for the linking lines to // the labels Rectangle2D linkAreaXX = new Rectangle2D.Double(linkX, linkY, linkW, linkH * (1 - this.depthFactor)); state.setLinkArea(linkAreaXX); if (DEBUG_DRAW_LINK_AREA) { g2.setPaint(Color.BLUE); g2.draw(linkAreaXX); g2.setPaint(Color.YELLOW); g2.draw(new Ellipse2D.Double(linkAreaXX.getX(), linkAreaXX.getY(), linkAreaXX.getWidth(), linkAreaXX.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 hh = linkW * getLabelLinkMargin(); double vv = linkH * getLabelLinkMargin(); 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); // the link area defines the dog-leg point for the linking lines to // the labels int depth = (int) (pieArea.getHeight() * this.depthFactor); Rectangle2D linkArea = new Rectangle2D.Double(linkX, linkY, linkW, linkH - depth); state.setLinkArea(linkArea); state.setPieArea(pieArea); state.setPieCenterX(pieArea.getCenterX()); state.setPieCenterY(pieArea.getCenterY() - depth / 2.0); state.setPieWRadius(pieArea.getWidth() / 2.0); state.setPieHRadius((pieArea.getHeight() - depth) / 2.0); // get the data source - return if null; PieDataset dataset = getDataset(); if (DatasetUtilities.isEmptyOrNull(getDataset())) { drawNoDataMessage(g2, plotArea); g2.setClip(savedClip); drawOutline(g2, plotArea); return; } // if too any elements if (dataset.getKeys().size() > plotArea.getWidth()) { String text = localizationResources.getString("Too_many_elements"); Font sfont = new Font("dialog", Font.BOLD, 10); g2.setFont(sfont); FontMetrics fm = g2.getFontMetrics(sfont); int stringWidth = fm.stringWidth(text); g2.drawString(text, (int) (plotArea.getX() + (plotArea.getWidth() - stringWidth) / 2), (int) (plotArea.getY() + (plotArea.getHeight() / 2))); return; } // if we are drawing a perfect circle, we need to readjust the top left // coordinates of the drawing area for the arcs to arrive at this // effect. if (isCircular()) { double min = Math.min(plotArea.getWidth(), plotArea.getHeight()) / 2; plotArea = new Rectangle2D.Double(plotArea.getCenterX() - min, plotArea.getCenterY() - min, 2 * min, 2 * min); } // get a list of keys... List<Comparable> sectionKeys = dataset.getKeys(); if (sectionKeys.isEmpty()) { return; } // establish the coordinates of the top left corner of the drawing area double arcX = pieArea.getX(); double arcY = pieArea.getY(); //g2.clip(clipArea); Composite originalComposite = g2.getComposite(); g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, getForegroundAlpha())); double totalValue = DatasetUtilities.calculatePieDatasetTotal(dataset); double runningTotal = 0; if (depth < 0) { return; // if depth is negative don't draw anything } ArrayList<Arc2D.Double> arcList = new ArrayList<Arc2D.Double>(); Arc2D.Double arc; Paint paint; Paint outlinePaint; Stroke outlineStroke; for (Comparable currentKey : sectionKeys) { Number dataValue = dataset.getValue(currentKey); if (dataValue == null) { arcList.add(null); continue; } double value = dataValue.doubleValue(); if (value <= 0) { arcList.add(null); continue; } double startAngle = getStartAngle(); double direction = getDirection().getFactor(); double angle1 = startAngle + (direction * (runningTotal * 360)) / totalValue; double angle2 = startAngle + (direction * (runningTotal + value) * 360) / totalValue; if (Math.abs(angle2 - angle1) > getMinimumArcAngleToDraw()) { arcList.add(new Arc2D.Double(arcX, arcY + depth, pieArea.getWidth(), pieArea.getHeight() - depth, angle1, angle2 - angle1, Arc2D.PIE)); } else { arcList.add(null); } runningTotal += value; } Shape oldClip = g2.getClip(); Ellipse2D top = new Ellipse2D.Double(pieArea.getX(), pieArea.getY(), pieArea.getWidth(), pieArea.getHeight() - depth); Ellipse2D bottom = new Ellipse2D.Double(pieArea.getX(), pieArea.getY() + depth, pieArea.getWidth(), pieArea.getHeight() - depth); Rectangle2D lower = new Rectangle2D.Double(top.getX(), top.getCenterY(), pieArea.getWidth(), bottom.getMaxY() - top.getCenterY()); Rectangle2D upper = new Rectangle2D.Double(pieArea.getX(), top.getY(), pieArea.getWidth(), bottom.getCenterY() - top.getY()); Area a = new Area(top); a.add(new Area(lower)); Area b = new Area(bottom); b.add(new Area(upper)); Area pie = new Area(a); pie.intersect(b); Area front = new Area(pie); front.subtract(new Area(top)); Area back = new Area(pie); back.subtract(new Area(bottom)); // draw the bottom circle int[] xs; int[] ys; int categoryCount = arcList.size(); for (int categoryIndex = 0; categoryIndex < categoryCount; categoryIndex++) { arc = arcList.get(categoryIndex); if (arc == null) { continue; } Comparable key = getSectionKey(categoryIndex); paint = lookupSectionPaint(key); outlinePaint = lookupSectionOutlinePaint(key); outlineStroke = lookupSectionOutlineStroke(key); g2.setPaint(paint); g2.fill(arc); g2.setPaint(outlinePaint); g2.setStroke(outlineStroke); g2.draw(arc); g2.setPaint(paint); Point2D p1 = arc.getStartPoint(); // draw the height xs = new int[] {(int) arc.getCenterX(), (int) arc.getCenterX(), (int) p1.getX(), (int) p1.getX()}; ys = new int[] {(int) arc.getCenterY(), (int) arc.getCenterY() - depth, (int) p1.getY() - depth, (int) p1.getY()}; Polygon polygon = new Polygon(xs, ys, 4); g2.setPaint(java.awt.Color.LIGHT_GRAY); g2.fill(polygon); g2.setPaint(outlinePaint); g2.setStroke(outlineStroke); g2.draw(polygon); g2.setPaint(paint); } g2.setPaint(Color.GRAY); g2.fill(back); g2.fill(front); // cycle through once drawing only the sides at the back... int cat = 0; for (Arc2D.Double segment : arcList) { if (segment != null) { Comparable key = getSectionKey(cat); paint = lookupSectionPaint(key); outlinePaint = lookupSectionOutlinePaint(key); outlineStroke = lookupSectionOutlineStroke(key); drawSide(g2, pieArea, segment, front, back, paint, outlinePaint, outlineStroke, false, true); } cat++; } // cycle through again drawing only the sides at the front... cat = 0; for (Arc2D.Double segment : arcList) { if (segment != null) { Comparable key = getSectionKey(cat); paint = lookupSectionPaint(key); outlinePaint = lookupSectionOutlinePaint(key); outlineStroke = lookupSectionOutlineStroke(key); drawSide(g2, pieArea, segment, front, back, paint, outlinePaint, outlineStroke, true, false); } cat++; } g2.setClip(oldClip); // draw the sections at the top of the pie (and set up tooltips)... Arc2D upperArc; for (int sectionIndex = 0; sectionIndex < categoryCount; sectionIndex++) { arc = arcList.get(sectionIndex); if (arc == null) { continue; } upperArc = new Arc2D.Double(arcX, arcY, pieArea.getWidth(), pieArea.getHeight() - depth, arc.getAngleStart(), arc.getAngleExtent(), Arc2D.PIE); Comparable currentKey = sectionKeys.get(sectionIndex); paint = lookupSectionPaint(currentKey, true); outlinePaint = lookupSectionOutlinePaint(currentKey); outlineStroke = lookupSectionOutlineStroke(currentKey); g2.setPaint(paint); g2.fill(upperArc); g2.setStroke(outlineStroke); g2.setPaint(outlinePaint); g2.draw(upperArc); // add a tooltip for the section... if (info != null) { EntityCollection entities = info.getOwner().getEntityCollection(); if (entities != null) { String tip = null; PieToolTipGenerator tipster = getToolTipGenerator(); if (tipster != null) { // @mgs: using the method's return value was missing tip = tipster.generateToolTip(dataset, currentKey); } String url = null; if (getURLGenerator() != null) { url = getURLGenerator().generateURL(dataset, currentKey, getPieIndex()); } PieSectionEntity entity = new PieSectionEntity( upperArc, dataset, getPieIndex(), sectionIndex, currentKey, tip, url); entities.add(entity); } } } List<Comparable> keys = dataset.getKeys(); Rectangle2D adjustedPlotArea = new Rectangle2D.Double( originalPlotArea.getX(), originalPlotArea.getY(), originalPlotArea.getWidth(), originalPlotArea.getHeight() - depth); if (getSimpleLabels()) { drawSimpleLabels(g2, keys, totalValue, adjustedPlotArea, linkArea, state); } else { drawLabels(g2, keys, totalValue, adjustedPlotArea, linkArea, state); } if (getShadowGenerator() != null) { BufferedImage shadowImage = getShadowGenerator().createDropShadow(dataImage); g2 = savedG2; g2.drawImage(shadowImage, (int) plotArea.getX() + getShadowGenerator().calculateOffsetX(), (int) plotArea.getY() + getShadowGenerator().calculateOffsetY(), null); g2.drawImage(dataImage, (int) plotArea.getX(), (int) plotArea.getY(), null); } g2.setClip(savedClip); g2.setComposite(originalComposite); drawOutline(g2, originalPlotArea); } /** * Draws the side of a pie section. * * @param g2 the graphics device. * @param plotArea the plot area. * @param arc the arc. * @param front the front of the pie. * @param back the back of the pie. * @param paint the color. * @param outlinePaint the outline paint. * @param outlineStroke the outline stroke. * @param drawFront draw the front? * @param drawBack draw the back? */ protected void drawSide(Graphics2D g2, Rectangle2D plotArea, Arc2D arc, Area front, Area back, Paint paint, Paint outlinePaint, Stroke outlineStroke, boolean drawFront, boolean drawBack) { if (getDarkerSides()) { if (paint instanceof Color) { Color c = (Color) paint; c = c.darker(); paint = c; } } double start = arc.getAngleStart(); double extent = arc.getAngleExtent(); double end = start + extent; g2.setStroke(outlineStroke); // for CLOCKWISE charts, the extent will be negative... if (extent < 0.0) { if (isAngleAtFront(start)) { // start at front if (!isAngleAtBack(end)) { if (extent > -180.0) { // the segment is entirely at the // front of the chart if (drawFront) { Area side = new Area(new Rectangle2D.Double( arc.getEndPoint().getX(), plotArea.getY(), arc.getStartPoint().getX() - arc.getEndPoint().getX(), plotArea.getHeight())); side.intersect(front); g2.setPaint(paint); g2.fill(side); g2.setPaint(outlinePaint); g2.draw(side); } } else { // the segment starts at the front, and wraps all // the way around // the back and finishes at the front again Area side1 = new Area(new Rectangle2D.Double( plotArea.getX(), plotArea.getY(), arc.getStartPoint().getX() - plotArea.getX(), plotArea.getHeight())); side1.intersect(front); Area side2 = new Area(new Rectangle2D.Double( arc.getEndPoint().getX(), plotArea.getY(), plotArea.getMaxX() - arc.getEndPoint().getX(), plotArea.getHeight())); side2.intersect(front); g2.setPaint(paint); if (drawFront) { g2.fill(side1); g2.fill(side2); } if (drawBack) { g2.fill(back); } g2.setPaint(outlinePaint); if (drawFront) { g2.draw(side1); g2.draw(side2); } if (drawBack) { g2.draw(back); } } } else { // starts at the front, finishes at the back (going // around the left side) if (drawBack) { Area side2 = new Area(new Rectangle2D.Double( plotArea.getX(), plotArea.getY(), arc.getEndPoint().getX() - plotArea.getX(), plotArea.getHeight())); side2.intersect(back); g2.setPaint(paint); g2.fill(side2); g2.setPaint(outlinePaint); g2.draw(side2); } if (drawFront) { Area side1 = new Area(new Rectangle2D.Double( plotArea.getX(), plotArea.getY(), arc.getStartPoint().getX() - plotArea.getX(), plotArea.getHeight())); side1.intersect(front); g2.setPaint(paint); g2.fill(side1); g2.setPaint(outlinePaint); g2.draw(side1); } } } else { // the segment starts at the back (still extending // CLOCKWISE) if (!isAngleAtFront(end)) { if (extent > -180.0) { // whole segment stays at the back if (drawBack) { Area side = new Area(new Rectangle2D.Double( arc.getStartPoint().getX(), plotArea.getY(), arc.getEndPoint().getX() - arc.getStartPoint().getX(), plotArea.getHeight())); side.intersect(back); g2.setPaint(paint); g2.fill(side); g2.setPaint(outlinePaint); g2.draw(side); } } else { // starts at the back, wraps around front, and // finishes at back again Area side1 = new Area(new Rectangle2D.Double( arc.getStartPoint().getX(), plotArea.getY(), plotArea.getMaxX() - arc.getStartPoint().getX(), plotArea.getHeight())); side1.intersect(back); Area side2 = new Area(new Rectangle2D.Double( plotArea.getX(), plotArea.getY(), arc.getEndPoint().getX() - plotArea.getX(), plotArea.getHeight())); side2.intersect(back); g2.setPaint(paint); if (drawBack) { g2.fill(side1); g2.fill(side2); } if (drawFront) { g2.fill(front); } g2.setPaint(outlinePaint); if (drawBack) { g2.draw(side1); g2.draw(side2); } if (drawFront) { g2.draw(front); } } } else { // starts at back, finishes at front (CLOCKWISE) if (drawBack) { Area side1 = new Area(new Rectangle2D.Double( arc.getStartPoint().getX(), plotArea.getY(), plotArea.getMaxX() - arc.getStartPoint().getX(), plotArea.getHeight())); side1.intersect(back); g2.setPaint(paint); g2.fill(side1); g2.setPaint(outlinePaint); g2.draw(side1); } if (drawFront) { Area side2 = new Area(new Rectangle2D.Double( arc.getEndPoint().getX(), plotArea.getY(), plotArea.getMaxX() - arc.getEndPoint().getX(), plotArea.getHeight())); side2.intersect(front); g2.setPaint(paint); g2.fill(side2); g2.setPaint(outlinePaint); g2.draw(side2); } } } } else if (extent > 0.0) { // the pie sections are arranged ANTICLOCKWISE if (isAngleAtFront(start)) { // segment starts at the front if (!isAngleAtBack(end)) { // and finishes at the front if (extent < 180.0) { // segment only occupies the front if (drawFront) { Area side = new Area(new Rectangle2D.Double( arc.getStartPoint().getX(), plotArea.getY(), arc.getEndPoint().getX() - arc.getStartPoint().getX(), plotArea.getHeight())); side.intersect(front); g2.setPaint(paint); g2.fill(side); g2.setPaint(outlinePaint); g2.draw(side); } } else { // segments wraps right around the back... Area side1 = new Area(new Rectangle2D.Double( arc.getStartPoint().getX(), plotArea.getY(), plotArea.getMaxX() - arc.getStartPoint().getX(), plotArea.getHeight())); side1.intersect(front); Area side2 = new Area(new Rectangle2D.Double( plotArea.getX(), plotArea.getY(), arc.getEndPoint().getX() - plotArea.getX(), plotArea.getHeight())); side2.intersect(front); g2.setPaint(paint); if (drawFront) { g2.fill(side1); g2.fill(side2); } if (drawBack) { g2.fill(back); } g2.setPaint(outlinePaint); if (drawFront) { g2.draw(side1); g2.draw(side2); } if (drawBack) { g2.draw(back); } } } else { // segments starts at front and finishes at back... if (drawBack) { Area side2 = new Area(new Rectangle2D.Double( arc.getEndPoint().getX(), plotArea.getY(), plotArea.getMaxX() - arc.getEndPoint().getX(), plotArea.getHeight())); side2.intersect(back); g2.setPaint(paint); g2.fill(side2); g2.setPaint(outlinePaint); g2.draw(side2); } if (drawFront) { Area side1 = new Area(new Rectangle2D.Double( arc.getStartPoint().getX(), plotArea.getY(), plotArea.getMaxX() - arc.getStartPoint().getX(), plotArea.getHeight())); side1.intersect(front); g2.setPaint(paint); g2.fill(side1); g2.setPaint(outlinePaint); g2.draw(side1); } } } else { // segment starts at back if (!isAngleAtFront(end)) { if (extent < 180.0) { // and finishes at back if (drawBack) { Area side = new Area(new Rectangle2D.Double( arc.getEndPoint().getX(), plotArea.getY(), arc.getStartPoint().getX() - arc.getEndPoint().getX(), plotArea.getHeight())); side.intersect(back); g2.setPaint(paint); g2.fill(side); g2.setPaint(outlinePaint); g2.draw(side); } } else { // starts at back and wraps right around to the // back again Area side1 = new Area(new Rectangle2D.Double( arc.getStartPoint().getX(), plotArea.getY(), plotArea.getX() - arc.getStartPoint().getX(), plotArea.getHeight())); side1.intersect(back); Area side2 = new Area(new Rectangle2D.Double( arc.getEndPoint().getX(), plotArea.getY(), plotArea.getMaxX() - arc.getEndPoint().getX(), plotArea.getHeight())); side2.intersect(back); g2.setPaint(paint); if (drawBack) { g2.fill(side1); g2.fill(side2); } if (drawFront) { g2.fill(front); } g2.setPaint(outlinePaint); if (drawBack) { g2.draw(side1); g2.draw(side2); } if (drawFront) { g2.draw(front); } } } else { // starts at the back and finishes at the front // (wrapping the left side) if (drawBack) { Area side1 = new Area(new Rectangle2D.Double( plotArea.getX(), plotArea.getY(), arc.getStartPoint().getX() - plotArea.getX(), plotArea.getHeight())); side1.intersect(back); g2.setPaint(paint); g2.fill(side1); g2.setPaint(outlinePaint); g2.draw(side1); } if (drawFront) { Area side2 = new Area(new Rectangle2D.Double( plotArea.getX(), plotArea.getY(), arc.getEndPoint().getX() - plotArea.getX(), plotArea.getHeight())); side2.intersect(front); g2.setPaint(paint); g2.fill(side2); g2.setPaint(outlinePaint); g2.draw(side2); } } } } } /** * Returns a short string describing the type of plot. * * @return <i>Pie 3D Plot</i>. */ @Override public String getPlotType() { return localizationResources.getString("Pie_3D_Plot"); } /** * A utility method that returns true if the angle represents a point at * the front of the 3D pie chart. 0 - 180 degrees is the back, 180 - 360 * is the front. * * @param angle the angle. * * @return A boolean. */ private boolean isAngleAtFront(double angle) { return (Math.sin(Math.toRadians(angle)) < 0.0); } /** * A utility method that returns true if the angle represents a point at * the back of the 3D pie chart. 0 - 180 degrees is the back, 180 - 360 * is the front. * * @param angle the angle. * * @return <code>true</code> if the angle is at the back of the pie. */ private boolean isAngleAtBack(double angle) { return (Math.sin(Math.toRadians(angle)) > 0.0); } /** * Tests this plot for equality with an arbitrary object. * * @param obj the object (<code>null</code> permitted). * * @return A boolean. */ @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof PiePlot3D)) { return false; } PiePlot3D that = (PiePlot3D) obj; if (this.depthFactor != that.depthFactor) { return false; } if (this.darkerSides != that.darkerSides) { return false; } return super.equals(obj); } }