Jun 19, 2008

Graphviz: Dabbling With Groovy Builders

Ok, so I'm up late and fancy playing around with Groovy builders. How about a builder for graphviz dot files I think to myself? So I took the dot example (image) and made it builder friendly. To do so I changed:

digraph unix {
size="6,6";
node [color=lightblue2, style=filled];
"5th Edition" -> "6th Edition";
...
To:
new DotBuilder().dot {
size "6,6";
node( [color:'lightblue2', style:'filled'] );
from "5th Edition"; to "6th Edition";
...
This code then exercises the required builder and displays a dot graph image in a swing frame (using the swing builder):
import groovy.swing.SwingBuilder
import java.awt.BorderLayout

class DotBuilderExample {

static void main(args) {
def image = new DotBuilder().dot {
size "6,6";
node( [color:'lightblue2', style:'filled'] );
from "Got Java"; to "Got Groovy"; to "Got DotBuilder";
from "Got Graphviz"; to "Got DotBuilder";
}.image

def frame = new SwingBuilder().frame(title:'Example Dot', size:[300,300]) {
borderLayout()
label(icon:imageIcon(image))
}
frame.pack()
frame.show()
}
}
The code for the builder is as follows:
import groovy.util.BuilderSupport
import javax.imageio.ImageIO

class DotBuilder extends BuilderSupport {
private def out
private def outTarget
private def from, to

DotBuilder() {
outTarget = new StringWriter()
out = new PrintWriter(outTarget)
}

protected void setParent(Object parent, Object child) { }

protected Object createNode(Object name) {
if (name == 'dot') return this
}

protected Object createNode(Object name, Object value) {
if (name == 'from') {
from = value
} else if (name == 'to') {
out.println """"$from" -> "$value";"""
from = value // lets us chain them
} else {
out.println """$name="$value";"""
}
}

protected Object createNode(Object name, Map attributes) {
out.println """$name [${attributes.collect { "$it.key=$it.value" }.join(", ")}];"""
}

protected Object createNode(Object name, Map attributes, Object value) { }

protected void nodeCompleted(Object parent, Object node) { }

def getImage() {
Process p = Runtime.getRuntime().exec("dot -Tgif")
p.outputStream.withStream { stream ->
stream << "digraph unix { $outTarget }"
}
p.waitFor()
ImageIO.read(p.inputStream)
}
}
Note: There are in fact closing triple quotes on the long line above.

6 comments:

Andres Almiray said...

That looks great! have you considered using FactoryBuilderSupport instead? that way you may support more nodes in a similar fashion as SwingBuilder does. I'd love to have some level of integration between DOTBuilder and GraphicsBuilder ;-)

And who knows maybe even integrate it with the upcoming Griffon's CompositeBuilder =)

Cheers,
Andres

Merlyn Albery-Speyer said...

It's nice to blog about a topic and have you find me. I didn't know about GraphicsBuilder. It looks powerful. Groovy extensions though? JAI for instance is great, but I wouldn't typically use it just because it needs the extra step of tweaking the deployment environment in order to get it to work.

I'm looking at FactoryBuilderSupport right now. Thanks for that.

The graphviz structure isn't really the best example for a groovy builder because it's much more list data than tree data. That and my code was really a dirty hack to get the syntax accepted.

Could you point me at this CompositeBuilder? A quick search only gets me obscure commit comments in FishEye like "changes to support CompositeBuilder/UberBuilder".

Merlyn Albery-Speyer said...

Ah. Ok. Griffon isn't a person's surname. More interesting material. Found the UberBuilder. Thanks.

I've been wanting to pick and choose parts of Grails, and if Griffon's doing this it could be the start a of rewarding trend.

Andres Almiray said...

Yes, Griffon is the first piece of the Swing/Grails puzzle. CompositeBuilder will be able to mix&match any builder based on FBS.

GraphicsBuilder relies on Java2D only, no need to meddle with JAI

Anonymous said...

Thansk about your DotBuilber!
One thing I could add to your
code, when running under windows:

def getImage() {
Process p = Runtime.getRuntime().exec("dot -Tgif")
p.outputStream.withStream { stream ->
stream << "digraph unix { $outTarget }"
}
def os = System.getenv("OS")
if (os == null && os.indexOf("Windows") != -1)
p.waitFor()
ImageIO.read(p.inputStream)
}

Tuomas

Curious Attempt Bunny said...

Hi Tuomas,

This code looks odd as the effect is to never call waitFor:

if (os == null && os.indexOf("Windows") != -1) p.waitFor()

Perhaps calling waitFor is redundant? I'll get around to trying the code on a Windows system to see what the problem with it is.

Thanks,
Merlyn