Quantcast
Channel: How-to – Lean Java Engineering
Viewing all articles
Browse latest Browse all 16

Free your mind (map) with Groovy

$
0
0

Freemind is an Open Source mind mapping tool. Version 0.9 (released Feb 2011) introduced Groovy scripting that can be used for processing the mind map nodes. In this article we’ll look at a couple of use cases and the supporting scripts.

This article originally appeared in the February 2013 issue of GroovyMag

Mind mapping

I’m a long-time user of mind mapping tools and have been using Freemind since 2005. I have even been known to work on mind maps on my phone whilst standing on a London Underground tube train.

One of the things I use mind maps for are brainstorming on projects – e.g. breaking down user stories into tasks as shown in Figure 1. If those tasks get given sizes, then it is useful to have an idea of how many small, medium or large tasks there are, or if you have estimates then you’ll probably want to sum them.

Project backlog in a mind map

Figure 1: Project backlog in a mind map

The full code listings and example mind maps (Freemind 0.9+ required) to accompany this article are available from the following GitHub repository: https://github.com/rbramley/GroovyMagFreemind

Basics

Script Editor

Freemind has a simple script editor (Figure 2) that can be accessed via the Tools > Script Editor menu option. It operates on the currently selected node.

Figure 2: The Freemind Script Editor

Figure 2: The Freemind Script Editor

Script evaluation

A script can be run from the script editor or all scripts can be evaluated by using the Tools > Evaluate menu option (Alt + F8 or ⌥ + F8 on OSX).

Attributes

These are assigned using the Tools > Assign Attributes… menu option shown in Figure 3 – you would typically want to select a node first! Once you have assigned an attribute, its name and value can be edited on the node without the need to launch the Attribute Manager.

Figure 3: Attribute assignment

Figure 3: Attribute assignment

Tips & Warnings

  1. Keep a copy of your script in a separate text editor / file as Freemind has a bad habit of flattening scripts to a single line. Which means that your scripts won’t evaluate as intended if you use line-terminated (//) comments. It’s quicker and easier to paste back in if necessary than to insert the line breaks.
  2. For scripting Freemind, it’s useful to access to the Javadoc – but be warned that currently the online Javadoc is out of date.

Recursive tree walking

Freemind uses a tree-structure, so we’ll create a simple recursive depth-first (see http://en.wikipedia.org/wiki/Depth-first_search) tree walk first and then look at how we can process the nodes that we traverse. Figure 4 shows the order that nodes are visited when a tree is traversed depth-first.

Figure 4: A depth-first search

Figure 4: A depth-first search

The method in Listing 1 checks whether a node has children, if it does then it recurses into the first child; if a node doesn’t have children (i.e. it is a leaf node) then the node is processed. Once leaf nodes have been processed, the algorithm returns back up the call stack where it will visit the next sibling (or return higher up the tree).

// recurse and process leaf nodes
def walkNodeChildren(node, map) {
    if (node.hasChildren()) {
        def children = node.childrenUnfolded()
        children.each { child ->
            walkNodeChildren(child, map)
        }
    } else {
        processNode(node, map)
    }
}

Listing 1: Tree walking

Example 1 – counting

In this example we’ll be counting Small, Medium and Large tasks using Foo.mm as shown in Figure 1.
For simplicity, this example uses child leaf nodes labelled with the task size and updates the node text with the outputs of the items.

Freemind 0.9 also added capability for custom name-value pair attributes per node, we’ll exploit these in the next example.

Processing nodes

Note that the map in Listing 1 contains the working variables and is propagated as there isn’t a global scope for variables within the script (e.g. def counterMap = [small:0, medium:0, large:0]).

In Listing 2 we perform some simple equality tests (feel free to use switch-case if you prefer, or case-insensitive or regex matching as required) and then increment the appropriate counter in the map.

// increment counters
def assessNode(node, map) {
    if(node.toString() == 'Small') { map.small++ }
    else if (node.toString() == 'Medium') { map.medium++ }
    else if (node.toString() == 'Large') { map.large++ }
}

Listing 2: Node value processing

Creating the output

As we’re updating the node text with our output, we need to replace task sizes if they already exist. This example uses a new-line as the delimiter – so would need adapting if you use long nodes to not remove subsequent lines.

As Freemind is a Swing application, we need to inform the controller that we have updated the node – this is achieved by sending a repaint instruction using c.nodeChanged(node) to get the result shown in Figure 5.

Figure 5: The resulting output after script evaluation

Figure 5: The resulting output after script evaluation

Example 2 – summing attributes

For this example we have two sets of estimates to sum: an optimistic 50% figure and a more conservative 90% likelihood figure.

As shown in Figure 6, each leaf node has 2 estimate attributes, “50” and “90”. These need to be summed up to total attributes on the ancestor node from which we run the script.

Figure 6: Attributes in a mind map

Figure 6: Attributes in a mind map

Working with attributes

The attributes use a specialised Swing TableModel implementation. Without some meta-programming it’s a bit of a clumsy API to work with if you’re accustomed to the luxury of Groovy collections handling.

This time in Listing 3 we have a variant of Listing 2 to handle iterating through the rows of the table and extracting the keys and values (as integers), then adding the sum of the value to the working map data structure.

def assessNode(node, map) {
    // get the table model
    def atts = node.getAttributes()

    def c = atts.rowCount
    for (i=0; i<c; i++) {
        def rowName = atts.getName(i)
        def rowValue = Integer.parseInt(atts.getValue(i))
        map[rowName] += rowValue
    }
}
Listing 3: Node attribute processing

This time we’ll output the sum of the individual estimates into attributes of the node that contains the script using the code in Listing 4 to get the resulting output on the User Stories node in Figure 7. Note that there is an order dependency in Listing 4 for simplicity – ideally this code should check the row index contains the expected attribute name (in this case ‘total_50′ and ‘total_90′ – which were also created before the script was added to the node).

node.attributes.setValue(0,counterMap['50'])
node.attributes.setValue(1,counterMap['90'])

Listing 4: Setting attribute value

Figure 7: Aggregated 50/90 attributes

Figure 7: Aggregated 50/90 attributes

Summary

In this article we’ve seen how to process nodes using a depth-first search and handle both node values and node attribute values. Hopefully this has inspired you to make greater use of Freemind!

More example scripts are available at: http://freemind.sourceforge.net/wiki/index.php/Example_scripts

Credits

Figure 4 is a Creative Commons licensed file created by Alexander Drichel, obtained from http://en.wikipedia.org/wiki/File:Depth-first-tree.svg



Viewing all articles
Browse latest Browse all 16

Trending Articles