001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2011, 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-2009, 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 *
041 * Changes
042 * -------
043 * 21-Jun-2002 : Version 1;
044 * 31-Jul-2002 : Modified to use startAngle and direction, drawing modified so
045 *               that charts render with foreground alpha < 1.0 (DG);
046 * 05-Aug-2002 : Small modification to draw method to support URLs for HTML
047 *               image maps (RA);
048 * 26-Sep-2002 : Fixed errors reported by Checkstyle (DG);
049 * 18-Oct-2002 : Added drawing bug fix sent in by Xun Kang, and made a couple
050 *               of other related fixes (DG);
051 * 30-Oct-2002 : Changed the PieDataset interface. Fixed another drawing
052 *               bug (DG);
053 * 12-Nov-2002 : Fixed null pointer exception for zero or negative values (DG);
054 * 07-Mar-2003 : Modified to pass pieIndex on to PieSectionEntity (DG);
055 * 21-Mar-2003 : Added workaround for bug id 620031 (DG);
056 * 26-Mar-2003 : Implemented Serializable (DG);
057 * 30-Jul-2003 : Modified entity constructor (CZ);
058 * 29-Aug-2003 : Small changes for API updates in PiePlot class (DG);
059 * 02-Sep-2003 : Fixed bug where the 'no data' message is not displayed (DG);
060 * 08-Sep-2003 : Added internationalization via use of properties
061 *               resourceBundle (RFE 690236) (AL);
062 * 29-Oct-2003 : Added workaround for font alignment in PDF output (DG);
063 * 20-Nov-2003 : Fixed bug 845289 (sides not showing) (DG);
064 * 25-Nov-2003 : Added patch (845095) to fix outline paint issues (DG);
065 * 10-Mar-2004 : Numerous changes to enhance labelling (DG);
066 * 31-Mar-2004 : Adjusted plot area when label generator is null (DG);
067 * 08-Apr-2004 : Added flag to PiePlot class to control the treatment of null
068 *               values (DG);
069 *               Added pieIndex to PieSectionEntity (DG);
070 * 15-Nov-2004 : Removed creation of default tool tip generator (DG);
071 * 16-Jun-2005 : Added default constructor (DG);
072 * ------------- JFREECHART 1.0.x ---------------------------------------------
073 * 27-Sep-2006 : Updated draw() method for new lookup methods (DG);
074 * 22-Mar-2007 : Added equals() override (DG);
075 * 18-Jun-2007 : Added handling for simple label option (DG);
076 * 04-Oct-2007 : Added option to darken sides of plot - thanks to Alex Moots
077 *               (see patch 1805262) (DG);
078 * 21-Nov-2007 : Changed default depth factor, fixed labelling bugs and added
079 *               debug code - see debug flags in PiePlot class (DG);
080 * 20-Mar-2008 : Fixed bug 1920854 - multiple redraws of the section
081 *               labels (DG);
082 * 19-May-2009 : Fixed FindBugs warnings, patch by Michal Wozniak (DG);
083 * 10-Jul-2009 : Added drop shaow support (DG);
084 * 10-Oct-2011 : Localization fix: bug #3353913 (MH);
085 * 18-Oct-2011 : Fix tooltip offset with shadow generator (DG);
086 * 
087 */
088
089package org.jfree.chart.plot;
090
091import java.awt.AlphaComposite;
092import java.awt.Color;
093import java.awt.Composite;
094import java.awt.Font;
095import java.awt.FontMetrics;
096import java.awt.Graphics2D;
097import java.awt.Paint;
098import java.awt.Polygon;
099import java.awt.Rectangle;
100import java.awt.Shape;
101import java.awt.Stroke;
102import java.awt.geom.Arc2D;
103import java.awt.geom.Area;
104import java.awt.geom.Ellipse2D;
105import java.awt.geom.Point2D;
106import java.awt.geom.Rectangle2D;
107import java.awt.image.BufferedImage;
108import java.io.Serializable;
109import java.util.ArrayList;
110import java.util.Iterator;
111import java.util.List;
112
113import org.jfree.chart.entity.EntityCollection;
114import org.jfree.chart.entity.PieSectionEntity;
115import org.jfree.chart.event.PlotChangeEvent;
116import org.jfree.chart.labels.PieToolTipGenerator;
117import org.jfree.data.general.DatasetUtilities;
118import org.jfree.data.general.PieDataset;
119import org.jfree.ui.RectangleInsets;
120
121/**
122 * A plot that displays data in the form of a 3D pie chart, using data from
123 * any class that implements the {@link PieDataset} interface.
124 * <P>
125 * Although this class extends {@link PiePlot}, it does not currently support
126 * exploded sections.
127 */
128public class PiePlot3D extends PiePlot implements Serializable {
129
130    /** For serialization. */
131    private static final long serialVersionUID = 3408984188945161432L;
132
133    /** The factor of the depth of the pie from the plot height */
134    private double depthFactor = 0.12;
135
136    /**
137     * A flag that controls whether or not the sides of the pie chart
138     * are rendered using a darker colour.
139     *
140     *  @since 1.0.7.
141     */
142    private boolean darkerSides = false;  // default preserves previous
143                                          // behaviour
144
145    /**
146     * Creates a new instance with no dataset.
147     */
148    public PiePlot3D() {
149        this(null);
150    }
151
152    /**
153     * Creates a pie chart with a three dimensional effect using the specified
154     * dataset.
155     *
156     * @param dataset  the dataset (<code>null</code> permitted).
157     */
158    public PiePlot3D(PieDataset dataset) {
159        super(dataset);
160        setCircular(false, false);
161    }
162
163    /**
164     * Returns the depth factor for the chart.
165     *
166     * @return The depth factor.
167     *
168     * @see #setDepthFactor(double)
169     */
170    public double getDepthFactor() {
171        return this.depthFactor;
172    }
173
174    /**
175     * Sets the pie depth as a percentage of the height of the plot area, and
176     * sends a {@link PlotChangeEvent} to all registered listeners.
177     *
178     * @param factor  the depth factor (for example, 0.20 is twenty percent).
179     *
180     * @see #getDepthFactor()
181     */
182    public void setDepthFactor(double factor) {
183        this.depthFactor = factor;
184        fireChangeEvent();
185    }
186
187    /**
188     * Returns a flag that controls whether or not the sides of the pie chart
189     * are rendered using a darker colour.  This is only applied if the
190     * section colour is an instance of {@link java.awt.Color}.
191     *
192     * @return A boolean.
193     *
194     * @see #setDarkerSides(boolean)
195     *
196     * @since 1.0.7
197     */
198    public boolean getDarkerSides() {
199        return this.darkerSides;
200    }
201
202    /**
203     * Sets a flag that controls whether or not the sides of the pie chart
204     * are rendered using a darker colour, and sends a {@link PlotChangeEvent}
205     * to all registered listeners.  This is only applied if the
206     * section colour is an instance of {@link java.awt.Color}.
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    public void draw(Graphics2D g2, Rectangle2D plotArea, Point2D anchor,
234                     PlotState parentState,
235                     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.size() == 0) {
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            if (paint instanceof Color) {
649                Color c = (Color) paint;
650                c = c.darker();
651                paint = c;
652            }
653        }
654
655        double start = arc.getAngleStart();
656        double extent = arc.getAngleExtent();
657        double end = start + extent;
658
659        g2.setStroke(outlineStroke);
660
661        // for CLOCKWISE charts, the extent will be negative...
662        if (extent < 0.0) {
663
664            if (isAngleAtFront(start)) {  // start at front
665
666                if (!isAngleAtBack(end)) {
667
668                    if (extent > -180.0) {  // the segment is entirely at the
669                                            // front of the chart
670                        if (drawFront) {
671                            Area side = new Area(new Rectangle2D.Double(
672                                    arc.getEndPoint().getX(), plotArea.getY(),
673                                    arc.getStartPoint().getX()
674                                    - arc.getEndPoint().getX(),
675                                    plotArea.getHeight()));
676                            side.intersect(front);
677                            g2.setPaint(paint);
678                            g2.fill(side);
679                            g2.setPaint(outlinePaint);
680                            g2.draw(side);
681                        }
682                    }
683                    else {  // the segment starts at the front, and wraps all
684                            // the way around
685                            // the back and finishes at the front again
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
692                        Area side2 = new Area(new Rectangle2D.Double(
693                                arc.getEndPoint().getX(), plotArea.getY(),
694                                plotArea.getMaxX() - arc.getEndPoint().getX(),
695                                plotArea.getHeight()));
696
697                        side2.intersect(front);
698                        g2.setPaint(paint);
699                        if (drawFront) {
700                            g2.fill(side1);
701                            g2.fill(side2);
702                        }
703
704                        if (drawBack) {
705                            g2.fill(back);
706                        }
707
708                        g2.setPaint(outlinePaint);
709                        if (drawFront) {
710                            g2.draw(side1);
711                            g2.draw(side2);
712                        }
713
714                        if (drawBack) {
715                            g2.draw(back);
716                        }
717
718                    }
719                }
720                else {  // starts at the front, finishes at the back (going
721                        // around the left side)
722
723                    if (drawBack) {
724                        Area side2 = new Area(new Rectangle2D.Double(
725                                plotArea.getX(), plotArea.getY(),
726                                arc.getEndPoint().getX() - plotArea.getX(),
727                                plotArea.getHeight()));
728                        side2.intersect(back);
729                        g2.setPaint(paint);
730                        g2.fill(side2);
731                        g2.setPaint(outlinePaint);
732                        g2.draw(side2);
733                    }
734
735                    if (drawFront) {
736                        Area side1 = new Area(new Rectangle2D.Double(
737                                plotArea.getX(), plotArea.getY(),
738                                arc.getStartPoint().getX() - plotArea.getX(),
739                                plotArea.getHeight()));
740                        side1.intersect(front);
741                        g2.setPaint(paint);
742                        g2.fill(side1);
743                        g2.setPaint(outlinePaint);
744                        g2.draw(side1);
745                    }
746                }
747            }
748            else {  // the segment starts at the back (still extending
749                    // CLOCKWISE)
750
751                if (!isAngleAtFront(end)) {
752                    if (extent > -180.0) {  // whole segment stays at the back
753                        if (drawBack) {
754                            Area side = new Area(new Rectangle2D.Double(
755                                    arc.getStartPoint().getX(), plotArea.getY(),
756                                    arc.getEndPoint().getX()
757                                    - arc.getStartPoint().getX(),
758                                    plotArea.getHeight()));
759                            side.intersect(back);
760                            g2.setPaint(paint);
761                            g2.fill(side);
762                            g2.setPaint(outlinePaint);
763                            g2.draw(side);
764                        }
765                    }
766                    else {  // starts at the back, wraps around front, and
767                            // finishes at back again
768                        Area side1 = new Area(new Rectangle2D.Double(
769                                arc.getStartPoint().getX(), plotArea.getY(),
770                                plotArea.getMaxX() - arc.getStartPoint().getX(),
771                                plotArea.getHeight()));
772                        side1.intersect(back);
773
774                        Area side2 = new Area(new Rectangle2D.Double(
775                                plotArea.getX(), plotArea.getY(),
776                                arc.getEndPoint().getX() - plotArea.getX(),
777                                plotArea.getHeight()));
778
779                        side2.intersect(back);
780
781                        g2.setPaint(paint);
782                        if (drawBack) {
783                            g2.fill(side1);
784                            g2.fill(side2);
785                        }
786
787                        if (drawFront) {
788                            g2.fill(front);
789                        }
790
791                        g2.setPaint(outlinePaint);
792                        if (drawBack) {
793                            g2.draw(side1);
794                            g2.draw(side2);
795                        }
796
797                        if (drawFront) {
798                            g2.draw(front);
799                        }
800
801                    }
802                }
803                else {  // starts at back, finishes at front (CLOCKWISE)
804
805                    if (drawBack) {
806                        Area side1 = new Area(new Rectangle2D.Double(
807                                arc.getStartPoint().getX(), plotArea.getY(),
808                                plotArea.getMaxX() - arc.getStartPoint().getX(),
809                                plotArea.getHeight()));
810                        side1.intersect(back);
811                        g2.setPaint(paint);
812                        g2.fill(side1);
813                        g2.setPaint(outlinePaint);
814                        g2.draw(side1);
815                    }
816
817                    if (drawFront) {
818                        Area side2 = new Area(new Rectangle2D.Double(
819                                arc.getEndPoint().getX(), plotArea.getY(),
820                                plotArea.getMaxX() - arc.getEndPoint().getX(),
821                                plotArea.getHeight()));
822                        side2.intersect(front);
823                        g2.setPaint(paint);
824                        g2.fill(side2);
825                        g2.setPaint(outlinePaint);
826                        g2.draw(side2);
827                    }
828
829                }
830            }
831        }
832        else if (extent > 0.0) {  // the pie sections are arranged ANTICLOCKWISE
833
834            if (isAngleAtFront(start)) {  // segment starts at the front
835
836                if (!isAngleAtBack(end)) {  // and finishes at the front
837
838                    if (extent < 180.0) {  // segment only occupies the front
839                        if (drawFront) {
840                            Area side = new Area(new Rectangle2D.Double(
841                                    arc.getStartPoint().getX(), plotArea.getY(),
842                                    arc.getEndPoint().getX()
843                                    - arc.getStartPoint().getX(),
844                                    plotArea.getHeight()));
845                            side.intersect(front);
846                            g2.setPaint(paint);
847                            g2.fill(side);
848                            g2.setPaint(outlinePaint);
849                            g2.draw(side);
850                        }
851                    }
852                    else {  // segments wraps right around the back...
853                        Area side1 = new Area(new Rectangle2D.Double(
854                                arc.getStartPoint().getX(), plotArea.getY(),
855                                plotArea.getMaxX() - arc.getStartPoint().getX(),
856                                plotArea.getHeight()));
857                        side1.intersect(front);
858
859                        Area side2 = new Area(new Rectangle2D.Double(
860                                plotArea.getX(), plotArea.getY(),
861                                arc.getEndPoint().getX() - plotArea.getX(),
862                                plotArea.getHeight()));
863                        side2.intersect(front);
864
865                        g2.setPaint(paint);
866                        if (drawFront) {
867                            g2.fill(side1);
868                            g2.fill(side2);
869                        }
870
871                        if (drawBack) {
872                            g2.fill(back);
873                        }
874
875                        g2.setPaint(outlinePaint);
876                        if (drawFront) {
877                            g2.draw(side1);
878                            g2.draw(side2);
879                        }
880
881                        if (drawBack) {
882                            g2.draw(back);
883                        }
884
885                    }
886                }
887                else {  // segments starts at front and finishes at back...
888                    if (drawBack) {
889                        Area side2 = new Area(new Rectangle2D.Double(
890                                arc.getEndPoint().getX(), plotArea.getY(),
891                                plotArea.getMaxX() - arc.getEndPoint().getX(),
892                                plotArea.getHeight()));
893                        side2.intersect(back);
894                        g2.setPaint(paint);
895                        g2.fill(side2);
896                        g2.setPaint(outlinePaint);
897                        g2.draw(side2);
898                    }
899
900                    if (drawFront) {
901                        Area side1 = new Area(new Rectangle2D.Double(
902                                arc.getStartPoint().getX(), plotArea.getY(),
903                                plotArea.getMaxX() - arc.getStartPoint().getX(),
904                                plotArea.getHeight()));
905                        side1.intersect(front);
906                        g2.setPaint(paint);
907                        g2.fill(side1);
908                        g2.setPaint(outlinePaint);
909                        g2.draw(side1);
910                    }
911                }
912            }
913            else {  // segment starts at back
914
915                if (!isAngleAtFront(end)) {
916                    if (extent < 180.0) {  // and finishes at back
917                        if (drawBack) {
918                            Area side = new Area(new Rectangle2D.Double(
919                                    arc.getEndPoint().getX(), plotArea.getY(),
920                                    arc.getStartPoint().getX()
921                                    - arc.getEndPoint().getX(),
922                                    plotArea.getHeight()));
923                            side.intersect(back);
924                            g2.setPaint(paint);
925                            g2.fill(side);
926                            g2.setPaint(outlinePaint);
927                            g2.draw(side);
928                        }
929                    }
930                    else {  // starts at back and wraps right around to the
931                            // back again
932                        Area side1 = new Area(new Rectangle2D.Double(
933                                arc.getStartPoint().getX(), plotArea.getY(),
934                                plotArea.getX() - arc.getStartPoint().getX(),
935                                plotArea.getHeight()));
936                        side1.intersect(back);
937
938                        Area side2 = new Area(new Rectangle2D.Double(
939                                arc.getEndPoint().getX(), plotArea.getY(),
940                                plotArea.getMaxX() - arc.getEndPoint().getX(),
941                                plotArea.getHeight()));
942                        side2.intersect(back);
943
944                        g2.setPaint(paint);
945                        if (drawBack) {
946                            g2.fill(side1);
947                            g2.fill(side2);
948                        }
949
950                        if (drawFront) {
951                            g2.fill(front);
952                        }
953
954                        g2.setPaint(outlinePaint);
955                        if (drawBack) {
956                            g2.draw(side1);
957                            g2.draw(side2);
958                        }
959
960                        if (drawFront) {
961                            g2.draw(front);
962                        }
963
964                    }
965                }
966                else {  // starts at the back and finishes at the front
967                        // (wrapping the left side)
968                    if (drawBack) {
969                        Area side1 = new Area(new Rectangle2D.Double(
970                                plotArea.getX(), plotArea.getY(),
971                                arc.getStartPoint().getX() - plotArea.getX(),
972                                plotArea.getHeight()));
973                        side1.intersect(back);
974                        g2.setPaint(paint);
975                        g2.fill(side1);
976                        g2.setPaint(outlinePaint);
977                        g2.draw(side1);
978                    }
979
980                    if (drawFront) {
981                        Area side2 = new Area(new Rectangle2D.Double(
982                                plotArea.getX(), plotArea.getY(),
983                                arc.getEndPoint().getX() - plotArea.getX(),
984                                plotArea.getHeight()));
985                        side2.intersect(front);
986                        g2.setPaint(paint);
987                        g2.fill(side2);
988                        g2.setPaint(outlinePaint);
989                        g2.draw(side2);
990                    }
991                }
992            }
993
994        }
995
996    }
997
998    /**
999     * Returns a short string describing the type of plot.
1000     *
1001     * @return <i>Pie 3D Plot</i>.
1002     */
1003    public String getPlotType() {
1004        return localizationResources.getString("Pie_3D_Plot");
1005    }
1006
1007    /**
1008     * A utility method that returns true if the angle represents a point at
1009     * the front of the 3D pie chart.  0 - 180 degrees is the back, 180 - 360
1010     * is the front.
1011     *
1012     * @param angle  the angle.
1013     *
1014     * @return A boolean.
1015     */
1016    private boolean isAngleAtFront(double angle) {
1017        return (Math.sin(Math.toRadians(angle)) < 0.0);
1018    }
1019
1020    /**
1021     * A utility method that returns true if the angle represents a point at
1022     * the back of the 3D pie chart.  0 - 180 degrees is the back, 180 - 360
1023     * is the front.
1024     *
1025     * @param angle  the angle.
1026     *
1027     * @return <code>true</code> if the angle is at the back of the pie.
1028     */
1029    private boolean isAngleAtBack(double angle) {
1030        return (Math.sin(Math.toRadians(angle)) > 0.0);
1031    }
1032
1033    /**
1034     * Tests this plot for equality with an arbitrary object.
1035     *
1036     * @param obj  the object (<code>null</code> permitted).
1037     *
1038     * @return A boolean.
1039     */
1040    public boolean equals(Object obj) {
1041        if (obj == this) {
1042            return true;
1043        }
1044        if (!(obj instanceof PiePlot3D)) {
1045            return false;
1046        }
1047        PiePlot3D that = (PiePlot3D) obj;
1048        if (this.depthFactor != that.depthFactor) {
1049            return false;
1050        }
1051        if (this.darkerSides != that.darkerSides) {
1052            return false;
1053        }
1054        return super.equals(obj);
1055    }
1056
1057}