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