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