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