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