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