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.
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 );
Graph
knows what Vertexes are linked to each other by what Edges,
and is implemented by a Graph
class. The basic Graph
class is SparseGraph
.
A Layout
specifies where vertices are to be drawn in a visualization;
it may also embody some calculation. (At the present time, edges' positions are determined by the positions
of their endpoint vertices.)
A Renderer
is responsible for drawing vertices and edges (and thus must know how they
are supposed to look). Some Renderer
s as well as any vertex/edge labels, if applicable.
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.
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
.
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:
Renderer
, including the PreRenderers (which paint under the topology) and the PostRenderers (which paint over the topology)
Handles ToolTipFunction
s, PickSupport
s, and GraphMouse
, if any.
If appropriate, it maintains an offscreen buffer.
Applies Transformer
s, if any, to either the View or the Layout: the ViewTransformer and the LayoutTransformer.
At initialization time:
VisualizationViewer
A size is chosen (default: 600x600)
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 VisualizationViewer
s
VisualizationModel
(see above)
VisualizationViewer
has a notion of both View Scale and Layout Scale (see below)
Improvements to GraphMouse
, PickSupport
, and ToolTip
(see below)
GraphDraw
. GraphDraw
was a convenience technique for creating a
JPanel
that contained a graph. As we've refactored and cleaned up, we've realized that
it was causing more confusion than needed. Most of your old code should work just fine by manually creating a VisualizationViewer
.
Layout.filter()
. As we've improved the dynamic graph code, the idea that the layout
should be responsible for maintaining "filters" has gotten increasingly difficult to sustain.
In addition, support was uneven. It is deprecated now, and should be removed by version 1.8.
NewGraphDraw
was an experimental system to learn more about visualization.
Most of the features that were available through NewGraphDraw
are now available in the new JUNG visualization system.
FadingVertexLayout
has been deprecated since version 1.3, and has gotten no emails. Time to let it go.
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):
Shape
: {Edge, Vertex}ShapeFunction
Stroke
: {Edge, Vertex}StrokeFunction
vertex/edge Paint
(includes Color
): {Edge, Vertex}PaintFunction
Paint
each specified separately; these
can be solid colors, gradients, ...
vertex/edge String
label: {Edge, Vertex}Stringer
vertex/edge label Font
: {Edge, Vertex}FontFunction
vertex/edge inclusion: Predicate
Predicate
will be drawn
edge label positioning: NumberEdgeValue
EdgeArrowFunction
edge arrow inclusion: Predicate
Icon
(VertexIconFunction
)
UnicodeLabelDemo
and VertexImageShaperDemo
for examples.
Non-rectangular image shaping
FourPassImageShaper
to extract tne non-rectangular shape from each image. This
effect is demonstrated in the VertexImageShaperDemo
where you can see that the
edge arrow placement and the shape pick range both conform to the non-rectangular opaque part of the images.
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.
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.
PickSupport
A VisualizationViewer
may be associated with a PickSupport
object
(this association is created via the call vv.setPickSupport( ... )
).
A PickSupport
returns a Vertex
, or Edge
, based on
a specified (x,y) location in a Layout
's coordinate space. (Some of these methods are
inherited from GraphElementAccessor
.) Generally (but not necessarily), these locations are generated
by mouse events.
Implementations include those that return...
Vertex
(ClassicPickSupport
, the default)
...the nearest object closer than a certain distance (RadiusPickSupport
)
...the object (if any) whose defining Shape
contains the specified point (ShapePickSupport
)
...there is even a mode that automatically centers the picked vertex with an animated transform when you CTRL-click it.
GraphMouse
The real truth of picking comes with a GraphMouse
. This class catches mouse events,
uses the PickSupport
to change them into an event on the graph, and then sends them onward.
The GraphMouse
is responsible for adjusting the (x,y) location associated with the
mouse event into the appropriate location in graph coordinates.
A GraphMouse
replaces a MouseListener
, MouseMotionListener
,
and MouseWheelListener
.
The default GraphMouse
calls pick on the current PickedState
object for clicks.
There are, however, GraphMouse
implementations designed for a variety of tasks:
PluggableGraphMouse
allows a user to select the behavior of the mouse
by linking sets of modifiers to plugins. See documentation associated with PluggableGraphMouse
.
DefaultModalGraphMouse
creates a menu that lets the user choose between modes: pick
(click to select; shift-click to multi-select) and transform
(drag, shift-drag, and control-drag to pan, rotate, and shear; scroll wheel to zoom).
SatelliteViewDemo
and, in particular, its help section.
vv.SetGraphMouse(gm)
replaces all GraphMouse
instances currently in use
with the specified GraphMouse
instance gm
.
PickedState
, PickEventListener
PickedState
classes store the "picked" status of vertices and edges.
The details of their implementation define policies such as the number of elements that may be
simultaneously selected. PickedState
instances also allow PickEventListener
instances to be registered with them; when elements are picked or unpicked, the PickedState
notifies any registered PickEventListener
of the specific event (picked or unpicked) and
the affected element (vertex or edge).
MultiPickedState
, the default implementation of PickedState
,
allows the system to maintain a set of picked points.
Historically, a Layout
would maintain mouse information, which is why Layout
s
must implement PickEventListener
; however, this requirement is likely to be removed in future versions.
ToolTipFunction
A ToolTipFunction specifies the text that is shown when mousing over a vertex or edge.
Several of these functions are made simpler by extending ToolTipFunctionAdapter
and overriding methods as they seem appropriate.
ZoomDemo
demonstrates the tooltip functionality. The tooltip function is set thus:
vv.setToolTipFunction( ... )
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:
ViewScalingGraphMousePlugin
.
model scaling: scaling the coordinate system used by the Layout
.
This is analogous to stretching or compressing the surface on which the network is drawn:
as the scale factor is increased, vertices get farther apart and edges get longer, but they
remain the same size. See ScalingGraphMousePlugin
.
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:
crossover
, then layout scaling is used:
the vertices stay the same size (that is, if crossover == 1.0
, they are not scaled) and
become farther apart.
If the scale factor is less than crossover
, then view scaling is used:
vertices become smaller and closer together.
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).
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.
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'.
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.
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:
GraphZoomScrollPaneDemo
uses it to paint the texture on the background
and the banner label on the foreground
SatelliteViewDemo
uses it to paint the rectangular lens and the optional grid.
While Layout
s 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.
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.
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 )