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