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