001: /*
002: * <copyright>
003: *
004: * Copyright 1997-2004 BBNT Solutions, LLC
005: * under sponsorship of the Defense Advanced Research Projects
006: * Agency (DARPA).
007: *
008: * You can redistribute this software and/or modify it under the
009: * terms of the Cougaar Open Source License as published on the
010: * Cougaar Open Source Website (www.cougaar.org).
011: *
012: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
013: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
014: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
015: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
016: * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
017: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
018: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
019: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
020: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
021: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
022: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
023: *
024: * </copyright>
025: */
026:
027: package org.cougaar.mlm.debug.ui;
028:
029: import java.awt.Color;
030: import java.awt.Dimension;
031: import java.awt.Font;
032: import java.awt.FontMetrics;
033: import java.awt.Graphics;
034:
035: import javax.swing.JComponent;
036:
037: import org.cougaar.mlm.debug.ui.draw.DrawUtil;
038: import org.cougaar.mlm.debug.ui.draw.OMVector;
039:
040: /** Creates a vertical bar graph display. Takes a two dimensional array
041: of values, and uses a different color bar to display each set of values.
042: */
043:
044: public class UIBarGraph extends JComponent {
045: private boolean hasValidValues = false;
046: private int numberOfXIntervals;
047: private String xLegend;
048: private String xLabels[];
049: private int numberOfYIntervals;
050: private String yLegend;
051: private String yLabels[];
052: private String legend[];
053: private int values[][]; // for each bar, the y-value for the xth interval
054: private boolean contiguous;
055: private int maxAscent;
056: private int xLength, yLength;
057: private int xIntervalSize;
058: private int nBars;
059: private int yOrigin;
060: private FontMetrics fm;
061: private int width;
062: private int height;
063: private int barWidth;
064: private static int X_ORIGIN = 120;
065: private static int Y_TOP = 50; // compute y-origin from this
066: private static int TICK_HALF_LENGTH = 5;
067: private static int X_SPACE = 20; // between the bar sets
068: private static int Y_INTERVAL_SIZE = 50;
069: private double yIntervalSize;
070: private static double Y_AXIS_SIZE = 160.0;
071: private static int DEFAULT_BAR_WIDTH = 6;
072: private static int MARGIN = 10;
073: private double tickSpacing;
074: private int tickLabel;
075: private int nTicks;
076: private static double MIN_TICK_SPACING = 14.0;
077: private boolean haveData = false;
078:
079: public UIBarGraph() {
080: }
081:
082: private synchronized void setValidValues(boolean isCacheValid) {
083: hasValidValues = isCacheValid;
084: }
085:
086: private synchronized boolean getValidValues() {
087: return hasValidValues;
088: }
089:
090: /** Set new values for bar graph and repaint.
091: @param numberOfXIntervals number of intervals on x-axis
092: @param xLegend string to display underneath x-axis
093: @param xLabels strings to display beneath the x-axis tick marks
094: @param numberOfYIntervals number of intervals on y-axis
095: @param yLegend string to display to the left of y-axis
096: @param yLabels strings to display to the left of the y-axis tick marks
097: @param legend strings to display with bar graph colors
098: @param values values of sets of vertical bars
099: */
100: public synchronized void setParameters(int numberOfXIntervals,
101: String xLegend, String xLabels[], int numberOfYIntervals,
102: String yLegend, String yLabels[], String legend[],
103: int values[][]) {
104: this .numberOfXIntervals = numberOfXIntervals;
105: this .xLegend = xLegend;
106: this .xLabels = xLabels;
107: this .numberOfYIntervals = numberOfYIntervals;
108: this .yLegend = yLegend;
109: this .yLabels = yLabels;
110: this .legend = legend;
111: this .values = values;
112: setValidValues(false);
113: haveData = true;
114: repaint();
115: }
116:
117: /** Figure out where to put tick marks and how to label them.
118: Count by 10s, 100s, or 1000s and have at least MIN_TICK_SPACING intervals.
119: */
120:
121: private void setTickSpacing(double interval) {
122: if (interval > MIN_TICK_SPACING) {
123: tickSpacing = interval;
124: tickLabel = 1;
125: } else if ((interval * 10.0) > MIN_TICK_SPACING) {
126: tickSpacing = interval * 10.0;
127: tickLabel = 10;
128: } else if ((interval * 100.0) > MIN_TICK_SPACING) {
129: tickSpacing = interval * 100.0;
130: tickLabel = 100;
131: } else if ((interval * 1000.0) > MIN_TICK_SPACING) {
132: tickSpacing = interval * 1000.0;
133: tickLabel = 1000;
134: } else if ((interval * 10000.0) > MIN_TICK_SPACING) {
135: tickSpacing = interval * 10000.0;
136: tickLabel = 10000;
137: } else {
138: tickSpacing = 0.0;
139: tickLabel = 0;
140: nTicks = 0; // prevents them from being drawn
141: return;
142: }
143: nTicks = (int) (Y_AXIS_SIZE / tickSpacing);
144: }
145:
146: private synchronized void computeGraphParameters() {
147: Graphics g = getGraphics();
148: g.setFont(new Font("Helvetica", Font.PLAIN, 12));
149: fm = g.getFontMetrics();
150: maxAscent = fm.getMaxAscent();
151: // compute some values used throughout
152: if (values == null)
153: nBars = 0;
154: else
155: nBars = values.length;
156: yIntervalSize = Y_AXIS_SIZE / (numberOfYIntervals);
157: setTickSpacing(yIntervalSize);
158: yOrigin = Y_TOP + (int) Y_AXIS_SIZE;
159: // determine the minimum width so that x-labels don't overlap
160: int maxWidth = 0;
161: for (int i = 0; i < numberOfXIntervals; i++)
162: maxWidth = Math.max(fm.stringWidth(xLabels[i]), maxWidth);
163: // adjust x interval to accomodate labels if necessary
164: // if want contiguous bars, then make the width of the bars
165: // be the width of the interval
166: if (contiguous) {
167: if (nBars == 0) { // special case to get an empty graph
168: barWidth = DEFAULT_BAR_WIDTH;
169: xIntervalSize = maxWidth + MARGIN;
170: } else {
171: xIntervalSize = nBars * 2; // minimum bar width
172: xIntervalSize = Math.max(xIntervalSize, maxWidth + 2); // min margin
173: barWidth = (xIntervalSize / nBars) + 1;
174: xIntervalSize = barWidth * nBars;
175: }
176: } else {
177: xIntervalSize = nBars * DEFAULT_BAR_WIDTH + X_SPACE;
178: xIntervalSize = Math.max(xIntervalSize, maxWidth + MARGIN);
179: barWidth = DEFAULT_BAR_WIDTH;
180: }
181: // make the x axis one interval longer than needed -- looks better
182: xLength = (numberOfXIntervals + 1) * xIntervalSize;
183: width = X_ORIGIN + xLength + MARGIN;
184: // y axis + labels + legend
185: height = yOrigin + maxAscent + MARGIN + maxAscent + MARGIN
186: + MARGIN;
187: setValidValues(true);
188: }
189:
190: /** Overrides method in JComponent which automatically provides
191: scrollbars when needed.
192: @return the width and height of the bar graph display
193: */
194:
195: public Dimension getPreferredSize() {
196: if (!haveData)
197: return new Dimension(100, 100);
198: if (!getValidValues())
199: computeGraphParameters();
200: return new Dimension(width, height);
201: }
202:
203: /** Draws the bar graph.
204: @param g graphics object associated with this object
205: */
206:
207: public synchronized void paint(Graphics g) {
208: if (!haveData)
209: return;
210: if (!getValidValues())
211: computeGraphParameters();
212: // draw the x and y axes
213: g.setColor(Color.black);
214: g.drawLine(X_ORIGIN, yOrigin, X_ORIGIN + xLength, yOrigin);
215: g.drawLine(X_ORIGIN, yOrigin, X_ORIGIN, Y_TOP);
216: // draw X-axis tick marks
217: for (int i = 1; i <= numberOfXIntervals; i++)
218: g.drawLine(X_ORIGIN + i * xIntervalSize, yOrigin
219: - TICK_HALF_LENGTH, X_ORIGIN + i * xIntervalSize,
220: yOrigin + TICK_HALF_LENGTH);
221: // draw Y-axis tick marks
222: for (int i = 1; i <= nTicks; i++)
223: g.drawLine(X_ORIGIN - TICK_HALF_LENGTH, (int) (yOrigin - i
224: * tickSpacing), X_ORIGIN + TICK_HALF_LENGTH,
225: (int) (yOrigin - i * tickSpacing));
226: // label the X-axis tick marks
227: int y = yOrigin + maxAscent + MARGIN;
228: for (int i = 1; i <= numberOfXIntervals; i++) {
229: String xLabel = xLabels[i - 1];
230: int w = fm.stringWidth(xLabel);
231: g.drawString(xLabel, X_ORIGIN + i * xIntervalSize - w / 2,
232: y);
233: }
234: // label the Y-axis tick marks
235: // ignore ylabels passed in and use tick labels instead
236: // assumes that the y-axis is simply a quantity
237: for (int i = 1; i <= nTicks; i++) {
238: String tickS = String.valueOf(i * tickLabel);
239: g.drawString(tickS, X_ORIGIN - fm.stringWidth(tickS)
240: - MARGIN,
241: (int) (yOrigin - i * tickSpacing + maxAscent / 2));
242: }
243: // label the axes
244: g.drawString(xLegend, X_ORIGIN + xLength / 2
245: - fm.stringWidth(xLegend) / 2, y + fm.getMaxAscent()
246: + MARGIN);
247: g.drawString(yLegend, MARGIN, Y_TOP);
248: // write the legend
249: Color colors[] = { Color.blue, Color.red, Color.green };
250: int yPosition = Y_TOP + 50;
251: for (int i = 0; i < legend.length; i++) {
252: g.setColor(colors[i % legend.length]);
253: g.drawString(legend[i], MARGIN, yPosition);
254: drawWideLine(g, 70, yPosition - barWidth / 2, 90, yPosition
255: - barWidth / 2, barWidth);
256: yPosition = yPosition + maxAscent + MARGIN;
257: }
258: int xPosition = (int) (X_ORIGIN + xIntervalSize - (nBars / 2.0)
259: * barWidth + barWidth / 2);
260: // draw the bars
261: // use yOrigin-1 so the bars don't overwrite the axis
262: for (int i = 0; i < nBars; i++) {
263: g.setColor(colors[i % nBars]);
264: for (int j = 0; j < numberOfXIntervals; j++)
265: drawWideLine(g, xPosition + xIntervalSize * j,
266: yOrigin - 1, xPosition + xIntervalSize * j,
267: (int) (yOrigin - 1 - values[i][j]
268: * yIntervalSize), barWidth);
269: xPosition = xPosition + barWidth;
270: }
271: }
272:
273: /** Used to draw the bars in the bar graph.
274: */
275:
276: private void drawWideLine(Graphics g, int x1, int y1, int x2,
277: int y2, int lineWidth) {
278: // if both width and length are zero, don't draw line
279: // DrawUtil generates 1 pixel height lines in this case
280: if ((x1 == x2) && (y1 == y2))
281: return;
282: OMVector vec = DrawUtil.generateWideLine(lineWidth, x1, y1, x2,
283: y2);
284: vec.resetEnumerator();
285: int[] x = (int[]) vec.nextElement(true);
286: int[] y = (int[]) vec.nextElement(true);
287: g.drawPolygon(x, y, 4);
288: g.fillPolygon(x, y, 4);
289: }
290:
291: }
|