Understanding the JUNG Visualization System

Danyel Fisher (edited by Joshua O'Madadhain)

JUNG 1.7.0 features a largely-revamped visualization system, thanks to a lot of work from Tom Nelson. While the JUNG visualization system is much more flexible than it once was, it also has a couple of tricks and a more sophisticated model.

I'll go through the major features of the JUNG 1.7.0 visualization system; when applicable, I'll refer to the demonstration code that shows how this works.

Let's walk though what's going on.

SHORT VERSION:

The correct and simplest way to create a visualization of a graph is
 
Graph g ...
Layout l = new FRLayout( g );
Renderer r = new PluggableRenderer();
VisualizationViewer vv = new VisualizationViewer( layout, renderer );
JFrame jf = new JFrame();
jf.getContentPane().add ( vv );

BASIC CONCEPTS

As with past JUNG systems, there is a distinction between the graph, the layout, and the renderer.

As of version 1.7, we now are moving more toward a model-view-controller model. The model is responsible for knowing what to display--that is, the Graph and the Layout; the view/controller is responsible for knowing how to display it.

  1. A VisualizationModel takes control of the layout process. It controls a thread that allows animated layouts to move forward; it contacts the View when its state changes. It controls the current layout (which would, in turn, have a reference to a relevant Graph.). The generic (and so far only) implementation is the DefaultVisualizationModel.
  2. A VisualizationViewer extends a JPanel, and thus is the thing that does the painting. It forms the center of the Visualization complex, and thus has a number of different tasks:
    1. It tracks the Model
    2. It tracks the Renderer, including the PreRenderers (which paint under the topology) and the PostRenderers (which paint over the topology)
    3. Handles ToolTipFunctions, PickSupports, and GraphMouse, if any.
    4. If appropriate, it maintains an offscreen buffer.
    5. Applies Transformers, if any, to either the View or the Layout: the ViewTransformer and the LayoutTransformer.

    At initialization time:

    1. A renderer and a layout must be created and supplied to the VisualizationViewer
    2. A size is chosen (default: 600x600)
    3. A model is built (default: DefaultVisualizationModel based on the layout and the size). See MultiViewDemo, among others, to see examples where the same Model might be shared by several VisualizationViewers

IMPORTANT CHANGES FROM 1.6

ADDED:

DEPRECATED:

DELETED:


RENDERING: Painting images, writing unicode, and curving lines

The core rendering code is the PluggableRenderer. While it's possible to write your own Renderer, the PluggableRenderer alone has a tremendous amount of flexibility; it's also possible that just inheriting from the PluggableRenderer will cover you.

PluggableRenderer's behavior can be changed by supplying functions which supply PluggableRenderer with information on the properties to use for each vertex and edge that PluggableRenderer is asked to draw. These properties include (but are not limited to):

Changing the functions that PluggableRenderer uses is very simple:

PluggableRenderer pr = new PluggableRenderer();
VertexStringer vs = ... 
pr.setVertexStringer(vs); 
pr.setEdgeShapeFunction(new EdgeShape.QuadraticCurve());

The PluggableRendererDemo shows how to use these various functions to customize your visualization. Also see the PluggableRenderer Javadoc for more information.


MOUSE INTERACTION and PICKING

JUNG now allows a wide variety of behaviors with the mouse. In particular, a mouse can be used to indicate or control any part of the graph, including clicking on both edges and vertices. Various among the demos show how to use the mouse to select (known in JUNG as "Pick") vertices and edges and choose which mouse event occurs when a click occurs.


TRANSFORMATIONS: TRANSLATION, SCALING, ROTATION, DISTORTION

In JUNG 1.6.0, we had begun to experiment with the idea that a view might be panned or zoomed. Fortunately, Java 2 makes this fairly easy, and so we've greatly expanded coverage. JUNG now supports several types of transformations, including translation (panning), scaling (zooming), rotating, shearing, and even nonlinear transformations such as hyperbolic projections.

JUNG, as of version 1.7, now offers two different types of scaling:

These scaling methods are combined by the CrossoverScalingGraphMousePlugin, which has a crossover parameter that specifies a boundary (by default, set to 1.0, that is, no scaling). It works as follows:

This "crossover scaling" is used in several demos, including GraphZoomScrollPaneDemo. This and other types of transformations can be seen in SatelliteViewDemo (scaling, panning, shearing, rotating) and HyperbolicLensDemo (hyperbolic projection).

Instead of using a JScrollPane...

JScrollPane will not give expected behavior as a container for the VisualizationViewer. Instead, use the GraphZoomScrollPane, a custom container that both responds to, and controls transformations of the VisualizationViewer; it provides vertical and horizontal scrollbars when the entire graph does not fit in the window. It listens for changes to the VisualizationViewer (zooming, panning) and adjusts the scrollbars' size and position accordingly.

Specifying Transformations

If you want to specify the transformers to use directly (as opposed to through a GraphMousePlugin), call vv.setLayoutTransformer() or vv.setViewTransformer() with an appropriate implementation of Transformer. In general, a Transformer is responsible for mapping points in one coordinate system to points in another. Because this interface is fairly generic, it even supports non-linear transformations. Check out the hyperbolic view defined in HyperbolicTransformer, which supports hyperbolic transformations over an affine 'delegate'.

Multiple Views of a Graph

The SatelliteVisualizationViewer is intended to provide an overview of the graph. It links to a VisualizationViewer and can share that VV's layout and use a similar renderer. This allows the system to display a small copy of the same graph for navigation; see SatelliteViewDemo for an example of how that works.


PRE-RENDER and POST-RENDER

Sometimes, you want to put text over top of a graph, or lay something out underneath it. The VisualizationViewer interface Paintable allows you to define a surface to be painted; UseTransform says whether it should zoom with the graph or should stay constant. Demonstrations for this include:


SUBLAYOUTS

While Layouts cover an entire graph, a SubLayout picks locations for a subset of vertices and lays them out in a tight grouping that serves as a visual proxy for all the vertices.

While traditionally a Layout can store its data in a variety of places, and uses a whole graph to decorate, the SubLayoutDecorator decorates a Layout with the knowledge that it may have to store a SubLayout. The design for this mechanism is still in progress.

Check out SubLayoutDemo to manually select vertices, or ClusteringDemo to see how automatic sublayouts may be chosen as appropriate.


WHEN THE GRAPH CHANGES

ChangeEventSupport and ChangeListener are now standard mechanisms for recognizing when either the underlying graph or the underlying model changes: that is, when a vertex gets moved or the graph changes. Both of these should trigger ChangeEvents which should in turn, trigger transformations and repaints as appropriate.


CAPTURING THE MOMENT: DUMPING TO PNG, JPG, or EPS

For JUNG 1.5 and before, there was no problem simply rendering the screen to a Buffer, and then saving that as a PNG or a JPG. As of JUNG 1.6, we added offscreen buffers that partially accelerated drawing--but didn't support this output modality. (And it wrecked the ability to swap in a new Graphics object).

JUNG 1.7 allows the best of both worlds. Double-buffering is off by default, but can be turned on for to increase performance. When the graph looks like you want, turn off double-buffering and then call a PNG, JPG, or EPS dumper. A GPL EPS dumper can be found at http://jibble.org/epsgraphics/; sample JPG and PNG dumping code is below.

 
// use double buffering until now
 
// turn it off to capture
visualizationViewer.setDoubleBuffered( false )
 
// capture: create a BufferedImage
// create the Graphics2D object that paints to it
visualizationViewer.paintComponent( replaced_graphics2D )
// and save out the BufferedImage
ImageIO.write(bi, "jpg", new file( ... ));
 
// turn double buffering back on
visualizationViewer.setDoubleBuffered( true )