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