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