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