001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2013, 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-2013, 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 * DaveLaw (dave ATT davelaw DOTT de); 041 * 042 * Changes 043 * ------- 044 * 21-Jun-2002 : Version 1; 045 * 31-Jul-2002 : Modified to use startAngle and direction, drawing modified so 046 * that charts render with foreground alpha < 1.0 (DG); 047 * 05-Aug-2002 : Small modification to draw method to support URLs for HTML 048 * image maps (RA); 049 * 26-Sep-2002 : Fixed errors reported by Checkstyle (DG); 050 * 18-Oct-2002 : Added drawing bug fix sent in by Xun Kang, and made a couple 051 * of other related fixes (DG); 052 * 30-Oct-2002 : Changed the PieDataset interface. Fixed another drawing 053 * bug (DG); 054 * 12-Nov-2002 : Fixed null pointer exception for zero or negative values (DG); 055 * 07-Mar-2003 : Modified to pass pieIndex on to PieSectionEntity (DG); 056 * 21-Mar-2003 : Added workaround for bug id 620031 (DG); 057 * 26-Mar-2003 : Implemented Serializable (DG); 058 * 30-Jul-2003 : Modified entity constructor (CZ); 059 * 29-Aug-2003 : Small changes for API updates in PiePlot class (DG); 060 * 02-Sep-2003 : Fixed bug where the 'no data' message is not displayed (DG); 061 * 08-Sep-2003 : Added internationalization via use of properties 062 * resourceBundle (RFE 690236) (AL); 063 * 29-Oct-2003 : Added workaround for font alignment in PDF output (DG); 064 * 20-Nov-2003 : Fixed bug 845289 (sides not showing) (DG); 065 * 25-Nov-2003 : Added patch (845095) to fix outline paint issues (DG); 066 * 10-Mar-2004 : Numerous changes to enhance labelling (DG); 067 * 31-Mar-2004 : Adjusted plot area when label generator is null (DG); 068 * 08-Apr-2004 : Added flag to PiePlot class to control the treatment of null 069 * values (DG); 070 * Added pieIndex to PieSectionEntity (DG); 071 * 15-Nov-2004 : Removed creation of default tool tip generator (DG); 072 * 16-Jun-2005 : Added default constructor (DG); 073 * ------------- JFREECHART 1.0.x --------------------------------------------- 074 * 27-Sep-2006 : Updated draw() method for new lookup methods (DG); 075 * 22-Mar-2007 : Added equals() override (DG); 076 * 18-Jun-2007 : Added handling for simple label option (DG); 077 * 04-Oct-2007 : Added option to darken sides of plot - thanks to Alex Moots 078 * (see patch 1805262) (DG); 079 * 21-Nov-2007 : Changed default depth factor, fixed labelling bugs and added 080 * debug code - see debug flags in PiePlot class (DG); 081 * 20-Mar-2008 : Fixed bug 1920854 - multiple redraws of the section 082 * labels (DG); 083 * 19-May-2009 : Fixed FindBugs warnings, patch by Michal Wozniak (DG); 084 * 10-Jul-2009 : Added drop shaow support (DG); 085 * 10-Oct-2011 : Localization fix: bug #3353913 (MH); 086 * 18-Oct-2011 : Fix tooltip offset with shadow generator (DG); 087 * 11-Jun-2012 : Utilise new PaintAlpha class (patch 3204823 from DaveLaw) (DG); 088 * 089 */ 090 091package org.jfree.chart.plot; 092 093import java.awt.AlphaComposite; 094import java.awt.Color; 095import java.awt.Composite; 096import java.awt.Font; 097import java.awt.FontMetrics; 098import java.awt.Graphics2D; 099import java.awt.Paint; 100import java.awt.Polygon; 101import java.awt.Shape; 102import java.awt.Stroke; 103import java.awt.geom.Arc2D; 104import java.awt.geom.Area; 105import java.awt.geom.Ellipse2D; 106import java.awt.geom.Point2D; 107import java.awt.geom.Rectangle2D; 108import java.awt.image.BufferedImage; 109import java.io.Serializable; 110import java.util.ArrayList; 111import java.util.Iterator; 112import java.util.List; 113 114import org.jfree.chart.entity.EntityCollection; 115import org.jfree.chart.entity.PieSectionEntity; 116import org.jfree.chart.event.PlotChangeEvent; 117import org.jfree.chart.labels.PieToolTipGenerator; 118import org.jfree.chart.util.PaintAlpha; 119import org.jfree.data.general.DatasetUtilities; 120import org.jfree.data.general.PieDataset; 121import org.jfree.ui.RectangleInsets; 122 123/** 124 * A plot that displays data in the form of a 3D pie chart, using data from 125 * any class that implements the {@link PieDataset} interface. 126 * <P> 127 * Although this class extends {@link PiePlot}, it does not currently support 128 * exploded sections. 129 */ 130public class PiePlot3D extends PiePlot implements Serializable { 131 132 /** For serialization. */ 133 private static final long serialVersionUID = 3408984188945161432L; 134 135 /** The factor of the depth of the pie from the plot height */ 136 private double depthFactor = 0.12; 137 138 /** 139 * A flag that controls whether or not the sides of the pie chart 140 * are rendered using a darker colour. 141 * 142 * @since 1.0.7. 143 */ 144 private boolean darkerSides = false; // default preserves previous 145 // behaviour 146 147 /** 148 * Creates a new instance with no dataset. 149 */ 150 public PiePlot3D() { 151 this(null); 152 } 153 154 /** 155 * Creates a pie chart with a three dimensional effect using the specified 156 * dataset. 157 * 158 * @param dataset the dataset (<code>null</code> permitted). 159 */ 160 public PiePlot3D(PieDataset dataset) { 161 super(dataset); 162 setCircular(false, false); 163 } 164 165 /** 166 * Returns the depth factor for the chart. 167 * 168 * @return The depth factor. 169 * 170 * @see #setDepthFactor(double) 171 */ 172 public double getDepthFactor() { 173 return this.depthFactor; 174 } 175 176 /** 177 * Sets the pie depth as a percentage of the height of the plot area, and 178 * sends a {@link PlotChangeEvent} to all registered listeners. 179 * 180 * @param factor the depth factor (for example, 0.20 is twenty percent). 181 * 182 * @see #getDepthFactor() 183 */ 184 public void setDepthFactor(double factor) { 185 this.depthFactor = factor; 186 fireChangeEvent(); 187 } 188 189 /** 190 * Returns a flag that controls whether or not the sides of the pie chart 191 * are rendered using a darker colour. 192 * 193 * @return A boolean. 194 * 195 * @see #setDarkerSides(boolean) 196 * 197 * @since 1.0.7 198 */ 199 public boolean getDarkerSides() { 200 return this.darkerSides; 201 } 202 203 /** 204 * Sets a flag that controls whether or not the sides of the pie chart 205 * are rendered using a darker colour, and sends a {@link PlotChangeEvent} 206 * to all registered listeners. 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 @Override 234 public void draw(Graphics2D g2, Rectangle2D plotArea, Point2D anchor, 235 PlotState parentState, 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.isEmpty()) { 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 paint = PaintAlpha.darker(paint); 649 } 650 651 double start = arc.getAngleStart(); 652 double extent = arc.getAngleExtent(); 653 double end = start + extent; 654 655 g2.setStroke(outlineStroke); 656 657 // for CLOCKWISE charts, the extent will be negative... 658 if (extent < 0.0) { 659 660 if (isAngleAtFront(start)) { // start at front 661 662 if (!isAngleAtBack(end)) { 663 664 if (extent > -180.0) { // the segment is entirely at the 665 // front of the chart 666 if (drawFront) { 667 Area side = new Area(new Rectangle2D.Double( 668 arc.getEndPoint().getX(), plotArea.getY(), 669 arc.getStartPoint().getX() 670 - arc.getEndPoint().getX(), 671 plotArea.getHeight())); 672 side.intersect(front); 673 g2.setPaint(paint); 674 g2.fill(side); 675 g2.setPaint(outlinePaint); 676 g2.draw(side); 677 } 678 } 679 else { // the segment starts at the front, and wraps all 680 // the way around 681 // the back and finishes at the front again 682 Area side1 = new Area(new Rectangle2D.Double( 683 plotArea.getX(), plotArea.getY(), 684 arc.getStartPoint().getX() - plotArea.getX(), 685 plotArea.getHeight())); 686 side1.intersect(front); 687 688 Area side2 = new Area(new Rectangle2D.Double( 689 arc.getEndPoint().getX(), plotArea.getY(), 690 plotArea.getMaxX() - arc.getEndPoint().getX(), 691 plotArea.getHeight())); 692 693 side2.intersect(front); 694 g2.setPaint(paint); 695 if (drawFront) { 696 g2.fill(side1); 697 g2.fill(side2); 698 } 699 700 if (drawBack) { 701 g2.fill(back); 702 } 703 704 g2.setPaint(outlinePaint); 705 if (drawFront) { 706 g2.draw(side1); 707 g2.draw(side2); 708 } 709 710 if (drawBack) { 711 g2.draw(back); 712 } 713 714 } 715 } 716 else { // starts at the front, finishes at the back (going 717 // around the left side) 718 719 if (drawBack) { 720 Area side2 = new Area(new Rectangle2D.Double( 721 plotArea.getX(), plotArea.getY(), 722 arc.getEndPoint().getX() - plotArea.getX(), 723 plotArea.getHeight())); 724 side2.intersect(back); 725 g2.setPaint(paint); 726 g2.fill(side2); 727 g2.setPaint(outlinePaint); 728 g2.draw(side2); 729 } 730 731 if (drawFront) { 732 Area side1 = new Area(new Rectangle2D.Double( 733 plotArea.getX(), plotArea.getY(), 734 arc.getStartPoint().getX() - plotArea.getX(), 735 plotArea.getHeight())); 736 side1.intersect(front); 737 g2.setPaint(paint); 738 g2.fill(side1); 739 g2.setPaint(outlinePaint); 740 g2.draw(side1); 741 } 742 } 743 } 744 else { // the segment starts at the back (still extending 745 // CLOCKWISE) 746 747 if (!isAngleAtFront(end)) { 748 if (extent > -180.0) { // whole segment stays at the back 749 if (drawBack) { 750 Area side = new Area(new Rectangle2D.Double( 751 arc.getStartPoint().getX(), plotArea.getY(), 752 arc.getEndPoint().getX() 753 - arc.getStartPoint().getX(), 754 plotArea.getHeight())); 755 side.intersect(back); 756 g2.setPaint(paint); 757 g2.fill(side); 758 g2.setPaint(outlinePaint); 759 g2.draw(side); 760 } 761 } 762 else { // starts at the back, wraps around front, and 763 // finishes at back again 764 Area side1 = new Area(new Rectangle2D.Double( 765 arc.getStartPoint().getX(), plotArea.getY(), 766 plotArea.getMaxX() - arc.getStartPoint().getX(), 767 plotArea.getHeight())); 768 side1.intersect(back); 769 770 Area side2 = new Area(new Rectangle2D.Double( 771 plotArea.getX(), plotArea.getY(), 772 arc.getEndPoint().getX() - plotArea.getX(), 773 plotArea.getHeight())); 774 775 side2.intersect(back); 776 777 g2.setPaint(paint); 778 if (drawBack) { 779 g2.fill(side1); 780 g2.fill(side2); 781 } 782 783 if (drawFront) { 784 g2.fill(front); 785 } 786 787 g2.setPaint(outlinePaint); 788 if (drawBack) { 789 g2.draw(side1); 790 g2.draw(side2); 791 } 792 793 if (drawFront) { 794 g2.draw(front); 795 } 796 797 } 798 } 799 else { // starts at back, finishes at front (CLOCKWISE) 800 801 if (drawBack) { 802 Area side1 = new Area(new Rectangle2D.Double( 803 arc.getStartPoint().getX(), plotArea.getY(), 804 plotArea.getMaxX() - arc.getStartPoint().getX(), 805 plotArea.getHeight())); 806 side1.intersect(back); 807 g2.setPaint(paint); 808 g2.fill(side1); 809 g2.setPaint(outlinePaint); 810 g2.draw(side1); 811 } 812 813 if (drawFront) { 814 Area side2 = new Area(new Rectangle2D.Double( 815 arc.getEndPoint().getX(), plotArea.getY(), 816 plotArea.getMaxX() - arc.getEndPoint().getX(), 817 plotArea.getHeight())); 818 side2.intersect(front); 819 g2.setPaint(paint); 820 g2.fill(side2); 821 g2.setPaint(outlinePaint); 822 g2.draw(side2); 823 } 824 825 } 826 } 827 } 828 else if (extent > 0.0) { // the pie sections are arranged ANTICLOCKWISE 829 830 if (isAngleAtFront(start)) { // segment starts at the front 831 832 if (!isAngleAtBack(end)) { // and finishes at the front 833 834 if (extent < 180.0) { // segment only occupies the front 835 if (drawFront) { 836 Area side = new Area(new Rectangle2D.Double( 837 arc.getStartPoint().getX(), plotArea.getY(), 838 arc.getEndPoint().getX() 839 - arc.getStartPoint().getX(), 840 plotArea.getHeight())); 841 side.intersect(front); 842 g2.setPaint(paint); 843 g2.fill(side); 844 g2.setPaint(outlinePaint); 845 g2.draw(side); 846 } 847 } 848 else { // segments wraps right around the back... 849 Area side1 = new Area(new Rectangle2D.Double( 850 arc.getStartPoint().getX(), plotArea.getY(), 851 plotArea.getMaxX() - arc.getStartPoint().getX(), 852 plotArea.getHeight())); 853 side1.intersect(front); 854 855 Area side2 = new Area(new Rectangle2D.Double( 856 plotArea.getX(), plotArea.getY(), 857 arc.getEndPoint().getX() - plotArea.getX(), 858 plotArea.getHeight())); 859 side2.intersect(front); 860 861 g2.setPaint(paint); 862 if (drawFront) { 863 g2.fill(side1); 864 g2.fill(side2); 865 } 866 867 if (drawBack) { 868 g2.fill(back); 869 } 870 871 g2.setPaint(outlinePaint); 872 if (drawFront) { 873 g2.draw(side1); 874 g2.draw(side2); 875 } 876 877 if (drawBack) { 878 g2.draw(back); 879 } 880 881 } 882 } 883 else { // segments starts at front and finishes at back... 884 if (drawBack) { 885 Area side2 = new Area(new Rectangle2D.Double( 886 arc.getEndPoint().getX(), plotArea.getY(), 887 plotArea.getMaxX() - arc.getEndPoint().getX(), 888 plotArea.getHeight())); 889 side2.intersect(back); 890 g2.setPaint(paint); 891 g2.fill(side2); 892 g2.setPaint(outlinePaint); 893 g2.draw(side2); 894 } 895 896 if (drawFront) { 897 Area side1 = new Area(new Rectangle2D.Double( 898 arc.getStartPoint().getX(), plotArea.getY(), 899 plotArea.getMaxX() - arc.getStartPoint().getX(), 900 plotArea.getHeight())); 901 side1.intersect(front); 902 g2.setPaint(paint); 903 g2.fill(side1); 904 g2.setPaint(outlinePaint); 905 g2.draw(side1); 906 } 907 } 908 } 909 else { // segment starts at back 910 911 if (!isAngleAtFront(end)) { 912 if (extent < 180.0) { // and finishes at back 913 if (drawBack) { 914 Area side = new Area(new Rectangle2D.Double( 915 arc.getEndPoint().getX(), plotArea.getY(), 916 arc.getStartPoint().getX() 917 - arc.getEndPoint().getX(), 918 plotArea.getHeight())); 919 side.intersect(back); 920 g2.setPaint(paint); 921 g2.fill(side); 922 g2.setPaint(outlinePaint); 923 g2.draw(side); 924 } 925 } 926 else { // starts at back and wraps right around to the 927 // back again 928 Area side1 = new Area(new Rectangle2D.Double( 929 arc.getStartPoint().getX(), plotArea.getY(), 930 plotArea.getX() - arc.getStartPoint().getX(), 931 plotArea.getHeight())); 932 side1.intersect(back); 933 934 Area side2 = new Area(new Rectangle2D.Double( 935 arc.getEndPoint().getX(), plotArea.getY(), 936 plotArea.getMaxX() - arc.getEndPoint().getX(), 937 plotArea.getHeight())); 938 side2.intersect(back); 939 940 g2.setPaint(paint); 941 if (drawBack) { 942 g2.fill(side1); 943 g2.fill(side2); 944 } 945 946 if (drawFront) { 947 g2.fill(front); 948 } 949 950 g2.setPaint(outlinePaint); 951 if (drawBack) { 952 g2.draw(side1); 953 g2.draw(side2); 954 } 955 956 if (drawFront) { 957 g2.draw(front); 958 } 959 960 } 961 } 962 else { // starts at the back and finishes at the front 963 // (wrapping the left side) 964 if (drawBack) { 965 Area side1 = new Area(new Rectangle2D.Double( 966 plotArea.getX(), plotArea.getY(), 967 arc.getStartPoint().getX() - plotArea.getX(), 968 plotArea.getHeight())); 969 side1.intersect(back); 970 g2.setPaint(paint); 971 g2.fill(side1); 972 g2.setPaint(outlinePaint); 973 g2.draw(side1); 974 } 975 976 if (drawFront) { 977 Area side2 = new Area(new Rectangle2D.Double( 978 plotArea.getX(), plotArea.getY(), 979 arc.getEndPoint().getX() - plotArea.getX(), 980 plotArea.getHeight())); 981 side2.intersect(front); 982 g2.setPaint(paint); 983 g2.fill(side2); 984 g2.setPaint(outlinePaint); 985 g2.draw(side2); 986 } 987 } 988 } 989 990 } 991 992 } 993 994 /** 995 * Returns a short string describing the type of plot. 996 * 997 * @return <i>Pie 3D Plot</i>. 998 */ 999 @Override 1000 public String getPlotType() { 1001 return localizationResources.getString("Pie_3D_Plot"); 1002 } 1003 1004 /** 1005 * A utility method that returns true if the angle represents a point at 1006 * the front of the 3D pie chart. 0 - 180 degrees is the back, 180 - 360 1007 * is the front. 1008 * 1009 * @param angle the angle. 1010 * 1011 * @return A boolean. 1012 */ 1013 private boolean isAngleAtFront(double angle) { 1014 return (Math.sin(Math.toRadians(angle)) < 0.0); 1015 } 1016 1017 /** 1018 * A utility method that returns true if the angle represents a point at 1019 * the back of the 3D pie chart. 0 - 180 degrees is the back, 180 - 360 1020 * is the front. 1021 * 1022 * @param angle the angle. 1023 * 1024 * @return <code>true</code> if the angle is at the back of the pie. 1025 */ 1026 private boolean isAngleAtBack(double angle) { 1027 return (Math.sin(Math.toRadians(angle)) > 0.0); 1028 } 1029 1030 /** 1031 * Tests this plot for equality with an arbitrary object. 1032 * 1033 * @param obj the object (<code>null</code> permitted). 1034 * 1035 * @return A boolean. 1036 */ 1037 @Override 1038 public boolean equals(Object obj) { 1039 if (obj == this) { 1040 return true; 1041 } 1042 if (!(obj instanceof PiePlot3D)) { 1043 return false; 1044 } 1045 PiePlot3D that = (PiePlot3D) obj; 1046 if (this.depthFactor != that.depthFactor) { 1047 return false; 1048 } 1049 if (this.darkerSides != that.darkerSides) { 1050 return false; 1051 } 1052 return super.equals(obj); 1053 } 1054 1055}