    001/* ===========================================================
    002 * JFreeChart : a free chart library for the Java(tm) platform
    003 * ===========================================================
    004 *
    005 * (C) Copyright 2000-2011, by Object Refinery Limited and Contributors.
    006 *
    007 * Project Info:  http://www.jfree.org/jfreechart/index.html
    008 *
    009 * This library is free software; you can redistribute it and/or modify it
    010 * under the terms of the GNU Lesser General Public License as published by
    011 * the Free Software Foundation; either version 2.1 of the License, or
    012 * (at your option) any later version.
    013 *
    014 * This library is distributed in the hope that it will be useful, but
    015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
    016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
    017 * License for more details.
    018 *
    019 * You should have received a copy of the GNU Lesser General Public
    020 * License along with this library; if not, write to the Free Software
    021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
    022 * USA.
    023 *
    024 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    025 * Other names may be trademarks of their respective owners.]
    026 *
    027 * --------------
    028 * PiePlot3D.java
    029 * --------------
    030 * (C) Copyright 2000-2009, by Object Refinery and Contributors.
    031 *
    032 * Original Author:  Tomer Peretz;
    033 * Contributor(s):   Richard Atkinson;
    034 *                   David Gilbert (for Object Refinery Limited);
    035 *                   Xun Kang;
    036 *                   Christian W. Zuckschwerdt;
    037 *                   Arnaud Lelievre;
    038 *                   Dave Crane;
    039 *                   Martin Hoeller;
    040 *
    041 * Changes
    042 * -------
    043 * 21-Jun-2002 : Version 1;
    044 * 31-Jul-2002 : Modified to use startAngle and direction, drawing modified so
    045 *               that charts render with foreground alpha < 1.0 (DG);
    046 * 05-Aug-2002 : Small modification to draw method to support URLs for HTML
    047 *               image maps (RA);
    048 * 26-Sep-2002 : Fixed errors reported by Checkstyle (DG);
    049 * 18-Oct-2002 : Added drawing bug fix sent in by Xun Kang, and made a couple
    050 *               of other related fixes (DG);
    051 * 30-Oct-2002 : Changed the PieDataset interface. Fixed another drawing
    052 *               bug (DG);
    053 * 12-Nov-2002 : Fixed null pointer exception for zero or negative values (DG);
    054 * 07-Mar-2003 : Modified to pass pieIndex on to PieSectionEntity (DG);
    055 * 21-Mar-2003 : Added workaround for bug id 620031 (DG);
    056 * 26-Mar-2003 : Implemented Serializable (DG);
    057 * 30-Jul-2003 : Modified entity constructor (CZ);
    058 * 29-Aug-2003 : Small changes for API updates in PiePlot class (DG);
    059 * 02-Sep-2003 : Fixed bug where the 'no data' message is not displayed (DG);
    060 * 08-Sep-2003 : Added internationalization via use of properties
    061 *               resourceBundle (RFE 690236) (AL);
    062 * 29-Oct-2003 : Added workaround for font alignment in PDF output (DG);
    063 * 20-Nov-2003 : Fixed bug 845289 (sides not showing) (DG);
    064 * 25-Nov-2003 : Added patch (845095) to fix outline paint issues (DG);
    065 * 10-Mar-2004 : Numerous changes to enhance labelling (DG);
    066 * 31-Mar-2004 : Adjusted plot area when label generator is null (DG);
    067 * 08-Apr-2004 : Added flag to PiePlot class to control the treatment of null
    068 *               values (DG);
    069 *               Added pieIndex to PieSectionEntity (DG);
    070 * 15-Nov-2004 : Removed creation of default tool tip generator (DG);
    071 * 16-Jun-2005 : Added default constructor (DG);
    072 * ------------- JFREECHART 1.0.x ---------------------------------------------
    073 * 27-Sep-2006 : Updated draw() method for new lookup methods (DG);
    074 * 22-Mar-2007 : Added equals() override (DG);
    075 * 18-Jun-2007 : Added handling for simple label option (DG);
    076 * 04-Oct-2007 : Added option to darken sides of plot - thanks to Alex Moots
    077 *               (see patch 1805262) (DG);
    078 * 21-Nov-2007 : Changed default depth factor, fixed labelling bugs and added
    079 *               debug code - see debug flags in PiePlot class (DG);
    080 * 20-Mar-2008 : Fixed bug 1920854 - multiple redraws of the section
    081 *               labels (DG);
    082 * 19-May-2009 : Fixed FindBugs warnings, patch by Michal Wozniak (DG);
    083 * 10-Jul-2009 : Added drop shaow support (DG);
    084 * 10-Oct-2011 : Localization fix: bug #3353913 (MH);
    085 * 18-Oct-2011 : Fix tooltip offset with shadow generator (DG);
    086 *
    087 */
    088
    089package org.jfree.chart.plot;
    090
    091import java.awt.AlphaComposite;
    092import java.awt.Color;
    093import java.awt.Composite;
    094import java.awt.Font;
    095import java.awt.FontMetrics;
    096import java.awt.Graphics2D;
    097import java.awt.Paint;
    098import java.awt.Polygon;
    099import java.awt.Rectangle;
    100import java.awt.Shape;
    101import java.awt.Stroke;
    102import java.awt.geom.Arc2D;
    103import java.awt.geom.Area;
    104import java.awt.geom.Ellipse2D;
    105import java.awt.geom.Point2D;
    106import java.awt.geom.Rectangle2D;
    107import java.awt.image.BufferedImage;
    108import java.io.Serializable;
    109import java.util.ArrayList;
    110import java.util.Iterator;
    111import java.util.List;
    112
    113import org.jfree.chart.entity.EntityCollection;
    114import org.jfree.chart.entity.PieSectionEntity;
    115import org.jfree.chart.event.PlotChangeEvent;
    116import org.jfree.chart.labels.PieToolTipGenerator;
    117import org.jfree.data.general.DatasetUtilities;
    118import org.jfree.data.general.PieDataset;
    119import org.jfree.ui.RectangleInsets;
    120
    121/**
    122 * A plot that displays data in the form of a 3D pie chart, using data from
    123 * any class that implements the {@link PieDataset} interface.
    124 * <P>
    125 * Although this class extends {@link PiePlot}, it does not currently support
    126 * exploded sections.
    127 */
    128public class PiePlot3D extends PiePlot implements Serializable {
    129
    130    /** For serialization. */
    131    private static final long serialVersionUID = 3408984188945161432L;
    132
    133    /** The factor of the depth of the pie from the plot height */
    134    private double depthFactor = 0.12;
    135
    136    /**
    137     * A flag that controls whether or not the sides of the pie chart
    138     * are rendered using a darker colour.
    139     *
    140     *  @since 1.0.7.
    141     */
    142    private boolean darkerSides = false;  // default preserves previous
    143                                          // behaviour
    144
    145    /**
    146     * Creates a new instance with no dataset.
    147     */
    148    public PiePlot3D() {
    149        this(null);
    150    }
    151
    152    /**
    153     * Creates a pie chart with a three dimensional effect using the specified
    154     * dataset.
    155     *
    156     * @param dataset  the dataset (<code>null</code> permitted).
    157     */
    158    public PiePlot3D(PieDataset dataset) {
    159        super(dataset);
    160        setCircular(false, false);
    161    }
    162
    163    /**
    164     * Returns the depth factor for the chart.
    165     *
    166     * @return The depth factor.
    167     *
    168     * @see #setDepthFactor(double)
    169     */
    170    public double getDepthFactor() {
    171        return this.depthFactor;
    172    }
    173
    174    /**
    175     * Sets the pie depth as a percentage of the height of the plot area, and
    176     * sends a {@link PlotChangeEvent} to all registered listeners.
    177     *
    178     * @param factor  the depth factor (for example, 0.20 is twenty percent).
    179     *
    180     * @see #getDepthFactor()
    181     */
    182    public void setDepthFactor(double factor) {
    183        this.depthFactor = factor;
    184        fireChangeEvent();
    185    }
    186
    187    /**
    188     * Returns a flag that controls whether or not the sides of the pie chart
    189     * are rendered using a darker colour.  This is only applied if the
    190     * section colour is an instance of {@link java.awt.Color}.
    191     *
    192     * @return A boolean.
    193     *
    194     * @see #setDarkerSides(boolean)
    195     *
    196     * @since 1.0.7
    197     */
    198    public boolean getDarkerSides() {
    199        return this.darkerSides;
    200    }
    201
    202    /**
    203     * Sets a flag that controls whether or not the sides of the pie chart
    204     * are rendered using a darker colour, and sends a {@link PlotChangeEvent}
    205     * to all registered listeners.  This is only applied if the
    206     * section colour is an instance of {@link java.awt.Color}.
    207     *
    208     * @param darker true to darken the sides, false to use the default
    209     *         behaviour.
    210     *
    211     * @see #getDarkerSides()
    212     *
    213     * @since 1.0.7.
    214     */
    215    public void setDarkerSides(boolean darker) {
    216        this.darkerSides = darker;
    217        fireChangeEvent();
    218    }
    219
    220    /**
    221     * Draws the plot on a Java 2D graphics device (such as the screen or a
    222     * printer).  This method is called by the
    223     * {@link org.jfree.chart.JFreeChart} class, you don't normally need
    224     * to call it yourself.
    225     *
    226     * @param g2  the graphics device.
    227     * @param plotArea  the area within which the plot should be drawn.
    228     * @param anchor  the anchor point.
    229     * @param parentState  the state from the parent plot, if there is one.
    230     * @param info  collects info about the drawing
    231     *              (<code>null</code> permitted).
    232     */
    233    public void draw(Graphics2D g2, Rectangle2D plotArea, Point2D anchor,
    234                     PlotState parentState,
    235                     PlotRenderingInfo info) {
    236
    237        // adjust for insets...
    238        RectangleInsets insets = getInsets();
    239        insets.trim(plotArea);
    240
    241        Rectangle2D originalPlotArea = (Rectangle2D) plotArea.clone();
    242        if (info != null) {
    243            info.setPlotArea(plotArea);
    244            info.setDataArea(plotArea);
    245        }
    246
    247        drawBackground(g2, plotArea);
    248
    249        Shape savedClip = g2.getClip();
    250        g2.clip(plotArea);
    251
    252        Graphics2D savedG2 = g2;
    253        BufferedImage dataImage = null;
    254        if (getShadowGenerator() != null) {
    255            dataImage = new BufferedImage((int) plotArea.getWidth(),
    256                (int) plotArea.getHeight(), BufferedImage.TYPE_INT_ARGB);
    257            g2 = dataImage.createGraphics();
    258            g2.translate(-plotArea.getX(), -plotArea.getY());
    259            g2.setRenderingHints(savedG2.getRenderingHints());
    260            originalPlotArea = (Rectangle2D) plotArea.clone();
    261        }
    262        // adjust the plot area by the interior spacing value
    263        double gapPercent = getInteriorGap();
    264        double labelPercent = 0.0;
    265        if (getLabelGenerator() != null) {
    266            labelPercent = getLabelGap() + getMaximumLabelWidth();
    267        }
    268        double gapHorizontal = plotArea.getWidth() * (gapPercent
    269                + labelPercent) * 2.0;
    270        double gapVertical = plotArea.getHeight() * gapPercent * 2.0;
    271
    272        if (DEBUG_DRAW_INTERIOR) {
    273            double hGap = plotArea.getWidth() * getInteriorGap();
    274            double vGap = plotArea.getHeight() * getInteriorGap();
    275            double igx1 = plotArea.getX() + hGap;
    276            double igx2 = plotArea.getMaxX() - hGap;
    277            double igy1 = plotArea.getY() + vGap;
    278            double igy2 = plotArea.getMaxY() - vGap;
    279            g2.setPaint(Color.lightGray);
    280            g2.draw(new Rectangle2D.Double(igx1, igy1, igx2 - igx1,
    281                    igy2 - igy1));
    282        }
    283
    284        double linkX = plotArea.getX() + gapHorizontal / 2;
    285        double linkY = plotArea.getY() + gapVertical / 2;
    286        double linkW = plotArea.getWidth() - gapHorizontal;
    287        double linkH = plotArea.getHeight() - gapVertical;
    288
    289        // make the link area a square if the pie chart is to be circular...
    290        if (isCircular()) { // is circular?
    291            double min = Math.min(linkW, linkH) / 2;
    292            linkX = (linkX + linkX + linkW) / 2 - min;
    293            linkY = (linkY + linkY + linkH) / 2 - min;
    294            linkW = 2 * min;
    295            linkH = 2 * min;
    296        }
    297
    298        PiePlotState state = initialise(g2, plotArea, this, null, info);
    299
    300        // the link area defines the dog leg points for the linking lines to
    301        // the labels
    302        Rectangle2D linkAreaXX = new Rectangle2D.Double(linkX, linkY, linkW,
    303                linkH * (1 - this.depthFactor));
    304        state.setLinkArea(linkAreaXX);
    305
    306        if (DEBUG_DRAW_LINK_AREA) {
    307            g2.setPaint(Color.blue);
    308            g2.draw(linkAreaXX);
    309            g2.setPaint(Color.yellow);
    310            g2.draw(new Ellipse2D.Double(linkAreaXX.getX(), linkAreaXX.getY(),
    311                    linkAreaXX.getWidth(), linkAreaXX.getHeight()));
    312        }
    313
    314        // the explode area defines the max circle/ellipse for the exploded pie
    315        // sections.
    316        // it is defined by shrinking the linkArea by the linkMargin factor.
    317        double hh = linkW * getLabelLinkMargin();
    318        double vv = linkH * getLabelLinkMargin();
    319        Rectangle2D explodeArea = new Rectangle2D.Double(linkX + hh / 2.0,
    320                linkY + vv / 2.0, linkW - hh, linkH - vv);
    321
    322        state.setExplodedPieArea(explodeArea);
    323
    324        // the pie area defines the circle/ellipse for regular pie sections.
    325        // it is defined by shrinking the explodeArea by the explodeMargin
    326        // factor.
    327        double maximumExplodePercent = getMaximumExplodePercent();
    328        double percent = maximumExplodePercent / (1.0 + maximumExplodePercent);
    329
    330        double h1 = explodeArea.getWidth() * percent;
    331        double v1 = explodeArea.getHeight() * percent;
    332        Rectangle2D pieArea = new Rectangle2D.Double(explodeArea.getX()
    333                + h1 / 2.0, explodeArea.getY() + v1 / 2.0,
    334                explodeArea.getWidth() - h1, explodeArea.getHeight() - v1);
    335
    336        // the link area defines the dog-leg point for the linking lines to
    337        // the labels
    338        int depth = (int) (pieArea.getHeight() * this.depthFactor);
    339        Rectangle2D linkArea = new Rectangle2D.Double(linkX, linkY, linkW,
    340                linkH - depth);
    341        state.setLinkArea(linkArea);
    342
    343        state.setPieArea(pieArea);
    344        state.setPieCenterX(pieArea.getCenterX());
    345        state.setPieCenterY(pieArea.getCenterY() - depth / 2.0);
    346        state.setPieWRadius(pieArea.getWidth() / 2.0);
    347        state.setPieHRadius((pieArea.getHeight() - depth) / 2.0);
    348
    349        // get the data source - return if null;
    350        PieDataset dataset = getDataset();
    351        if (DatasetUtilities.isEmptyOrNull(getDataset())) {
    352            drawNoDataMessage(g2, plotArea);
    353            g2.setClip(savedClip);
    354            drawOutline(g2, plotArea);
    355            return;
    356        }
    357
    358        // if too any elements
    359        if (dataset.getKeys().size() > plotArea.getWidth()) {
    360            String text = localizationResources.getString("Too_many_elements");
    361            Font sfont = new Font("dialog", Font.BOLD, 10);
    362            g2.setFont(sfont);
    363            FontMetrics fm = g2.getFontMetrics(sfont);
    364            int stringWidth = fm.stringWidth(text);
    365
    366            g2.drawString(text, (int) (plotArea.getX() + (plotArea.getWidth()
    367                    - stringWidth) / 2), (int) (plotArea.getY()
    368                    + (plotArea.getHeight() / 2)));
    369            return;
    370        }
    371        // if we are drawing a perfect circle, we need to readjust the top left
    372        // coordinates of the drawing area for the arcs to arrive at this
    373        // effect.
    374        if (isCircular()) {
    375            double min = Math.min(plotArea.getWidth(),
    376                    plotArea.getHeight()) / 2;
    377            plotArea = new Rectangle2D.Double(plotArea.getCenterX() - min,
    378                    plotArea.getCenterY() - min, 2 * min, 2 * min);
    379        }
    380        // get a list of keys...
    381        List sectionKeys = dataset.getKeys();
    382
    383        if (sectionKeys.size() == 0) {
    384            return;
    385        }
    386
    387        // establish the coordinates of the top left corner of the drawing area
    388        double arcX = pieArea.getX();
    389        double arcY = pieArea.getY();
    390
    391        //g2.clip(clipArea);
    392        Composite originalComposite = g2.getComposite();
    393        g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
    394                getForegroundAlpha()));
    395
    396        double totalValue = DatasetUtilities.calculatePieDatasetTotal(dataset);
    397        double runningTotal = 0;
    398        if (depth < 0) {
    399            return;  // if depth is negative don't draw anything
    400        }
    401
    402        ArrayList arcList = new ArrayList();
    403        Arc2D.Double arc;
    404        Paint paint;
    405        Paint outlinePaint;
    406        Stroke outlineStroke;
    407
    408        Iterator iterator = sectionKeys.iterator();
    409        while (iterator.hasNext()) {
    410
    411            Comparable currentKey = (Comparable) iterator.next();
    412            Number dataValue = dataset.getValue(currentKey);
    413            if (dataValue == null) {
    414                arcList.add(null);
    415                continue;
    416            }
    417            double value = dataValue.doubleValue();
    418            if (value <= 0) {
    419                arcList.add(null);
    420                continue;
    421            }
    422            double startAngle = getStartAngle();
    423            double direction = getDirection().getFactor();
    424            double angle1 = startAngle + (direction * (runningTotal * 360))
    425                    / totalValue;
    426            double angle2 = startAngle + (direction * (runningTotal + value)
    427                    * 360) / totalValue;
    428            if (Math.abs(angle2 - angle1) > getMinimumArcAngleToDraw()) {
    429                arcList.add(new Arc2D.Double(arcX, arcY + depth,
    430                        pieArea.getWidth(), pieArea.getHeight() - depth,
    431                        angle1, angle2 - angle1, Arc2D.PIE));
    432            }
    433            else {
    434                arcList.add(null);
    435            }
    436            runningTotal += value;
    437        }
    438
    439        Shape oldClip = g2.getClip();
    440
    441        Ellipse2D top = new Ellipse2D.Double(pieArea.getX(), pieArea.getY(),
    442                pieArea.getWidth(), pieArea.getHeight() - depth);
    443
    444        Ellipse2D bottom = new Ellipse2D.Double(pieArea.getX(), pieArea.getY()
    445                + depth, pieArea.getWidth(), pieArea.getHeight() - depth);
    446
    447        Rectangle2D lower = new Rectangle2D.Double(top.getX(),
    448                top.getCenterY(), pieArea.getWidth(), bottom.getMaxY()
    449                - top.getCenterY());
    450
    451        Rectangle2D upper = new Rectangle2D.Double(pieArea.getX(), top.getY(),
    452                pieArea.getWidth(), bottom.getCenterY() - top.getY());
    453
    454        Area a = new Area(top);
    455        a.add(new Area(lower));
    456        Area b = new Area(bottom);
    457        b.add(new Area(upper));
    458        Area pie = new Area(a);
    459        pie.intersect(b);
    460
    461        Area front = new Area(pie);
    462        front.subtract(new Area(top));
    463
    464        Area back = new Area(pie);
    465        back.subtract(new Area(bottom));
    466
    467        // draw the bottom circle
    468        int[] xs;
    469        int[] ys;
    470
    471        int categoryCount = arcList.size();
    472        for (int categoryIndex = 0; categoryIndex < categoryCount;
    473                 categoryIndex++) {
    474            arc = (Arc2D.Double) arcList.get(categoryIndex);
    475            if (arc == null) {
    476                continue;
    477            }
    478            Comparable key = getSectionKey(categoryIndex);
    479            paint = lookupSectionPaint(key);
    480            outlinePaint = lookupSectionOutlinePaint(key);
    481            outlineStroke = lookupSectionOutlineStroke(key);
    482            g2.setPaint(paint);
    483            g2.fill(arc);
    484            g2.setPaint(outlinePaint);
    485            g2.setStroke(outlineStroke);
    486            g2.draw(arc);
    487            g2.setPaint(paint);
    488
    489            Point2D p1 = arc.getStartPoint();
    490
    491            // draw the height
    492            xs = new int[] {(int) arc.getCenterX(), (int) arc.getCenterX(),
    493                    (int) p1.getX(), (int) p1.getX()};
    494            ys = new int[] {(int) arc.getCenterY(), (int) arc.getCenterY()
    495                    - depth, (int) p1.getY() - depth, (int) p1.getY()};
    496            Polygon polygon = new Polygon(xs, ys, 4);
    497            g2.setPaint(java.awt.Color.lightGray);
    498            g2.fill(polygon);
    499            g2.setPaint(outlinePaint);
    500            g2.setStroke(outlineStroke);
    501            g2.draw(polygon);
    502            g2.setPaint(paint);
    503
    504        }
    505
    506        g2.setPaint(Color.gray);
    507        g2.fill(back);
    508        g2.fill(front);
    509
    510        // cycle through once drawing only the sides at the back...
    511        int cat = 0;
    512        iterator = arcList.iterator();
    513        while (iterator.hasNext()) {
    514            Arc2D segment = (Arc2D) iterator.next();
    515            if (segment != null) {
    516                Comparable key = getSectionKey(cat);
    517                paint = lookupSectionPaint(key);
    518                outlinePaint = lookupSectionOutlinePaint(key);
    519                outlineStroke = lookupSectionOutlineStroke(key);
    520                drawSide(g2, pieArea, segment, front, back, paint,
    521                        outlinePaint, outlineStroke, false, true);
    522            }
    523            cat++;
    524        }
    525
    526        // cycle through again drawing only the sides at the front...
    527        cat = 0;
    528        iterator = arcList.iterator();
    529        while (iterator.hasNext()) {
    530            Arc2D segment = (Arc2D) iterator.next();
    531            if (segment != null) {
    532                Comparable key = getSectionKey(cat);
    533                paint = lookupSectionPaint(key);
    534                outlinePaint = lookupSectionOutlinePaint(key);
    535                outlineStroke = lookupSectionOutlineStroke(key);
    536                drawSide(g2, pieArea, segment, front, back, paint,
    537                        outlinePaint, outlineStroke, true, false);
    538            }
    539            cat++;
    540        }
    541
    542        g2.setClip(oldClip);
    543
    544        // draw the sections at the top of the pie (and set up tooltips)...
    545        Arc2D upperArc;
    546        for (int sectionIndex = 0; sectionIndex < categoryCount;
    547                 sectionIndex++) {
    548            arc = (Arc2D.Double) arcList.get(sectionIndex);
    549            if (arc == null) {
    550                continue;
    551            }
    552            upperArc = new Arc2D.Double(arcX, arcY, pieArea.getWidth(),
    553                    pieArea.getHeight() - depth, arc.getAngleStart(),
    554                    arc.getAngleExtent(), Arc2D.PIE);
    555
    556            Comparable currentKey = (Comparable) sectionKeys.get(sectionIndex);
    557            paint = lookupSectionPaint(currentKey, true);
    558            outlinePaint = lookupSectionOutlinePaint(currentKey);
    559            outlineStroke = lookupSectionOutlineStroke(currentKey);
    560            g2.setPaint(paint);
    561            g2.fill(upperArc);
    562            g2.setStroke(outlineStroke);
    563            g2.setPaint(outlinePaint);
    564            g2.draw(upperArc);
    565
    566           // add a tooltip for the section...
    567            if (info != null) {
    568                EntityCollection entities
    569                        = info.getOwner().getEntityCollection();
    570                if (entities != null) {
    571                    String tip = null;
    572                    PieToolTipGenerator tipster = getToolTipGenerator();
    573                    if (tipster != null) {
    574                        // @mgs: using the method's return value was missing
    575                        tip = tipster.generateToolTip(dataset, currentKey);
    576                    }
    577                    String url = null;
    578                    if (getURLGenerator() != null) {
    579                        url = getURLGenerator().generateURL(dataset, currentKey,
    580                                getPieIndex());
    581                    }
    582                    PieSectionEntity entity = new PieSectionEntity(
    583                            upperArc, dataset, getPieIndex(), sectionIndex,
    584                            currentKey, tip, url);
    585                    entities.add(entity);
    586                }
    587            }
    588        }
    589
    590        List keys = dataset.getKeys();
    591        Rectangle2D adjustedPlotArea = new Rectangle2D.Double(
    592                originalPlotArea.getX(), originalPlotArea.getY(),
    593                originalPlotArea.getWidth(), originalPlotArea.getHeight()
    594                - depth);
    595        if (getSimpleLabels()) {
    596            drawSimpleLabels(g2, keys, totalValue, adjustedPlotArea,
    597                    linkArea, state);
    598        }
    599        else {
    600            drawLabels(g2, keys, totalValue, adjustedPlotArea, linkArea,
    601                    state);
    602        }
    603
    604        if (getShadowGenerator() != null) {
    605            BufferedImage shadowImage
    606                    = getShadowGenerator().createDropShadow(dataImage);
    607            g2 = savedG2;
    608            g2.drawImage(shadowImage, (int) plotArea.getX()
    609                    + getShadowGenerator().calculateOffsetX(),
    610                    (int) plotArea.getY()
    611                    + getShadowGenerator().calculateOffsetY(), null);
    612            g2.drawImage(dataImage, (int) plotArea.getX(),
    613                    (int) plotArea.getY(), null);
    614        }
    615
    616        g2.setClip(savedClip);
    617        g2.setComposite(originalComposite);
    618        drawOutline(g2, originalPlotArea);
    619
    620    }
    621
    622    /**
    623     * Draws the side of a pie section.
    624     *
    625     * @param g2  the graphics device.
    626     * @param plotArea  the plot area.
    627     * @param arc  the arc.
    628     * @param front  the front of the pie.
    629     * @param back  the back of the pie.
    630     * @param paint  the color.
    631     * @param outlinePaint  the outline paint.
    632     * @param outlineStroke  the outline stroke.
    633     * @param drawFront  draw the front?
    634     * @param drawBack  draw the back?
    635     */
    636    protected void drawSide(Graphics2D g2,
    637                            Rectangle2D plotArea,
    638                            Arc2D arc,
    639                            Area front,
    640                            Area back,
    641                            Paint paint,
    642                            Paint outlinePaint,
    643                            Stroke outlineStroke,
    644                            boolean drawFront,
    645                            boolean drawBack) {
    646
    647        if (getDarkerSides()) {
    648            if (paint instanceof Color) {
    649                Color c = (Color) paint;
    650                c = c.darker();
    651                paint = c;
    652            }
    653        }
    654
    655        double start = arc.getAngleStart();
    656        double extent = arc.getAngleExtent();
    657        double end = start + extent;
    658
    659        g2.setStroke(outlineStroke);
    660
    661        // for CLOCKWISE charts, the extent will be negative...
    662        if (extent < 0.0) {
    663
    664            if (isAngleAtFront(start)) {  // start at front
    665
    666                if (!isAngleAtBack(end)) {
    667
    668                    if (extent > -180.0) {  // the segment is entirely at the
    669                                            // front of the chart
    670                        if (drawFront) {
    671                            Area side = new Area(new Rectangle2D.Double(
    672                                    arc.getEndPoint().getX(), plotArea.getY(),
    673                                    arc.getStartPoint().getX()
    674                                    - arc.getEndPoint().getX(),
    675                                    plotArea.getHeight()));
    676                            side.intersect(front);
    677                            g2.setPaint(paint);
    678                            g2.fill(side);
    679                            g2.setPaint(outlinePaint);
    680                            g2.draw(side);
    681                        }
    682                    }
    683                    else {  // the segment starts at the front, and wraps all
    684                            // the way around
    685                            // the back and finishes at the front again
    686                        Area side1 = new Area(new Rectangle2D.Double(
    687                                plotArea.getX(), plotArea.getY(),
    688                                arc.getStartPoint().getX() - plotArea.getX(),
    689                                plotArea.getHeight()));
    690                        side1.intersect(front);
    691
    692                        Area side2 = new Area(new Rectangle2D.Double(
    693                                arc.getEndPoint().getX(), plotArea.getY(),
    694                                plotArea.getMaxX() - arc.getEndPoint().getX(),
    695                                plotArea.getHeight()));
    696
    697                        side2.intersect(front);
    698                        g2.setPaint(paint);
    699                        if (drawFront) {
    700                            g2.fill(side1);
    701                            g2.fill(side2);
    702                        }
    703
    704                        if (drawBack) {
    705                            g2.fill(back);
    706                        }
    707
    708                        g2.setPaint(outlinePaint);
    709                        if (drawFront) {
    710                            g2.draw(side1);
    711                            g2.draw(side2);
    712                        }
    713
    714                        if (drawBack) {
    715                            g2.draw(back);
    716                        }
    717
    718                    }
    719                }
    720                else {  // starts at the front, finishes at the back (going
    721                        // around the left side)
    722
    723                    if (drawBack) {
    724                        Area side2 = new Area(new Rectangle2D.Double(
    725                                plotArea.getX(), plotArea.getY(),
    726                                arc.getEndPoint().getX() - plotArea.getX(),
    727                                plotArea.getHeight()));
    728                        side2.intersect(back);
    729                        g2.setPaint(paint);
    730                        g2.fill(side2);
    731                        g2.setPaint(outlinePaint);
    732                        g2.draw(side2);
    733                    }
    734
    735                    if (drawFront) {
    736                        Area side1 = new Area(new Rectangle2D.Double(
    737                                plotArea.getX(), plotArea.getY(),
    738                                arc.getStartPoint().getX() - plotArea.getX(),
    739                                plotArea.getHeight()));
    740                        side1.intersect(front);
    741                        g2.setPaint(paint);
    742                        g2.fill(side1);
    743                        g2.setPaint(outlinePaint);
    744                        g2.draw(side1);
    745                    }
    746                }
    747            }
    748            else {  // the segment starts at the back (still extending
    749                    // CLOCKWISE)
    750
    751                if (!isAngleAtFront(end)) {
    752                    if (extent > -180.0) {  // whole segment stays at the back
    753                        if (drawBack) {
    754                            Area side = new Area(new Rectangle2D.Double(
    755                                    arc.getStartPoint().getX(), plotArea.getY(),
    756                                    arc.getEndPoint().getX()
    757                                    - arc.getStartPoint().getX(),
    758                                    plotArea.getHeight()));
    759                            side.intersect(back);
    760                            g2.setPaint(paint);
    761                            g2.fill(side);
    762                            g2.setPaint(outlinePaint);
    763                            g2.draw(side);
    764                        }
    765                    }
    766                    else {  // starts at the back, wraps around front, and
    767                            // finishes at back again
    768                        Area side1 = new Area(new Rectangle2D.Double(
    769                                arc.getStartPoint().getX(), plotArea.getY(),
    770                                plotArea.getMaxX() - arc.getStartPoint().getX(),
    771                                plotArea.getHeight()));
    772                        side1.intersect(back);
    773
    774                        Area side2 = new Area(new Rectangle2D.Double(
    775                                plotArea.getX(), plotArea.getY(),
    776                                arc.getEndPoint().getX() - plotArea.getX(),
    777                                plotArea.getHeight()));
    778
    779                        side2.intersect(back);
    780
    781                        g2.setPaint(paint);
    782                        if (drawBack) {
    783                            g2.fill(side1);
    784                            g2.fill(side2);
    785                        }
    786
    787                        if (drawFront) {
    788                            g2.fill(front);
    789                        }
    790
    791                        g2.setPaint(outlinePaint);
    792                        if (drawBack) {
    793                            g2.draw(side1);
    794                            g2.draw(side2);
    795                        }
    796
    797                        if (drawFront) {
    798                            g2.draw(front);
    799                        }
    800
    801                    }
    802                }
    803                else {  // starts at back, finishes at front (CLOCKWISE)
    804
    805                    if (drawBack) {
    806                        Area side1 = new Area(new Rectangle2D.Double(
    807                                arc.getStartPoint().getX(), plotArea.getY(),
    808                                plotArea.getMaxX() - arc.getStartPoint().getX(),
    809                                plotArea.getHeight()));
    810                        side1.intersect(back);
    811                        g2.setPaint(paint);
    812                        g2.fill(side1);
    813                        g2.setPaint(outlinePaint);
    814                        g2.draw(side1);
    815                    }
    816
    817                    if (drawFront) {
    818                        Area side2 = new Area(new Rectangle2D.Double(
    819                                arc.getEndPoint().getX(), plotArea.getY(),
    820                                plotArea.getMaxX() - arc.getEndPoint().getX(),
    821                                plotArea.getHeight()));
    822                        side2.intersect(front);
    823                        g2.setPaint(paint);
    824                        g2.fill(side2);
    825                        g2.setPaint(outlinePaint);
    826                        g2.draw(side2);
    827                    }
    828
    829                }
    830            }
    831        }
    832        else if (extent > 0.0) {  // the pie sections are arranged ANTICLOCKWISE
    833
    834            if (isAngleAtFront(start)) {  // segment starts at the front
    835
    836                if (!isAngleAtBack(end)) {  // and finishes at the front
    837
    838                    if (extent < 180.0) {  // segment only occupies the front
    839                        if (drawFront) {
    840                            Area side = new Area(new Rectangle2D.Double(
    841                                    arc.getStartPoint().getX(), plotArea.getY(),
    842                                    arc.getEndPoint().getX()
    843                                    - arc.getStartPoint().getX(),
    844                                    plotArea.getHeight()));
    845                            side.intersect(front);
    846                            g2.setPaint(paint);
    847                            g2.fill(side);
    848                            g2.setPaint(outlinePaint);
    849                            g2.draw(side);
    850                        }
    851                    }
    852                    else {  // segments wraps right around the back...
    853                        Area side1 = new Area(new Rectangle2D.Double(
    854                                arc.getStartPoint().getX(), plotArea.getY(),
    855                                plotArea.getMaxX() - arc.getStartPoint().getX(),
    856                                plotArea.getHeight()));
    857                        side1.intersect(front);
    858
    859                        Area side2 = new Area(new Rectangle2D.Double(
    860                                plotArea.getX(), plotArea.getY(),
    861                                arc.getEndPoint().getX() - plotArea.getX(),
    862                                plotArea.getHeight()));
    863                        side2.intersect(front);
    864
    865                        g2.setPaint(paint);
    866                        if (drawFront) {
    867                            g2.fill(side1);
    868                            g2.fill(side2);
    869                        }
    870
    871                        if (drawBack) {
    872                            g2.fill(back);
    873                        }
    874
    875                        g2.setPaint(outlinePaint);
    876                        if (drawFront) {
    877                            g2.draw(side1);
    878                            g2.draw(side2);
    879                        }
    880
    881                        if (drawBack) {
    882                            g2.draw(back);
    883                        }
    884
    885                    }
    886                }
    887                else {  // segments starts at front and finishes at back...
    888                    if (drawBack) {
    889                        Area side2 = new Area(new Rectangle2D.Double(
    890                                arc.getEndPoint().getX(), plotArea.getY(),
    891                                plotArea.getMaxX() - arc.getEndPoint().getX(),
    892                                plotArea.getHeight()));
    893                        side2.intersect(back);
    894                        g2.setPaint(paint);
    895                        g2.fill(side2);
    896                        g2.setPaint(outlinePaint);
    897                        g2.draw(side2);
    898                    }
    899
    900                    if (drawFront) {
    901                        Area side1 = new Area(new Rectangle2D.Double(
    902                                arc.getStartPoint().getX(), plotArea.getY(),
    903                                plotArea.getMaxX() - arc.getStartPoint().getX(),
    904                                plotArea.getHeight()));
    905                        side1.intersect(front);
    906                        g2.setPaint(paint);
    907                        g2.fill(side1);
    908                        g2.setPaint(outlinePaint);
    909                        g2.draw(side1);
    910                    }
    911                }
    912            }
    913            else {  // segment starts at back
    914
    915                if (!isAngleAtFront(end)) {
    916                    if (extent < 180.0) {  // and finishes at back
    917                        if (drawBack) {
    918                            Area side = new Area(new Rectangle2D.Double(
    919                                    arc.getEndPoint().getX(), plotArea.getY(),
    920                                    arc.getStartPoint().getX()
    921                                    - arc.getEndPoint().getX(),
    922                                    plotArea.getHeight()));
    923                            side.intersect(back);
    924                            g2.setPaint(paint);
    925                            g2.fill(side);
    926                            g2.setPaint(outlinePaint);
    927                            g2.draw(side);
    928                        }
    929                    }
    930                    else {  // starts at back and wraps right around to the
    931                            // back again
    932                        Area side1 = new Area(new Rectangle2D.Double(
    933                                arc.getStartPoint().getX(), plotArea.getY(),
    934                                plotArea.getX() - arc.getStartPoint().getX(),
    935                                plotArea.getHeight()));
    936                        side1.intersect(back);
    937
    938                        Area side2 = new Area(new Rectangle2D.Double(
    939                                arc.getEndPoint().getX(), plotArea.getY(),
    940                                plotArea.getMaxX() - arc.getEndPoint().getX(),
    941                                plotArea.getHeight()));
    942                        side2.intersect(back);
    943
    944                        g2.setPaint(paint);
    945                        if (drawBack) {
    946                            g2.fill(side1);
    947                            g2.fill(side2);
    948                        }
    949
    950                        if (drawFront) {
    951                            g2.fill(front);
    952                        }
    953
    954                        g2.setPaint(outlinePaint);
    955                        if (drawBack) {
    956                            g2.draw(side1);
    957                            g2.draw(side2);
    958                        }
    959
    960                        if (drawFront) {
    961                            g2.draw(front);
    962                        }
    963
    964                    }
    965                }
    966                else {  // starts at the back and finishes at the front
    967                        // (wrapping the left side)
    968                    if (drawBack) {
    969                        Area side1 = new Area(new Rectangle2D.Double(
    970                                plotArea.getX(), plotArea.getY(),
    971                                arc.getStartPoint().getX() - plotArea.getX(),
    972                                plotArea.getHeight()));
    973                        side1.intersect(back);
    974                        g2.setPaint(paint);
    975                        g2.fill(side1);
    976                        g2.setPaint(outlinePaint);
    977                        g2.draw(side1);
    978                    }
    979
    980                    if (drawFront) {
    981                        Area side2 = new Area(new Rectangle2D.Double(
    982                                plotArea.getX(), plotArea.getY(),
    983                                arc.getEndPoint().getX() - plotArea.getX(),
    984                                plotArea.getHeight()));
    985                        side2.intersect(front);
    986                        g2.setPaint(paint);
    987                        g2.fill(side2);
    988                        g2.setPaint(outlinePaint);
    989                        g2.draw(side2);
    990                    }
    991                }
    992            }
    993
    994        }
    995
    996    }
    997
    998    /**
    999     * Returns a short string describing the type of plot.
    1000     *
    1001     * @return <i>Pie 3D Plot</i>.
    1002     */
    1003    public String getPlotType() {
    1004        return localizationResources.getString("Pie_3D_Plot");
    1005    }
    1006
    1007    /**
    1008     * A utility method that returns true if the angle represents a point at
    1009     * the front of the 3D pie chart.  0 - 180 degrees is the back, 180 - 360
    1010     * is the front.
    1011     *
    1012     * @param angle  the angle.
    1013     *
    1014     * @return A boolean.
    1015     */
    1016    private boolean isAngleAtFront(double angle) {
    1017        return (Math.sin(Math.toRadians(angle)) < 0.0);
    1018    }
    1019
    1020    /**
    1021     * A utility method that returns true if the angle represents a point at
    1022     * the back of the 3D pie chart.  0 - 180 degrees is the back, 180 - 360
    1023     * is the front.
    1024     *
    1025     * @param angle  the angle.
    1026     *
    1027     * @return <code>true</code> if the angle is at the back of the pie.
    1028     */
    1029    private boolean isAngleAtBack(double angle) {
    1030        return (Math.sin(Math.toRadians(angle)) > 0.0);
    1031    }
    1032
    1033    /**
    1034     * Tests this plot for equality with an arbitrary object.
    1035     *
    1036     * @param obj  the object (<code>null</code> permitted).
    1037     *
    1038     * @return A boolean.
    1039     */
    1040    public boolean equals(Object obj) {
    1041        if (obj == this) {
    1042            return true;
    1043        }
    1044        if (!(obj instanceof PiePlot3D)) {
    1045            return false;
    1046        }
    1047        PiePlot3D that = (PiePlot3D) obj;
    1048        if (this.depthFactor != that.depthFactor) {
    1049            return false;
    1050        }
    1051        if (this.darkerSides != that.darkerSides) {
    1052            return false;
    1053        }
    1054        return super.equals(obj);
    1055    }
    1056
    1057}