Binary tree with zoom/pan, percent leaves, link labels and path highlights
Created
April 15, 2013 22:25
-
-
Save biovisualize/20437c547a3ab051d93d to your computer and use it in GitHub Desktop.
Binary tree
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<script src="http://d3js.org/d3.v3.min.js"></script> | |
<style> | |
</style> | |
</head> | |
<body> | |
<script> | |
var test = { | |
"sourceSheet":"mushroom.csv", | |
"visualization":{ | |
"members":{ | |
"edges":[[1,2],[1,3],[2,4],[2,5],[5,10],[5,11],[10,20],[10,21],[11,22],[11,23]], | |
"nodes":[ | |
{"id":1,"isLeaf":false,"leftChild":3,"leftProbability":0.5622,"type":"categorical","values":["a","n","l"]}, | |
{"id":2,"isLeaf":false,"leftChild":5,"leftProbability":0.018498754891497687,"type":"categorical","values":["r"]}, | |
{"class":"p","id":4,"isLeaf":true,"splitPoint":0}, | |
{"id":5,"isLeaf":false,"leftChild":11,"leftProbability":0.012323305545487495,"type":"categorical","values":["k"]}, | |
{"id":10,"isLeaf":false,"leftChild":21,"leftProbability":0.29411764705882354,"type":"categorical","values":["w"]}, | |
{"class":"e","id":20,"isLeaf":true,"splitPoint":0},{"class":"p","id":21,"isLeaf":true,"splitPoint":0}, | |
{"id":11,"isLeaf":false,"leftChild":23,"leftProbability":0.0011009174311926607,"type":"categorical","values":["g"]}, | |
{"class":"p","id":22,"isLeaf":true,"splitPoint":0},{"class":"e","id":23,"isLeaf":true,"splitPoint":0}, | |
{"class":"p","id":3,"isLeaf":true,"splitPoint":0} | |
] | |
} | |
}, | |
"workbookId":3 | |
}; | |
var dataEdges = test.visualization.members.edges; | |
var dataNodes = test.visualization.members.nodes; | |
var links = dataEdges.map(function(d, i){return {source: d[0], target: d[1]};}) | |
var nodesByName = {}; | |
links.forEach(function(link) { | |
var parent = link.source = nodeByName(link.source), | |
child = link.target = nodeByName(link.target); | |
if (parent.children) parent.children.push(child); | |
else parent.children = [child]; | |
}); | |
function nodeByName(name) { | |
var node = dataNodes.filter(function(d, i){return d.id == name;})[0]; | |
console.log(); | |
return nodesByName[name] | |
|| (nodesByName[name] = { | |
name: (node.isLeaf) ? name + ': ' + node.class : name + ': ' + node.values.join(','), | |
leftChild: node.leftChild, | |
id: name | |
}); | |
} | |
var width = 960, | |
height = 500; | |
var tree = d3.layout.tree() | |
.size([width, height]); | |
var nodes = tree.nodes(links[0].source); | |
nodes.forEach(function(d, i){ | |
if(!d.children) d.percentTrue = ~~(Math.random()*100); | |
d.linkName = (d.parent && d.parent.leftChild == d.id) ? 'No' : 'Yes'; | |
}); | |
// SVG skeleton | |
///////////////////////////////// | |
var svg = d3.select('body').append('svg') | |
.attr({width: width, | |
height: height+100 | |
}) | |
.append('g') | |
.call(d3.behavior.zoom() | |
.on("zoom", function() { | |
d3.select(this).select('g.pan').attr("transform","translate(" + d3.event.translate + ") scale(" + d3.event.scale + ")"); | |
}) | |
); | |
var chartGroup = svg.append('g') | |
.attr({'class': 'pan', width: width, height: height}); | |
chartGroup.append('rect') | |
.attr({'class': 'background', x: -width, y: -height, width: width*3, height: height*3}) | |
.style({stroke: 'silver', fill: 'white'}); | |
// Marker | |
///////////////////////////////// | |
var markerSize = 6; | |
var marker = svg.append('defs') | |
.append('marker') | |
.attr({ | |
id: 'Triangle', | |
refX: markerSize*2, | |
refY: markerSize, | |
markerUnits: 'userSpaceOnUse', | |
markerWidth: markerSize + markerSize, | |
markerHeight: markerSize * 2 + markerSize, | |
orient: '90deg' | |
}) | |
.append('path') | |
.attr({d: 'M 0 0 '+markerSize*2+' '+markerSize+' 0 '+markerSize*2+' '+markerSize/2+' '+markerSize}); | |
// Labels | |
///////////////////////////////// | |
var selectedPath = []; | |
var nodeWidth = 50; | |
var nodeHeight = 20; | |
var nodeGroup = chartGroup.selectAll('g.node') | |
.data(tree.nodes(links[0].source), function(d){ return d.id; }) | |
.enter().append('g') | |
.attr({ | |
'class': 'node', | |
transform: function(d, i){ return 'translate('+(d.px = d.x)+','+(d.py = d.y)+')'; } | |
}) | |
.on('mouseover', function(d, i){ | |
var parentTmp = nodes[i]; | |
selectedPath = []; | |
d3.range(nodes[i].depth + 1).forEach(function(d, i){ | |
selectedPath.push(parentTmp.name); | |
parentTmp = parentTmp.parent | |
}); | |
nodeGroup.selectAll('rect.label-frame') | |
.filter(function(d, i){return selectedPath.indexOf(d.name) != -1;}) | |
.style({stroke: 'red', 'stroke-width': 3}); | |
chartGroup.selectAll('path.link').filter(function(d, i){ | |
return selectedPath.indexOf(d.source.name) != -1 | |
&& selectedPath.indexOf(d.target.name) != -1; | |
}) | |
.style({stroke: 'red', 'stroke-width': 3}); | |
}) | |
.on('mouseout', function(d, i){ | |
nodeGroup.selectAll('rect.label-frame') | |
.style({stroke: 'none'}); | |
svg.selectAll('path.link') | |
.style({stroke: 'grey'}); | |
}); | |
nodeGroup.append('rect') | |
.attr({ | |
'class': 'label-frame', | |
x: -nodeWidth/2, | |
y: 0, | |
width: nodeWidth, | |
height: nodeHeight | |
}) | |
.style({fill: function(d, i){ return (d.children) ? '#336699' : 'yellowgreen'; }}); | |
nodeGroup.filter(function(d, i){ return (!d.children); }) | |
.append('rect') | |
.attr({ | |
'class': 'percent', | |
x: function(d, i){ return -nodeWidth/2 + nodeWidth - nodeWidth / 100 * d.percentTrue; }, | |
y: 0, | |
width: function(d, i){ return nodeWidth / 100 * d.percentTrue; }, | |
height: nodeHeight | |
}) | |
.style({fill: 'red'}); | |
nodeGroup.append('text') | |
.attr({ | |
'class': 'label-text', | |
x: -nodeWidth/2, | |
y: 0, | |
dx: '0.4em', | |
dy: '1em', | |
'pointer-events': 'none' | |
}) | |
.style({fill: 'white'}) | |
.text(function(d, i){ return d.name; }); | |
// Links | |
///////////////////////////////// | |
var diagonal = d3.svg.diagonal(); | |
var commonTextAttr = { | |
'class': 'edge-text', | |
x: -nodeWidth/2, | |
y: 0, | |
dx: '1em', | |
dy: '-1.5em' | |
}; | |
nodeGroup.append('text') | |
.attr(commonTextAttr) | |
.style({fill: 'white', stroke: 'white', 'font-size': '14px', 'stroke-width': '3px', 'stroke-opacity': 1}) | |
.text(function(d, i){ return d.linkName; }); | |
nodeGroup.append('text') | |
.attr(commonTextAttr) | |
.style({fill: 'black', 'font-size': '14px'}) | |
.text(function(d, i){return d.linkName;}); | |
chartGroup.selectAll('path.link') | |
.data(tree.links(nodes), function(d){ return d.source.name + '-' + d.target.name; }) | |
.enter().insert('path', '.node') | |
.attr({ | |
'class': 'link', | |
d: diagonal, | |
'marker-end': 'url(#Triangle)' | |
}) | |
.style({fill: 'none', stroke: 'grey', 'stroke-width': 2}); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment