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.