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