001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: * The Original Software is NetBeans. The Initial Developer of the Original
026: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
027: * Microsystems, Inc. All Rights Reserved.
028: *
029: * If you wish your version of this file to be governed by only the CDDL
030: * or only the GPL Version 2, indicate your decision by adding
031: * "[Contributor] elects to include this software in this distribution
032: * under the [CDDL or GPL Version 2] license." If you do not indicate a
033: * single choice of license, a recipient has the option to distribute
034: * your version of this file under either the CDDL, the GPL Version 2 or
035: * to extend the choice of license to its licensees as provided above.
036: * However, if you add GPL Version 2 code and therefore, elected the GPL
037: * Version 2 license, then the option applies only if the new code is
038: * made subject to such option by the copyright holder.
039: */
040:
041: package org.netbeans.lib.profiler.ui.charts;
042:
043: import java.awt.*;
044: import java.awt.event.*;
045: import java.awt.geom.*;
046: import java.util.Vector;
047: import javax.accessibility.Accessible;
048: import javax.accessibility.AccessibleContext;
049: import javax.swing.*;
050:
051: /**
052: *
053: * @author Jiri Sedlacek
054: */
055: public class PieChart extends JComponent implements ComponentListener,
056: ChartModelListener, Accessible {
057: //~ Static fields/initializers -----------------------------------------------------------------------------------------------
058:
059: private static Color evenSelectionSegmentsColor = Color.WHITE;
060: private static Stroke evenSelectionSegmentsStroke = new BasicStroke(
061: 1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0,
062: new float[] { 5, 5 }, 0);
063: private static Color oddSelectionSegmentColor = Color.BLACK;
064: private static Stroke oddSelectionSegmentStroke = new BasicStroke(
065: 1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0,
066: new float[] { 5, 5 }, 5);
067:
068: //~ Instance fields ----------------------------------------------------------------------------------------------------------
069:
070: private AccessibleContext accessibleContext;
071: private Area pieArea;
072: private Graphics2D offScreenGraphics;
073: private Image offScreenImage;
074: private Insets insets = new Insets(0, 0, 0, 0);
075: private PieChartModel model;
076: private Vector arcs = new Vector();
077: private Vector bottoms = new Vector();
078: private Vector selectedItems = new Vector();
079: private boolean draw3D = true; // (chartHeight > 0)
080: private boolean offScreenImageInvalid;
081: private boolean offScreenImageSizeInvalid;
082: private int chartHeight = 15; // height of the 3D chart (0 means 2D chart)
083: private int drawHeight;
084: private int drawWidth;
085: private int focusedItem = -1;
086: private int initialAngle = 0; // start of first item (degrees)
087: private int pieCenterY; // (pieHeight / 2)
088: private int pieHeight; // (drawHeight - chartHeight)
089:
090: //~ Constructors -------------------------------------------------------------------------------------------------------------
091:
092: /** Creates a new instance of PieChart */
093: public PieChart() {
094: offScreenImageSizeInvalid = true;
095:
096: addComponentListener(this );
097: }
098:
099: //~ Methods ------------------------------------------------------------------------------------------------------------------
100:
101: public void setAccessibleContext(AccessibleContext accessibleContext) {
102: this .accessibleContext = accessibleContext;
103: }
104:
105: public AccessibleContext getAccessibleContext() {
106: return accessibleContext;
107: }
108:
109: public void setChartHeight(int chartHeight) {
110: this .chartHeight = chartHeight;
111: draw3D = (chartHeight > 0);
112: }
113:
114: public int getChartHeight() {
115: return chartHeight;
116: }
117:
118: public void setFocusedItem(int focusedItem) {
119: if (this .focusedItem != focusedItem) {
120: this .focusedItem = focusedItem;
121: offScreenImageInvalid = true;
122: repaint();
123: }
124:
125: ;
126: }
127:
128: public int getItemIndexAt(int x, int y) {
129: // switch to geometry coordinate space
130: x -= insets.left;
131: y -= insets.top;
132:
133: // test arcs
134: for (int i = 0; i < arcs.size(); i++) {
135: if (((Arc2D) arcs.get(i)).contains(x, y)) {
136: return i;
137: }
138: }
139:
140: // test 3D bottoms
141: if (draw3D) {
142: for (int i = 0; i < bottoms.size(); i++) {
143: Area area = (Area) bottoms.get(i);
144:
145: if (area == null) {
146: continue;
147: } else if (area.contains(x, y)) {
148: return i;
149: }
150: }
151: }
152:
153: // no item hit
154: return -1;
155: }
156:
157: public void setModel(PieChartModel model) {
158: // automatically unregister itself as a ChartModelListener from current model
159: if (this .model != null) {
160: this .model.removeChartModelListener(this );
161: }
162:
163: // automatically register itself as a ChartModelListener for new model
164: if (model != null) {
165: model.addChartModelListener(this );
166: }
167:
168: this .model = model;
169:
170: chartDataChanged();
171: }
172:
173: public PieChartModel getModel() {
174: return model;
175: }
176:
177: public void setSelectedItem(int selectedItem) {
178: if (selectedItems.contains(selectedItem)
179: && (selectedItems.size() == 1)) {
180: return;
181: }
182:
183: selectedItems.clear();
184: selectedItems.add(selectedItem);
185:
186: offScreenImageInvalid = true;
187: repaint();
188: }
189:
190: public int[] getSelectedItems() {
191: int[] items = new int[selectedItems.size()];
192:
193: for (int i = 0; i < selectedItems.size(); i++) {
194: items[i] = ((Integer) selectedItems.get(i)).intValue();
195: }
196:
197: return items;
198: }
199:
200: public void setStartAngle(int initialAngle) {
201: this .initialAngle = initialAngle;
202: }
203:
204: public int getStartAngle() {
205: return initialAngle;
206: }
207:
208: public void addSelectedItem(int selectedItem) {
209: if (selectedItems.contains(selectedItem)) {
210: return;
211: }
212:
213: selectedItems.add(selectedItem);
214:
215: offScreenImageInvalid = true;
216: repaint();
217: }
218:
219: // Used for public chart update & listener implementation
220: public void chartDataChanged() {
221: // offScreenImageInvalid = true;
222: // repaint();
223: selectAllItems(); // also invalidates offscreen image and repaints
224: }
225:
226: // --- ComponentListener implementation --------------------------------------
227: public void componentHidden(ComponentEvent e) {
228: }
229:
230: public void componentMoved(ComponentEvent e) {
231: }
232:
233: public void componentResized(ComponentEvent e) {
234: offScreenImageSizeInvalid = true;
235: repaint();
236: }
237:
238: public void componentShown(ComponentEvent e) {
239: }
240:
241: public void deselectAllItems() {
242: selectedItems.clear();
243:
244: offScreenImageInvalid = true;
245: repaint();
246: }
247:
248: // --- Main (Tester Frame) ---------------------------------------------------
249:
250: /**
251: * @param args the command line arguments
252: */
253: public static void main(String[] args) {
254: final PieChart pieChart = new PieChart();
255: pieChart.setBorder(BorderFactory
256: .createEmptyBorder(5, 20, 5, 20));
257: pieChart.setPreferredSize(new Dimension(300, 200));
258:
259: DynamicPieChartModel pieChartModel = new DynamicPieChartModel();
260: pieChartModel.setupModel(new String[] { "Item 1", "Item 2",
261: "Item 3", "Item 4" }, // NOI18N
262: new Color[] { Color.RED, Color.GREEN, Color.BLUE,
263: Color.YELLOW });
264: pieChartModel.setItemValues(new double[] { 10, 5, 15, 7 });
265:
266: JFrame frame = new JFrame("PieChart Tester"); // NOI18N
267: frame.getContentPane().add(pieChart);
268: frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
269: frame.pack();
270: frame.setVisible(true);
271:
272: pieChart.addMouseListener(new MouseAdapter() {
273: public void mouseClicked(MouseEvent e) {
274: int clickedItem = pieChart.getItemIndexAt(e.getX(), e
275: .getY());
276: pieChart.toggleItemSelection(clickedItem);
277: }
278: });
279:
280: pieChart.addMouseMotionListener(new MouseMotionAdapter() {
281: public void mouseMoved(MouseEvent e) {
282: int focusedItem = pieChart.getItemIndexAt(e.getX(), e
283: .getY());
284:
285: if (focusedItem != -1) {
286: pieChart.setCursor(Cursor
287: .getPredefinedCursor(Cursor.HAND_CURSOR));
288: } else {
289: pieChart.setCursor(Cursor.getDefaultCursor());
290: }
291:
292: pieChart.setFocusedItem(focusedItem);
293: }
294: });
295:
296: pieChart.setModel(pieChartModel);
297: }
298:
299: public void paintComponent(Graphics g) {
300: // super.paintComponent
301: super .paintComponent(g);
302:
303: // check if ChartModel is assigned
304: if (model == null) {
305: return;
306: }
307:
308: // check if the offScreenImage has to be updated
309: if (offScreenImageSizeInvalid) {
310: updateOffScreenImageSize();
311: }
312:
313: // paint component to the offScreenImage
314: if (offScreenImageInvalid) {
315: drawChart(offScreenGraphics);
316: }
317:
318: // paint offScreenImage to the output Graphics
319: g.drawImage(offScreenImage, insets.left, insets.top, this );
320: }
321:
322: public void removeSelectedItem(int selectedItem) {
323: if (!selectedItems.contains(selectedItem)) {
324: return;
325: }
326:
327: selectedItems.remove((Integer) selectedItem);
328:
329: offScreenImageInvalid = true;
330: repaint();
331: }
332:
333: public void resetFocusedItem() {
334: if (focusedItem != -1) {
335: focusedItem = -1;
336: offScreenImageInvalid = true;
337: repaint();
338: }
339: }
340:
341: public void selectAllItems() {
342: for (int i = 0; i < model.getItemCount(); i++) {
343: selectedItems.add(i);
344: }
345:
346: offScreenImageInvalid = true;
347: repaint();
348: }
349:
350: public void toggleItemSelection(int selectedItem) {
351: if (selectedItems.contains(selectedItem)) {
352: removeSelectedItem(selectedItem);
353: } else {
354: addSelectedItem(selectedItem);
355: }
356: }
357:
358: protected Color getDisabledColor(Color color) {
359: int r = color.getRed();
360: int g = color.getGreen();
361: int b = color.getBlue();
362:
363: return new Color(r, g, b, 50);
364: }
365:
366: protected void drawChart(Graphics2D g2) {
367: arcs.clear();
368: bottoms.clear();
369:
370: Area focusedItemArea = null;
371:
372: g2.setColor(getBackground());
373: g2.fillRect(0, 0, drawWidth + 1, drawHeight + 1);
374:
375: g2.setStroke(new BasicStroke(0.5f));
376:
377: if (!model.hasData()) {
378: // no data to display, draws only pie outline
379: Area rectAreaUpper = new Area(new Rectangle2D.Double(0,
380: pieCenterY + 1, drawWidth - 1, pieCenterY
381: + pieHeight));
382: rectAreaUpper.subtract(pieArea);
383:
384: Area rectAreaLower = new Area(rectAreaUpper);
385: rectAreaLower.transform(AffineTransform
386: .getTranslateInstance(0, chartHeight));
387: rectAreaUpper.subtract(rectAreaLower);
388:
389: g2.setPaint(Color.BLACK);
390: g2.draw(rectAreaUpper);
391: g2.drawArc(0, 0, drawWidth - 1, pieHeight, 0, 180);
392: } else {
393: // data collected, draws standard pie
394: int startAngle = this .initialAngle;
395: int extentAngle;
396:
397: Point2D startPoint;
398: Point2D endPoint;
399:
400: Point2D bottomStartPoint;
401: Point2D bottomEndPoint;
402:
403: boolean startPointVisible;
404: boolean endPointVisible;
405:
406: double left = 0;
407: double right = 0;
408: double width = 0;
409:
410: double top = 0;
411: double bottom = 0;
412: double height = 0;
413:
414: Arc2D.Double arc;
415: Rectangle2D.Double rectangle;
416:
417: int pieParts = model.getItemCount();
418:
419: for (int i = 0; i < pieParts; i++) {
420: if (model.getItemValueRel(i) == 0) {
421: continue;
422: }
423:
424: extentAngle = (int) Math.min(Math.ceil(model
425: .getItemValueRel(i) * 360), 360 - startAngle);
426:
427: if (extentAngle == 0) {
428: continue;
429: }
430:
431: arc = new Arc2D.Double(0, 1, drawWidth - 1, pieHeight,
432: startAngle, extentAngle, Arc2D.PIE);
433:
434: arcs.add(arc);
435:
436: if (i == focusedItem) {
437: if (pieParts == 1) {
438: focusedItemArea = new Area(
439: new Ellipse2D.Double(0, 1,
440: drawWidth - 1, pieHeight));
441: } else {
442: focusedItemArea = new Area(arc);
443: }
444: }
445:
446: if (draw3D) {
447: startPoint = arc.getStartPoint();
448: endPoint = arc.getEndPoint();
449:
450: startPointVisible = ((startAngle < 0) || (startAngle > 180));
451: endPointVisible = (((startAngle + extentAngle) < 0) || ((startAngle + extentAngle) > 180));
452:
453: if (startPointVisible && endPointVisible) {
454: // both endpoints visible
455: if (startPoint.getX() < endPoint.getX()) {
456: // whole pie is visible
457: left = startPoint.getX();
458: right = endPoint.getX();
459: width = right - left;
460:
461: top = Math.min(startPoint.getY(), endPoint
462: .getY()) - 1;
463: bottom = drawHeight;
464: height = bottom - top;
465:
466: Area bottomArea = drawChartPartSide(g2,
467: pieArea, left, top, width, height,
468: selectedItems.contains(i) ? model
469: .getItemColor(i).darker()
470: : getDisabledColor(model
471: .getItemColor(i)
472: .darker()));
473: bottoms.add(bottomArea);
474:
475: if ((i == focusedItem)
476: && (focusedItemArea != null)) {
477: focusedItemArea.add(bottomArea);
478: }
479: } else {
480: // pie is splitted into two parts
481: left = startPoint.getX();
482: right = drawWidth - 1;
483: width = right - left;
484:
485: top = pieCenterY + 1;
486: bottom = drawHeight;
487: height = bottom - top;
488:
489: Area bottomArea = drawChartPartSide(g2,
490: pieArea, left, top, width, height,
491: selectedItems.contains(i) ? model
492: .getItemColor(i).darker()
493: : getDisabledColor(model
494: .getItemColor(i)
495: .darker()));
496:
497: left = 0;
498: right = endPoint.getX();
499: width = right - left;
500:
501: top = pieCenterY + 1;
502: bottom = drawHeight;
503: height = bottom - top;
504:
505: Area bottomArea2 = drawChartPartSide(g2,
506: pieArea, left, top, width, height,
507: selectedItems.contains(i) ? model
508: .getItemColor(i).darker()
509: : getDisabledColor(model
510: .getItemColor(i)
511: .darker()));
512: bottomArea.add(bottomArea2);
513: bottoms.add(bottomArea);
514:
515: if ((i == focusedItem)
516: && (focusedItemArea != null)) {
517: focusedItemArea.add(bottomArea);
518: }
519: }
520: } else if (startPointVisible || endPointVisible) {
521: // one endpoint visible
522: if (startPointVisible && !endPointVisible) {
523: left = startPoint.getX();
524: right = drawWidth - 1;
525: width = right - left;
526:
527: top = pieCenterY + 1;
528: bottom = drawHeight;
529: height = bottom - top;
530:
531: Area bottomArea = drawChartPartSide(g2,
532: pieArea, left, top, width, height,
533: selectedItems.contains(i) ? model
534: .getItemColor(i).darker()
535: : getDisabledColor(model
536: .getItemColor(i)
537: .darker()));
538: bottoms.add(bottomArea);
539:
540: if ((i == focusedItem)
541: && (focusedItemArea != null)) {
542: focusedItemArea.add(bottomArea);
543: }
544: } else {
545: left = 0;
546: right = endPoint.getX();
547: width = right - left;
548:
549: top = pieCenterY + 1;
550: bottom = drawHeight;
551: height = bottom - top;
552:
553: Area bottomArea = drawChartPartSide(g2,
554: pieArea, left, top, width, height,
555: selectedItems.contains(i) ? model
556: .getItemColor(i).darker()
557: : getDisabledColor(model
558: .getItemColor(i)
559: .darker()));
560: bottoms.add(bottomArea);
561:
562: if ((i == focusedItem)
563: && (focusedItemArea != null)) {
564: focusedItemArea.add(bottomArea);
565: }
566: }
567: } else if (extentAngle >= 180) {
568: // no endpoint visible
569: left = 0;
570: right = drawWidth - 1;
571: width = right - left;
572:
573: top = pieCenterY + 1;
574: bottom = drawHeight;
575: height = bottom - top;
576:
577: Area bottomArea = drawChartPartSide(g2,
578: pieArea, left, top, width, height,
579: selectedItems.contains(i) ? model
580: .getItemColor(i).darker()
581: : getDisabledColor(model
582: .getItemColor(i)
583: .darker()));
584: bottoms.add(bottomArea);
585:
586: if ((i == focusedItem)
587: && (focusedItemArea != null)) {
588: focusedItemArea.add(bottomArea);
589: }
590: } else {
591: // no bottom visible
592: bottoms.add(null);
593: }
594: }
595:
596: g2.setPaint(selectedItems.contains(i) ? model
597: .getItemColor(i) : getDisabledColor(model
598: .getItemColor(i)));
599: g2.fill(arc);
600: // g2.setPaint(model.getItemColor(i).darker().darker());
601: // g2.setPaint(Color.BLACK);
602: // g2.draw(arc);
603: startAngle += extentAngle;
604: }
605: }
606:
607: if (focusedItemArea != null) {
608: g2.setColor(evenSelectionSegmentsColor);
609: g2.setStroke(evenSelectionSegmentsStroke);
610: g2.draw(focusedItemArea);
611:
612: g2.setColor(oddSelectionSegmentColor);
613: g2.setStroke(oddSelectionSegmentStroke);
614: g2.draw(focusedItemArea);
615: }
616:
617: offScreenImageInvalid = false;
618: }
619:
620: protected Area drawChartPartSide(Graphics2D g2, Area pieArea,
621: double left, double top, double width, double height,
622: Color color) {
623: Area rectAreaUpper = new Area(new Rectangle2D.Double(left, top,
624: width, height));
625: rectAreaUpper.subtract(pieArea);
626:
627: Area rectAreaLower = new Area(rectAreaUpper);
628: rectAreaLower.transform(AffineTransform.getTranslateInstance(0,
629: chartHeight));
630: rectAreaUpper.subtract(rectAreaLower);
631: rectAreaUpper.transform(AffineTransform.getTranslateInstance(0,
632: -1));
633: rectAreaUpper.subtract(pieArea);
634:
635: g2.setPaint(color);
636: g2.fill(rectAreaUpper);
637:
638: // g2.setPaint(Color.BLACK);
639: // g2.draw(rectAreaUpper);
640: return rectAreaUpper;
641: }
642:
643: // --- Protected implementation ------------------------------------------------
644: protected void updateOffScreenImageSize() {
645: insets = getInsets();
646:
647: drawWidth = getWidth() - insets.left - insets.right;
648: drawHeight = getHeight() - insets.top - insets.bottom - 1;
649:
650: pieHeight = drawHeight - chartHeight;
651: pieCenterY = pieHeight / 2;
652:
653: offScreenImage = createImage(drawWidth + 1, drawHeight + 1);
654: offScreenGraphics = (Graphics2D) offScreenImage.getGraphics();
655:
656: offScreenGraphics.setRenderingHint(
657: RenderingHints.KEY_ANTIALIASING,
658: RenderingHints.VALUE_ANTIALIAS_ON);
659:
660: offScreenImageSizeInvalid = false;
661: offScreenImageInvalid = true;
662:
663: pieArea = new Area(new Ellipse2D.Double(0, 0, drawWidth - 1,
664: pieHeight));
665: }
666: }
|