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
084package org.jfree.chart.plot;
085
086import java.awt.AlphaComposite;
087import java.awt.Color;
088import java.awt.Composite;
089import java.awt.Font;
090import java.awt.FontMetrics;
091import java.awt.Graphics2D;
092import java.awt.Paint;
093import java.awt.Polygon;
094import java.awt.Shape;
095import java.awt.Stroke;
096import java.awt.geom.Arc2D;
097import java.awt.geom.Area;
098import java.awt.geom.Ellipse2D;
099import java.awt.geom.Point2D;
100import java.awt.geom.Rectangle2D;
101import java.io.Serializable;
102import java.util.ArrayList;
103import java.util.Iterator;
104import java.util.List;
105
106import org.jfree.chart.entity.EntityCollection;
107import org.jfree.chart.entity.PieSectionEntity;
108import org.jfree.chart.event.PlotChangeEvent;
109import org.jfree.chart.labels.PieToolTipGenerator;
110import org.jfree.data.general.DatasetUtilities;
111import org.jfree.data.general.PieDataset;
112import 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 */
121public 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}