Thursday, October 11, 2007

Creating a Groovy Builder for Graphs using Prefuse

Groovy's BuilderSupport provides the capability to create a convenient syntax for building different types of structures. Some of the most commonly used examples may be for building XML documents or Swing user interfaces. In this post I am going to create a simple builder that creates a hierarchy of nodes that will be displayed visually using the Prefuse visualization library in a swing application.

A good approach to defining a convenient syntax is to start with a test driven approach and start coding a script that will use the builder. This example is only going to define the simplest of graphs with prefuse. Prefuse is a very powerful visualization library and there is so much more that could be added.

Below is the groovy code for working with the PrefuseBuilder:
  // create the prefuse graph
def prefuse = new PrefuseBuilder()
def graph = prefuse.graph {
node("Grand Parent") {
node("Parent") {
node("Child 1")
node("Child 2")
}
}
}
// create a swing app using the Groovy SwingBuilder to display the graph
def swing = new groovy.swing.SwingBuilder()
def frame = swing.frame(
title:'PrefuseBuilder Test', defaultCloseOperation:javax.swing.WindowConstants.EXIT_ON_CLOSE) {
widget(graph)
}
frame.show()


The result of this graph will be the a swing application that looks like the following:



To simplify this example I am going to code the builder in Groovy, but this could also be implemented in a regular java class if preferred.

Below is the completed PrefuseBuilder class:

import groovy.util.BuilderSupport
import prefuse.Constants
import prefuse.Display
import prefuse.Visualization
import prefuse.action.ActionList
import prefuse.action.RepaintAction
import prefuse.action.assignment.ColorAction
import prefuse.action.assignment.DataColorAction
import prefuse.action.layout.graph.ForceDirectedLayout
import prefuse.activity.Activity
import prefuse.controls.DragControl
import prefuse.controls.PanControl
import prefuse.controls.ZoomControl
import prefuse.data.Graph
import prefuse.data.Node
import prefuse.render.DefaultRendererFactory
import prefuse.render.LabelRenderer
import prefuse.util.ColorLib
import prefuse.visual.VisualItem

class PrefuseBuilder extends BuilderSupport {

def graph
def visualization

void setParent(Object parent, Object child) {
if (parent instanceof Node && child instanceof Node) {
graph.addEdge(parent, child)
}
}

Object createNode(Object name) {
return createNode(name, null, null)
}

Object createNode(Object name, Object value) {
return createNode(name, null, value)
}

Object createNode(Object name, Map attributes) {
return createNode(name, attributes, null)
}

Object createNode(Object name, Map attributes, Object value) {
def node = null
if (name == 'node') {
node = graph.addNode()
node.setString("name", value)
visualization.run("color")
}
if (name == 'graph') {
graph = new Graph()
graph.addColumn("name", String.class)
visualization = new Visualization()
visualization.add("graph", graph)
def labelRenderer = new LabelRenderer("name")
labelRenderer.setRoundedCorner(8, 8)
visualization.setRendererFactory(new DefaultRendererFactory(labelRenderer))
def fill = new ColorAction("graph.nodes", VisualItem.FILLCOLOR, ColorLib.rgb(190,190,255))
def text = new ColorAction("graph.nodes", VisualItem.TEXTCOLOR, ColorLib.gray(0))
def edges = new ColorAction("graph.edges", VisualItem.STROKECOLOR, ColorLib.gray(200))
def color = new ActionList()
color.add(fill)
color.add(text)
color.add(edges)
def layout = new ActionList(Activity.INFINITY)
layout.add(new ForceDirectedLayout("graph", true))
layout.add(new RepaintAction())
visualization.putAction("color", color)
visualization.putAction("layout", layout)
def display = new Display(visualization)
display.setSize(720, 500)
display.addControlListener(new DragControl())
display.addControlListener(new PanControl())
display.addControlListener(new ZoomControl())
display.setHighQuality(true)
visualization.run("color")
visualization.run("layout")
node = display
}
return node
}

}


That's all the code required to create this simple PrefuseBuilder. I am pretty new to Prefuse so I am not 100% sure that I am using the library correctly and I pretty much came up with the code for this from reverse engineering the Prefuse demos. The goal of this post was to present how easily builders can be created and to provide an example of the powerful capabilities they provide.

7 comments:

Andres Almiray said...

Great work Craig! would you be willing to contribute it to the Groovy project? there is a groovy-contrib repo where your Builder may find a cozzy home =)

I found Prefuse a year ago (along with Jung) when looking for Graph options in Java, Prefuse seemed to be the best choice at the time.

Keep on Groovying!

Craig MacKay said...

Thanks Andres. I would definitely be willing to contribute this to Groovy. I will polish it up a bit and then move it to the groovy-contrib repo.

Thanks,

Craig

Chris said...

For those of us new to groovy you need these two import statements at the top of your example:
import groovy.swing.SwingBuilder
import javax.swing.WindowConstants
the first one was easy to figure out as I couldn't compile w/o it. The 2nd one was a little confusing as it compiled but when running it this this error message came up:
Caught: groovy.lang.MissingPropertyException: No such property: WindowConstants for class: PrefuseRunner

Thanks for posting this looks really cool

Craig MacKay said...

Good catch, I have updated the example code above to use the fully qualified paths. :)

Thanks Chris

Jim Shingler said...

Craig,

Since you are interest in software like prefuse, I hoping you might be able to help me track down a piece of software used for visualization. I can't remember the name of it.

It generate graphs where the nodes are dragable, . . . kinda like visuwords.com. I thought the name started with an o.

Any help would be appreciated

Craig MacKay said...

Hi Jim,

Actually the nodes in prefuse are also dragable. Prefuse has many capabilities and is definitely worth looking into if you are looking for a good visualization library.

As for the tool you are referring to, I am not familiar with one that begins with an o. Good luck in trying to find it.

Thanks,

-Craig

Florimon said...

@Jim, you might be thinking of JGraph, http://www.jgraph.com/jgraph.html
(doesn't start with an 'o' though :)