From eb0b2a39f61bea3a1c1e0054f9552e07ad6a5b60 Mon Sep 17 00:00:00 2001 From: Matej Focko Date: Sun, 1 May 2022 17:10:24 +0200 Subject: [PATCH] feat: implement recorder and visualization Signed-off-by: Matej Focko --- avl.js | 82 +++++-- ranked_tree.js | 44 +++- tree-creation.js | 541 ------------------------------------------ tree-visualization.js | 159 ------------- visualization.js | 93 ++++++++ 5 files changed, 190 insertions(+), 729 deletions(-) delete mode 100755 tree-creation.js delete mode 100755 tree-visualization.js create mode 100644 visualization.js diff --git a/avl.js b/avl.js index 2c6a732..2090157 100644 --- a/avl.js +++ b/avl.js @@ -54,22 +54,33 @@ class AVLTree extends RankedTree { ); } - fix0Child(x, y, z, rotateLeft, rotateRight) { + fix0Child(x, y, z, rotatingAroundRoot, rotateLeft, rotateRight) { let newRoot = x.parent; if (!y || nodeDifference(y) == 2) { newRoot = rotateRight(z); z.demote(); + + if (rotatingAroundRoot) { + this.root = newRoot; + } + this.record(); } else if (nodeDifference(y) == 1) { rotateLeft(x); + this.record(); + newRoot = rotateRight(z); + if (rotatingAroundRoot) { + this.root = newRoot; + } + this.record(); y.promote(); x.demote(); z.demote(); - } - return newRoot; + this.record(); + } } insertRebalance(x) { @@ -78,6 +89,8 @@ class AVLTree extends RankedTree { x.parent.promote(); x = x.parent; + this.record(); + diffs = nodeDifferences(x.parent).sort(); } @@ -86,7 +99,6 @@ class AVLTree extends RankedTree { } let rotatingAroundRoot = x.parent.parent == null; - let newRoot = x.parent; let rankDifference = nodeDifference(x); if (rankDifference != 0) { @@ -96,18 +108,24 @@ class AVLTree extends RankedTree { let xParent = x.parent; if (rankDifference == 0 && x.parent.left === x) { - newRoot = this.fix0Child( - x, x.right, xParent, rotateLeft, rotateRight + this.fix0Child( + x, + x.right, + xParent, + rotatingAroundRoot, + rotateLeft, + rotateRight ); } else if (rankDifference == 0 && x.parent.right === x) { - newRoot = this.fix0Child( - x, x.left, xParent, rotateRight, rotateLeft + this.fix0Child( + x, + x.left, + xParent, + rotatingAroundRoot, + rotateRight, + rotateLeft ); } - - if (rotatingAroundRoot) { - this.root = newRoot; - } } deleteRotate(x, y, leaning, rotatingAroundRoot, rotateLeft, rotateRight) { @@ -124,14 +142,18 @@ class AVLTree extends RankedTree { newRoot = rotateLeft(x); break; } - - [newRoot.left, newRoot.right, newRoot].filter(x => x).forEach(n => { - updateRank(n) - }); - if (rotatingAroundRoot) { this.root = newRoot; } + this.record(); + + [newRoot.left, newRoot.right, newRoot] + .filter((x) => x) + .forEach((n) => { + updateRank(n); + this.record(); + }); + return factor == 0; } @@ -142,6 +164,9 @@ class AVLTree extends RankedTree { switch (factor) { case 0: updateRank(x); + + this.record(); + return false; case -1: case 1: @@ -149,12 +174,29 @@ class AVLTree extends RankedTree { } let rotatingAroundRoot = x.parent == null; - let [z, leaning, toLeft, toRight] = [x.left, -1, rotateRight, rotateLeft]; + let [z, leaning, toLeft, toRight] = [ + x.left, + -1, + rotateRight, + rotateLeft, + ]; if (factor == 2) { - [z, leaning, toLeft, toRight] = [x.right, 1, rotateLeft, rotateRight]; + [z, leaning, toLeft, toRight] = [ + x.right, + 1, + rotateLeft, + rotateRight, + ]; } - return this.deleteRotate(x, z, leaning, rotatingAroundRoot, toLeft, toRight); + return this.deleteRotate( + x, + z, + leaning, + rotatingAroundRoot, + toLeft, + toRight + ); } deleteRebalance(node, parent) { diff --git a/ranked_tree.js b/ranked_tree.js index 89c936c..1e3499e 100644 --- a/ranked_tree.js +++ b/ranked_tree.js @@ -19,10 +19,11 @@ class Queue { class RankedTree { constructor() { this.root = null; + this.recorder = null; } _getUnwrappedGraph() { - let result = ""; + let result = []; let q = new Queue(); q.enqueue(this.root); @@ -34,24 +35,45 @@ class RankedTree { continue; } - result += `"${node.toString()}" [label="${node.value}, ${node.rank}"];\n`; + result.push( + `\t"${node.toString()}" [label="${node.value}, ${node.rank}"];` + ); - [node.left, node.right].filter(child => child).forEach(child => { - edges.push([node, child]); - q.enqueue(child); - }); + [node.left, node.right] + .filter((child) => child) + .forEach((child) => { + edges.push([node, child]); + q.enqueue(child); + }); } - edges.forEach(vertices => { + result.push(""); + + edges.forEach((vertices) => { let [u, v] = vertices; - result += `"${u.toString()}" -> "${v.toString()}" [label="${nodeDifference(v)}"]\n`; + result.push( + `\t"${u.toString()}" -> "${v.toString()}" [label="${nodeDifference( + v + )}"]` + ); }); return result; } + toDot() { + return ["digraph {", ...this._getUnwrappedGraph(), "}"]; + } + toString() { - return `digraph {\n${this._getUnwrappedGraph()}}\n`; + return this.toDot().join("\n"); + } + + record() { + if (!this.recorder) { + return; + } + this.recorder.record(this); } rank() { @@ -71,6 +93,7 @@ class RankedTree { if (!this.root) { this.root = insertedNode; + this.record(); return; } @@ -86,6 +109,7 @@ class RankedTree { } else { parent.right = insertedNode; } + this.record(); this.insertRebalance(insertedNode); } @@ -134,6 +158,8 @@ class RankedTree { node.left.parent = successor; } + this.record(); + return [y, parent]; } diff --git a/tree-creation.js b/tree-creation.js deleted file mode 100755 index 8c0b72c..0000000 --- a/tree-creation.js +++ /dev/null @@ -1,541 +0,0 @@ -var id = 1; - -var rootNode = { - id: id++, - key: null, - rank: -1, - parent: null, - children: [] -}; -var changeRoot = false; - -// Find tree's height -var findHeight = function (node) { - if (node.data === null || !node) return 0; - else { - var left = node.children[0] ? findHeight(node.children[0]) : 0; - var right = node.children[1] ? findHeight(node.children[1]) : 0; - return 1 + ((left > right) ? left : right); - } -}; - -var getHeight = function (node) { - if (node === null) { - return -1; - } - return node.rank; -} - -// Binary Search Tree rotation -var rotateLeft = function (node, callback) { - var parent = node.parent, - leftChild = node.children[0], - rightChild = node.children[1]; - - if (rightChild.children.length === 0) { - rightChild.children.push({ id: id++, key: null, rank: -1, parent: rightChild, children: [] }); - rightChild.children.push({ id: id++, key: null, rank: -1, parent: rightChild, children: [] }); - } - - rightChild.children[0].parent = node; - node.children[1] = rightChild.children[0]; - node.parent = rightChild; - rightChild.children[0] = node; - rightChild.parent = parent; - - if (parent === null) { // Root node - rootNode = rightChild; - - gLinks.selectAll('path').filter(function (d) { // Update root node - return d.data.id === node.parent.id; - }).datum(d3.hierarchy(node).descendants()[0]); - changeRoot = true; - - } else if (node === parent.children[0]) { // Left child of parent - parent.children[0] = rightChild; - changeRoot = false; - - } else if (node === parent.children[1]) { // Right child of parent - parent.children[1] = rightChild; - changeRoot = false; - } - - if (node.children.length !== 0 && node.children[0].key === null && node.children[1].key === null) node.children = []; - - setTimeout(function () { - if (callback instanceof Function) { - callback(); - } - }, duration); - - return rightChild; -}; - -var rotateRight = function (node, callback) { - var parent = node.parent, - leftChild = node.children[0], - rightChild = node.children[1]; - - if (leftChild.children.length === 0) { - leftChild.children.push({ id: id++, key: null, rank: -1, parent: leftChild, children: [] }); - leftChild.children.push({ id: id++, key: null, rank: -1, parent: leftChild, children: [] }); - } - - if (parent === null) { // Root node - leftChild.children[1].parent = node; - node.children[0] = leftChild.children[1]; - - node.parent = leftChild; - leftChild.children[1] = node; - - leftChild.parent = parent; - rootNode = leftChild; - - gLinks.selectAll('path').filter(function (d) { // Update root node's link - return d.data.id === node.parent.id; - }).datum(d3.hierarchy(node).descendants()[0]); - changeRoot = true; - - } else if (node === parent.children[0]) { // Left child of parent - leftChild.children[1].parent = node; - node.children[0] = leftChild.children[1]; - - node.parent = leftChild; - leftChild.children[1] = node; - - leftChild.parent = parent; - parent.children[0] = leftChild; - - changeRoot = false; - - } else if (node === parent.children[1]) { // Left child of parent - leftChild.children[1].parent = node; - node.children[0] = leftChild.children[1]; - - node.parent = leftChild; - leftChild.children[1] = node; - - leftChild.parent = parent; - parent.children[1] = leftChild; - - changeRoot = false; - } - - if (node.children.length !== 0 && node.children[0].key === null && node.children[1].key === null) node.children = []; - - setTimeout(function () { - if (callback instanceof Function) { - callback(); - } - }, duration); - - return leftChild; -}; - -// Node highlight for better visualization -var highlight = function (node) { - var hlNode = gNodes.selectAll('circle').filter(function (d) { - return d.data.id === node.id; - }); - hlNode.transition() - .duration(duration / 3) - .style('stroke', '#e74c3c') - .style('stroke-width', '3.5px'); -}; - -var removeHighlight = function (node) { - var hlNode = gNodes.selectAll('circle').filter(function (d) { - return d.data.id === node.id; - }); - hlNode.transition() - .duration(duration / 3) - .style('stroke', '#3498db') - .style('stroke-width', '2.5px'); -}; - -// AVL Tree balancing -var balance = function (node, callback) { - highlight(node); - var hLeft = node.children[0] ? findHeight(node.children[0]) : 0; - var hRight = node.children[1] ? findHeight(node.children[1]) : 0; - var hl, hr, - defer = 0.5; - if (hLeft - hRight >= 2) { // Left unbalance - var leftChild = node.children[0]; - hl = leftChild.children[0] ? findHeight(leftChild.children[0]) : 0; - hr = leftChild.children[1] ? findHeight(leftChild.children[1]) : 0; - if (hl >= hr) { // Left of left - rotateRight(node, updateTree); - defer = 1; - } else { // Right of left - defer = 3; - rotateLeft(leftChild, function () { - updateTree(); - setTimeout(function () { - rotateRight(node, updateTree); - }, duration); - }); - } - } else if (hRight - hLeft >= 2) { // Right unbalance - rotated = false; - isChanged = true; - var rightChild = node.children[1]; - hl = rightChild.children[0] ? findHeight(rightChild.children[0]) : 0; - hr = rightChild.children[1] ? findHeight(rightChild.children[1]) : 0; - if (hr >= hl) { // Right of right - rotateLeft(node, updateTree); - defer = 1; - } else { // Left of right - defer = 3; - rotateRight(rightChild, function () { - updateTree(); - setTimeout(function () { - rotateLeft(node, updateTree); - }, duration); - }); - } - } - - if (!node.parent) { - node.rank = 69; - } - - setTimeout(function () { - removeHighlight(node); - if (!node.parent) { // End balancing - if (callback instanceof Function) callback(); - } - else balance(node.parent, callback); - }, duration * defer); -}; - -var rank = function (node) { - if (node === null) { - return -1; - } - return node.rank; -}; - -var difference = function (node, parent) { - return rank(parent) - rank(node); -}; - -var differences = function (node) { - const nodeRank = rank(node); - - const leftRank = node ? rank(node.children[0]) : -1; - const rightRank = node ? rank(node.children[1]) : -1; - - return [nodeRank - leftRank, nodeRank - rightRank]; -}; - -var fix0Child = function (x, y, z, rotateL, rotateR) { - var newRoot = x.parent; - - if (!y || difference(y, y.parent) == 2) { - newRoot = rotateR(z, updateTree); - z.rank--; - } else if (difference(y, y.parent) == 1) { - rotateL(x, function () { - updateTree(); - setTimeout(function () { - newRoot = rotateR(z, updateTree); - }, duration); - }); - - y.rank++; - x.rank--; - z.rank--; - } - - return newRoot; -}; - -var insertFixup = function (x, callback) { - highlight(x); - - let [lDiff, rDiff] = differences(x.parent); - - while (x.parent && ((lDiff == 0 && rDiff == 1) || (lDiff == 1 && rDiff == 0))) { - console.log(`Node with key: ${x.parent.key} is ${lDiff},${rDiff}-node`); - x.parent.rank++; - - removeHighlight(x); - x = x.parent; - highlight(x); - - [lDiff, rDiff] = differences(x.parent); - } - - console.log(`Rank of node with key=${x.key}: ${x.rank}`); - console.log(`Parent node is ${lDiff},${rDiff}-node`); - - if (x.parent === null) { - console.log("exiting"); - setTimeout(function () { - removeHighlight(x); - if (callback instanceof Function) { - callback(); - } - }, duration * 3); - return; - } - - const rotatingAroundRoot = x.parent.parent === null; - var newRoot = x.parent; - - var rankDifference = difference(x, x.parent); - console.log(x); - console.log(`Before fix 0-child: ${rankDifference}`); - if (rankDifference != 0) { - setTimeout(function () { - removeHighlight(x); - if (callback instanceof Function) { - callback(); - } - }, duration * 3); - return; - } - - if (rankDifference == 0 && x.parent.children[0] === x) { - console.log("Fixing 0-child on left"); - newRoot = fix0Child( - x, x.children[1], x.parent, rotateLeft, rotateRight - ); - } else if (rankDifference == 0 && x.parent.children[1] === x) { - console.log("Fixing 0-child on right"); - newRoot = fix0Child( - x, x.children[0], x.parent, rotateRight, rotateLeft - ); - } - - // if (rotatingAroundRoot) { - // rootNode = newRoot; - // } - - setTimeout(function () { - removeHighlight(x); - if (callback instanceof Function) { - callback(); - } - }, duration * 3); -}; - -// Tree insertion -var insert = function (n, callback) { - console.log('Insert', n); - if (!n || !Number.isInteger(n)) return; - if (!rootNode.key) { - rootNode.key = n; - rootNode.rank++; - updateTree(); - callback(); - return; - } - - var walker = rootNode, - newNode; - - while (!newNode) { - if (n <= walker.key) { - if (walker.children.length === 0) { // No child - walker.children.push({ id: id++, key: n, rank: 0, parent: walker, children: [] }); // Left child - walker.children.push({ id: id++, key: null, rank: -1, parent: walker, children: [] }); // Empty right child - newNode = walker.children[0]; - } else if (walker.children[0].key === null) { // Already have right child, left child is empty - walker.children[0].key = n; - walker.children[0].rank++; - newNode = walker.children[0]; - } else { // Move left - walker = walker.children[0]; - } - } else { - if (walker.children.length === 0) { // No child - walker.children.push({ id: id++, key: null, rank: -1, parent: walker, children: [] }); // Empty left child - walker.children.push({ id: id++, key: n, rank: 0, parent: walker, children: [] }); // Right child - newNode = walker.children[1]; - } else if (walker.children[1].key === null) { // Already have left child, right child is empty - walker.children[1].key = n; - walker.children[1].rank++; - newNode = walker.children[1]; - } else { // Move left - walker = walker.children[1]; - } - } - } - updateTree(); - setTimeout(function () { - insertFixup(newNode, callback); - //callback(); - }, duration); -}; - -// Tree deletion -var deleteTree = function (n, callback) { - if (!rootNode.key) return false; - var walker = rootNode, - nodeDelete = null, // Node to be deleted - nodeReplace = null, // Node to replace deleted node - nodeBalance = null, // After deleting, perform balance on this node to root - parent; - - // Find node - if (n === walker.key) { // Deleting root - nodeDelete = walker; - if (nodeDelete.children.length === 0) nodeDelete.key = null; // Tree only has root node - else { - if (nodeDelete.children[0].key === null) { // Root does not have left subtree - rootNode = rootNode.children[1]; // Right subtree becomes new tree - rootNode.parent = null; - - gLinks.selectAll('path').filter(function (d) { - return d.data.id === rootNode.id; - }).remove(); - } else { - nodeReplace = nodeDelete.children[0]; - // In-order predecessor, largest child of left subtree - while (nodeReplace) { - if (!nodeReplace.children[1] || !nodeReplace.children[1].key) break; - nodeReplace = nodeReplace.children[1]; - } - - parent = nodeReplace.parent; - nodeBalance = parent; // Will start balacing from this node - - if (parent.children[0] === nodeReplace) { - if (nodeReplace.children[0]) { - nodeReplace.children[0].parent = parent; - parent.children[0] = nodeReplace.children[0] - } else { - parent.children[0] = { id: id++, key: null, parent: parent, children: [] }; - } - } - else if (parent.children[1] === nodeReplace) { - if (nodeReplace.children[0]) { - nodeReplace.children[0].parent = parent; - parent.children[1] = nodeReplace.children[0] - } else { - parent.children[1] = { id: id++, key: null, parent: parent, children: [] }; - } - } - // After moving, if nodeReplace's parent has 2 empty children - if (parent.children.length !== 0 && parent.children[0].key === null && parent.children[1].key === null) parent.children = []; - - // Add 2 subtrees of old root to new root - if (nodeDelete.children[0]) { - nodeDelete.children[0].parent = nodeReplace; - nodeReplace.children[0] = nodeDelete.children[0]; - } - if (nodeDelete.children[1]) { - nodeDelete.children[1].parent = nodeReplace; - nodeReplace.children[1] = nodeDelete.children[1]; - } - - // Replace deleted root node by largest node of left subtree (in-order predecessor) - nodeReplace.parent = null; - rootNode = nodeReplace; - - gLinks.selectAll('path').filter(function (d) { - return d.data.id === nodeReplace.id; - }).remove(); - } - } - - updateTree(); - setTimeout(function () { - if (nodeBalance) balance(nodeBalance, callback); - else if (callback instanceof Function) callback(); - }, duration); - return true; - } - - // Finding node - while (walker.key) { - if (n < walker.key) walker = walker.children[0]; // Move left - else if (n > walker.key) walker = walker.children[1]; // Move right - else if (n === walker.key) { - nodeDelete = walker; - break; - } - } - - if (!nodeDelete) return false; - - // Deletion - if (nodeDelete.children.length === 0) { // Node to be deleted is leaf node - parent = nodeDelete.parent; - nodeBalance = parent; // Will start balacing from this node - - if (parent.children[0] === nodeDelete) { // Remove left child - parent.children[0] = { id: id++, key: null, parent: parent, children: [] }; // Empty child - } - else if (parent.children[1] === nodeDelete) { // Remove right child - parent.children[1] = { id: id++, key: null, parent: parent, children: [] }; - } - - if (parent.children.length !== 0 && parent.children[0].key === null && parent.children[1].key === null) parent.children = []; - - } else { // Node to be deleted is internal node - nodeReplace = nodeDelete.children[0].key ? nodeDelete.children[0] : null; - // In-order predecessor, largest child of left subtree - while (nodeReplace) { - if (!nodeReplace.children[1] || !nodeReplace.children[1].key) break; - nodeReplace = nodeReplace.children[1]; - } - - if (!nodeReplace) { // No left child, right child of nodeDelete replace its position - parent = nodeDelete.parent; - nodeBalance = parent; // Will start balacing from this node - - nodeDelete.children[1].parent = parent; - if (parent.children[0] === nodeDelete) parent.children[0] = nodeDelete.children[1]; // Left child of parent - else if (parent.children[1] === nodeDelete) parent.children[1] = nodeDelete.children[1]; // Right child of parent - } else { - // Update nodeReplace's parent - parent = nodeReplace.parent; - nodeBalance = parent; // Will start balacing from this node - - if (parent.children[0] === nodeReplace) { - if (nodeReplace.children[0]) { - nodeReplace.children[0].parent = parent; - parent.children[0] = nodeReplace.children[0] - } else { - parent.children[0] = { id: id++, key: null, parent: parent, children: [] }; - } - } - else if (parent.children[1] === nodeReplace) { - if (nodeReplace.children[0]) { - nodeReplace.children[0].parent = parent; - parent.children[1] = nodeReplace.children[0] - } else { - parent.children[1] = { id: id++, key: null, parent: parent, children: [] }; - } - } - // After moving, if nodeReplace's parent has 2 empty children - if (parent.children.length !== 0 && parent.children[0].key === null && parent.children[1].key === null) parent.children = []; - - // Replace deleted node by largest node of left subtree (in-order predecessor) - parent = nodeDelete.parent; - nodeReplace.parent = parent; - if (parent.children[0] === nodeDelete) parent.children[0] = nodeReplace; // Left child of parent - else if (parent.children[1] === nodeDelete) parent.children[1] = nodeReplace; // Right child of parent - - if (nodeDelete.children[0]) { - nodeDelete.children[0].parent = nodeReplace; - nodeReplace.children[0] = nodeDelete.children[0]; - } - if (nodeDelete.children[1]) { - nodeDelete.children[1].parent = nodeReplace; - nodeReplace.children[1] = nodeDelete.children[1]; - } - } - } - - updateTree(); - setTimeout(function () { - if (nodeBalance) balance(nodeBalance, callback); - else if (callback instanceof Function) callback(); - }, duration); - return true; -}; - diff --git a/tree-visualization.js b/tree-visualization.js deleted file mode 100755 index cb41523..0000000 --- a/tree-visualization.js +++ /dev/null @@ -1,159 +0,0 @@ -/*************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; -}; \ No newline at end of file diff --git a/visualization.js b/visualization.js new file mode 100644 index 0000000..01f2f10 --- /dev/null +++ b/visualization.js @@ -0,0 +1,93 @@ +class Recorder { + constructor(graph) { + this.graph = graph; + + this.rendered = -1; + this.rendering = false; + + this.states = new Array(); + + this.unblockRendering = ((recorder) => () => { + recorder.rendering = false; + })(this); + + this.initGraph(); + } + + initGraph() { + this.graph.transition(() => + d3 + .transition("main") + .ease(d3.easeLinear) + .on("end", this.unblockRendering) + .delay(500) + .duration(1500) + ); + } + + render() { + if ( + this.rendering || + !this.states || + this.rendered == this.states.length - 1 + ) { + return; + } + + this.rendering = true; + + this.rendered++; + this.graph.renderDot(this.states[this.rendered]); + } + + previous() { + throw "not implemented!"; + } + + next() { + throw "not implemented!"; + } + + record(tree) { + // TODO: adjust join if necessary + this.states.push(tree.toDot().join("")); + this.render(); + } +} + +let recorder = new Recorder(d3.select("#graph").graphviz()); + +let tree = new AVLTree(); +tree.recorder = recorder; + +function insertCallback() { + let number = document.getElementById("insertInput").value; + if (number === "") { + return false; + } + + let value = parseInt(number); + tree.insert(value); + + document.getElementById("insertInput").value = ""; + return false; +} + +function deleteCallback() { + let number = document.getElementById("deleteInput").value; + if (number === "") { + return false; + } + + let value = parseInt(number); + tree.delete(value); + + document.getElementById("deleteInput").value = ""; + return false; +} + +function render() { + recorder.render(); + setTimeout(render); +} +render();