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