    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
    089    package org.jfree.chart.plot;
    090
    091    import java.awt.AlphaComposite;
    092    import java.awt.Color;
    093    import java.awt.Composite;
    094    import java.awt.Font;
    095    import java.awt.FontMetrics;
    096    import java.awt.Graphics2D;
    097    import java.awt.Paint;
    098    import java.awt.Polygon;
    099    import java.awt.Rectangle;
    100    import java.awt.Shape;
    101    import java.awt.Stroke;
    102    import java.awt.geom.Arc2D;
    103    import java.awt.geom.Area;
    104    import java.awt.geom.Ellipse2D;
    105    import java.awt.geom.Point2D;
    106    import java.awt.geom.Rectangle2D;
    107    import java.awt.image.BufferedImage;
    108    import java.io.Serializable;
    109    import java.util.ArrayList;
    110    import java.util.Iterator;
    111    import java.util.List;
    112
    113    import org.jfree.chart.entity.EntityCollection;
    114    import org.jfree.chart.entity.PieSectionEntity;
    115    import org.jfree.chart.event.PlotChangeEvent;
    116    import org.jfree.chart.labels.PieToolTipGenerator;
    117    import org.jfree.data.general.DatasetUtilities;
    118    import org.jfree.data.general.PieDataset;
    119    import 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     */
    128    public 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    }