This tutorial is an introduction to the graph viewing features of GraphStream. In this tutorial you will learn the basic concepts of the graph viewer, and learn several ways to access it, depending on the complexity of the task at hand. This tutorial is not terribly exciting, but contains terribly important informations.
Introduction to the UI package
The org.miv.graphstream.ui package is the root of the hierarchy of graph rendering features.
As always, the rendering and GUIs part of a project quickly takes up more space than the original project. GraphStream graph viewing capabilities have also grown a lot. However, although the API doc of org.miv.graphstream.ui may seem very large and complex, we tried to make it modular. We made efforts so that each of the following tasks are easy to tackle :
- merely display the graph,
- incorporate the graph viewer in a program,
- customise the visual aspect,
- add graphic indicators on the graph to visualise attached data or the computation of a particular algorithm,
- extend the graph viewer capabilities,
- or even implement new graph viewers.
We will focus here on the first two tasks. Each of the following tutorials will deal with one of the other tasks.
Various ways to display a graph
You already know the display() method. This is the easiest way to display a graph. Under the hood, this method creates a graph viewer. This creation is hidden because most of the time the graph viewer runs in a distinct thread, and it is difficult to interact with an object whose methods can be called in parallel in another thread.This allows rendering to occur in parallel with your algorithms operating on the graph.
In other words, once you called the display() method, you can operate on the graph, and the display will follow the changes you made on the graph. This means that most of the time you can call display() just after having created the graph, and let the viewer do its work in parallel.
The display() method however gives you a way to dialog with this graph viewer: it returns an instance of GraphViewerRemote. As its name (may) suggests, this allows to send commands to the graph viewer across the thread boundary.
These commands provide for example a way to change the overall rendering quality, take snap shots of the view, etc. It also allows to access another remote that controls the graph layout algorithm which also run in its own dedicated thread (when enabled).
Here is a first example that shows how to change the rendering quality of the graph viewer using the GraphViewerRemote:
import org.miv.graphstream.graph.Graph;
import org.miv.graphstream.graph.implementations.DefaultGraph;
import org.miv.graphstream.ui2.GraphViewerRemote;
public class TutorialUI001
{
public static void main( String args[] )
{
Graph graph = new DefaultGraph( false, true );
GraphViewerRemote viewerRemote = graph.display();
viewerRemote.setQuality( 4 ); // 4 is the max quality (range is 0-4).
graph.addEdge( "AB", "A", "B" );
graph.addEdge( "BC", "B", "C" );
graph.addEdge( "CA", "C", "A" );
}
}
The two screen shots under show at left the result with low quality, and at right with the rendering quality set to four :

With the default graph renderer (that uses Swing and Java2D), the quality sets things like line, polygon and text anti-aliasing, image interpolation quality, overall Java2D quality, etc. This is a compromise of speed versus quality.
Making screen shots
Screen shots are a way to display your graphs. GraphStream know several output formats for screen shots, including several bitmap formats like the popular JPEG and PNG formats, as well as a vector based one: SVG.
Making screen shots with the GraphViewerRemote is easy, just use the screenShot() method. However one problem may arise if you use an automatic layout algorithm : you may want to wait for the layout algorithm stabilisation. Otherwise the screen shot may show a graph with an inappropriate layout.
Here is an example of the way screen shots can be done. This program generates a large enough graph and displays it using the automatic layout feature. It then waits for the layout algorithm stabilisation and then do a screen shot :
import org.miv.graphstream.algorithm.generator.DorogovtsevMendesGenerator;
import org.miv.graphstream.algorithm.generator.Generator;
import org.miv.graphstream.graph.Graph;
import org.miv.graphstream.graph.implementations.DefaultGraph;
import org.miv.graphstream.ui.GraphViewer;
import org.miv.graphstream.ui.GraphViewerRemote;
public class TutorialUI001b {
public static void main( String args[] ) {
new TutorialUI001b();
}
public TutorialUI001b() {
Graph graph = new DefaultGraph();
Generator generator = new DorogovtsevMendesGenerator();
generator.begin( graph );
for( int i=0; i<200; i++ )
generator.nextElement();
generator.end();
GraphViewerRemote remote = graph.display( true );
remote.setQuality( 3 );
remote.waitForLayoutStabilisation( 60000 );
remote.screenShot( "screenshot", GraphViewer.ScreenshotType.PNG );
System.out.printf( "Ok !!" );
System.exit( 0 );
}
}
The waitForLayoutStabilisation() method allows to block the program until the layout algorithm finished its layout. To avoid blocking for ever, a time out in milliseconds is given (here we wait for a minute). Then the screenShot() method is called with a file name and a file type.
Instancing your own GraphViewer
The other way to display a graph is to create the graph viewer yourself. This allows to embed it in your own applications. We have seen that the graph viewer runs in a thread, and by default, when using the Swing graph viewer, this thread is the Swing thread. Therefore it is easy to use the viewer in your Swing GUIs.
GraphViewer is an interface. Then several implementations can be created. The default one uses Swing and Java2D to render the graph and is named org.miv.graphstream.ui.swing.SwingGraphViewer. This has been chosen since theses drawing libraries are available with every JRE, therefore making it quite portable (a sister project of GraphStream proposes other user interfaces, and one particularly provides a 3D graph viewer).
You can create the swing graph viewer in any thread, but as soon as created, it starts running in the Swing thread and therefore it would be dangerous to call methods on it (remember the remote is used for that).
There are several ways to build a graph viewer. The default one creates a window. It is used by the Graph.display(). However we want to embed the viewer in our application. So another mode exist that creates the viewer as a JPanel that can then be added to any Swing GUI. Let's see an example of this :
import java.awt.BorderLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import org.miv.graphstream.graph.Graph;
import org.miv.graphstream.graph.implementations.DefaultGraph;
import org.miv.graphstream.ui2.GraphViewerRemote;
import org.miv.graphstream.ui2.swing.SwingGraphViewer;
public class TutorialUI001a extends JFrame
{
private static final long serialVersionUID = 1;
public static void main( String args[] )
{
new TutorialUI001a();
}
public TutorialUI001a()
{
Graph graph = new DefaultGraph( false, true );
SwingGraphViewer viewer = new SwingGraphViewer( graph, true /*isPanel*/ );
GraphViewerRemote viewerRemote = viewer.newViewerRemote();
viewerRemote.setQuality( 4 ); // 4 is the max quality (range is 0-4).
graph.addEdge( "AB", "A", "B" );
graph.addEdge( "BC", "B", "C" );
graph.addEdge( "CA", "C", "A" );
add( (JComponent)viewer.getComponent(), BorderLayout.CENTER );
add( new JButton( "Click Me !" ), BorderLayout.SOUTH );
setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
pack();
setVisible( true );
}
}
This powerful example shows how to integrate the viewer's panel inside the GUI using the getComponent() method. It also adds a powerful button at the bottom of the window that does nothing (but, hey ! we are now able to add a button).
The getComponent() method returns an Object. This behaviour allows the GraphViewer interface to be ignorant of the graphical toolkit used (this is why we use a cast to JComponent). Here we chosen to use Swing, but other implementations may choose to use another GUI toolkit (A Qt version is in the way, for example).
The arguments we given to our SwingGraphViewer are the graph to listen at, and a boolean value that tells the viewer "create only a panel, do not create a window".
But what if my program runs in the Swing thread ?
If you are sure all of the code that modifies de graph runs in the Swing thread, it would be a pity to use the inter-thread communication artillery. Furthermore, this could lead to very subtle wrong behaviors due to the buffering introduced by the communication mechanism used.
It is therefore possible to ask the Swing graph viewer to start in ``direct-mode''. This means that the graph viewer will not verify any inter-thread communication, will not use any synchronize and will send all its events in the Swing thread.
This make possible using a special form of the SwingGraphViewer constructor : SwingGraphViewer(Graph g,boolean autoLayout,boolean isPanel,boolean isDirect).
The last argument requests a "direct-mode" graph viewer. This mode should be faster and use less memory, but has the drawback of forcing you to put all your code in the Swing thread (or ensure correct threading, which may become tricky).
The SwingGraphViewer share a large subset of methods with the GraphViewerRemote, this allows to set the quality or take screen shots for example. Note however some of the objects, although they share the same interface, are not implemented the same. So be careful not to call any method of SwingGraphViewer if you are not in direct mode.
Some remarks on what have been done
You may ask why does the display() method returns a GraphViewerRemote and not directly an instance of GraphViewer since one is created anyway ? Because this would imply that almost all methods of GraphViewer be synchronised. This would be quite slow and somewhat tedious. The remote allows minimal synchronisation and clearly establish the fact the viewer runs in another thread. Therefore this solution is faster, and maybe also cleaner.
We tell you that the viewer run in the Swing thread, however in the code just above, our graph is created and modified in the "main" default thread. However we passed a reference of the graph to the graph viewer that may use it in its own thread. There is no problem with that because the viewer will only use the graph reference once to register a special listener in it (and it will do it in the GraphViewer reference that still run in the main thread). This listener will then automatically take care of the different threads.