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