information visualization toolkit

introduction > example application

In this section we'll deconstruct an example of a prefuse application: a simple social network visualization. Our visualization will provide an animated force-directed layout of a social network, with nodes colored according to gender and showing the names of people they represent. All code presented below is assumed to be within the main method of an application class. The full code for this example is available as Example.java.


Example Application: A Simple Social Network Visualization

1. loading data

The first step is to load the graph data into a prefuse Graph instance. First take a look at the content of the file: socialnet.xml (the file is also available in the data directory of the prefuse distribution). Note that the nodes have two data attributes: a name field and a gender field. The following code shows how to load the GraphML-formatted socialnet.xml file into a prefuse Graph data structure using the GraphMLReader class (an instance of the more general GraphReader class).

Graph graph = null;
try {
    graph = new GraphMLReader().readGraph("/socialnet.xml");
} catch ( DataIOException e ) {
    e.printStackTrace();
    System.err.println("Error loading graph. Exiting...");
    System.exit(1);
}

The code attempts to load the graph, and if an error occurs, exits with an error code (any exit value not equal to zero signals an error). More should be said about the input to the readGraph method. Why does "/socialnet.xml" work? This is because the data reader tries to resolve input strings in multiple ways. First, it checks to see if the string is a URL (e.g., if it starts with http://, ftp://, or file://) and if so, opens a URL connection. If not, it checks to see if the string points to a resource currently on the Java runtime's classpath. Since the data folder of the prefuse distribution should be included on the classpath, "/socialnet.xml" should load without incident -- it is located in the root of the classpath. Finally, if the string does not successfully resolve to a resource on the classpath, it is treated as the path to a file on the file system.

If you are having trouble loading the file, either make sure the prefuse data directory is on your classpath, or, alternatively, change the file location. For example, http://prefuse.org/doc/manual/intro/socialnet.xml should do the trick. In any case, the end result should be that the contents of the socialnet.xml file are now loaded into graph.

2. the visualization

Now we need to create a visual abstraction of the graph. To do this, we first create a new Visualization instance and add the graph data to it. We register the loaded graph with the data group name "graph". This data group name will be used to reference the visual data later. When Graph or Tree instances are added to a visualization, two other subgroups are automatically registered: one for the nodes (with a ".nodes" suffix) and one for edges (with a ".edges" suffix). We will see these subgroups used later in the example.

// add the graph to the visualization as the data group "graph"
// nodes and edges are accessible as "graph.nodes" and "graph.edges"
Visualization vis = new Visualization();
vis.add("graph", graph);

3. the renderers and renderer factory

The next step is to set up the Renderers used to draw the VisualItems now contained in the Visualiaztion. By default, the Visualization contains an instance of a DefaultRendererFactory that uses an EdgeRenderer (which draws straight-line edges by default) for any EdgeItems encountered and a ShapeRenderer (which draws items as basic shapes such as squares and triangles) for all other items. Since we want to see text labels on the nodes, we instead create a new LabelRenderer and tell it to use the value of the "name" data field for the labels. We also tell the renderer to use rounded edges for the label shape, giving a smoother look. We then create a new DefaultRendererFactory that uses the new label renderer as the default renderer for all non-edge items (all EdgeItems will use the aforementioned default EdgeRenderer). DefaultRendererFactory has a lot more functionality that we are not currently using -- we could also set the default edge renderer and add any number of additional rules governing Renderer assignment (see the API documentation for more).

// draw the "name" label for NodeItems
LabelRenderer r = new LabelRenderer("name");
r.setRoundedCorner(8, 8); // round the corners

// create a new default renderer factory
// return our name label renderer as the default for all non-EdgeItems
// includes straight line edges for EdgeItems by default
vis.setRendererFactory(new DefaultRendererFactory(r));

4. the processing actions

We are now ready to set up the visual encodings. These are provided by creating Action modules that process the VisualItems in the Visualization. We start by creating a set of ColorActions. Each VisualItem supports three color values by default: the stroke, fill, and text colors. The stroke color is the color for lines and outlines, the fill color is for the internal color of the item, and the text color is the color for any text strings or labels. The default value for all colors is pure transparency, and so by default nothing is drawn. In prefuse, each color value is encoded as single integer, representing RGBA (red-green-blue-alpha) values, each ranging from 0 to 255. Alpha represents transparency, with 0 being fully transparent and 255 being fully opaque. The ColorLib class provides a number of useful methods for creating color values.

To assign colors based on the gender of people in the social network, we first create a custom color palette. This is just an array of the allowed color values. In this case, we create an array with pink for females and baby blue for males. We then create a DataColorAction that computes the color assignment. The DataColorAction constructor takes as arguments

  • The name of the data group to process (in this case graph.nodes)
  • The name of the data field on which to base the encoding (in this case gender)
  • The data type of the field, one of NOMINAL (for sets of category labels), ORDINAL (for an ordered list), or NUMERICAL (for numbers)
  • The color field to set, typically one of the stroke, fill, or text color fields.
  • An optional color palette (if no palette is supplied, a default one will be created).
One note about the ordering: The DataColorAction will order the values of both NOMINAL and ORDINAL data by their natural sort-order, which for text strings is alphabetical order. This is why the pink color comes first in the color palette, because the 'F' for female precedes the 'M' for male in the sort order.

Similarly, we assign the colors for the node text to black and the stroke color for edges to a light gray using the ColorAction component. ColorActions can be used to set a default color value for all items, but also can support any number of additional rules for more complicated color assignment.

Finally, we create an ActionList instance that groups all the color assignment actions into a single executable unit.

// create our nominal color palette
// pink for females, baby blue for males
int[] palette = new int[] {
    ColorLib.rgb(255,180,180), ColorLib.rgb(190,190,255)
};
// map nominal data values to colors using our provided palette
DataColorAction fill = new DataColorAction("graph.nodes", "gender",
    Constants.NOMINAL, VisualItem.FILLCOLOR, palette);
// use black for node text
ColorAction text = new ColorAction("graph.nodes",
    VisualItem.TEXTCOLOR, ColorLib.gray(0));
// use light grey for edges
ColorAction edges = new ColorAction("graph.edges",
    VisualItem.STROKECOLOR, ColorLib.gray(200));
	
// create an action list containing all color assignments
ActionList color = new ActionList();
color.add(fill);
color.add(text);
color.add(edges);

Next, we create a separate ActionList providing an animated layout. All Action instances can either be parameterized to run once (the default), or to run repeatedly within a given time duration. By providing the parameter INFINITY as the duration value, we are telling the ActionList to run indefinitely, causing a continuous update of the layout. We then add a ForceDirectedLayout to assign the spatial positions of the elements of the graph. We also add a RepaintAction to signal that any Displays should be repainted after the layout has been recomputed.

// create an action list with an animated layout
// the INFINITY parameter tells the action list to run indefinitely
ActionList layout = new ActionList(Activity.INFINITY);
layout.add(new ForceDirectedLayout("graph"));
layout.add(new RepaintAction());

We now add our two ActionLists with the Visualization. This causes the Actions to be properly registered with the Visualization such that they will have access to the Visualization when later invoked. Each registered Action is also given a unique name for reference.

// add the actions to the visualization
vis.putAction("color", color);
vis.putAction("layout", layout);

5. the display and interactive controls

We still need to create a Display for the visualized data. Here we create a new Display instance configured to pull all VisualItems from the Visualization (you can also optionally supply a Predicate controlling which items the Display will consider). We also set the desired size, in pixels, of the Display. We then add three interactive controls to the Display:

  • A DragControl for dragging VisualItems around with a left-click mouse drag
  • A PanControl for moving the Display region with a left-click mouse drag on the Display background
  • A ZoomControl for zooming the display in or out with a vertical right-click mouse drag
We use the default settings of the Controls. The mouse button used to trigger the control and other settings can be changed by using alternative constructors.

// create a new Display that pull from our Visualization
Display display = new Display(vis);
display.setSize(720, 500); // set display size
display.addControlListener(new DragControl()); // drag items around
display.addControlListener(new PanControl());  // pan with background left-drag
display.addControlListener(new ZoomControl()); // zoom with vertical right-drag

6. launching the visualization

We're almost done! All that's left to do is to add the Display to a new application window and set things running. We create a new JFrame instance (the top-level window class in the Java Swing user interface toolkit), set it's title, and make sure the application exits when the window is closed. We then add our Display, "pack" the window (making sure the components--in this case just our Display--are laid out properly), and make the window visible. Lastly, we run the color assignment action list, making sure each item has the proper colors assigned, and then set the continuously-running layout list running.

// create a new window to hold the visualization
JFrame frame = new JFrame("prefuse example");
// ensure application exits when window is closed
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(display);
frame.pack();           // layout components in window
frame.setVisible(true); // show the window

vis.run("color");  // assign the colors
vis.run("layout"); // start up the animated layout

wrap-up

You can download this example in it's entirety as the file Example.java. After you get the example running, try replacing some of the components. For example, you could change the layout ActionList to only run once by removing the INFINITY parameter and then try any of the other graph layouts included with the toolkit.

In addition, there are plenty of other example applications included in the demos folder of the prefuse toolkit distribution. If you are comfortable with the example presented above, you are ready to start exploring the included demos.

post a comment





You may use HTML tags for style. Please enclose source code within <pre class="codebox">...</pre> tags.

If you haven't left a comment here before, you will need to be approved before your comment will appear. Until then, it won't appear on the entry. Thanks for waiting.