Source for org.jfree.chart.plot.PiePlot3D

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
   6:  *
   7:  * Project Info:  http://www.jfree.org/jfreechart/index.html
   8:  *
   9:  * This library is free software; you can redistribute it and/or modify it 
  10:  * under the terms of the GNU Lesser General Public License as published by 
  11:  * the Free Software Foundation; either version 2.1 of the License, or 
  12:  * (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but 
  15:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
  16:  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
  17:  * License for more details.
  18:  *
  19:  * You should have received a copy of the GNU Lesser General Public
  20:  * License along with this library; if not, write to the Free Software
  21:  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
  22:  * USA.  
  23:  *
  24:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
  25:  * in the United States and other countries.]
  26:  *
  27:  * --------------
  28:  * PiePlot3D.java
  29:  * --------------
  30:  * (C) Copyright 2000-2005, by Object Refinery and Contributors.
  31:  *
  32:  * Original Author:  Tomer Peretz;
  33:  * Contributor(s):   Richard Atkinson;
  34:  *                   David Gilbert (for Object Refinery Limited);
  35:  *                   Xun Kang;
  36:  *                   Christian W. Zuckschwerdt;
  37:  *                   Arnaud Lelievre;
  38:  *                   Dave Crane;
  39:  *
  40:  * $Id: PiePlot3D.java,v 1.10.2.2 2005/10/25 20:52:08 mungady Exp $
  41:  *
  42:  * Changes
  43:  * -------
  44:  * 21-Jun-2002 : Version 1;
  45:  * 31-Jul-2002 : Modified to use startAngle and direction, drawing modified so 
  46:  *               that charts render with foreground alpha < 1.0 (DG);
  47:  * 05-Aug-2002 : Small modification to draw method to support URLs for HTML 
  48:  *               image maps (RA);
  49:  * 26-Sep-2002 : Fixed errors reported by Checkstyle (DG);
  50:  * 18-Oct-2002 : Added drawing bug fix sent in by Xun Kang, and made a couple 
  51:  *               of other related fixes (DG);
  52:  * 30-Oct-2002 : Changed the PieDataset interface. Fixed another drawing 
  53:  *               bug (DG);
  54:  * 12-Nov-2002 : Fixed null pointer exception for zero or negative values (DG);
  55:  * 07-Mar-2003 : Modified to pass pieIndex on to PieSectionEntity (DG);
  56:  * 21-Mar-2003 : Added workaround for bug id 620031 (DG);
  57:  * 26-Mar-2003 : Implemented Serializable (DG);
  58:  * 30-Jul-2003 : Modified entity constructor (CZ);
  59:  * 29-Aug-2003 : Small changes for API updates in PiePlot class (DG);
  60:  * 02-Sep-2003 : Fixed bug where the 'no data' message is not displayed (DG);
  61:  * 08-Sep-2003 : Added internationalization via use of properties 
  62:  *               resourceBundle (RFE 690236) (AL); 
  63:  * 29-Oct-2003 : Added workaround for font alignment in PDF output (DG);
  64:  * 20-Nov-2003 : Fixed bug 845289 (sides not showing) (DG);
  65:  * 25-Nov-2003 : Added patch (845095) to fix outline paint issues (DG);
  66:  * 10-Mar-2004 : Numerous changes to enhance labelling (DG);
  67:  * 31-Mar-2004 : Adjusted plot area when label generator is null (DG);
  68:  * 08-Apr-2004 : Added flag to PiePlot class to control the treatment of null 
  69:  *               values (DG);
  70:  *               Added pieIndex to PieSectionEntity (DG);
  71:  * 15-Nov-2004 : Removed creation of default tool tip generator (DG);
  72:  * 16-Jun-2005 : Added default constructor (DG);
  73:  * 
  74:  */
  75: 
  76: package org.jfree.chart.plot;
  77: 
  78: import java.awt.AlphaComposite;
  79: import java.awt.Color;
  80: import java.awt.Composite;
  81: import java.awt.Font;
  82: import java.awt.FontMetrics;
  83: import java.awt.Graphics2D;
  84: import java.awt.Paint;
  85: import java.awt.Polygon;
  86: import java.awt.Shape;
  87: import java.awt.Stroke;
  88: import java.awt.geom.Arc2D;
  89: import java.awt.geom.Area;
  90: import java.awt.geom.Ellipse2D;
  91: import java.awt.geom.Point2D;
  92: import java.awt.geom.Rectangle2D;
  93: import java.io.Serializable;
  94: import java.util.ArrayList;
  95: import java.util.Iterator;
  96: import java.util.List;
  97: 
  98: import org.jfree.chart.entity.EntityCollection;
  99: import org.jfree.chart.entity.PieSectionEntity;
 100: import org.jfree.chart.labels.PieToolTipGenerator;
 101: import org.jfree.data.general.DatasetUtilities;
 102: import org.jfree.data.general.PieDataset;
 103: import org.jfree.ui.RectangleInsets;
 104: 
 105: /**
 106:  * A plot that displays data in the form of a 3D pie chart, using data from
 107:  * any class that implements the {@link PieDataset} interface.
 108:  * <P>
 109:  * Although this class extends {@link PiePlot}, it does not currently support
 110:  * exploded sections.
 111:  */
 112: public class PiePlot3D extends PiePlot implements Serializable {
 113: 
 114:     /** For serialization. */
 115:     private static final long serialVersionUID = 3408984188945161432L;
 116:     
 117:     /** The factor of the depth of the pie from the plot height */
 118:     private double depthFactor = 0.2;
 119: 
 120:     /**
 121:      * Creates a new instance with no dataset.
 122:      */
 123:     public PiePlot3D() {
 124:         this(null);   
 125:     }
 126:     
 127:     /**
 128:      * Creates a pie chart with a three dimensional effect using the specified 
 129:      * dataset.
 130:      *
 131:      * @param dataset  the dataset (<code>null</code> permitted).
 132:      */
 133:     public PiePlot3D(PieDataset dataset) {
 134:         super(dataset);
 135:         setCircular(false, false);
 136:     }
 137: 
 138:     /**
 139:      * Sets the pie depth as a percentage of the height of the plot area.
 140:      *
 141:      * @param factor  the depth factor (for example, 0.20 is twenty percent).
 142:      */
 143:     public void setDepthFactor(double factor) {
 144:         this.depthFactor = factor;
 145:     }
 146: 
 147:     /**
 148:      * The depth factor for the chart.
 149:      *
 150:      * @return The depth factor.
 151:      */
 152:     public double getDepthFactor () {
 153:         return this.depthFactor;
 154:     }
 155: 
 156:     /**
 157:      * Draws the plot on a Java 2D graphics device (such as the screen or a 
 158:      * printer).  This method is called by the 
 159:      * {@link org.jfree.chart.JFreeChart} class, you don't normally need 
 160:      * to call it yourself.
 161:      *
 162:      * @param g2  the graphics device.
 163:      * @param plotArea  the area within which the plot should be drawn.
 164:      * @param anchor  the anchor point.
 165:      * @param parentState  the state from the parent plot, if there is one.
 166:      * @param info  collects info about the drawing 
 167:      *              (<code>null</code> permitted).
 168:      */
 169:     public void draw(Graphics2D g2, Rectangle2D plotArea, Point2D anchor,
 170:                      PlotState parentState,
 171:                      PlotRenderingInfo info) {
 172: 
 173:         // adjust for insets...
 174:         RectangleInsets insets = getInsets();
 175:         insets.trim(plotArea);
 176: 
 177:         Rectangle2D originalPlotArea = (Rectangle2D) plotArea.clone();
 178:         if (info != null) {
 179:             info.setPlotArea(plotArea);
 180:             info.setDataArea(plotArea);
 181:         }
 182: 
 183:         Shape savedClip = g2.getClip();
 184:         g2.clip(plotArea);
 185: 
 186:         // adjust the plot area by the interior spacing value
 187:         double gapPercent = getInteriorGap();
 188:         double labelPercent = 0.0;
 189:         if (getLabelGenerator() != null) {
 190:             labelPercent = getLabelGap() + getMaximumLabelWidth() 
 191:                            + getLabelLinkMargin();   
 192:         }
 193:         double gapHorizontal = plotArea.getWidth() 
 194:                                * (gapPercent + labelPercent);
 195:         double gapVertical = plotArea.getHeight() * gapPercent;
 196: 
 197:         double linkX = plotArea.getX() + gapHorizontal / 2;
 198:         double linkY = plotArea.getY() + gapVertical / 2;
 199:         double linkW = plotArea.getWidth() - gapHorizontal;
 200:         double linkH = plotArea.getHeight() - gapVertical;
 201:         
 202:         // make the link area a square if the pie chart is to be circular...
 203:         if (isCircular()) { // is circular?
 204:             double min = Math.min(linkW, linkH) / 2;
 205:             linkX = (linkX + linkX + linkW) / 2 - min;
 206:             linkY = (linkY + linkY + linkH) / 2 - min;
 207:             linkW = 2 * min;
 208:             linkH = 2 * min;
 209:         }
 210:         
 211:         PiePlotState state = initialise(g2, plotArea, this, null, info);
 212:         // the explode area defines the max circle/ellipse for the exploded pie 
 213:         // sections.
 214:         // it is defined by shrinking the linkArea by the linkMargin factor.
 215:         double hh = linkW * getLabelLinkMargin();
 216:         double vv = linkH * getLabelLinkMargin();
 217:         Rectangle2D explodeArea = new Rectangle2D.Double(
 218:             linkX + hh / 2.0, linkY + vv / 2.0, linkW - hh, linkH - vv
 219:         );
 220:        
 221:         state.setExplodedPieArea(explodeArea);
 222:         
 223:         // the pie area defines the circle/ellipse for regular pie sections.
 224:         // it is defined by shrinking the explodeArea by the explodeMargin 
 225:         // factor. 
 226:         double maximumExplodePercent = getMaximumExplodePercent();
 227:         double percent = maximumExplodePercent / (1.0 + maximumExplodePercent);
 228:         
 229:         double h1 = explodeArea.getWidth() * percent;
 230:         double v1 = explodeArea.getHeight() * percent;
 231:         Rectangle2D pieArea = new Rectangle2D.Double(
 232:             explodeArea.getX() + h1 / 2.0, explodeArea.getY() + v1 / 2.0,
 233:             explodeArea.getWidth() - h1, explodeArea.getHeight() - v1
 234:         );
 235: 
 236:         int depth = (int) (pieArea.getHeight() * this.depthFactor);
 237:         // the link area defines the dog-leg point for the linking lines to 
 238:         // the labels
 239:         Rectangle2D linkArea = new Rectangle2D.Double(
 240:             linkX, linkY, linkW, linkH - depth
 241:         );
 242:         state.setLinkArea(linkArea);   
 243: 
 244:         state.setPieArea(pieArea);
 245:         state.setPieCenterX(pieArea.getCenterX());
 246:         state.setPieCenterY(pieArea.getCenterY() - depth / 2.0);
 247:         state.setPieWRadius(pieArea.getWidth() / 2.0);
 248:         state.setPieHRadius((pieArea.getHeight() - depth) / 2.0);
 249: 
 250:         drawBackground(g2, plotArea);
 251:         // get the data source - return if null;
 252:         PieDataset dataset = getDataset();
 253:         if (DatasetUtilities.isEmptyOrNull(getDataset())) {
 254:             drawNoDataMessage(g2, plotArea);
 255:             g2.setClip(savedClip);
 256:             drawOutline(g2, plotArea);
 257:             return;
 258:         }
 259: 
 260:         // if too any elements
 261:         if (dataset.getKeys().size() > plotArea.getWidth()) {
 262:             String text = "Too many elements";
 263:             Font sfont = new Font("dialog", Font.BOLD, 10);
 264:             g2.setFont(sfont);
 265:             FontMetrics fm = g2.getFontMetrics(sfont);
 266:             int stringWidth = fm.stringWidth(text);
 267: 
 268:             g2.drawString(
 269:                 text, 
 270:                 (int) (plotArea.getX() + (plotArea.getWidth() - stringWidth) 
 271:                         / 2),
 272:                 (int) (plotArea.getY() + (plotArea.getHeight() / 2))
 273:             );
 274:             return;
 275:         }
 276:         // if we are drawing a perfect circle, we need to readjust the top left
 277:         // coordinates of the drawing area for the arcs to arrive at this
 278:         // effect.
 279:         if (isCircular()) {
 280:             double min = Math.min(plotArea.getWidth(), 
 281:                     plotArea.getHeight()) / 2;
 282:             plotArea = new Rectangle2D.Double(
 283:                 plotArea.getCenterX() - min, plotArea.getCenterY() - min, 
 284:                 2 * min, 2 * min
 285:             );
 286:         }
 287:         // get a list of keys...
 288:         List sectionKeys = dataset.getKeys();
 289: 
 290:         if (sectionKeys.size() == 0) {
 291:             return;
 292:         }
 293: 
 294:         // establish the coordinates of the top left corner of the drawing area
 295:         double arcX = pieArea.getX();
 296:         double arcY = pieArea.getY();
 297: 
 298:         //g2.clip(clipArea);
 299:         Composite originalComposite = g2.getComposite();
 300:         g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 
 301:                 getForegroundAlpha()));
 302: 
 303:         double totalValue = DatasetUtilities.calculatePieDatasetTotal(dataset);
 304:         double runningTotal = 0;
 305:         if (depth < 0) {
 306:             return;  // if depth is negative don't draw anything
 307:         }
 308: 
 309:         ArrayList arcList = new ArrayList();
 310:         Arc2D.Double arc;
 311:         Paint paint;
 312:         Paint outlinePaint;
 313:         Stroke outlineStroke;
 314: 
 315:         Iterator iterator = sectionKeys.iterator();
 316:         while (iterator.hasNext()) {
 317: 
 318:             Comparable currentKey = (Comparable) iterator.next();
 319:             Number dataValue = dataset.getValue(currentKey);
 320:             if (dataValue == null) {
 321:                 arcList.add(null);
 322:                 continue;
 323:             }
 324:             double value = dataValue.doubleValue();
 325:             if (value <= 0) {
 326:                 arcList.add(null);
 327:                 continue;
 328:             }
 329:             double startAngle = getStartAngle();
 330:             double direction = getDirection().getFactor();
 331:             double angle1 = startAngle + (direction * (runningTotal * 360)) 
 332:             / totalValue;
 333:             double angle2 = startAngle + (direction * (runningTotal + value) 
 334:                     * 360) / totalValue;
 335:             if (Math.abs(angle2 - angle1) > getMinimumArcAngleToDraw()) {
 336:                 arcList.add(
 337:                     new Arc2D.Double(
 338:                         arcX, arcY + depth, pieArea.getWidth(), 
 339:                         pieArea.getHeight() - depth,
 340:                         angle1, angle2 - angle1, Arc2D.PIE
 341:                     )
 342:                 );
 343:             }
 344:             else {
 345:                 arcList.add(null);
 346:             }
 347:             runningTotal += value;
 348:         }
 349: 
 350:         Shape oldClip = g2.getClip();
 351: 
 352:         Ellipse2D top = new Ellipse2D.Double(
 353:             pieArea.getX(), pieArea.getY(), pieArea.getWidth(), 
 354:             pieArea.getHeight() - depth
 355:         );
 356: 
 357:         Ellipse2D bottom = new Ellipse2D.Double(
 358:             pieArea.getX(), pieArea.getY() + depth, pieArea.getWidth(), 
 359:             pieArea.getHeight() - depth
 360:         );
 361: 
 362:         Rectangle2D lower = new Rectangle2D.Double(
 363:             top.getX(), top.getCenterY(), pieArea.getWidth(), 
 364:             bottom.getMaxY() - top.getCenterY()
 365:         );
 366: 
 367:         Rectangle2D upper = new Rectangle2D.Double(
 368:             pieArea.getX(), top.getY(), pieArea.getWidth(),
 369:             bottom.getCenterY() - top.getY()
 370:         );
 371: 
 372:         Area a = new Area(top);
 373:         a.add(new Area(lower));
 374:         Area b = new Area(bottom);
 375:         b.add(new Area(upper));
 376:         Area pie = new Area(a);
 377:         pie.intersect(b);
 378: 
 379:         Area front = new Area(pie);
 380:         front.subtract(new Area(top));
 381: 
 382:         Area back = new Area(pie);
 383:         back.subtract(new Area(bottom));
 384: 
 385:         // draw the bottom circle
 386:         int[] xs;
 387:         int[] ys;
 388:         outlinePaint = getSectionOutlinePaint(0);
 389:         arc = new Arc2D.Double(
 390:             arcX, arcY + depth, pieArea.getWidth(), pieArea.getHeight() - depth,
 391:             0, 360, Arc2D.PIE
 392:         );
 393: 
 394:         int categoryCount = arcList.size();
 395:         for (int categoryIndex = 0; categoryIndex < categoryCount; 
 396:              categoryIndex++) {
 397:             arc = (Arc2D.Double) arcList.get(categoryIndex);
 398:             if (arc == null) {
 399:                 continue;
 400:             }
 401:             paint = getSectionPaint(categoryIndex);
 402:             outlinePaint = getSectionOutlinePaint(categoryIndex);
 403:             outlineStroke = getSectionOutlineStroke(categoryIndex);
 404:             g2.setPaint(paint);
 405:             g2.fill(arc);
 406:             g2.setPaint(outlinePaint);
 407:             g2.setStroke(outlineStroke);
 408:             g2.draw(arc);
 409:             g2.setPaint(paint);
 410: 
 411:             Point2D p1 = arc.getStartPoint();
 412: 
 413:             // draw the height
 414:             xs = new int[] {
 415:                 (int) arc.getCenterX(), (int) arc.getCenterX(),
 416:                 (int) p1.getX(), (int) p1.getX() 
 417:             };
 418:             ys = new int[] {
 419:                 (int) arc.getCenterY(), (int) arc.getCenterY() - depth,
 420:                 (int) p1.getY() - depth, (int) p1.getY() 
 421:             };
 422:             Polygon polygon = new Polygon(xs, ys, 4);
 423:             g2.setPaint(java.awt.Color.lightGray);
 424:             g2.fill(polygon);
 425:             g2.setPaint(outlinePaint);
 426:             g2.setStroke(outlineStroke);
 427:             g2.draw(polygon);
 428:             g2.setPaint(paint);
 429: 
 430:         }
 431: 
 432:         g2.setPaint(Color.gray);
 433:         g2.fill(back);
 434:         g2.fill(front);
 435: 
 436:         // cycle through once drawing only the sides at the back...
 437:         int cat = 0;
 438:         iterator = arcList.iterator();
 439:         while (iterator.hasNext()) {
 440:             Arc2D segment = (Arc2D) iterator.next();
 441:             if (segment != null) {
 442:                 paint = getSectionPaint(cat);
 443:                 outlinePaint = getSectionOutlinePaint(cat);
 444:                 outlineStroke = getSectionOutlineStroke(cat);
 445:                 drawSide(
 446:                     g2, pieArea, segment, front, back, paint, 
 447:                     outlinePaint, outlineStroke, 
 448:                     false, true
 449:                 );
 450:             }
 451:             cat++;
 452:         }
 453: 
 454:         // cycle through again drawing only the sides at the front...
 455:         cat = 0;
 456:         iterator = arcList.iterator();
 457:         while (iterator.hasNext()) {
 458:             Arc2D segment = (Arc2D) iterator.next();
 459:             if (segment != null) {
 460:                 paint = getSectionPaint(cat);
 461:                 outlinePaint = getSectionOutlinePaint(cat);
 462:                 outlineStroke = getSectionOutlineStroke(cat);
 463:                 drawSide(
 464:                     g2, pieArea, segment, front, back, paint, 
 465:                     outlinePaint, outlineStroke, 
 466:                     true, false
 467:                 );
 468:             }
 469:             cat++;
 470:         }
 471: 
 472:         g2.setClip(oldClip);
 473: 
 474:         // draw the sections at the top of the pie (and set up tooltips)...
 475:         Arc2D upperArc;
 476:         for (int sectionIndex = 0; sectionIndex < categoryCount; 
 477:              sectionIndex++) {
 478:             arc = (Arc2D.Double) arcList.get(sectionIndex);
 479:             if (arc == null) {
 480:                 continue;
 481:             }
 482:             upperArc = new Arc2D.Double(
 483:                 arcX, arcY, pieArea.getWidth(), pieArea.getHeight() - depth,
 484:                 arc.getAngleStart(), arc.getAngleExtent(), Arc2D.PIE
 485:             );
 486:             
 487:             paint = getSectionPaint(sectionIndex);
 488:             outlinePaint = getSectionOutlinePaint(sectionIndex);
 489:             outlineStroke = getSectionOutlineStroke(sectionIndex);
 490:             g2.setPaint(paint);
 491:             g2.fill(upperArc);
 492:             g2.setStroke(outlineStroke);
 493:             g2.setPaint(outlinePaint);
 494:             g2.draw(upperArc);
 495: 
 496:            // add a tooltip for the section...
 497:             Comparable currentKey = (Comparable) sectionKeys.get(sectionIndex);
 498:             if (info != null) {
 499:                 EntityCollection entities 
 500:                     = info.getOwner().getEntityCollection();
 501:                 if (entities != null) {
 502:                     String tip = null;
 503:                     PieToolTipGenerator tipster = getToolTipGenerator();
 504:                     if (tipster != null) {
 505:                         // @mgs: using the method's return value was missing 
 506:                         tip = tipster.generateToolTip(dataset, currentKey);
 507:                     }
 508:                     String url = null;
 509:                     if (getURLGenerator() != null) {
 510:                         url = getURLGenerator().generateURL(dataset, currentKey,
 511:                                 getPieIndex());
 512:                     }
 513:                     PieSectionEntity entity = new PieSectionEntity(
 514:                         upperArc, dataset, getPieIndex(), sectionIndex, 
 515:                         currentKey, tip, url
 516:                     );
 517:                     entities.add(entity);
 518:                 }
 519:             }
 520:             List keys = dataset.getKeys();
 521:             Rectangle2D adjustedPlotArea = new Rectangle2D.Double(
 522:                 originalPlotArea.getX(), originalPlotArea.getY(), 
 523:                 originalPlotArea.getWidth(), 
 524:                 originalPlotArea.getHeight() - depth
 525:             );
 526:             drawLabels(g2, keys, totalValue, adjustedPlotArea, linkArea, state);
 527:         }
 528: 
 529:         g2.setClip(savedClip);
 530:         g2.setComposite(originalComposite);
 531:         drawOutline(g2, originalPlotArea);
 532: 
 533:     }
 534: 
 535:     /**
 536:      * Draws the side of a pie section.
 537:      *
 538:      * @param g2  the graphics device.
 539:      * @param plotArea  the plot area.
 540:      * @param arc  the arc.
 541:      * @param front  the front of the pie.
 542:      * @param back  the back of the pie.
 543:      * @param paint  the color.
 544:      * @param outlinePaint  the outline paint.
 545:      * @param outlineStroke  the outline stroke.
 546:      * @param drawFront  draw the front?
 547:      * @param drawBack  draw the back?
 548:      */
 549:     protected void drawSide(Graphics2D g2,
 550:                             Rectangle2D plotArea, 
 551:                             Arc2D arc, 
 552:                             Area front, 
 553:                             Area back,
 554:                             Paint paint, 
 555:                             Paint outlinePaint,
 556:                             Stroke outlineStroke,
 557:                             boolean drawFront, 
 558:                             boolean drawBack) {
 559: 
 560:         double start = arc.getAngleStart();
 561:         double extent = arc.getAngleExtent();
 562:         double end = start + extent;
 563: 
 564:         g2.setStroke(outlineStroke);
 565:         
 566:         // for CLOCKWISE charts, the extent will be negative...
 567:         if (extent < 0.0) {
 568: 
 569:             if (isAngleAtFront(start)) {  // start at front
 570: 
 571:                 if (!isAngleAtBack(end)) {
 572: 
 573:                     if (extent > -180.0) {  // the segment is entirely at the 
 574:                                             // front of the chart
 575:                         if (drawFront) {
 576:                             Area side = new Area(
 577:                                 new Rectangle2D.Double(
 578:                                     arc.getEndPoint().getX(), plotArea.getY(),
 579:                                     arc.getStartPoint().getX() 
 580:                                     - arc.getEndPoint().getX(),
 581:                                     plotArea.getHeight()
 582:                                 )
 583:                             );
 584:                             side.intersect(front);
 585:                             g2.setPaint(paint);
 586:                             g2.fill(side);
 587:                             g2.setPaint(outlinePaint);
 588:                             g2.draw(side);
 589:                         }
 590:                     }
 591:                     else {  // the segment starts at the front, and wraps all 
 592:                             // the way around
 593:                             // the back and finishes at the front again
 594:                         Area side1 = new Area(
 595:                             new Rectangle2D.Double(
 596:                                 plotArea.getX(), plotArea.getY(),
 597:                                 arc.getStartPoint().getX() - plotArea.getX(), 
 598:                                 plotArea.getHeight()
 599:                             )
 600:                         );
 601:                         side1.intersect(front);
 602: 
 603:                         Area side2 = new Area(
 604:                             new Rectangle2D.Double(
 605:                                 arc.getEndPoint().getX(), plotArea.getY(),
 606:                                 plotArea.getMaxX() - arc.getEndPoint().getX(),
 607:                                 plotArea.getHeight()
 608:                             )
 609:                         );
 610: 
 611:                         side2.intersect(front);
 612:                         g2.setPaint(paint);
 613:                         if (drawFront) {
 614:                             g2.fill(side1);
 615:                             g2.fill(side2);
 616:                         }
 617: 
 618:                         if (drawBack) {
 619:                             g2.fill(back);
 620:                         }
 621: 
 622:                         g2.setPaint(outlinePaint);
 623:                         if (drawFront) {
 624:                             g2.draw(side1);
 625:                             g2.draw(side2);
 626:                         }
 627: 
 628:                         if (drawBack) {
 629:                             g2.draw(back);
 630:                         }
 631: 
 632:                     }
 633:                 }
 634:                 else {  // starts at the front, finishes at the back (going 
 635:                         // around the left side)
 636: 
 637:                     if (drawBack) {
 638:                         Area side2 = new Area(
 639:                             new Rectangle2D.Double(
 640:                                 plotArea.getX(), plotArea.getY(),
 641:                                 arc.getEndPoint().getX() - plotArea.getX(), 
 642:                                 plotArea.getHeight()
 643:                             )
 644:                         );
 645:                         side2.intersect(back);
 646:                         g2.setPaint(paint);
 647:                         g2.fill(side2);
 648:                         g2.setPaint(outlinePaint);
 649:                         g2.draw(side2);
 650:                     }
 651: 
 652:                     if (drawFront) {
 653:                         Area side1 = new Area(
 654:                             new Rectangle2D.Double(
 655:                                 plotArea.getX(), plotArea.getY(),
 656:                                 arc.getStartPoint().getX() - plotArea.getX(),
 657:                                 plotArea.getHeight()
 658:                             )
 659:                         );
 660:                         side1.intersect(front);
 661:                         g2.setPaint(paint);
 662:                         g2.fill(side1);
 663:                         g2.setPaint(outlinePaint);
 664:                         g2.draw(side1);
 665:                     }
 666:                 }
 667:             }
 668:             else {  // the segment starts at the back (still extending 
 669:                     // CLOCKWISE)
 670: 
 671:                 if (!isAngleAtFront(end)) {
 672:                     if (extent > -180.0) {  // whole segment stays at the back
 673:                         if (drawBack) {
 674:                             Area side = new Area(
 675:                                 new Rectangle2D.Double(
 676:                                     arc.getStartPoint().getX(), plotArea.getY(),
 677:                                     arc.getEndPoint().getX() 
 678:                                     - arc.getStartPoint().getX(),
 679:                                     plotArea.getHeight()
 680:                                 )
 681:                             );
 682:                             side.intersect(back);
 683:                             g2.setPaint(paint);
 684:                             g2.fill(side);
 685:                             g2.setPaint(outlinePaint);
 686:                             g2.draw(side);
 687:                         }
 688:                     }
 689:                     else {  // starts at the back, wraps around front, and 
 690:                             // finishes at back again
 691:                         Area side1 = new Area(
 692:                             new Rectangle2D.Double(
 693:                                 arc.getStartPoint().getX(), plotArea.getY(),
 694:                                 plotArea.getMaxX() - arc.getStartPoint().getX(),
 695:                                 plotArea.getHeight()
 696:                             )
 697:                         );
 698:                         side1.intersect(back);
 699: 
 700:                         Area side2 = new Area(
 701:                             new Rectangle2D.Double(
 702:                                 plotArea.getX(), plotArea.getY(),
 703:                                 arc.getEndPoint().getX() - plotArea.getX(),
 704:                                 plotArea.getHeight()
 705:                             )
 706:                         );
 707: 
 708:                         side2.intersect(back);
 709: 
 710:                         g2.setPaint(paint);
 711:                         if (drawBack) {
 712:                             g2.fill(side1);
 713:                             g2.fill(side2);
 714:                         }
 715: 
 716:                         if (drawFront) {
 717:                             g2.fill(front);
 718:                         }
 719: 
 720:                         g2.setPaint(outlinePaint);
 721:                         if (drawBack) {
 722:                             g2.draw(side1);
 723:                             g2.draw(side2);
 724:                         }
 725: 
 726:                         if (drawFront) {
 727:                             g2.draw(front);
 728:                         }
 729: 
 730:                     }
 731:                 }
 732:                 else {  // starts at back, finishes at front (CLOCKWISE)
 733: 
 734:                     if (drawBack) {
 735:                         Area side1 = new Area(
 736:                             new Rectangle2D.Double(
 737:                                 arc.getStartPoint().getX(), plotArea.getY(),
 738:                                 plotArea.getMaxX() - arc.getStartPoint().getX(),
 739:                                 plotArea.getHeight()
 740:                             )
 741:                         );
 742:                         side1.intersect(back);
 743:                         g2.setPaint(paint);
 744:                         g2.fill(side1);
 745:                         g2.setPaint(outlinePaint);
 746:                         g2.draw(side1);
 747:                     }
 748: 
 749:                     if (drawFront) {
 750:                         Area side2 = new Area(
 751:                             new Rectangle2D.Double(
 752:                                 arc.getEndPoint().getX(), plotArea.getY(),
 753:                                 plotArea.getMaxX() - arc.getEndPoint().getX(),
 754:                                 plotArea.getHeight()
 755:                             )
 756:                         );
 757:                         side2.intersect(front);
 758:                         g2.setPaint(paint);
 759:                         g2.fill(side2);
 760:                         g2.setPaint(outlinePaint);
 761:                         g2.draw(side2);
 762:                     }
 763: 
 764:                 }
 765:             }
 766:         }
 767:         else if (extent > 0.0) {  // the pie sections are arranged ANTICLOCKWISE
 768: 
 769:             if (isAngleAtFront(start)) {  // segment starts at the front
 770: 
 771:                 if (!isAngleAtBack(end)) {  // and finishes at the front
 772: 
 773:                     if (extent < 180.0) {  // segment only occupies the front
 774:                         if (drawFront) {
 775:                             Area side = new Area(
 776:                                 new Rectangle2D.Double(
 777:                                     arc.getStartPoint().getX(), plotArea.getY(),
 778:                                     arc.getEndPoint().getX() 
 779:                                     - arc.getStartPoint().getX(),
 780:                                     plotArea.getHeight()
 781:                                 )
 782:                             );
 783:                             side.intersect(front);
 784:                             g2.setPaint(paint);
 785:                             g2.fill(side);
 786:                             g2.setPaint(outlinePaint);
 787:                             g2.draw(side);
 788:                         }
 789:                     }
 790:                     else {  // segments wraps right around the back...
 791:                         Area side1 = new Area(
 792:                             new Rectangle2D.Double(
 793:                                 arc.getStartPoint().getX(), plotArea.getY(),
 794:                                 plotArea.getMaxX() - arc.getStartPoint().getX(),
 795:                                 plotArea.getHeight()
 796:                             )
 797:                         );
 798:                         side1.intersect(front);
 799: 
 800:                         Area side2 = new Area(
 801:                             new Rectangle2D.Double(
 802:                                 plotArea.getX(), plotArea.getY(),
 803:                                 arc.getEndPoint().getX() - plotArea.getX(),
 804:                                 plotArea.getHeight()
 805:                             )
 806:                         );
 807:                         side2.intersect(front);
 808: 
 809:                         g2.setPaint(paint);
 810:                         if (drawFront) {
 811:                             g2.fill(side1);
 812:                             g2.fill(side2);
 813:                         }
 814: 
 815:                         if (drawBack) {
 816:                             g2.fill(back);
 817:                         }
 818: 
 819:                         g2.setPaint(outlinePaint);
 820:                         if (drawFront) {
 821:                             g2.draw(side1);
 822:                             g2.draw(side2);
 823:                         }
 824: 
 825:                         if (drawBack) {
 826:                             g2.draw(back);
 827:                         }
 828: 
 829:                     }
 830:                 }
 831:                 else {  // segments starts at front and finishes at back...
 832:                     if (drawBack) {
 833:                         Area side2 = new Area(
 834:                             new Rectangle2D.Double(
 835:                                 arc.getEndPoint().getX(), plotArea.getY(),
 836:                                 plotArea.getMaxX() - arc.getEndPoint().getX(),
 837:                                 plotArea.getHeight()
 838:                             )
 839:                         );
 840:                         side2.intersect(back);
 841:                         g2.setPaint(paint);
 842:                         g2.fill(side2);
 843:                         g2.setPaint(outlinePaint);
 844:                         g2.draw(side2);
 845:                     }
 846: 
 847:                     if (drawFront) {
 848:                         Area side1 = new Area(
 849:                             new Rectangle2D.Double(
 850:                                 arc.getStartPoint().getX(), plotArea.getY(),
 851:                                 plotArea.getMaxX() - arc.getStartPoint().getX(),
 852:                                 plotArea.getHeight()
 853:                             )
 854:                         );
 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(
 869:                                 new Rectangle2D.Double(
 870:                                     arc.getEndPoint().getX(), plotArea.getY(),
 871:                                     arc.getStartPoint().getX() 
 872:                                     - arc.getEndPoint().getX(),
 873:                                     plotArea.getHeight()
 874:                                 )
 875:                             );
 876:                             side.intersect(back);
 877:                             g2.setPaint(paint);
 878:                             g2.fill(side);
 879:                             g2.setPaint(outlinePaint);
 880:                             g2.draw(side);
 881:                         }
 882:                     }
 883:                     else {  // starts at back and wraps right around to the 
 884:                             // back again
 885:                         Area side1 = new Area(
 886:                             new Rectangle2D.Double(
 887:                                 arc.getStartPoint().getX(), plotArea.getY(),
 888:                                 plotArea.getX() - arc.getStartPoint().getX(),
 889:                                 plotArea.getHeight()
 890:                             )
 891:                         );
 892:                         side1.intersect(back);
 893: 
 894:                         Area side2 = new Area(
 895:                             new Rectangle2D.Double(
 896:                                 arc.getEndPoint().getX(), plotArea.getY(),
 897:                                 plotArea.getMaxX() - arc.getEndPoint().getX(),
 898:                                 plotArea.getHeight()
 899:                             )
 900:                         );
 901:                         side2.intersect(back);
 902: 
 903:                         g2.setPaint(paint);
 904:                         if (drawBack) {
 905:                             g2.fill(side1);
 906:                             g2.fill(side2);
 907:                         }
 908: 
 909:                         if (drawFront) {
 910:                             g2.fill(front);
 911:                         }
 912: 
 913:                         g2.setPaint(outlinePaint);
 914:                         if (drawBack) {
 915:                             g2.draw(side1);
 916:                             g2.draw(side2);
 917:                         }
 918: 
 919:                         if (drawFront) {
 920:                             g2.draw(front);
 921:                         }
 922: 
 923:                     }
 924:                 }
 925:                 else {  // starts at the back and finishes at the front 
 926:                         // (wrapping the left side)
 927:                     if (drawBack) {
 928:                         Area side1 = new Area(
 929:                             new Rectangle2D.Double(
 930:                                 plotArea.getX(), plotArea.getY(),
 931:                                 arc.getStartPoint().getX() - plotArea.getX(),
 932:                                 plotArea.getHeight()
 933:                             )
 934:                         );
 935:                         side1.intersect(back);
 936:                         g2.setPaint(paint);
 937:                         g2.fill(side1);
 938:                         g2.setPaint(outlinePaint);
 939:                         g2.draw(side1);
 940:                     }
 941: 
 942:                     if (drawFront) {
 943:                         Area side2 = new Area(
 944:                             new Rectangle2D.Double(
 945:                                 plotArea.getX(), plotArea.getY(),
 946:                                 arc.getEndPoint().getX() - plotArea.getX(),
 947:                                 plotArea.getHeight()
 948:                             )
 949:                         );
 950:                         side2.intersect(front);
 951:                         g2.setPaint(paint);
 952:                         g2.fill(side2);
 953:                         g2.setPaint(outlinePaint);
 954:                         g2.draw(side2);
 955:                     }
 956:                 }
 957:             }
 958: 
 959:         }
 960: 
 961:     }
 962: 
 963:     /**
 964:      * Returns a short string describing the type of plot.
 965:      *
 966:      * @return <i>Pie 3D Plot</i>.
 967:      */
 968:     public String getPlotType () {
 969:         return localizationResources.getString("Pie_3D_Plot");
 970:     }
 971: 
 972:     /**
 973:      * A utility method that returns true if the angle represents a point at 
 974:      * the front of the 3D pie chart.  0 - 180 degrees is the back, 180 - 360 
 975:      * is the front.
 976:      *
 977:      * @param angle  the angle.
 978:      *
 979:      * @return A boolean.
 980:      */
 981:     private boolean isAngleAtFront(double angle) {
 982:         return (Math.sin(Math.toRadians(angle)) < 0.0);
 983:     }
 984: 
 985:     /**
 986:      * A utility method that returns true if the angle represents a point at 
 987:      * the back of the 3D pie chart.  0 - 180 degrees is the back, 180 - 360 
 988:      * is the front.
 989:      *
 990:      * @param angle  the angle.
 991:      *
 992:      * @return <code>true</code> if the angle is at the back of the pie.
 993:      */
 994:     private boolean isAngleAtBack(double angle) {
 995:         return (Math.sin(Math.toRadians(angle)) > 0.0);
 996:     }
 997: 
 998: }