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.BasicStroke;
044: import java.awt.Color;
045: import java.awt.Dimension;
046: import java.awt.Font;
047: import java.awt.Graphics;
048: import java.awt.Graphics2D;
049: import java.awt.Image;
050: import java.awt.Insets;
051: import java.awt.Paint;
052: import java.awt.Polygon;
053: import java.awt.RenderingHints;
054: import java.awt.Stroke;
055: import java.awt.event.ComponentEvent;
056: import java.awt.event.ComponentListener;
057: import java.util.LinkedList;
058: import java.util.List;
059: import javax.accessibility.Accessible;
060: import javax.accessibility.AccessibleContext;
061: import javax.swing.BorderFactory;
062: import javax.swing.JComponent;
063: import javax.swing.JFrame;
064: import javax.swing.SwingUtilities;
065: import javax.swing.event.AncestorEvent;
066: import javax.swing.event.AncestorListener;
067:
068: /**
069: *
070: * @author Jiri Sedlacek
071: */
072: public class BarChart extends JComponent implements ComponentListener,
073: AncestorListener, ChartModelListener, Accessible {
074: //~ Instance fields ----------------------------------------------------------------------------------------------------------
075:
076: List horizAxisXes = new LinkedList();
077: int horizAxisHeight = 0;
078: int horizLegendWidth = 0;
079: int vertAxisWidth = 0;
080: int vertLegendHeight = 0;
081: private AccessibleContext accessibleContext;
082: private BarChartModel model;
083: private Graphics2D offScreenGraphics;
084: private Image offScreenImage;
085: private Insets insets;
086: private Paint axisMeshPaint = new Color(80, 80, 80, 50);
087: private Paint axisPaint = Color.BLACK;
088: private Paint fillPaint = Color.CYAN;
089: private Paint outlinePaint = Color.BLACK;
090: private Stroke axisStroke = new BasicStroke(1f);
091: private Stroke outlineStroke = new BasicStroke(1f,
092: BasicStroke.CAP_SQUARE, BasicStroke.JOIN_ROUND);
093: private boolean draw3D = false;
094: private boolean modelIncorrect = true;
095: private boolean offScreenImageInvalid;
096: private boolean offScreenImageSizeInvalid;
097: private int drawHeight;
098: private int drawWidth;
099: private int leftOffset = 0;
100: private int maxHeight;
101: private int rightOffset = 0;
102: private int topOffset = 0;
103: private int xSpacing = 0;
104:
105: //~ Constructors -------------------------------------------------------------------------------------------------------------
106:
107: /** Creates a new instance of BarChart */
108: public BarChart() {
109: offScreenImageSizeInvalid = true;
110: addAncestorListener(this );
111: addComponentListener(this );
112: }
113:
114: //~ Methods ------------------------------------------------------------------------------------------------------------------
115:
116: public void setAccessibleContext(AccessibleContext accessibleContext) {
117: this .accessibleContext = accessibleContext;
118: }
119:
120: public AccessibleContext getAccessibleContext() {
121: return accessibleContext;
122: }
123:
124: // --- Public interface ------------------------------------------------------
125: public void setDraw3D(boolean draw3D) {
126: if (this .draw3D != draw3D) {
127: this .draw3D = draw3D;
128: chartDataChanged();
129: }
130: }
131:
132: public boolean getDraw3D() {
133: return draw3D;
134: }
135:
136: public void setFillPaint(Paint fillPaint) {
137: if (!this .fillPaint.equals(fillPaint)) {
138: this .fillPaint = fillPaint;
139: chartDataChanged();
140: }
141: }
142:
143: public Paint getFillPaint() {
144: return fillPaint;
145: }
146:
147: public void setFont(Font font) {
148: if (!getFont().equals(font)) {
149: super .setFont(font);
150: chartDataChanged();
151: }
152: }
153:
154: public void setLeftOffset(int leftOffset) {
155: if (this .leftOffset != leftOffset) {
156: this .leftOffset = leftOffset;
157: chartDataChanged();
158: }
159: }
160:
161: public int getLeftOffset() {
162: return leftOffset;
163: }
164:
165: public void setModel(BarChartModel model) {
166: // automatically unregister itself as a ChartModelListener from current model
167: if (this .model != null) {
168: this .model.removeChartModelListener(this );
169: }
170:
171: // automatically register itself as a ChartModelListener for new model
172: if (model != null) {
173: model.addChartModelListener(this );
174: }
175:
176: // set new model
177: this .model = model;
178:
179: // process data change
180: chartDataChanged();
181: }
182:
183: public BarChartModel getModel() {
184: return model;
185: }
186:
187: public void setOutlinePaint(Paint outlinePaint) {
188: if (!this .outlinePaint.equals(outlinePaint)) {
189: this .outlinePaint = outlinePaint;
190: chartDataChanged();
191: }
192: }
193:
194: public Paint getOutlinePaint() {
195: return outlinePaint;
196: }
197:
198: public void setOutlineStroke(Stroke outlineStroke) {
199: if (!this .outlineStroke.equals(outlineStroke)) {
200: this .outlineStroke = outlineStroke;
201: chartDataChanged();
202: }
203: }
204:
205: public Stroke getOutlineStroke() {
206: return outlineStroke;
207: }
208:
209: public void setRightOffset(int rightOffset) {
210: if (this .rightOffset != rightOffset) {
211: this .rightOffset = rightOffset;
212: chartDataChanged();
213: }
214: }
215:
216: public int getRightOffset() {
217: return rightOffset;
218: }
219:
220: public void setTopOffset(int topOffset) {
221: if (this .topOffset != topOffset) {
222: this .topOffset = topOffset;
223: chartDataChanged();
224: }
225: }
226:
227: public int getTopOffset() {
228: return topOffset;
229: }
230:
231: public void setXSpacing(int xSpacing) {
232: if (this .xSpacing != xSpacing) {
233: this .xSpacing = xSpacing;
234: chartDataChanged();
235: }
236: }
237:
238: public int getXSpacing() {
239: return xSpacing;
240: }
241:
242: public void ancestorAdded(AncestorEvent event) {
243: chartDataChanged();
244: }
245:
246: public void ancestorMoved(AncestorEvent event) {
247: }
248:
249: public void ancestorRemoved(AncestorEvent event) {
250: }
251:
252: // Used for public chart update & listener implementation
253: public void chartDataChanged() {
254: if (model != null) {
255: // assume model is incorrect
256: modelIncorrect = true;
257:
258: // check model correctness
259: String[] xItems = model.getXLabels();
260: int[] yItems = model.getYValues();
261:
262: if (xItems == null) {
263: throw new RuntimeException("X labels cannot be null!"); // NOI18N
264: }
265:
266: if (yItems == null) {
267: throw new RuntimeException("Y values cannot be null!"); // NOI18N
268: }
269:
270: if ((xItems.length - 1) != yItems.length) {
271: throw new RuntimeException(
272: "Incorrect x-y values count!"); // NOI18N
273: }
274:
275: // model is correct
276: modelIncorrect = false;
277:
278: // update max yvalue
279: maxHeight = getMaxY(yItems);
280:
281: // update axes metrics
282: if ((getFont() != null) && (getGraphics() != null)
283: && (getGraphics().getFontMetrics() != null)) {
284: horizAxisHeight = getFont().getSize() + 10;
285: horizLegendWidth = (int) getGraphics().getFontMetrics()
286: .getStringBounds(model.getXAxisDesc(),
287: getGraphics()).getWidth();
288:
289: int maxYMarkWidth = (int) getGraphics()
290: .getFontMetrics().getStringBounds(
291: Integer.toString(maxHeight) + "0",
292: getGraphics()).getWidth() + 10;
293: int vertLegendWidth = (int) getGraphics()
294: .getFontMetrics().getStringBounds(
295: model.getYAxisDesc(), getGraphics())
296: .getWidth();
297: vertLegendHeight = getFont().getSize();
298: vertAxisWidth = Math.max(maxYMarkWidth,
299: vertLegendWidth + 4);
300: } else {
301: horizAxisHeight = 0;
302: horizLegendWidth = 0;
303: vertAxisWidth = 0;
304: }
305: }
306:
307: // paintComponent() may be running and would clear offScreenImageInvalid flag,
308: // so this code has to be invoked later
309: SwingUtilities.invokeLater(new Runnable() {
310: public void run() {
311: offScreenImageInvalid = true;
312: repaint();
313: }
314: });
315: }
316:
317: public void componentHidden(ComponentEvent e) {
318: }
319:
320: public void componentMoved(ComponentEvent e) {
321: }
322:
323: public void componentResized(ComponentEvent e) {
324: offScreenImageSizeInvalid = true;
325: repaint();
326: }
327:
328: // ---------------------------------------------------------------------------
329:
330: // --- ComponentListener & AncestorListener implementation ---------------------
331: public void componentShown(ComponentEvent e) {
332: }
333:
334: // ---------------------------------------------------------------------------
335:
336: // --- Static tester ---------------------------------------------------------
337:
338: /**
339: * @param args the command line arguments
340: */
341: public static void main(String[] args) {
342: BarChart barChart = new BarChart();
343:
344: BarChartModel barChartModel = new AbstractBarChartModel() {
345: public String[] getXLabels() {
346: return new String[] { "37", "41", "45", "49", "53",
347: "57" };
348: } // NOI18N
349:
350: public String getXAxisDesc() {
351: return "[ms]";
352: } // NOI18N
353:
354: public String getYAxisDesc() {
355: return "[freq]";
356: } // NOI18N
357:
358: public int[] getYValues() {
359: return new int[] { 55, 60, 90, 80, 10 };
360: }
361: };
362:
363: barChart.setModel(barChartModel);
364:
365: barChart.setBackground(Color.WHITE);
366: barChart.setBorder(BorderFactory.createEmptyBorder(10, 10, 10,
367: 10));
368: barChart.setPreferredSize(new Dimension(300, 200));
369:
370: barChart.setDraw3D(true);
371: barChart.setLeftOffset(20);
372: barChart.setRightOffset(5);
373: barChart.setTopOffset(30);
374: barChart.setXSpacing(10);
375:
376: JFrame frame = new JFrame("BarChart Tester"); // NOI18N
377: frame.getContentPane().add(barChart);
378: frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
379: frame.pack();
380: frame.setVisible(true);
381: }
382:
383: // ---------------------------------------------------------------------------
384:
385: // --- Internal implementation -----------------------------------------------
386: public void paintComponent(Graphics g) {
387: // super.paintComponent
388: super .paintComponent(g);
389:
390: // check if ChartModel is assigned
391: if ((model == null) || modelIncorrect) {
392: return;
393: }
394:
395: // check if the offScreenImage has to be updated
396: if (offScreenImageSizeInvalid) {
397: updateOffScreenImageSize();
398: }
399:
400: // paint component to the offScreenImage
401: if (offScreenImageInvalid) {
402: drawChart(offScreenGraphics);
403: }
404:
405: // paint offScreenImage to the output Graphics
406: g.drawImage(offScreenImage, insets.left, insets.top, this );
407: }
408:
409: protected void drawBar(Graphics2D g2, int startX, int startY,
410: int width, int height) {
411: Polygon topSide = null;
412: Polygon rightSide = null;
413:
414: g2.setPaint(fillPaint);
415: g2.fillRect(startX, startY, width, height);
416:
417: if (draw3D) {
418: topSide = new Polygon();
419: topSide.addPoint(startX, startY);
420: topSide
421: .addPoint(startX + (width / 3), startY
422: - (width / 3));
423: topSide.addPoint(startX + width + (width / 3), startY
424: - (width / 3));
425: topSide.addPoint(startX + width, startY);
426:
427: rightSide = new Polygon();
428: rightSide.addPoint(startX + width, startY);
429: rightSide.addPoint(startX + width + (width / 3), startY
430: - (width / 3));
431: rightSide.addPoint(startX + width + (width / 3),
432: (startY + height) - (width / 3));
433: rightSide.addPoint(startX + width, startY + height);
434:
435: if (fillPaint instanceof Color) {
436: g2.setPaint(((Color) fillPaint).brighter());
437: }
438:
439: g2.fillPolygon(topSide);
440:
441: if (fillPaint instanceof Color) {
442: g2.setPaint(((Color) fillPaint).darker());
443: }
444:
445: g2.fillPolygon(rightSide);
446: }
447:
448: g2.setStroke(outlineStroke);
449: g2.setPaint(outlinePaint);
450: g2.drawRect(startX, startY, width, height);
451:
452: if (draw3D) {
453: g2.drawPolygon(topSide);
454: g2.drawPolygon(rightSide);
455: }
456: }
457:
458: protected void drawChart(Graphics2D g2) {
459: // clear component area
460: g2.setColor(getBackground());
461: g2.fillRect(0, 0, drawWidth + 1, drawHeight + 1);
462:
463: // fetch data from model
464: String[] xItems = model.getXLabels();
465: int[] yItems = model.getYValues();
466:
467: // process only if data available and component has valid size
468: if ((yItems.length > 0) && (drawWidth > 0) && (drawHeight > 0)) {
469: // default outline stroke
470: int outlineStrokeWidth = 0;
471:
472: // most likely stroke will be descendant of BasicStroke, set correct stroke width
473: if (outlineStroke instanceof BasicStroke) {
474: outlineStrokeWidth = (int) Math
475: .ceil((((BasicStroke) outlineStroke)
476: .getLineWidth() - 1) / 2);
477: }
478:
479: // initialize basic scene description
480: int barsCount = yItems.length; // number of bars to be drawn
481: int drawableWidth = drawWidth // effective width of drawing area
482: - vertAxisWidth // width of vertical axis
483: - horizLegendWidth // width of horizontal axis legend
484: - leftOffset // extra left spacing (between vertical axis and first bar)
485: - rightOffset // extra right spacing (betweel last bar and end of horizontal axis)
486: - (barsCount * xSpacing) // spacing between bars + one more before horizontal axis legend
487: - ((horizLegendWidth == 0) ? 0 : 5) // extra space before horizontal axis legend
488: - (outlineStrokeWidth * 2); // effective stroke width
489: int drawableHeight = drawHeight // effective height of drawing area
490: - topOffset // extra top spacing (between highest bar and end of vertical axis)
491: - horizAxisHeight // height of horizontal axis
492: - (outlineStrokeWidth * 2); // effective stroke width
493:
494: // initialize drawing status
495: int drawnWidth = 0;
496: int horizontal3DCorrection = 0;
497: int vertical3DCorrection = 0;
498: int currentX = vertAxisWidth + leftOffset
499: + outlineStrokeWidth;
500: horizAxisXes.clear();
501:
502: if (draw3D) {
503: horizontal3DCorrection = drawableWidth / barsCount / 3;
504: drawableWidth -= horizontal3DCorrection;
505: vertical3DCorrection = drawableWidth / barsCount / 3;
506: drawableHeight -= vertical3DCorrection;
507: }
508:
509: // draw vertical chart axis
510: drawVerticalAxis(g2, vertical3DCorrection, yItems);
511:
512: // draw each bar
513: for (int i = 0; i < barsCount; i++) {
514: int width = (int) ((drawableWidth - drawnWidth) / (barsCount - i));
515: int height = (int) ((drawableHeight * yItems[i]) / (float) maxHeight);
516: int horizLegendX = ((i == 0) ? Math.max(currentX
517: - (xSpacing / 2), vertAxisWidth)
518: : (currentX - (xSpacing / 2)));
519: drawBar(g2, currentX, drawHeight - horizAxisHeight
520: - height - outlineStrokeWidth, width, height);
521: horizAxisXes.add(new Integer(horizLegendX));
522: currentX += (width + xSpacing);
523: drawnWidth += width;
524: }
525:
526: horizAxisXes.add(new Integer(Math.min(currentX
527: - (xSpacing / 2), drawWidth)));
528:
529: // draw horizontal chart axis
530: drawHorizontalAxis(g2, horizAxisXes, xItems);
531: }
532:
533: // offScreen image is now valid
534: offScreenImageInvalid = false;
535: }
536:
537: protected void drawHorizontalAxis(Graphics2D g2, List horizAxisXes,
538: String[] xItems) {
539: g2.setPaint(axisPaint);
540: g2.setStroke(axisStroke);
541:
542: g2.drawLine(vertAxisWidth - 3, drawHeight - horizAxisHeight,
543: drawWidth, drawHeight - horizAxisHeight);
544:
545: for (int i = 0; i < horizAxisXes.size(); i++) {
546: int x = ((Integer) horizAxisXes.get(i)).intValue();
547: g2.drawLine(x, drawHeight - horizAxisHeight + 1, x,
548: drawHeight - horizAxisHeight + 3);
549: drawHorizontalAxisLegendItem(g2, x, xItems[i]);
550: }
551:
552: g2.drawString(model.getXAxisDesc(), drawWidth
553: - horizLegendWidth - 2, drawHeight - 5);
554: }
555:
556: protected void drawHorizontalAxisLegendItem(Graphics2D g2, int x,
557: String string) {
558: int legendWidth = (int) g2.getFontMetrics().getStringBounds(
559: string, g2).getWidth();
560: int legendX = Math.min(x - (legendWidth / 2), drawWidth
561: - legendWidth - horizLegendWidth - 3);
562: g2.drawString(string, legendX, drawHeight - 5);
563: }
564:
565: protected void drawVerticalAxis(Graphics2D g2,
566: int vertical3DCorrection, int[] yItems) {
567: g2.setPaint(axisPaint);
568: g2.setStroke(axisStroke);
569:
570: g2.drawLine(vertAxisWidth, 0, vertAxisWidth, drawHeight
571: - horizAxisHeight);
572:
573: double factor = (double) (drawHeight - horizAxisHeight
574: - topOffset - vertical3DCorrection)
575: / (double) (maxHeight);
576: long optimalUnits = DecimalAxisUtils
577: .getOptimalUnits(factor, 30);
578:
579: long firstMark = 0;
580: long currentMark = firstMark;
581: int markPosition = drawHeight - horizAxisHeight
582: - (int) (currentMark * factor);
583:
584: while (markPosition >= (vertLegendHeight + 5)) {
585: g2.setPaint(axisPaint);
586: g2.setStroke(axisStroke);
587: g2.drawLine(vertAxisWidth - 3, markPosition,
588: vertAxisWidth - 1, markPosition);
589:
590: drawVerticalAxisLegendItem(g2, markPosition, Long
591: .toString(currentMark));
592:
593: g2.setPaint(axisMeshPaint);
594: g2.drawLine(vertAxisWidth, markPosition, vertAxisWidth
595: + drawWidth, markPosition);
596:
597: currentMark += optimalUnits;
598: markPosition = drawHeight - horizAxisHeight
599: - (int) (currentMark * factor);
600: }
601:
602: g2.setPaint(axisPaint);
603: g2.drawString(model.getYAxisDesc(), 2, vertLegendHeight);
604: }
605:
606: protected void drawVerticalAxisLegendItem(Graphics2D g2, int y,
607: String string) {
608: int legendWidth = (int) g2.getFontMetrics().getStringBounds(
609: string, g2).getWidth();
610: int legendHeight = vertLegendHeight;
611: int legendX = vertAxisWidth - legendWidth - 5;
612: int legendY = Math.max((y + (legendHeight / 2)) - 2,
613: (2 * legendHeight) + 3);
614: g2.drawString(string, legendX, legendY);
615: }
616:
617: protected void updateOffScreenImageSize() {
618: insets = getInsets();
619:
620: drawWidth = getWidth() - insets.left - insets.right;
621: drawHeight = getHeight() - insets.top - insets.bottom - 1;
622:
623: offScreenImage = createImage(drawWidth + 1, drawHeight + 1);
624: offScreenGraphics = (Graphics2D) offScreenImage.getGraphics();
625:
626: offScreenGraphics.setRenderingHint(
627: RenderingHints.KEY_ANTIALIASING,
628: RenderingHints.VALUE_ANTIALIAS_ON);
629:
630: offScreenImageSizeInvalid = false;
631: offScreenImageInvalid = true;
632: }
633:
634: private int getMaxY(int[] yItems) {
635: int max = Integer.MIN_VALUE;
636:
637: for (int i = 0; i < yItems.length; i++) {
638: if (max < yItems[i]) {
639: max = yItems[i];
640: }
641: }
642:
643: return max;
644: }
645:
646: // ---------------------------------------------------------------------------
647: }
|