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