TreeBASE visualization using JSON/NeXML and SVG

In follow up to a post that used HTML5 canvas to draw trees from TreeBASE here is an alternative implementation that has the same general layout and logic but uses SVG instead.

First place the SVG element inside the html body:

Then place the JavaScript code that pre-processes the Yahoo! pipe's output, instantiates objects from the NeXML/JSON API, computes node coordinates and draws labels and branches in SVG:

// This variable is used to deal with different XML2JSON mappings. 
// By default, the nexml.js library expects the badgerfish mapping,
// where XML attributes become object properties with an '@' prefix.
// Yahoo! pipes emits a different XML2JSON mapping, without a prefix
// for attributes. We need to configure this here.
var NeXMLAttributePrefix = '';  

// Increments every time we visit a tip in post-order.
// Used to compute Y-coordinates.
var tipcounter = 0;

// Sets the distance between tips.
var verticalDistance = 15;

var svgNS = 'http://www.w3.org/2000/svg';

// Callback that is executed by the output of the Yahoo! pipe.
function processJson(json) {

    // the nexml.js library expects an object with a 
    // field nex$nexml that subtends the document
    var nexml = { 'nex$nexml' : json.value.items[0] };
    var nexmlDoc = new NeXML.Document(nexml); 
    
    // there can be multiple tree blocks...
    var treesList = nexmlDoc.getTreesList()[0];
    
    // ...with multiple trees
    var tree = treesList.getTreeList()[0];
    var root = tree.getRootNode();
    
    // compute the y coordinates and depths for all nodes,
    // once we've computed the root node's depth we can
    // divide the canvas with by that to compute the x
    // coordinates
    computeCoordinates(tree,root);
    
    // svg canvas
    var svg = document.getElementById('MyCanvas');
    svg.style.height = new String( ( tipcounter + 1 ) * verticalDistance ) + "px";
    var width = new String(svg.style.width);
    drawTree(tree,root,svg,root.depth,width.replace("px",""));
}

function drawLine(svg,x1,y1,x2,y2) {
    var lineElt = document.createElementNS(svgNS,'line');
    lineElt.setAttributeNS( null, "x1", new String(x1) + "px" );
    lineElt.setAttributeNS( null, "y1", new String(y1) + "px" );                
    lineElt.setAttributeNS( null, "x2", new String(x2) + "px" );
    lineElt.setAttributeNS( null, "y2", new String(y2) + "px" );
    lineElt.setAttributeNS( null, "style", "stroke:black" );
    svg.appendChild(lineElt);
}

function drawTree(tree,node,svg,maxdepth,width) {
    var children = tree.getChildNodes(node);
    for ( var i = 0; i < children.length; i++ ) {
        drawTree(tree,children[i],svg,maxdepth,width);
    }   
    var parent = tree.getParentNode(node);
    if ( null != parent ) {
        var y1 = parent.y;
        var y2 = node.y;
        
        // 'width' is that of the canvas element. We subtract 200 to
        // leave space for tip labels, then divide the rest by the
        // longest root-to-tip path so we know the length of one 
        // cladogram branch and multiply by how many there are between
        // the focal node and the root.
        var x1 = ( ( width - 250 ) / maxdepth ) * ( maxdepth - parent.depth );
        var x2 = ( ( width - 250 ) / maxdepth ) * ( maxdepth - node.depth );
        
        // Writes the vertical path segment starting at the parent
        drawLine(svg,x1,y1,x1,y2);
        
        // Writes the horizontal path segment from the end of the
        // vertical segment to the child
        drawLine(svg,x1,y2,x2,y2);                  

        if ( children.length == 0 ) {
        
            // Writes the tip label
            var txtElt = document.createElementNS(svgNS,'text');
            var txt = document.createTextNode(node.getLabel());
            txtElt.appendChild(txt);
            txtElt.setAttributeNS( null, "x", new String( x2 + 5 ) + "px" );
            txtElt.setAttributeNS( null, "y", new String( y2 + 5 ) + "px" );
            txtElt.setAttributeNS( null, "style", "font: 10px verdana" );
            svg.appendChild(txtElt);
        }
    }
}

function computeCoordinates(tree,node) {
    var children = tree.getChildNodes(node);
    
    // we do post-order traversal so that we first compute
    // child nodes' coordinates because their parents are
    // relative to them
    for ( var i = 0; i < children.length; i++ ) {
        computeCoordinates(tree,children[i]);
    }
    
    // processing tips is easy: they're just spread apart
    // by verticalDistance, and they're the shallowest ones
    if ( children.length == 0 ) {
        node.y = ++tipcounter * verticalDistance;
        node.depth = 1;
    }
    
    // for internal nodes we take as the y coordinate the
    // average of their immediate children. Of those children
    // we need to know which one is deepest (i.e. farthest 
    // away from the tips) and go one deeper than that
    else {
        var y_sum = 0;
        var max_depth = 0;
        for ( var i = 0; i < children.length; i++ ) {
            y_sum += children[i].y;
            if ( children[i].depth > max_depth ) {
                max_depth = children[i].depth;
            }
        }
        node.y = y_sum / children.length;
        node.depth = max_depth + 1;
    }
}

Then import the NeXML/JSON API:

And finally call the Yahoo! pipe:

  

There should be a result here:

In follow-up to this post:

  1. An extension of the SVG tree drawer that uses RDFa annotations to link to the NCBI taxonomy

1 comment: