Introduction:

Welcome to the third JUNG Tutorial developed by Game2Learn, a member of UNC-Charlotte’s Games + Learning Laboratory. This time we will address the issue of Storing our edge and node data in a hash-table then using that table to access our specific data. There are two advantages of this approach, the first is decoupling, a software engineering term used to describe reducing the interconnection of two portions of a program. If two components are highly coupled that means they depend on each other, if they have low coupling it means that they are more like individual items and can stand alone. In object oriented programming, the goal is generally to have low coupling when possible in order to make our programs more modular.

In this tutorial by storing our data in a hash table, we can decouple our data from both our graph and our visual representation, the visualizationViewer. The second benefit is, you as a programmer, can have quicker access to your data because hash-table look ups have an O(1), read as Big O of 1. In addition the process of decoupling our data from the other components makes sense when we have some feature of our application which only needs to interact with the data of some node, and not the graph topology, like changing the color of one of our nodes in our example program here. As you will see in this tutorial, some component of our program does not require the entire graph in order to change the color field of our nodeData object, it just needs to access our hash-table which stores all of the data specific to each node.

Click here to download the Node & Edge Hash-Table Demo associated with this tutorial.

The result of our executed code will look as follows:

The results of the HashTable Demo. The importance here is how this result is generated not necessarily the result itself.

Hash Tables:

Alright if you have never used a hash table before in Java, here is a quick overview of how they work. Basically it is a two-column table where each column is a specific piece of data, often referred to as the key and the value. Java offers a number of classes that behave like a hashtable with slight variations. In any case, the idea is, if you have the key and give it to the hashtable, the hashtable will happily return you the value. The reason this is useful, is because many components of your program can store the key which is relatively small, and they can all access the value of the hashtable which is likely not very small. Often times, people will use an integer or a String for the key, and use their own object for the value portion of the hashtable. One simple example would be using a student name as a key, to access a student object, which would be the value.

An attempt to show an example of what a hash table, from our demo code, might look like. The key is of type String and it is linked to the value of type nodeData. The text in bold are the actual values of each piece of the hash table.

In this tutorial we are going to use a node-ID as our key, which is of type String, which will access our own class which is of type nodeData. Although we do store the edgeData of our demo in a hashtable we do not actually use it, but it would be done just the same as we are doing with our node.

First we declare our a Hashtable for our nodeData.

1
private Hashtable<string, nodedata> nodeTable = new Hashtable<string, nodedata>();

Notice in our declaration we tell Java that we want a hashtable which uses a String as the key, and a nodeData object (an object I made up), as our value for the hashtable. This way if I give the hashTable a String that is contained in the table, and it will provide me with the value, which is my nodeData.

Next we need to add some dummy data for the purposes of this tutorial to our hashtable, so we will make a simple function in order to do that. PopulateNodeDataTable will use our hashtable, nodeTable, which we declared above, and fill it with a set of keys and values.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* This function will add data to our nodeTable, which is the data for each specific node.
Our node class for our Jung graph is of type String, so we supply that as our key,
(the "Node1", "Node2", etc.) and we make a new reference to our nodeData object, and
supply the information we want for each node. In this case an integer (the mID), a String
for mValue, and a String for mColor.*/
 
private void PopulateNodeDataTable()
{
     nodeTable.put("Node1", new nodeData(1,"Val: 1", "Color: Red"));
     nodeTable.put("Node2", new nodeData(2,"Val: 2", "Color: Green"));
     nodeTable.put("Node3", new nodeData(3,"Val: 3", "Color: Blue"));
     nodeTable.put("Node4", new nodeData(4,"Val: 4", "Color: Magenta"));
     nodeTable.put("Node5", new nodeData(5,"Val: 5", "Color: Yellow"));
     nodeTable.put("Node6", new nodeData(6,"Val: 6", "Color: Orange"));
}

So in the code above, we decalre a hashtable, nodeTable, to store our data that we want on each node, and we populate it with some made up data. We also do the same for edges, and an edgeTable, which is done in the exact same manner. Check the source code provided at the top of this tutorial.

Jung, Graphs and VisualizationViewers:

So the purpose of using a hashtable is to allow us to decouple our data from our representation, which means we do not want to supply our data to the Jung graph and the visualizationViewer (vv). Instead we will use our keys of our hashtables in the graph and the VV.

1
2
3
4
// Declare our graph.
final UndirectedGraph<String, String> graph = new UndirectedSparseMultigraph<String, String>();
// Declare our VisualizationViewer
VisualizationViewer<String, String> vv = new VisualizationViewer<String, String>(new FRLayout<String, String>(graph));

Notice in the lines above, our graph and our vv are of type <String, String>, this makes our data and our representation decoupled, because our graph does not know anything about the data contained within the nodes, it only has a String.

Vertex Painters and Transformers:

Next we need to address the vertexPainter, because in previous tutorials our Transform function always took the node-type as an argument, and we do that here as well. However this time our node-type is String and not our Node-class, so being a String, our node is much less knowledgeable about the data contained in our nodes. Here is how we address that issue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class vertexPainter implements Transformer<String, Paint>
{
    private Hashtable<String, nodeData> mNodeDataTable;
 
    public vertexPainter(Hashtable<String,nodeData> nodeDataTable)
    {
        mNodeDataTable = nodeDataTable;
    }
    public Paint transform(String v) //So for each node that we draw...
    {
        //We check the member variable, mColor, of the node. By first sending our key, in this case v
        // to our nodeDataTable to get our value from the table. Immediately we can call .getColor() 
        // to get the mColor value of our node, which in our case is actually a String, so we do a string compare.
        if (mNodeDataTable.get(v).getColor().equalsIgnoreCase("Color: Red"))
            return (Color.red); //If the node's mColor value is equal to "Color: Red" we return our color, Color.Red
        else if (mNodeDataTable.get(v).getColor().equalsIgnoreCase("Color: Green"))
            return (Color.green);
        else if (mNodeDataTable.get(v).getColor().equalsIgnoreCase("Color: Blue"))
            return (Color.blue);
        else if (mNodeDataTable.get(v).getColor().equalsIgnoreCase("Color: Orange"))
            return (Color.orange);
        else if (mNodeDataTable.get(v).getColor().equalsIgnoreCase("Color: Yellow"))
            return (Color.yellow);
        else if (mNodeDataTable.get(v).getColor().equalsIgnoreCase("Color: Magenta"))
            return (Color.magenta);
        else
            return (Color.pink);
    }
}

Pay particular attention to the transform function, which receives a vertex v or type String. Notice however that we passed a hashtable<String,nodeData> to our constructor of our vertexPainter. This hashtable allows us to access the data of our node. So in our transform function, we get v, which is our key for the hashtable, and we use that to access our data from our nodeTable, by performing nodeTable.get(v) we are retrieving an object of type nodeData and that lets us access our member functions allowing us to retrieve our color for the node.

Well there you have it, you can now store your data, decoupled from the graph, in a hashTable and still access the data when you want to change the behavior of our visualizationViewer. However before we finish and conclude this tutorial, let us take a look at two other interesting things to consider.

Other Considerations:

There are two more interesting aspects to consider when performing this type of decoupling. The first has to do with Jung’s default VertexLabelTransformer, which we have used in previous tutorials.

1
vv.getRenderContext().setVertexLabelTransformer(new ToStringLabeller<String>());

Basically we can no longer depend on this VertexLabelTransformer, because now our vertex is of type String and so the only label information we will have is the object’s key, but this may be an insufficient amount of information for labeling the node in a useful way. Consider we want to post some nodeData-specific information to the node-label, perhaps the node’s color for example.

Well in order to make that possible it is necessary to write a new VertexLabelTransformer, note that in the code I offer two different versions of the transform function, so we can highlight their differences Nevertheless we write our nodeLabeller as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class nodeLabeller implements Transformer<String, String>
{
        private Hashtable<String, nodeData> mNodeDataTable;
 
    // It is necessary to provide our hashTable to this function so that we can access our class specific data.
    public nodeLabeller(Hashtable<String,nodeData> nodeDataTable)
    {
        mNodeDataTable = nodeDataTable;
    }
 
    /* Note here we are accessing the nodeTable to get our data and not just the
node object, this is because our specific data is contained within our hashTable, and
not the node object used by Jung. The node object in the Jung graph is merely a String,
but that String is a key to our hashTable which contains all of our detailed information.*/
 
    /* Also note that we use html tags, this is a nice feature of the Jung library that lets
you make use of html within the vertex label, enabling a wider range of formatting functionality. */
    /*
    public String transform(String s)
    {
        return ("<html>NodeLabeller:"+mNodeDataTable.get(s).getValue()+""+mNodeDataTable.get(s).getColor()+"</html>");
    }
    */
 
    /* A second option that exists is to use the toString() method defined by the nodeData
class, rather than  one defined by the nodeLabeller. This code is provided here, and in nodeData.
The advantage is that only one call is necessary to to the hashTable, rather than multiple calls,
however both are sufficiently fast and the difference is not likely noticed in practice.*/
    public String transform(String s)
    {
        return (mNodeDataTable.get(s).toString());
    }
}

So in the commented transform function, we construct a String in the nodeLabeller class and return it, as such it is necessary to refer to our nodeTable each time we want to get some data, so we call mNodeDataTable.get(s).someFunction() multiple times, once for each piece of data we want. A second option is to the our nodeData class’s toString() function, which is in the second part of the code above and is not commented. In this case we just make one call of mNodeDataTable.get(s) and then we are calling the nodeData class’s toString() function, where the toString() function has constructed our desired output.

Conclusion:

Well that will do it for this tutorial on using a hashtable to store data for edges and nodes. We stored data in two hashtables, one for edges and one for nodes, then supplied Jung and our graph with only Strings, which were our keys for accessing our hashtable. Next we offer a new vertexPainter and VertexLabelTransformer to use which will make use of our new structure, namely our hashtable of data.

This tutorial and source code for this project were written by Matt Johnson with help of Michael Eagle. For more information about Jung visit their webpage or take a look at the other examples we provide here at Game2Learn.com.