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