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