Dynamic graph visualisation using JLink/Java and GraphStream

I am writing this answer for a person who is familiar with Mathematica and has a good understanding of computer programming, but not so familiar with Java programming language. Using GraphStream is not so different from using any other Java library. You need to download the GraphStream core files from here and extract it. gs-core-1.1.2.jar is the only file you need. You can remove the rest of the files. Here is a minimal demo.

Needs["JLink`"]
(* Use InstallJava for the first time or see Todd's answer for how to use AddToClassPath *)
ReinstallJava[ClassPath -> "/full/path/to/jar/file/gs-core-1.1.2.jar"]
g = JavaNew["org.graphstream.graph.implementations.SingleGraph", "graph"]
g@addNode["A"]
g@addNode["B"]
g@addEdge["AB", "A", "B"]
g@display[]

Remember to modify /full/path/to/jar/file/gs-core-1.1.2.jar to the correct one on your system. If you want to use multiple jar files, you need to separate the paths by : on unix like systems and ; on Windows, e.g., ClassPath -> "/path/to/jar1.jar:/path/to/jar2.jar" (we don't have multiple jar files here, but I mentioned it for the sake of completeness). The rest is just a translation from Java calls to Mathematica calls. Consider the following example from here:

import org.graphstream.graph.*;
import org.graphstream.graph.implementations.*;

public class Tutorial1 {
        public static void main(String args[]) {
                Graph graph = new SingleGraph("Tutorial 1");

                graph.addNode("A");
                graph.addNode("B");
                graph.addNode("C");
                graph.addEdge("AB", "A", "B");
                graph.addEdge("BC", "B", "C");
                graph.addEdge("CA", "C", "A");

                graph.display();
        }
}

To translate it into Mathematica, the following tips might be useful:

  • You can safely ignore public class XXX { ... and public static void main(String args[]) { lines. They are just the repeated parts in the main file of a Java program. Main files are actually the starting point of the Java programs. There is no such a thing in Mathematica.

  • Creating new objects: To translate something like Graph graph = new SingleGraph("Tutorial 1"); into Mathematica, you first need to find the full class name of SingleGraph (attention: SingleGraph at the RHS of =, not Graph which is at the LHS) with the package name. To do so, you can either make a guess, or browse the javadoc. If you have a look at the first two lines of the above code, you may guess that SingleGraph is either imported from org.graphstream.graph or org.graphstream.graph.implementations, and if you guessed the second one, you are right. Once you found the full class name you can simple call g = JavaNew["org.graphstream.graph.implementations.SingleGraph", "graph"] to create a new object.

  • Calling methods: graph.addNode("A"); can be simply be converted into mathematica like this: g@addNode["A"]

Here is a sample code that imports a GraphML file:

folder = "/path/to/a/folder/"; (* make sure it ends with a slash *)
g = JavaNew["org.graphstream.graph.implementations.DefaultGraph", "demo-graph"];
fs = JavaNew["org.graphstream.stream.file.FileSourceGraphML"];
fs@addSink[g];
fs@readAll[folder <> "g.graphml"];
g@display[];

You can use Export[folder <> "g.graphml", RandomGraph[{50, 200}]] to generate a random graph.

Appendix: General properties/tips about Java for a Mathematica programmer:

  • Java is a compiled programming language. Java source files have a .java extension. Using the Java compiler, called javac, .java files are compiled into .class files. Class files are then executed using the Java Virtual Machine (JVM). From the command line, you can use the java command to run the class files.

  • Jar files are essentially a bunch of .class files that are zipped. So, you can simply change the extension of a jar file to .zip and extract it using your favourite unzipper.

Java

  • To use Java in Mathematica, you need to load the JVM and the extra libraries you need (e.g., GraphStream jar file). However, keep in mind that even without loading extra libraries, you have access to the HUGE Java standard library. So, for instance, you can use Sockets or do some cryptography without any extra library.

  • ClassPath is the set of paths from which the required Java classes are loaded. To use the extra libraries, you need to add it to the classpath.

  • Despite Mathematica, which is mostly a functional language, Java is an Object Oriented language. Having some knowledge about OO programming is very useful.


Nice answer by Mohsen, +1. I am continually impressed by the quality of the J/Link and .NET/Link expertise on this site. I have a couple remarks and then an example program.

The question asked about some general tips for getting started with J/Link. This GraphStream library provides a perfect example for the typical workflow of a J/Link project. The following are exactly the steps I went through when tinkering with this, and I'm sure Mohsen did as well.

  • Download the library materials. Generally this will be one or more .jar files
  • Make sure J/Link can find the jar files, by calling AddToClassPath
  • Locate the javadocs for the library. Keep these open as a handy reference
  • Skim the documentation, looking for any Getting Started/Tutorial type of information

Those steps might seem obvious, and could hardly be considered "advice", but the key point is that you are looking for a trivial example Java program. That is always the starting point. Once you find a small bit of sample code, you can translate it directly into Mathematica. Mohsen's first few lines of code, ending in g@display[], are right out of the "Getting Started" tutorial for GraphStream, literally the first lines of Java code they demonstrate. They translate directly into Mathematica almost trivially, as Mohsen describes (and the J/Link docs do, in more detail). Within a few minutes you have a Java window on your screen with a graph in it. This is an incredibly empowering feeling, and from there you can delve deeper into what the library provides. To do fancy things you will probably need to learn some subtleties of J/Link, and some familiarity with Java is extremely useful, but once you have something basic working, you can build from there.

I have tinkered with many Java libraries using J/Link, and I almost always have something running within a few minutes. Although I know J/Link very well, it usually takes only the most basic J/Link knowledge to get that far.

I strongly recommend not using ReinstallJava[ClassPath -> ...] for making Java libraries available to J/Link. Calling ReinstallJava is a destructive operation that you should only call if you absolutely need to. Any other Mathematica components or packages that are using J/Link might have some state wiped out if you restart Java. Instead, call AddToClassPath, which is exactly what you did in your question. What is particularly convenient about AddToClassPath is that you can give the directory in which a set of jar files reside, and all the jar files will be added.

Here is a sample program that uses GraphStream to dynamically render the sample graph. It also displays the output of Mathematica's GraphPlot for comparison.

Needs["JLink`"];
InstallJava[];
AddToClassPath["dir/with/graphstream/jar/files"];

(* We only ever need the node names as strings, so convert them ahead of time *)
graphData = dynamicGraph /. {a_Integer, b_Integer, c_, d_} :> {ToString[a], ToString[b], c, d};

graph = JavaNew["org.graphstream.graph.implementations.SingleGraph", "StackExchange"];
viewer = graph@display[];

(* We need this only for computing the coordinates of the middle of the image.*)
ggraph = viewer@getGraphicGraph[];

(* This makes the window go away when its close box is clicked. *)
LoadJavaClass["org.graphstream.ui.swingViewer.Viewer$CloseFramePolicy"];
    viewer@setCloseFramePolicy[Viewer$CloseFramePolicy`HIDEUONLY];

graph@setStrict[False];
nodes = {};
edges = {};

Manipulate[
    previousNodes = nodes;
    previousEdges = edges;

    edges = Select[graphData, #[[3]] <= t <= #[[4]]&][[All, 1;;2]];
    nodes = Union @ Flatten @ edges;

    losingNodes = Complement[previousNodes, nodes];
    addingNodes = Complement[nodes, previousNodes];
    losingEdges = Complement[previousEdges, edges];
    addingEdges = Complement[edges, previousEdges];

    (* We will create a lot of temporary Java objects, so use JavaBlock to ensure they  get cleaned up. *)
    JavaBlock[
        Function[nodeName,
            node = graph@addNode[nodeName];
            (* This whole bit is just to add new points near the middle of the image,
               otherwise you get ugly initial edges drawn to the edge of the image.
            *)
            If[Length[nodes] > 2,
                min = ggraph@getMinPos[];
                max = ggraph@getMaxPos[];
                middlex = Round@Mean[{min@x, max@x}];
                middley = Round@Mean[{min@y, max@y}];
                node@setAttribute["xyz", {MakeJavaObject[middlex], MakeJavaObject[middley], MakeJavaObject[0]}]
            ];
            node@addAttribute["ui.style", {MakeJavaObject["text-size: 14;"]}];
            node@addAttribute["ui.label", {MakeJavaObject[nodeName]}]
        ] /@ addingNodes;
        graph@removeNode[#]& /@ losingNodes;

        Function[{startNode, endNode},
            graph@addEdge[startNode <> endNode, startNode, endNode]
        ] @@@ addingEdges;

        Function[{startNode, endNode},
            graph@removeEdge[startNode <> endNode]
        ] @@@ losingEdges
    ];

    (* GraphPlot's display for comparison. *)
    GraphPlot[#1->#2& @@@ Select[dynamicGraph, #[[3]] <= t <= #[[4]]&], VertexRenderingFunction -> (Text[#2, #1]&)]
    ,
    {t, 0, 31, 1},
    TrackedSymbols -> {t}
]

You could do much, much more with this, of course. GraphStream has many controls for styling and behavior.