Draw2D is a lightweight widget system hosted on a SWT Composite. A Draw2D instance consists of a SWT Composite, a lightweight system, and its contents' figures. Figures are the building blocks for Draw2D. Complete details on the Draw2D API come with Eclipse help in the Draw2D Developer's Guide. Because this article is not intended to teach Draw2D, for simplicity's sake, it is enough that you understand that the Draw2D API helps you draw figures on an SWT Canvas. You can use some standard shape figures, such as Ellipse, Polyline, RectangleFigure, and Triangle, directly; or you can extend them to create your own figures. Also, some container figures like Panel can act as an overall container for all child figures.
Draw2D has two important packages: org.eclipse.draw2d.geometry and org.eclipse.draw2d.graph, which I use in this article. The org.eclipse.draw2d.geometry package has useful classes, like Rectangle, Point, and PointList, which are self-explanatory. Developers have probably not explored the other package, org.eclipse.draw2d.graph, much. This package provides some important classes, such as DirectedGraph, Node, Edge, NodeList, and EdgeList, which help in creating a graph.
In this article, I explain how to use Draw2D to write code that can help you visualize your data graphically. I start by describing the technique to scale a data value lying in one range (for example, 0 to 2048) to its equivalent data value lying in another range (for example, 0 to 100). Then I illustrate how to chart out an X-Y plot of any number of series, each series containing a set of data elements. You can easily plot other chart types, such as pie charts and bar graphs, after you learn the concepts in this article.
Step-by-step chart-plotting process
Step 1: What do you want to plot?
Obviously, you want to plot data from some data source. So, you need the data you want to visualize in a graphical format. For brevity, instead of reading data from an XML file or some other data source, I generated data in a simple function named dataGenerator that uses a for(;;) loop and returns the generated values as an array list.
Listing 1. Generate some data
private ArrayList dataGenerator() {
double series1[] = new double[5];
for(int i=0; i<series1.length; i++)
series1[i] = (i*10) + 10; // a linear
series containing 10,20,30,40,50
double series2[] = new double[9];
series2[0] = 20; series2[1] = 150; series2[2] = 5;
series2[3] = 90; series2[4] = 35; series2[5] = 20;
series2[6] = 150; series2[7] = 5; series2[8] = 45;
double series3[] = new double[7];
for(int i=0; i<series3.length; i++)
series3[i] = (i*20) + 15;
seriesData.add(series1);
seriesData.add(series2);
seriesData.add(series3);
return seriesData;
}
|
Step 2: Scaling technique -- Generate X and Y coordinates from given data
When you want to plot points on a 2-D plane, you must find the X and Y coordinates of each point. The magic of plotting graphs lies in being able to scale a given data value from one range to another; that is, given a set of values like {10,20,30}, you should be able to decide exactly which points (X and Y coordinates) on a 2-D plane represent the data values 10, 20, and 30.
Always plot on a finite scale. In other words, you can plot any number of points in the same finite area. Because the area is fixed, you can always find the span (length) of the X axis and the span (height) of the Y axis. Knowing the span of the X and Y axes is only one part of the equation. The other part is to find the range of data values and calculate a coordinate of each value by finding its equivalent value in the new range.
Calculating X and Y coordinates
X coordinates: The X coordinate is a point's horizontal distance from the origin. Compute the horizontal distances of all points in a set simply by counting the number of elements and dividing the span of the X axis into n segments, where n equals the number of elements in a given set. This division yields the length of each segment. The first point in a set lies at a distance equal to the segment length. Each successive point lies at a distance of the segment length plus the distance of the previous point from the origin.
For example, given a set {10,20,30,40}, you immediately know that you want to plot four points because the set contains four elements. Therefore, the X-axis span should be divided into four equal segments of length=span/4. So, if the span of the X axis is 800, the segment length will be 800/4, which is 200. The X coordinate of the first element (10) will be 200, the second element (20) will be 400, and so on.
Listing 2. Calculate X coordinates
private int[] getXCoordinates(ArrayList seriesData){
int xSpan = (int)GraFixConstants.xSpan;
int longestSeries = Utilities.getLongestSeries(seriesData);
int numSegments =
((double[])seriesData.get(longestSeries)).length;
int sectionWidth =
(int)xSpan / numSegments; //want to divide span of xAxis
int xPositions[] =
new int[numSegments]; // will contain X-coordinate of all dots.
for(int i=0; i<numSegments; i++){
xPositions[i]=
(i+1)*sectionWidth;//dots spaced at distance of sectionWidth
}
return xPositions;
}
|
Y coordinates: The Y coordinate is a point's vertical distance from the origin. Computing Y coordinates involves scaling a value from one range to another. For example, given the same set {10,20,30,40}, you can see that the data range is 0 to 40, and the new range is the span (height) of the Y axis. Assuming that the height of the Y axis is 400, the equivalent height of the first element (10) will be 100, the second element (20) will be 200, and so on.
You can better understand scaling a value from one range to another by the following example: Assuming that one range spans from 0 to 2048, and you want to scale any value from this range (for example, 1024) into another range that spans from 0 to 100, you can immediately see that the equivalent scaled value is 50. The three-line arithmetic behind the scaling follows:
line 1---> 2048 / 1024 equals 2. line 2---> 100 - 0 equals 100. line 3---> 100 / 2 equals 50, which is the desired scaled value. |
Step 3: Where do you want to plot?
You need some area on which you want to plot. Create your own view by extending an Eclipse ViewPart and using the SWT Composite. Alternatively, you can also use a SWT shell invoked from inside a main() function.
When you extend an Eclipse ViewPart, you must implement at least two functions: createPartControl(Composite parent) and setFocus(). The function createPartControl(Composite parent) is called automatically when your view has to be painted on the screen. Your interest is only in the SWT Composite thus received. Pass it on to a class you will code to plot the graphs.
Listing 3. Using an Eclipse ViewPart to plot
public class MainGraFixView extends ViewPart{
public void createPartControl(Composite parent) {
//create or get data in an arraylist
ArrayList seriesData = dataGenerator();
//instantiate a plotter, and provide data to it.
DirectedGraphXYPlotter dgXYGraph = new DirectedGraphXYPlotter(parent);
dgXYGraph.setData(seriesData);
dgXYGraph.plot(); //ask it to plot
}
public void setFocus() {
}
}
|
Step 4: What kind of graph do you need?
Once you have the data and the area you want to plot, you must decide which kind of visualization you need. In this article, I demonstrate how to write code to create an X-Y plot or line graph. Once you understand the technique behind an X-Y plot, you should be able to chart out other plotters, like bar and pie. To understand more about the X-Y plot, look at the DirectedGraphXYPlotter class I wrote for this article (see \src\GraFix\Plotters\DirectedGraphXYPlotter.java in the attached source code).
Step 5: Create your own X-Y plotter
An X-Y plotter should be able to plot any number of series lines on a 2-D plane. Each series line should graphically show the position of each point in the series with reference to the X and Y reference lines. Each point should be connected to the next in series by a line. You can create such a plotter by using Draw2D figures that represent a point and a line. For example, to represent a point, I created a Dot figure by extending an Ellipse figure and used a PolylineConnection figure to represent the connecting lines.
The DirectedGraphXYPlotter class has only two public functions: setData(ArrayList seriesData) and plot(). The function setData(ArrayList seriesData) accepts data (see Step 1) you want to visualize graphically, and the plot() function starts plotting the graph.
Once the plot() function is called, you need to take the following steps, in sequence:
- Take an SWT Composite and put a
FigureCanvason it. Then put a general-purpose container figure like Panel on the canvas. - Count the number of series to be plotted and populate as many
NodeListsandEdgeListsas required to createDirectedGraphs. - Draw the X axis and the Y axis on the Panel figure. (See XRulerBar.java and YRulerBar.java under \src\GraFix\Figure in the attached source code.)
- Create a number of
DirectedGraphsequal to the number of series to plot. - Draw dots and connecting wires on the Panel figure while taking the graph data from the
DirectedGraphscreated in Step d. - Finally, set the contents of the canvas by providing the Panel figure, which contains all the dots and connecting lines you have prepared so far.
In the code below:
- Lines 6-11 correspond to Step a above.
- Line 14, the
populateNodesAndEdges()function, corresponds to Step b above. - Line 16, the
drawAxis()function, corresponds to Step c above. - Lines 17, 18, and 19 correspond to Steps d and e above.
- Line 20 corresponds to Step f above.
Listing 4. The
plot() function
1. public void plot(){
2. //if no place to plot, or no data to plot, return.
3. if(null==_parent || null==_seriesData)
4. return;
5.
6. Composite composite = new Composite(_parent, SWT.BORDER);
7. composite.setLayout(new FillLayout());
8. FigureCanvas canvas = new FigureCanvas(composite);
9.
10. Panel contents = new Panel();//A Panel is a general purpose container figure
11. contents.setLayoutManager(new XYLayout());
12. initializeSpan(contents.getClientArea());
13.
14. populateNodesAndEdges();
15.
16. drawAxis(contents);
17. for(int i=0; i<_numSeries; i++){
18. drawDotsAndConnections(contents,getDirectedGraph(i)); //
draw points & connecting wires
19. }
20. canvas.setContents(contents);
21. }
|
Two important internal functions called by plot() help in plotting points: populateNodesAndEdges() and drawDotsAndConnections(). Before you discover exactly what these functions do, let's look at a DirectedGraph.
What is a DirectedGraph? To plot a graph in Draw2D, you must first create a graph virtually that will define the points and lines to be plotted. Once you have created this graph, you can use it later to actually start drawing figures on a canvas. You can visualize a DirectedGraph as a 2-D graph that has a finite number of Nodes, with each Node located at some Point, and the adjacent Nodes joined (or connected) to each other by Edges.
You can understand the crux of creating a DirectedGraph with the following lines of code. First, create a list of Nodes and a list of Edges. Next, create a new DirectedGraph and set its members (Nodes and Edges) by the NodeList and EdgeList just created. Now use a GraphVisitor to visit this DirectedGraph. To make things simple, the package org.eclipse.draw2d.internal.graph has many implementations of GraphVisitor that already have specific algorithms to visit a graph.
So, the sample code to make a DirectedGraph turns out something like this:
Listing 5. A sample DirectedGraph
//This is a sample, you will need to add actual Node(s) to this NodeList. NodeList nodes = new NodeList(); //create a list of nodes. //This is a sample, you will need to add actual Edge(s) to this EdgeList. EdgeList edges = new EdgeList(); //create a list of edges. DirectedGraph graph = new DirectedGraph(); graph.nodes = nodes; graph.edges = edges; new BreakCycles().visit(graph);//ask BreakCycles to visit the graph. //now our "graph" is ready to be used. |
Now that you know that a DirectedGraph contains a list of Nodes, where each Node may contain some data and also store its X and Y coordinates, and a list of Edges, where each Edge knows it has a Node at both its ends, you can use this information to plot graphs with the following technique, which involves two parts:
Part A -- Populate Nodes and Edges by:
- Creating a
NodeListhaving oneNodeper element in a set. For example, the set {10,20,30,40} requires fourNodes. - Finding the X and Y coordinates for each element and storing them in node.x and node.y member variables.
- Creating an
EdgeListhaving n-1Edges, where n is the number of elements in a set. For example, the set {10,20,30,40} requires threeEdges. - Associating a
Nodeto the left and right of eachEdgeand setting the edge.start and edge.end member variables appropriately.
Part B -- Draw figures that represent Nodes and Edges by:
- Drawing a Dot figure to represent each
Node. - Drawing a PolylineConnection figure to represent each
Edge. - Anchoring every PolylineConnection figure to a Dot figure on the left and right.
Now, coming back to the workings of the internal functions:
- The function
populateNodesAndEdges()implements Part A of the technique, anddrawDotsAndConnections()implements Part B of the technique. - The function
populateNodesAndEdges()counts how many series have to be plotted. It creates oneNodeListand oneEdgeListfor each series. - Every
NodeListcontains a list ofNodesfor a particular series. EveryNodekeeps the information on where X and Y coordinates should be plotted. The functionsgetXCoordinates()andgetYCoordinates()retrieve the value of the X and Y coordinates, respectively. These functions also internally scale data values from one range to another, using the same algorithm in Step 2. - Every
EdgeListcontains a list ofEdgesfor a particular series. EveryEdgehas aNodeon its left and anotherNodeon its right.
Listing 6. The
populateNodesAndEdges() function
private void populateNodesAndEdges(){
_seriesScaledValues = new ArrayList(getScaledValues(_seriesData));
_nodeLists = new ArrayList();
_edgeLists = new ArrayList();
for(int i=0; i<_numSeries; i++){
_nodeLists.add(new NodeList());// one NodeList per series.
_edgeLists.add(new EdgeList());// one EdgeList per series.
}
//populate all NodeLists with the Nodes.
for(int i=0; i<_numSeries; i++){//for each series
double data[] = (double[])_seriesData.get(i);//get the series
int xCoOrds[] = getXCoordinates(_seriesData);
int yCoOrds[] = getYCoordinates(i, data);
//each NodeList will have as many Nodes as number of points in a series
for(int j=0; j<data.length; j++){
Double doubleValue = new Double(data[j]);
Node node = new Node(doubleValue);
node.x = xCoOrds[j];
node.y = yCoOrds[j];
((NodeList)_nodeLists.get(i)).add(node);
}
}
//populate all EdgeLists with the Edges.
for(int i=0; i<_numSeries; i++){
NodeList nodes = (NodeList)_nodeLists.get(i);
for(int j=0; j<nodes.size()-1; j++){
Node leftNode = nodes.getNode(j);
Node rightNode = nodes.getNode(j+1);
Edge edge = new Edge(leftNode,rightNode);
edge.start = new Point(leftNode.x, leftNode.y);
edge.end = new Point(rightNode.x, rightNode.y);
((EdgeList)_edgeLists.get(i)).add(edge);
}
}
int breakpoint = 0;
}
|
Once the function populateNodesAndEdges() has done its job of creating NodeLists and EdgeLists for all the series to be plotted, the other function drawDotsAndConnections() starts drawing a Dot figure for each Node and a PolylineConnection figure for each Edge.
Listing 7. The
drawDotsAndConnections(), drawNode(), and drawEdge() functions
private void drawDotsAndConnections(IFigure contents, DirectedGraph graph){
for (int i = 0; i < graph.nodes.size(); i++) {
Node node = graph.nodes.getNode(i);
drawNode(contents, node);
}
for (int i = 0; i < graph.edges.size(); i++) {
Edge edge = graph.edges.getEdge(i);
drawEdge(contents, edge);
}
}
|
Plotted graph results
If you're drawn to graphically representing data, Draw2D is a good tool. Using Draw2D to write your own Java language code to plot charts and graphs can help you concentrate on scaling and plotting code, leaving the rendering/painting related jobs to Draw2D and SWT. You can also control the appearance of your graphs by using Draw2D figures of your choice. Draw2D simplifies the basics of drawing charts and graphs, and minimizes your dependence on third-party tool kits.
| Description | Name | Size | Download method |
|---|---|---|---|
| Unzip and open this plug-in as an Eclipse project | GraFix.zip | 24KB | HTTP |
Information about download methods
- "Getting started with the Eclipse Platform" (developerWorks, November 2002) provides a history and overview of Eclipse, including details on how to install Eclipse and plug-ins.
- Want to learn about the Graphical Editing Framework? Take a look at "Create an Eclipse-based application using the Graphical Editing Framework" (developerWorks, July 2003).
- Check out "Java 2D imaging for the Standard Widget Toolkit" (developerWorks, June 2004) for more 2-D imaging information.
- If you are new to SWT, take a look at SWT Online Tutorials at Eclipse.org.
- IBM's eclipseCON 2004 The Graphical Editing Framework neatly describes Draw2D and SWT layers.
- Visit the developerWorks Open source zone for extensive how-to information, tools, and project updates to help you develop with open source technologies and
use them with IBM's products.
- Innovate your next open source development project with
IBM trial software, available for download or on DVD.
- Browse for books on these and other technical topics.
- Get involved in the developerWorks community by participating in developerWorks blogs.

Indiver Dwivedi is a senior software engineer working in the Pune Lab for IBM India Software Labs. He joined IBM in 2000, and has worked on projects like Lotus® SmartSuite and an IBM data access tool for IBM Workplace. He has four years of programming experience in Ladder Logic, C, and C++. He has programmed logic controllers, and PC-based supervisory control and data acquisition (SCADA) software for industrial automation and control (IAC) systems. He has an interest in topics related to device communications and charting/plotting historical data stored by data acquisition systems under the IAC domain. He is currently working with a team in the IBM Pune Lab and contributing to the IBM Workplace Designer project.



