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