Java 2D may be the most obvious solution for programming 2D graphs in Java programs, but it's not the only one. In this article, Java developer John Carr proposes an elegant alternative in the form of Java Objects for Science (JSci), an open-source package that lets you create 2D bar graphs, pie charts, and line graphs in Swing.
For most Java developers, any type of graphics development is intrinsically bound up with the Java 2D and 3D APIs and java.awt.Graphics. While the Java 2D and 3D APIs provide an excellent tool for creating graphics in Swing, they aren't the only ones at your disposal, and they certainly aren't the easiest ones to learn. And for those of you who don't have the time, need, or inclination to burn the midnight oil getting to know java.awt.Graphics intimately, I propose an open-source alternative: JSci.
The Java Objects for Science (JSci) Open Source Project was founded by Mark Hale, a third-year postgraduate student at the Centre for Particle Theory at the University of Durham (Durham, U.K.). JSci is a collection of packages containing mathematical and scientific classes. As of this writing, JSci is at version .87 and runs on either Java 1.1.8, 1.2.x, and 1.3.x, but newer versions of JSci will probably be written for Java 1.4. The project's aim is to encapsulate scientific methods and principles in the most natural way possible to aid the development of science-based software. The design philosophy behind JSci is based on the idea of going "straight from the blackboard to code." That is, mathematical concepts and constructions should somehow be encapsulated in code. In a way, JSci is as much an experiment in object design as it is a math library.
You can use JSci to create simple bar, line, and pie graphs in both AWT and Swing. The JSci.swing.JBarGraph, JSci.swing.JPieChart, and JSci.swing.JLineGraph API components are well thought out, and both the components and the AWT graphing classes adhere to the MVC architecture.
In this article, I'll introduce the JSci.swing package and show you how to work with its classes and methods to create a bar graph, pie chart, and line graph. We'll start with a look at the classes that make up the core of the package.
The JSci.swing package
The classes used to create graphs in Swing are located in the JSci.swing package. Figure 1 shows the class diagram for JSci.swing. All classes within JSci.swing, with the exception of JImageCanvas, extend from JDoubleBufferedComponent. Notice that JDoubleBufferedComponent and JImageCanvas extend from javax.swing.JComponent.
We'll discuss the function of each class in detail in the sections that follow.
JDoubleBufferedComponent
The superclass of JSci.swing is an abstract class called JDoubleBufferedComponent. This class is relatively simple and provides double buffering functionality for the graphs to build upon. Double buffering indicates whether the receiving component should use a buffer to paint. If double buffering is set to true, all the drawing from this component will be done in an offscreen painting buffer. The offscreen painting buffer will later be copied onto the screen. According to the javadocs, the Swing painting system always uses a maximum of one double buffer. If a component is buffered and one of its ancestors is also buffered, then the ancestor's buffer will be used.
JDoubleBufferedComponent handles double buffering itself rather than relying on Swing's double buffering implementation, JComponent. This provides developers using the JSci.swing package more fine-grained control over double buffering than that found under Swing alone.
JImageCanvas
JImageCanvas is another straightforward class. Its purpose is to allow an image to be directly added to a container. JImageCanvas creates an instance of java.awt.MediaTracker to load and keep track of images.
JContourPlot
A contour plot is a graphic representation of the relationships among three numeric variables in two dimensions. Two variables are used for the x-axes and y-axes, the third variable, z, is used for contour levels. The contour levels are plotted as curves; the area between curves can be color coded to express interpolated values.
JGraph2D
The JGraph2D superclass provides abstract encapsulation for 2D graphs. JScatterGraph and JLineGraph both extend from JGraph2D. A scatter graph is a statistical diagram drawn to compare two sets of data. It can be used to look for correlation between the two sets of data. A line graph shows how two pieces of information are related and how they vary depending on one another. The numbers along a side of the line graph are called the scale. I'll go into more detail below about line graphs and how to construct one.
JCategoryGraph2D
The JCategoryGraph2D superclass provides abstract encapsulation for categories of 2D graphs. JBarGraph and JPieChart both extend from JCategoryGraph2D. A bar graph consists of an axis and a series of labeled horizontal or vertical bars that show different values for each bar. A pie chart is a circle graph divided into pieces, each displaying the size of some related piece of information. Pie charts are used to display the sizes of parts that make up a whole. We'll talk more below about constructing pie charts and bar graphs.
JLineGraph3D and JLineTrace
If you feel like you need a challenge, then try JLineGraph3D. As the name suggests, JLineGraph3D lets you add a third dimension to your line graphs. Instead of thinking in terms of x (horizontal) and y (vertical), JLineGraph3D lets you account for z (depth), too. JLineTrace allows you to trace a 2D-line graph using mouse listeners.
|
Constructing a JPieChart
To construct any form of a graph, you must understand how to organize data in a meaningful way. When it comes to modeling data, the procedure is identical whether you're constructing a bar graph or a pie chart. Figure 2 offers an example of data that can be used to create either a bar graph or a pie chart.
Figure 2. Example data used to create a bar graph or pie chart

A series represents an array of numbers. For JPieChart, a series can be of either type float or type double. A category represents an identifier for a column of data within a series. For JPieChart, the category is represented as a String. Of course, you would want a category to have some meaning to your audience, which is why a city name seemed appropriate for each category in the pie chart example in PieGraph.java. For simplicity, only one series of data will be used for the pie chart. Listing 1 contains the category and series class variables, respectively.
// ...
public class PieGraph extends JFrame
implements ItemListener {
// ...
private static final String CATEGORY_NAMES[]
= { "London", "Paris", "New York" };
private static final float CONSUMERS_SERIES[]
= { 45.3f, 27.1f, 55.5f };
// ...
}
|
With the CONSUMERS_NAMES and SALES_SERIES array now defined, we can begin constructing the pie chart. The model used in this example is DefaultCategoryGraph2DModel. I'll talk more in depth about how the model works in the next section. Listing 2 shows how to create an instance of DefaultCategoryGraph2DModel.
// ...
private JPieChart getPieGraph() {
// ...
DefaultCategoryGraph2DModel model = new DefaultCategoryGraph2DModel();
model.setCategories(CATEGORY_NAMES);
model.addSeries(CONSUMERS_SERIES);
// ...
}
// ...
|
After DefaultCategoryGraph2DModel is instantiated, the category and series must be included in the model. A category is set through the setCategories(String categoryNames[]) method. Because the model allows for more than one series, the methods addSeries(float series[]) or addSeries(double series[]) can be used for this purpose. DefaultCategoryGraph2DModel stores each series added within a Vector.
The model instance can now be used as an input parameter for the JPieChart's constructor. After creating an instance of JPieChart, the color of each category can be defined with a setColor(int category, java.awt.Color c) method. Listing 3 shows how to create an instance of JPieChart and set a category color.
// ...
public class PieGraph extends JFrame
implements ItemListener {
// ...
private static final int LONDON = 0;
private static final int PARIS = 1;
private static final int NEW_YORK = 2;
// ...
private JPieChart getPieGraph() {
// ...
JPieChart pieChart = new JPieChart(model);
pieChart.setColor(LONDON,Color.blue);
pieChart.setColor(PARIS,Color.yellow);
pieChart.setColor(NEW_YORK,Color.orange);
// ...
}
// ...
|
Figure 3 illustrates a pie graph created using PieGraph.java. When a user requests that a category's color be changed (via list boxes on the right-hand side of the application), the method JPieChart.setColor(int category, java.awt.Color c) is called to implement the change. To make this change visible, the JPieChart.redraw() method is called after the new segment color has been set.
Figure 3. Example output of a pie chart from PieGraph.java

We've only scratched the surface of what you can do with DefaultCategoryGraph2DModel. Many other useful methods exist, including methods to let you hide a series (setSeriesVisible(int series, boolean visible)) or change series data (changeSeries(int series, float newSeries[])). I encourage you to explore these classes further when you have time (see Resources).
|
Constructing a JBarGraph
Constructing a bar graph entails, first, modeling the data in the same way as we did for the pie chart. A bar graph, like a pie chart, has both a data series array (or arrays) and a category array. Instead of using DefaultCategoryGraph2DModel as the model for the bar graph, I'll explain how to create a custom model for your data, and how JBarGraph works with this model during runtime to generate the bar graph. To make this exercise more interesting, I'll show you how to dynamically change the value of a series element and redraw the bar graph.
As shown in Figure 4, a custom category graph model must extend from AbstractGraphModel and implement CategoryGraph2DModel (see SalesGraphModel.java). Note that the methods referred to in this section are from the CategoryGraph2DModel interface.
Figure 4. Creating a custom category graph model

Table 1 shows the methods of the CategoryGraph2DModel interface and how JBarGraph utilizes a model during run-time.
Table 1. The CategoryGraph2DModel interface
| Method | Description |
public abstract void addGraphDataListener(GraphDataListener) | Adds a listener |
public abstract void firstSeries() | Selects the first data series |
public abstract String getCategory(int i) | Returns the ith category |
public abstract float getValue(int i) | Returns the value for the ith category |
public abstract boolean nextSeries() | Selects the next data series |
public abstract void removeGraphDataListener(GraphDataListener) | Removes a listener |
public abstract int seriesLength() | Returns the length of the current series |
Let's look at these methods in a bit more detail.
During run time, JBarGraph calls the public void firstSeries(), which selects the first data series. If you were working with multiple data series it would make sense to store them in a Vector. The next method called is public int seriesLength(); this method returns the length of the current series. The integer value returned from the seriesLength() method is used to form a looping construct to get each category and associated integer value for that series. In this series loop, public String getCategory(int i) is called to get the ith category name, and public float getValue(int i) is called to get the value ith category. (The term ith refers to a category order, such as first, second, third, and so on.) After all data elements are collected for the current series, public boolean nextSeries() is called to select the next data series. If the nextSeries() method returns true, then the process of collecting data elements for the new data series begins again, starting with the seriesLength() method. When nextSeries() returns false, there are no more data series in the model.
In BarGraph.java, the SalesGraphModel extends from AbstractGraphModel and implements CategoryGraph2DModel. For simplicity, the SalesGraphModel is equipped to handle only one data series. If you look at the nextSeries() method in SalesGraphModel, you'll notice that it will only return false; this indicates only one data series is contained within this model. The constructor input parameters to SalesGraphModel contains category names and a data series. The code to create an instance of the SalesGraphModel and JBarGraph can be found in the getBarGraph() method of the Listing 4.
Listing 4. Creating a JBarGraph
// ...
public class BarGraph extends JFrame
implements ActionListener {
// ...
private static final String CATEGORY_NAMES[]
= { "London", "Paris", "New York" };
// ...
private static final float SALES_SERIES[]
= { 24.1f, 14.4f, 36.8f };
// ...
private JBarGraph getBarGraph() {
SalesGraphModel model
= new SalesGraphModel(CATEGORY_NAMES,
SALES_SERIES);
barGraph = new JBarGraph(model);
return barGraph;
}
// ...
|
Figure 5 shows the output of what BarGraph.java and SalesGraphModel.java produce. You may notice that all the category bars are the same color, as opposed to the pie chart example where each category had a different color. This is because JBarGraph only allows you to change the color representation of a data series, not a category within a series.
Figure 5. Example output of a bar graph from BarGraph.java

SalesGraphModel has the functionality to allow a category value to be incremented within a data series. When a user selects a category from the BarGraph example (via the radio button) and presses the "Add 1 to total" button, the public void incrementCategoryTotal(int i) method is called. This method passes the selected category to increment as a method argument, after which a notification is sent to the JBarGraph instance via the public void dataChanged(GraphDataEvent) method that the data in the model has changed and the bar graph must be redrawn. Listing 5 shows the process by which a category total is incremented.
Listing 5. Incrementing a category total
// ...
public class BarGraph extends JFrame
implements ActionListener {
private void addToCategoryTotal(int category) {
SalesGraphModel model
= (SalesGraphModel)barGraph.getModel();
model.incrementCategoryTotal(category);
}
// ...
}
// ...
public class SalesGraphModel
extends AbstractGraphModel
implements CategoryGraph2DModel {
// ...
private float seriesTotals[];
// ...
public void incrementCategoryTotal(int i) {
seriesTotals[i]++;
}
// ...
}
|
|
Constructing a JLineGraph
The model for a JLineGraph is similar to a model for a JPieChart or a JBarGraph; both models can have multiple data series and a category to identify data within a series. A model for a JLineGraph, however, introduces the complexity of x-axis and y-axis coordinates. In this section, we'll examine how to create a custom model and how JLineGraph works with this model during run time to generate a line graph. Note that the methods referred to in this section are from the Graph2DModel interface.
A custom line graph model must extend from AbstractGraphModel and implement the Graph2DModel interface, as shown in Table 2.
Table 2. The Graph2DModel interface
| Method | Description |
public abstract void addGraphDataListener(GraphDataListener) | Adds a listener |
public abstract void firstSeries() | Selects the first data series |
public abstract float getXCoord(int i) | Returns the ith category |
public abstract float getYCoord(int i) | Returns the value for the ith category |
public abstract boolean nextSeries() | Selects the next data series |
public abstract void removeGraphDataListener(GraphDataListener) | Removes a listener |
public abstract int seriesLength() | Returns the length of the current series |
The methods perform similarly to those shown in Table 1.
During run time, JLineGraph calls the public void firstSeries() method; this selects the first data series. The next method called is public int seriesLength(); this returns the length of the current series. The integer value returned from the seriesLength() method is used to form a looping construct to get the value of each x-axis and y-axis coordinate for that series. In this series loop, public float getXCoord(int i) is called to get the ith category, and public float getYCoord(int i) is called to get the value ith category. After all data elements are collected for the current series, public boolean nextSeries() is called to select the next data series. If the nextSeries() method returns true, then the process of collecting data elements for the new data series begins again, starting with the seriesLength() method. When nextSeries() returns false it is because there are no more data series in the model.
By default, the JLineGraph accepts only float values for its x-axis and y-axis. In my line graph, the x-axis should contain a String representing the first six months of the year (January to June). To accomplish this, two things must occur: First, the Graph2DModel interface must be extended to include a public String getXLabel(float i) method. This will enable you to get the String representation of x-axis. Next, JLineGraph must be extended and override the drawLabeledAxes(Graphics g) method. This step will allow you to use the new interface to access the getXLabel(float f) method. Figure 6 is a class diagram showing the classes involved in creating a labeled line graph.
Figure 6. Class diagram for a labeled line graph

The code to create an instance of the DemandGraphModel and JLineGraph classes is found in the getLineGraph() method, which is shown in Listing 6.
// ...
public class LineGraph extends JFrame {
private JLineGraph lineGraph;
private static final String MONTH_NAMES[] = { "Jan", "Feb", //... };
private static final int MONTH_NUMBERING[] = {0, 1, 2, 3, 4, 5 };
private static final int LONDON_SERIES = 0;
private static final int PARIS_SERIES = 1;
private static final int NEW_YORK_SERIES= 2;
private static final float LONDON_DEMAND[] = { 1.2f, 3.7f, 6.7f, // ... };
private static final float PARIS_DEMAND[] = { 12.6f, 15.0f, 13.7f, // ... };
private static final float NEW_YORK_DEMAND[] = { 15.0f, 13.9f, 10.1f, // ... };
// ...
private JLineGraph getLineGraph() {
DemandGraphModel model = new DemandGraphModel();
model.addSeries(LONDON_DEMAND);
model.addSeries(PARIS_DEMAND);
model.addSeries(NEW_YORK_DEMAND);
model.setXAxisLabel(MONTH_NAMES);
model.setXAxis(MONTH_NUMBERING);
lineGraph = new LabeledLineGraph(model);
lineGraph.setColor(LONDON_SERIES, Color.red);
lineGraph.setColor(PARIS_SERIES, Color.yellow);
lineGraph.setColor(NEW_YORK_SERIES, Color.blue);
lineGraph.setXIncrement(1);
return lineGraph;
}
// ...
}
|
The last line of Listing 6 shows a call to the LabeledLineGraph setXIncrement(int i) method. This is done to ensure the x-axis is drawn following a strict increment. In other words, JLabeledLineGraph will mark each "tick" of the x-axis using the same coordinates from either a maximized application window or a floating window. Figure 7 shows the example output of what LineGraph.java produces. Resize the application window to see how the line graph is redrawn to fit to the new dimensions of the window.
Figure 7. Example output of a line chart from LineGraph.java

|
Conclusion
JSci is an open source effort mainly for scientific applications. In this article, I've introduced some of JSci's possible uses as a tool for creating 2D graphics. JSci is a freeware alternative to professional graphing packages. While JSci doesn't come with the type of built-in support you get with a professional graphing package, you do have access to the source code, and you're also free to make changes to the code and submit them back to the JSci community. JSci is an evolving effort, and I believe it's a good alternative to consider for the creation of 2D graphics in Java.
|
| Name | Size | Download method |
|---|---|---|
| j-2dgraphics.zip | 48KB | HTTP |
discuss this topic to forum
