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