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