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