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