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