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