feat: First working version
Signed-off-by: Matej Focko <me@mfocko.xyz>
This commit is contained in:
parent
f80eaa8a2f
commit
2beb2d9fc7
6 changed files with 752 additions and 0 deletions
6
README.md
Executable file
6
README.md
Executable file
|
@ -0,0 +1,6 @@
|
|||
# avl
|
||||
WAVL Tree Visualization using D3JS
|
||||
|
||||
Simply fork repository and run index.html
|
||||
|
||||
![ScreenShot](avl.png)
|
BIN
avl.png
Executable file
BIN
avl.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 75 KiB |
23
index.html
Executable file
23
index.html
Executable file
|
@ -0,0 +1,23 @@
|
|||
<!DOCTYPE html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans:600" rel="stylesheet">
|
||||
<link href="style.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<form id="insertTree" onSubmit="return handleInsert()">
|
||||
<input id="insertInput" type="text" placeholder="Number to insert" maxlength="4" size="14" autofocus>
|
||||
<input type="submit" value="Insert">
|
||||
</form>
|
||||
<form id="deleteTree" onSubmit="return handleDelete()">
|
||||
<input id="deleteInput" type="text" placeholder="Number to delete" maxlength="4" size="14">
|
||||
<input type="submit" value="Delete">
|
||||
</form>
|
||||
<svg></svg>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.0.0/jquery.min.js"></script>
|
||||
<script src="https://d3js.org/d3.v4.min.js"></script>
|
||||
<script src="tree-creation.js"></script>
|
||||
<script src="tree-visualization.js"></script>
|
||||
</body>
|
23
style.css
Executable file
23
style.css
Executable file
|
@ -0,0 +1,23 @@
|
|||
.node circle {
|
||||
fill: #ecf0f1;
|
||||
stroke: #3498db;
|
||||
stroke-opacity: 0.8;
|
||||
stroke-width: 2.5px;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
fill: #3498db;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.link {
|
||||
fill: none;
|
||||
stroke: #3498db;
|
||||
stroke-opacity: 0.8;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
|
||||
form {
|
||||
display: inline;
|
||||
}
|
541
tree-creation.js
Executable file
541
tree-creation.js
Executable file
|
@ -0,0 +1,541 @@
|
|||
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;
|
||||
};
|
||||
|
159
tree-visualization.js
Executable file
159
tree-visualization.js
Executable file
|
@ -0,0 +1,159 @@
|
|||
/*************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;
|
||||
};
|
Loading…
Reference in a new issue