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