|
(function() { |
|
var distance, margin, max_depth, rand_tree, width, zip; |
|
|
|
rand_tree = function(d, MAX_D, MAX_N) { |
|
/* return a tree with maximum depth MAX_D that branches with probability p at most N times for each internal node. p starts from 1 and decreases linearly with d, reaching zero at MAX_D |
|
*/ |
|
/* this still seems to be necessary to avoid infinte recursion (floating point precision?) |
|
*/ |
|
var children, i, n, p; |
|
if (d === MAX_D) { |
|
return { |
|
children: [] |
|
}; |
|
} |
|
p = (MAX_D - d) / MAX_D; |
|
/* if the tree branches, at least one branch is made |
|
*/ |
|
n = Math.floor(Math.random() * MAX_N) + 1; |
|
children = []; |
|
for (i = 0; 0 <= n ? i < n : i > n; 0 <= n ? i++ : i--) { |
|
if (p >= Math.random()) { |
|
children.push(rand_tree(d + 1, MAX_D, MAX_N)); |
|
} else { |
|
children.push({ |
|
children: [] |
|
}); |
|
} |
|
} |
|
return { |
|
children: children |
|
}; |
|
}; |
|
|
|
width = 960; |
|
|
|
distance = 14; |
|
|
|
margin = 40; |
|
|
|
max_depth = 10; |
|
|
|
d3.select(self.frameElement).style('height', "" + (2 * margin) + "px"); |
|
|
|
/* python-like zip |
|
*/ |
|
|
|
zip = function() { |
|
var args, shortest; |
|
args = [].slice.call(arguments); |
|
shortest = args.length === 0 ? [] : args.reduce((function(a, b) { |
|
if (a.length < b.length) { |
|
return a; |
|
} else { |
|
return b; |
|
} |
|
})); |
|
return shortest.map((function(_, i) { |
|
return args.map(function(array) { |
|
return array[i]; |
|
}); |
|
})); |
|
}; |
|
|
|
window.main = function() { |
|
/* create the tree |
|
*/ |
|
var diagonal, height, link, links, node, nodes, root, rsort, tcmp, tree, vis; |
|
root = rand_tree(1, max_depth, 3); |
|
/* sort the tree |
|
*/ |
|
tcmp = function(a, b) { |
|
var ai, bi, ci, _i, _len, _ref, _ref2; |
|
_ref = zip(a.children, b.children); |
|
for (_i = 0, _len = _ref.length; _i < _len; _i++) { |
|
_ref2 = _ref[_i], ai = _ref2[0], bi = _ref2[1]; |
|
ci = tcmp(ai, bi); |
|
if (ci !== 0) return ci; |
|
} |
|
return b.children.length - a.children.length; |
|
}; |
|
rsort = function(t) { |
|
var c, _i, _len, _ref; |
|
_ref = t.children; |
|
for (_i = 0, _len = _ref.length; _i < _len; _i++) { |
|
c = _ref[_i]; |
|
rsort(c); |
|
} |
|
return t.children.sort(tcmp); |
|
}; |
|
rsort(root); |
|
/* initialize the layout |
|
*/ |
|
tree = d3.layout.tree().size([0, 0]); |
|
nodes = tree.nodes(root); |
|
links = tree.links(nodes); |
|
height = 0; |
|
/* force the layout to display nodes in fixed rows and columns |
|
*/ |
|
nodes.forEach(function(n) { |
|
if ((n.parent != null) && n.parent.children[0] !== n) height += distance; |
|
n.x = height; |
|
return n.y = n.depth * (width / max_depth); |
|
}); |
|
/* draw the vis |
|
*/ |
|
diagonal = d3.svg.diagonal().projection(function(d) { |
|
return [d.y, d.x]; |
|
}); |
|
vis = d3.select('body').append('svg').attr('width', width).attr('height', height + 2 * margin).append('g').attr('transform', "translate(" + margin + "," + margin + ")"); |
|
link = vis.selectAll('path.link').data(links).enter().append('path').attr('class', 'link').attr('d', diagonal); |
|
node = vis.selectAll('g.node').data(nodes).enter().append('g').attr('class', 'node').attr('transform', function(d) { |
|
return "translate(" + d.y + "," + d.x + ")"; |
|
}); |
|
node.append('circle').attr('r', 4).attr('fill', function(d) { |
|
if (d.children) { |
|
return 'white'; |
|
} else { |
|
return 'steelblue'; |
|
} |
|
}); |
|
/* adapt bl.ocks.org frame to the tree |
|
*/ |
|
return d3.select(self.frameElement).transition().duration(500).style('height', "" + (height + 2 * margin) + "px"); |
|
}; |
|
|
|
}).call(this); |