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,
180: Point2D anchor, 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
228: / 2.0, 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
237: / (1.0 + maximumExplodePercent);
238:
239: double h1 = explodeArea.getWidth() * percent;
240: double v1 = explodeArea.getHeight() * percent;
241: Rectangle2D pieArea = new Rectangle2D.Double(explodeArea.getX()
242: + h1 / 2.0, explodeArea.getY() + v1 / 2.0, explodeArea
243: .getWidth()
244: - h1, explodeArea.getHeight() - v1);
245:
246: int depth = (int) (pieArea.getHeight() * this .depthFactor);
247: // the link area defines the dog-leg point for the linking lines to
248: // the labels
249: Rectangle2D linkArea = new Rectangle2D.Double(linkX, linkY,
250: linkW, linkH - depth);
251: state.setLinkArea(linkArea);
252:
253: state.setPieArea(pieArea);
254: state.setPieCenterX(pieArea.getCenterX());
255: state.setPieCenterY(pieArea.getCenterY() - depth / 2.0);
256: state.setPieWRadius(pieArea.getWidth() / 2.0);
257: state.setPieHRadius((pieArea.getHeight() - depth) / 2.0);
258:
259: drawBackground(g2, plotArea);
260: // get the data source - return if null;
261: PieDataset dataset = getDataset();
262: if (DatasetUtilities.isEmptyOrNull(getDataset())) {
263: drawNoDataMessage(g2, plotArea);
264: g2.setClip(savedClip);
265: drawOutline(g2, plotArea);
266: return;
267: }
268:
269: // if too any elements
270: if (dataset.getKeys().size() > plotArea.getWidth()) {
271: String text = "Too many elements";
272: Font sfont = new Font("dialog", Font.BOLD, 10);
273: g2.setFont(sfont);
274: FontMetrics fm = g2.getFontMetrics(sfont);
275: int stringWidth = fm.stringWidth(text);
276:
277: g2.drawString(text, (int) (plotArea.getX() + (plotArea
278: .getWidth() - stringWidth) / 2), (int) (plotArea
279: .getY() + (plotArea.getHeight() / 2)));
280: return;
281: }
282: // if we are drawing a perfect circle, we need to readjust the top left
283: // coordinates of the drawing area for the arcs to arrive at this
284: // effect.
285: if (isCircular()) {
286: double min = Math.min(plotArea.getWidth(), plotArea
287: .getHeight()) / 2;
288: plotArea = new Rectangle2D.Double(plotArea.getCenterX()
289: - min, plotArea.getCenterY() - min, 2 * min,
290: 2 * min);
291: }
292: // get a list of keys...
293: List sectionKeys = dataset.getKeys();
294:
295: if (sectionKeys.size() == 0) {
296: return;
297: }
298:
299: // establish the coordinates of the top left corner of the drawing area
300: double arcX = pieArea.getX();
301: double arcY = pieArea.getY();
302:
303: //g2.clip(clipArea);
304: Composite originalComposite = g2.getComposite();
305: g2.setComposite(AlphaComposite.getInstance(
306: AlphaComposite.SRC_OVER, getForegroundAlpha()));
307:
308: double totalValue = DatasetUtilities
309: .calculatePieDatasetTotal(dataset);
310: double runningTotal = 0;
311: if (depth < 0) {
312: return; // if depth is negative don't draw anything
313: }
314:
315: ArrayList arcList = new ArrayList();
316: Arc2D.Double arc;
317: Paint paint;
318: Paint outlinePaint;
319: Stroke outlineStroke;
320:
321: Iterator iterator = sectionKeys.iterator();
322: while (iterator.hasNext()) {
323:
324: Comparable currentKey = (Comparable) iterator.next();
325: Number dataValue = dataset.getValue(currentKey);
326: if (dataValue == null) {
327: arcList.add(null);
328: continue;
329: }
330: double value = dataValue.doubleValue();
331: if (value <= 0) {
332: arcList.add(null);
333: continue;
334: }
335: double startAngle = getStartAngle();
336: double direction = getDirection().getFactor();
337: double angle1 = startAngle
338: + (direction * (runningTotal * 360)) / totalValue;
339: double angle2 = startAngle
340: + (direction * (runningTotal + value) * 360)
341: / totalValue;
342: if (Math.abs(angle2 - angle1) > getMinimumArcAngleToDraw()) {
343: arcList.add(new Arc2D.Double(arcX, arcY + depth,
344: pieArea.getWidth(),
345: pieArea.getHeight() - depth, angle1, angle2
346: - angle1, Arc2D.PIE));
347: } else {
348: arcList.add(null);
349: }
350: runningTotal += value;
351: }
352:
353: Shape oldClip = g2.getClip();
354:
355: Ellipse2D top = new Ellipse2D.Double(pieArea.getX(), pieArea
356: .getY(), pieArea.getWidth(), pieArea.getHeight()
357: - depth);
358:
359: Ellipse2D bottom = new Ellipse2D.Double(pieArea.getX(), pieArea
360: .getY()
361: + depth, pieArea.getWidth(), pieArea.getHeight()
362: - depth);
363:
364: Rectangle2D lower = new Rectangle2D.Double(top.getX(), top
365: .getCenterY(), pieArea.getWidth(), bottom.getMaxY()
366: - top.getCenterY());
367:
368: Rectangle2D upper = new Rectangle2D.Double(pieArea.getX(), top
369: .getY(), pieArea.getWidth(), bottom.getCenterY()
370: - top.getY());
371:
372: Area a = new Area(top);
373: a.add(new Area(lower));
374: Area b = new Area(bottom);
375: b.add(new Area(upper));
376: Area pie = new Area(a);
377: pie.intersect(b);
378:
379: Area front = new Area(pie);
380: front.subtract(new Area(top));
381:
382: Area back = new Area(pie);
383: back.subtract(new Area(bottom));
384:
385: // draw the bottom circle
386: int[] xs;
387: int[] ys;
388: arc = new Arc2D.Double(arcX, arcY + depth, pieArea.getWidth(),
389: pieArea.getHeight() - depth, 0, 360, Arc2D.PIE);
390:
391: int categoryCount = arcList.size();
392: for (int categoryIndex = 0; categoryIndex < categoryCount; categoryIndex++) {
393: arc = (Arc2D.Double) arcList.get(categoryIndex);
394: if (arc == null) {
395: continue;
396: }
397: Comparable key = getSectionKey(categoryIndex);
398: paint = lookupSectionPaint(key, true);
399: outlinePaint = lookupSectionOutlinePaint(key);
400: outlineStroke = lookupSectionOutlineStroke(key);
401: g2.setPaint(paint);
402: g2.fill(arc);
403: g2.setPaint(outlinePaint);
404: g2.setStroke(outlineStroke);
405: g2.draw(arc);
406: g2.setPaint(paint);
407:
408: Point2D p1 = arc.getStartPoint();
409:
410: // draw the height
411: xs = new int[] { (int) arc.getCenterX(),
412: (int) arc.getCenterX(), (int) p1.getX(),
413: (int) p1.getX() };
414: ys = new int[] { (int) arc.getCenterY(),
415: (int) arc.getCenterY() - depth,
416: (int) p1.getY() - depth, (int) p1.getY() };
417: Polygon polygon = new Polygon(xs, ys, 4);
418: g2.setPaint(java.awt.Color.lightGray);
419: g2.fill(polygon);
420: g2.setPaint(outlinePaint);
421: g2.setStroke(outlineStroke);
422: g2.draw(polygon);
423: g2.setPaint(paint);
424:
425: }
426:
427: g2.setPaint(Color.gray);
428: g2.fill(back);
429: g2.fill(front);
430:
431: // cycle through once drawing only the sides at the back...
432: int cat = 0;
433: iterator = arcList.iterator();
434: while (iterator.hasNext()) {
435: Arc2D segment = (Arc2D) iterator.next();
436: if (segment != null) {
437: Comparable key = getSectionKey(cat);
438: paint = lookupSectionPaint(key, true);
439: outlinePaint = lookupSectionOutlinePaint(key);
440: outlineStroke = lookupSectionOutlineStroke(key);
441: drawSide(g2, pieArea, segment, front, back, paint,
442: outlinePaint, outlineStroke, false, true);
443: }
444: cat++;
445: }
446:
447: // cycle through again drawing only the sides at the front...
448: cat = 0;
449: iterator = arcList.iterator();
450: while (iterator.hasNext()) {
451: Arc2D segment = (Arc2D) iterator.next();
452: if (segment != null) {
453: Comparable key = getSectionKey(cat);
454: paint = lookupSectionPaint(key);
455: outlinePaint = lookupSectionOutlinePaint(key);
456: outlineStroke = lookupSectionOutlineStroke(key);
457: drawSide(g2, pieArea, segment, front, back, paint,
458: outlinePaint, outlineStroke, true, false);
459: }
460: cat++;
461: }
462:
463: g2.setClip(oldClip);
464:
465: // draw the sections at the top of the pie (and set up tooltips)...
466: Arc2D upperArc;
467: for (int sectionIndex = 0; sectionIndex < categoryCount; sectionIndex++) {
468: arc = (Arc2D.Double) arcList.get(sectionIndex);
469: if (arc == null) {
470: continue;
471: }
472: upperArc = new Arc2D.Double(arcX, arcY, pieArea.getWidth(),
473: pieArea.getHeight() - depth, arc.getAngleStart(),
474: arc.getAngleExtent(), Arc2D.PIE);
475:
476: Comparable currentKey = (Comparable) sectionKeys
477: .get(sectionIndex);
478: paint = lookupSectionPaint(currentKey, true);
479: outlinePaint = lookupSectionOutlinePaint(currentKey);
480: outlineStroke = lookupSectionOutlineStroke(currentKey);
481: g2.setPaint(paint);
482: g2.fill(upperArc);
483: g2.setStroke(outlineStroke);
484: g2.setPaint(outlinePaint);
485: g2.draw(upperArc);
486:
487: // add a tooltip for the section...
488: if (info != null) {
489: EntityCollection entities = info.getOwner()
490: .getEntityCollection();
491: if (entities != null) {
492: String tip = null;
493: PieToolTipGenerator tipster = getToolTipGenerator();
494: if (tipster != null) {
495: // @mgs: using the method's return value was missing
496: tip = tipster.generateToolTip(dataset,
497: currentKey);
498: }
499: String url = null;
500: if (getURLGenerator() != null) {
501: url = getURLGenerator().generateURL(dataset,
502: currentKey, getPieIndex());
503: }
504: PieSectionEntity entity = new PieSectionEntity(
505: upperArc, dataset, getPieIndex(),
506: sectionIndex, currentKey, tip, url);
507: entities.add(entity);
508: }
509: }
510: List keys = dataset.getKeys();
511: Rectangle2D adjustedPlotArea = new Rectangle2D.Double(
512: originalPlotArea.getX(), originalPlotArea.getY(),
513: originalPlotArea.getWidth(), originalPlotArea
514: .getHeight()
515: - depth);
516: drawLabels(g2, keys, totalValue, adjustedPlotArea,
517: linkArea, state);
518: }
519:
520: g2.setClip(savedClip);
521: g2.setComposite(originalComposite);
522: drawOutline(g2, originalPlotArea);
523:
524: }
525:
526: /**
527: * Draws the side of a pie section.
528: *
529: * @param g2 the graphics device.
530: * @param plotArea the plot area.
531: * @param arc the arc.
532: * @param front the front of the pie.
533: * @param back the back of the pie.
534: * @param paint the color.
535: * @param outlinePaint the outline paint.
536: * @param outlineStroke the outline stroke.
537: * @param drawFront draw the front?
538: * @param drawBack draw the back?
539: */
540: protected void drawSide(Graphics2D g2, Rectangle2D plotArea,
541: Arc2D arc, Area front, Area back, Paint paint,
542: Paint outlinePaint, Stroke outlineStroke,
543: boolean drawFront, boolean drawBack) {
544:
545: double start = arc.getAngleStart();
546: double extent = arc.getAngleExtent();
547: double end = start + extent;
548:
549: g2.setStroke(outlineStroke);
550:
551: // for CLOCKWISE charts, the extent will be negative...
552: if (extent < 0.0) {
553:
554: if (isAngleAtFront(start)) { // start at front
555:
556: if (!isAngleAtBack(end)) {
557:
558: if (extent > -180.0) { // the segment is entirely at the
559: // front of the chart
560: if (drawFront) {
561: Area side = new Area(
562: new Rectangle2D.Double(arc
563: .getEndPoint().getX(),
564: plotArea.getY(), arc
565: .getStartPoint()
566: .getX()
567: - arc.getEndPoint()
568: .getX(),
569: plotArea.getHeight()));
570: side.intersect(front);
571: g2.setPaint(paint);
572: g2.fill(side);
573: g2.setPaint(outlinePaint);
574: g2.draw(side);
575: }
576: } else { // the segment starts at the front, and wraps all
577: // the way around
578: // the back and finishes at the front again
579: Area side1 = new Area(new Rectangle2D.Double(
580: plotArea.getX(), plotArea.getY(), arc
581: .getStartPoint().getX()
582: - plotArea.getX(), plotArea
583: .getHeight()));
584: side1.intersect(front);
585:
586: Area side2 = new Area(new Rectangle2D.Double(
587: arc.getEndPoint().getX(), plotArea
588: .getY(), plotArea.getMaxX()
589: - arc.getEndPoint().getX(),
590: plotArea.getHeight()));
591:
592: side2.intersect(front);
593: g2.setPaint(paint);
594: if (drawFront) {
595: g2.fill(side1);
596: g2.fill(side2);
597: }
598:
599: if (drawBack) {
600: g2.fill(back);
601: }
602:
603: g2.setPaint(outlinePaint);
604: if (drawFront) {
605: g2.draw(side1);
606: g2.draw(side2);
607: }
608:
609: if (drawBack) {
610: g2.draw(back);
611: }
612:
613: }
614: } else { // starts at the front, finishes at the back (going
615: // around the left side)
616:
617: if (drawBack) {
618: Area side2 = new Area(new Rectangle2D.Double(
619: plotArea.getX(), plotArea.getY(), arc
620: .getEndPoint().getX()
621: - plotArea.getX(), plotArea
622: .getHeight()));
623: side2.intersect(back);
624: g2.setPaint(paint);
625: g2.fill(side2);
626: g2.setPaint(outlinePaint);
627: g2.draw(side2);
628: }
629:
630: if (drawFront) {
631: Area side1 = new Area(new Rectangle2D.Double(
632: plotArea.getX(), plotArea.getY(), arc
633: .getStartPoint().getX()
634: - plotArea.getX(), plotArea
635: .getHeight()));
636: side1.intersect(front);
637: g2.setPaint(paint);
638: g2.fill(side1);
639: g2.setPaint(outlinePaint);
640: g2.draw(side1);
641: }
642: }
643: } else { // the segment starts at the back (still extending
644: // CLOCKWISE)
645:
646: if (!isAngleAtFront(end)) {
647: if (extent > -180.0) { // whole segment stays at the back
648: if (drawBack) {
649: Area side = new Area(
650: new Rectangle2D.Double(
651: arc.getStartPoint().getX(),
652: plotArea.getY(),
653: arc.getEndPoint().getX()
654: - arc
655: .getStartPoint()
656: .getX(),
657: plotArea.getHeight()));
658: side.intersect(back);
659: g2.setPaint(paint);
660: g2.fill(side);
661: g2.setPaint(outlinePaint);
662: g2.draw(side);
663: }
664: } else { // starts at the back, wraps around front, and
665: // finishes at back again
666: Area side1 = new Area(new Rectangle2D.Double(
667: arc.getStartPoint().getX(), plotArea
668: .getY(), plotArea.getMaxX()
669: - arc.getStartPoint().getX(),
670: plotArea.getHeight()));
671: side1.intersect(back);
672:
673: Area side2 = new Area(new Rectangle2D.Double(
674: plotArea.getX(), plotArea.getY(), arc
675: .getEndPoint().getX()
676: - plotArea.getX(), plotArea
677: .getHeight()));
678:
679: side2.intersect(back);
680:
681: g2.setPaint(paint);
682: if (drawBack) {
683: g2.fill(side1);
684: g2.fill(side2);
685: }
686:
687: if (drawFront) {
688: g2.fill(front);
689: }
690:
691: g2.setPaint(outlinePaint);
692: if (drawBack) {
693: g2.draw(side1);
694: g2.draw(side2);
695: }
696:
697: if (drawFront) {
698: g2.draw(front);
699: }
700:
701: }
702: } else { // starts at back, finishes at front (CLOCKWISE)
703:
704: if (drawBack) {
705: Area side1 = new Area(new Rectangle2D.Double(
706: arc.getStartPoint().getX(), plotArea
707: .getY(), plotArea.getMaxX()
708: - arc.getStartPoint().getX(),
709: plotArea.getHeight()));
710: side1.intersect(back);
711: g2.setPaint(paint);
712: g2.fill(side1);
713: g2.setPaint(outlinePaint);
714: g2.draw(side1);
715: }
716:
717: if (drawFront) {
718: Area side2 = new Area(new Rectangle2D.Double(
719: arc.getEndPoint().getX(), plotArea
720: .getY(), plotArea.getMaxX()
721: - arc.getEndPoint().getX(),
722: plotArea.getHeight()));
723: side2.intersect(front);
724: g2.setPaint(paint);
725: g2.fill(side2);
726: g2.setPaint(outlinePaint);
727: g2.draw(side2);
728: }
729:
730: }
731: }
732: } else if (extent > 0.0) { // the pie sections are arranged ANTICLOCKWISE
733:
734: if (isAngleAtFront(start)) { // segment starts at the front
735:
736: if (!isAngleAtBack(end)) { // and finishes at the front
737:
738: if (extent < 180.0) { // segment only occupies the front
739: if (drawFront) {
740: Area side = new Area(
741: new Rectangle2D.Double(
742: arc.getStartPoint().getX(),
743: plotArea.getY(),
744: arc.getEndPoint().getX()
745: - arc
746: .getStartPoint()
747: .getX(),
748: plotArea.getHeight()));
749: side.intersect(front);
750: g2.setPaint(paint);
751: g2.fill(side);
752: g2.setPaint(outlinePaint);
753: g2.draw(side);
754: }
755: } else { // segments wraps right around the back...
756: Area side1 = new Area(new Rectangle2D.Double(
757: arc.getStartPoint().getX(), plotArea
758: .getY(), plotArea.getMaxX()
759: - arc.getStartPoint().getX(),
760: plotArea.getHeight()));
761: side1.intersect(front);
762:
763: Area side2 = new Area(new Rectangle2D.Double(
764: plotArea.getX(), plotArea.getY(), arc
765: .getEndPoint().getX()
766: - plotArea.getX(), plotArea
767: .getHeight()));
768: side2.intersect(front);
769:
770: g2.setPaint(paint);
771: if (drawFront) {
772: g2.fill(side1);
773: g2.fill(side2);
774: }
775:
776: if (drawBack) {
777: g2.fill(back);
778: }
779:
780: g2.setPaint(outlinePaint);
781: if (drawFront) {
782: g2.draw(side1);
783: g2.draw(side2);
784: }
785:
786: if (drawBack) {
787: g2.draw(back);
788: }
789:
790: }
791: } else { // segments starts at front and finishes at back...
792: if (drawBack) {
793: Area side2 = new Area(new Rectangle2D.Double(
794: arc.getEndPoint().getX(), plotArea
795: .getY(), plotArea.getMaxX()
796: - arc.getEndPoint().getX(),
797: plotArea.getHeight()));
798: side2.intersect(back);
799: g2.setPaint(paint);
800: g2.fill(side2);
801: g2.setPaint(outlinePaint);
802: g2.draw(side2);
803: }
804:
805: if (drawFront) {
806: Area side1 = new Area(new Rectangle2D.Double(
807: arc.getStartPoint().getX(), plotArea
808: .getY(), plotArea.getMaxX()
809: - arc.getStartPoint().getX(),
810: plotArea.getHeight()));
811: side1.intersect(front);
812: g2.setPaint(paint);
813: g2.fill(side1);
814: g2.setPaint(outlinePaint);
815: g2.draw(side1);
816: }
817: }
818: } else { // segment starts at back
819:
820: if (!isAngleAtFront(end)) {
821: if (extent < 180.0) { // and finishes at back
822: if (drawBack) {
823: Area side = new Area(
824: new Rectangle2D.Double(arc
825: .getEndPoint().getX(),
826: plotArea.getY(), arc
827: .getStartPoint()
828: .getX()
829: - arc.getEndPoint()
830: .getX(),
831: plotArea.getHeight()));
832: side.intersect(back);
833: g2.setPaint(paint);
834: g2.fill(side);
835: g2.setPaint(outlinePaint);
836: g2.draw(side);
837: }
838: } else { // starts at back and wraps right around to the
839: // back again
840: Area side1 = new Area(new Rectangle2D.Double(
841: arc.getStartPoint().getX(), plotArea
842: .getY(), plotArea.getX()
843: - arc.getStartPoint().getX(),
844: plotArea.getHeight()));
845: side1.intersect(back);
846:
847: Area side2 = new Area(new Rectangle2D.Double(
848: arc.getEndPoint().getX(), plotArea
849: .getY(), plotArea.getMaxX()
850: - arc.getEndPoint().getX(),
851: plotArea.getHeight()));
852: side2.intersect(back);
853:
854: g2.setPaint(paint);
855: if (drawBack) {
856: g2.fill(side1);
857: g2.fill(side2);
858: }
859:
860: if (drawFront) {
861: g2.fill(front);
862: }
863:
864: g2.setPaint(outlinePaint);
865: if (drawBack) {
866: g2.draw(side1);
867: g2.draw(side2);
868: }
869:
870: if (drawFront) {
871: g2.draw(front);
872: }
873:
874: }
875: } else { // starts at the back and finishes at the front
876: // (wrapping the left side)
877: if (drawBack) {
878: Area side1 = new Area(new Rectangle2D.Double(
879: plotArea.getX(), plotArea.getY(), arc
880: .getStartPoint().getX()
881: - plotArea.getX(), plotArea
882: .getHeight()));
883: side1.intersect(back);
884: g2.setPaint(paint);
885: g2.fill(side1);
886: g2.setPaint(outlinePaint);
887: g2.draw(side1);
888: }
889:
890: if (drawFront) {
891: Area side2 = new Area(new Rectangle2D.Double(
892: plotArea.getX(), plotArea.getY(), arc
893: .getEndPoint().getX()
894: - plotArea.getX(), plotArea
895: .getHeight()));
896: side2.intersect(front);
897: g2.setPaint(paint);
898: g2.fill(side2);
899: g2.setPaint(outlinePaint);
900: g2.draw(side2);
901: }
902: }
903: }
904:
905: }
906:
907: }
908:
909: /**
910: * Returns a short string describing the type of plot.
911: *
912: * @return <i>Pie 3D Plot</i>.
913: */
914: public String getPlotType() {
915: return localizationResources.getString("Pie_3D_Plot");
916: }
917:
918: /**
919: * A utility method that returns true if the angle represents a point at
920: * the front of the 3D pie chart. 0 - 180 degrees is the back, 180 - 360
921: * is the front.
922: *
923: * @param angle the angle.
924: *
925: * @return A boolean.
926: */
927: private boolean isAngleAtFront(double angle) {
928: return (Math.sin(Math.toRadians(angle)) < 0.0);
929: }
930:
931: /**
932: * A utility method that returns true if the angle represents a point at
933: * the back of the 3D pie chart. 0 - 180 degrees is the back, 180 - 360
934: * is the front.
935: *
936: * @param angle the angle.
937: *
938: * @return <code>true</code> if the angle is at the back of the pie.
939: */
940: private boolean isAngleAtBack(double angle) {
941: return (Math.sin(Math.toRadians(angle)) > 0.0);
942: }
943:
944: /**
945: * Tests this plot for equality with an arbitrary object.
946: *
947: * @param obj the object (<code>null</code> permitted).
948: *
949: * @return A boolean.
950: */
951: public boolean equals(Object obj) {
952: if (obj == this ) {
953: return true;
954: }
955: if (!(obj instanceof PiePlot3D)) {
956: return false;
957: }
958: PiePlot3D that = (PiePlot3D) obj;
959: if (this .depthFactor != that.depthFactor) {
960: return false;
961: }
962: return super.equals(obj);
963: }
964:
965: }
|