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