/*************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; };