web/tree-visualization.js

159 lines
5.9 KiB
JavaScript
Raw Normal View History

/*************Binary Search Tree Visualization using D3JS *************/
const NODE_SIZE = 40;
var duration = 400;
var tree = d3.tree().separation(function () { return 3 * NODE_SIZE; });
var svg = d3.select('svg'),
g = svg.append('g').attr('transform', `translate(${3 * NODE_SIZE},${3 * NODE_SIZE})`);
var gLinks = g.append('g'),
gNodes = g.append('g');
svg.attr('width', '1920')
.attr('height', '1080');
var oldPos = {};
var updateTree = function () {
var root = d3.hierarchy(rootNode);
var newTreeSize = [root.descendants().length * 3 * NODE_SIZE, ((root.height + 1) * 2 - 1) * 30];
if (tree.size()[0] !== newTreeSize[0] || tree.size()[1] !== newTreeSize[1]) {
tree.size(newTreeSize);
center = pos = svg.attr('width') / 2 - tree.size()[0] / 2;
}
tree(root);
var nodes = root.descendants().filter(function (d) {
return d.data.key !== null;
});
var link = gLinks.selectAll('path')
.data(nodes, function (d) { return d.data.id; });
link.exit().remove();
link.transition() // Update new position of old links
.duration(duration)
.attrTween('d', function (d) {
var oldDraw = d3.select(this).attr('d');
if (oldDraw) {
oldDraw = oldDraw.match(/(M.*)(L.*)/);
var oldMoveto = oldMoveto = oldDraw[1].slice(1).split(',').map(Number),
oldLineto = oldDraw[2].slice(1).split(',').map(Number);
// If root is changed, reverse to correctly animated if rotate left
if (changeRoot && oldMoveto[1] === 0) { // Old root node
oldMoveto = oldDraw[2].slice(1).split(',').map(Number);
oldLineto = oldDraw[1].slice(1).split(',').map(Number);
changeRoot = false;
}
if ((oldLineto !== [d.x, d.y]) && (oldMoveto !== [d.parent.x, d.parent.y])) {
/*console.log(d.data.key, oldMoveto, oldLineto);
console.log(d.data.key, [d.parent.x, d.parent.y], [d.x, d.y]);*/
var interpolatorMX = d3.interpolateNumber(oldMoveto[0], d.parent.x);
var interpolatorMY = d3.interpolateNumber(oldMoveto[1], d.parent.y);
var interpolatorLX = d3.interpolateNumber(oldLineto[0], d.x);
var interpolatorLY = d3.interpolateNumber(oldLineto[1], d.y);
return function (t) {
return 'M' + interpolatorMX(t) + ',' + interpolatorMY(t) + 'L' + interpolatorLX(t) + ',' + interpolatorLY(t);
};
}
}
});
link.enter().append('path') // Add new element for new data
.attr('class', 'link')
.transition()
.duration(duration)
.attrTween('d', function (d) {
if (d.parent) {
var parentOldPos = oldPos[d.parent.data.id.toString()];
var interpolatorMX = d3.interpolateNumber(parentOldPos[0], d.parent.x);
var interpolatorMY = d3.interpolateNumber(parentOldPos[1], d.parent.y);
var interpolatorLX = d3.interpolateNumber(parentOldPos[0], d.x);
var interpolatorLY = d3.interpolateNumber(parentOldPos[1], d.y);
return function (t) {
return 'M' + interpolatorMX(t) + ',' + interpolatorMY(t) + 'L' + interpolatorLX(t) + ',' + interpolatorLY(t);
};
}
else {
d3.select(this).remove();
}
});
var node = gNodes.selectAll('g')
.data(nodes, function (d) { return d.data.id; });
node.exit().remove();
node.transition()
.duration(duration)
.attr('transform', function (d) {
setTimeout(function () { // Finish transition, update old position of this node
oldPos[d.data.id.toString()] = [d.x, d.y];
}, duration);
return 'translate(' + d.x + ',' + d.y + ')';
});
var newNode = node.enter().append('g')
.attr('transform', function (d) {
if (!d.parent) return 'translate(' + d.x + ',' + (d.y - 30) + ')';
else return 'translate(' + oldPos[d.parent.data.id.toString()][0] + ',' + (oldPos[d.parent.data.id.toString()][1] - 30) + ')';
})
.attr('class', 'node');
newNode.transition()
.duration(duration)
.attr('transform', function (d) {
oldPos[d.data.id.toString()] = [d.x, d.y];
return 'translate(' + d.x + ',' + d.y + ')';
});
newNode.append('circle')
.attr('r', NODE_SIZE);
newNode.append('text')
.attr('class', 'text')
.attr('text-anchor', 'middle')
.attr('dy', 5)
.text(function (d) { return `${d.data.key} (r=${d.data.rank})`; });
};
var handleInsert = function (event) {
var num = document.getElementById('insertInput').value;
if (num) {
document.getElementById('insertInput').value = '';
d3.selectAll('#insertTree input').each(function () { // Disable insert
d3.select(this).attr('disabled', '')
});
insert(parseInt(num), function () {
d3.selectAll('#insertTree input').each(function () { // Enable insert
d3.select(this).attr('disabled', null);
});
});
}
return false;
};
var handleDelete = function (event) {
var num = document.getElementById('deleteInput').value;
if (num && rootNode.key !== null) { // Tree is not empty
document.getElementById('deleteInput').value = '';
d3.selectAll('#deleteTree input').each(function () { // Disable insert
d3.select(this).attr('disabled', '')
});
deleteTree(parseInt(num), function () {
d3.selectAll('#deleteTree input').each(function () { // Enable insert
d3.select(this).attr('disabled', null);
});
});
}
return false;
};