Page 2 of 4
public Graph(String title, int min, int max) {
this.title = title;
this.min = min;
this.max = max;
items = new Vector();
} // end constructor
The constructor takes the graph title and the range of values, and we create empty vector for the individual graph items.
public void reshape(int x, int y, int width, int height) {
super.reshape(x, y,width, height);
fm = getFontMetrics(getFont());
titleHeight = fm.getHeight();
labelWidth = Math.max(fm.stringWidth(new Integer(min).toString()),
fm.stringWidth(new Integer(max).toString())) + 2;
top = padding + titleHeight;
bottom = size().height - padding;
left = padding + labelWidth;
right = size().width - padding;
} // end reshape
Note: In JDK 1.1, the reshape method is replaced with public void setBounds(Rectangle r). See the API documentation for details.
We override the reshape method, which is inherited down the chain from the Component class. The reshape method is called when the component is resized and when it is laid out the first time. We use this method to collect measurements,
so that they will always be updated if the component is resized. We get the font metrics for the current font and assign the
titleHeight variable the maximum height of that font. We get the maximum width of the labels, testing to see which one is bigger and
then using that one. The top, bottom, left, and right variables are calculated from the other variables and represent the borders of the center graph drawing region. We'll use
these variables in the subclasses of Graph. Note that all of the measurements take into account a current size of the component so that redrawing will be correct at any size or aspect. If we used hard-coded values, the component
could not be resized.
Next, we'll draw the framework for the graph.
public void paint(Graphics g) {
// draw the title
fm = getFontMetrics(getFont());
g.drawString(title, (size().width - fm.stringWidth(title))/2, top);
// draw the max and min values
g.drawString(new Integer(min).toString(), padding, bottom);
g.drawString(new Integer(max).toString(), padding, top + titleHeight);
// draw the vertical and horizontal lines
g.drawLine(left, top, left, bottom);
g.drawLine(left, bottom, right, bottom);
} // end paint
The framework is drawn in the paint method. We draw the title and labels in their appropriate places. We draw a vertical line at the left border of the graph
drawing region, and a horizontal line at the bottom border.
In this next snippet we set the preferred size for the component by overriding the preferredSize method. The preferredSize method is also inherited from the Component class. Components can specify a preferred size and a minimum size. I have chosen a preferred width of 300 and a preferred
height of 200. The layout manager will call this method when it lays out the component.
public Dimension preferredSize() {
return(new Dimension(300, 200));
}
} // end Graph
Note: In JDK 1.1, the preferredSize method is replaced with public Dimension getPreferredSize().
Next, we need a facility for adding and removing the items to be graphed.
public void addItem(String name, int value, Color col) {
items.addElement(new GraphItem(name, value, col));
} // end addItem
public void addItem(String name, int value) {
items.addElement(new GraphItem(name, value, Color.black));
} // end addItem
public void removeItem(String name) {
for (int i = 0; i < items.size(); i++) {
if (((GraphItem)items.elementAt(i)).title.equals(name))
items.removeElementAt(i);
}
} // end removeItem
} // end Graph
I've modeled the addItem and removeItem methods after similar methods in the Choice class, so the code will have a familiar feel. Notice that we use two addItem methods here; we need a way to add items with or without a color. When an item is added, a new GraphItem object is created and added to the items vector. When an item is removed, the first one in the vector with that name will
be removed. The GraphItem class is very simple; here is the code:
import java.awt.*;
class GraphItem {
String title;
int value;
Color color;
public GraphItem(String title, int value, Color color) {
this.title = title;
this.value = value;
this.color = color;
} // end constructor
} // end GraphItem
The GraphItem class acts as a holder for the variables relating to graph items. I've included Color here in case it will be used in a subclass of Graph.
With this framework in place, we can create extensions to handle each type of graph. This strategy is quite convenient; we don't have to go to the trouble of measuring the pixels for the framework again, and we can create subclasses to focus on filling in the graph drawing region.
Graph and implementing custom drawing. We'll begin with a simple bar chart, which we can use just like any other component. A typical
bar chart is illustrated below. We'll fill in the graph drawing region by overriding the paint method to call the superclass paint method (to draw the framework), then we'll perform the custom drawing needed for this type of graph.
import java.awt.*;
public class BarChart extends Graph {
int position;
int increment;
public BarChart(String title, int min, int max) {
super(title, min, max);
} // end constructor
To space the items evenly, we keep an increment variable to indicate the amount we will shift to the right for each item. The position variable is the current position,
and the increment value is added to it each time. The constructor simply takes in values for the super constructor (Graph), which we call explicitly.
Now we can get down to some actual drawing.
public void paint(Graphics g) {
super.paint(g);
increment = (right - left)/(items.size());
position = left;
Color temp = g.getColor();
for (int i = 0; i < items.size(); i++) {
GraphItem item = (GraphItem)items.elementAt(i);
int adjustedValue = bottom - (((item.value - min)*(bottom - top))
/(max - min));
g.drawString(item.title, position + (increment -
fm.stringWidth(item.title))/2, adjustedValue - 2);
g.setColor(item.color);
g.fillRect(position, adjustedValue, increment,
bottom - adjustedValue);
position+=increment;
g.setColor(temp);
}
} // end paint
} // end BarChart
Let's take a close look at what's happening here. In the paint method, we call the superclass paint method to draw the graph framework. We then find the increment by subtracting the right edge from the left edge, and then dividing the result by the number of items. This value is the
distance between the left edges of the graph items. Because we want the graph to be resizable, we base these values on the
current value of the left and right variables inherited from Graph. Recall that the left, right, top, and bottom values are the current actual pixel measurements of the graph drawing region taken in the reshape method of Graph, and therefore available for our use. If we did not base our measurements on these values, the graph would not be resizable.
Yes, it does work!By Anonymous on March 26, 2009, 11:20 amThe author may take this lesson out of context and assume that you can wrap his work with a simple JFrame yourself, but indeed it does work just fine. www.GoMotocross.com
Reply | Read entire comment
graph framework and custom graph componentsBy danjospehj82 on March 11, 2009, 9:49 pmDoes this code actually work I wonder??
Reply | Read entire comment
View all comments