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