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