Skip to content

Instantly share code, notes, and snippets.

@ludwigschubert
Last active July 2, 2018 22:17
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ludwigschubert/0a300d8a41d9e6b49144fff8b62637f8 to your computer and use it in GitHub Desktop.
Save ludwigschubert/0a300d8a41d9e6b49144fff8b62637f8 to your computer and use it in GitHub Desktop.
kD-tree explorations in D3.js
license: "cc-by-4.0"
height: 500
scrolling: "no"
border: "no"

K-d Tree Exploration

K-d trees are space-partitioning data structures for organizing points in k-dimensional space. They are a useful data structure for finding, for example, the n nearest neighbors of a point in k-dimensional space.

This block provides a visualization of k-d tree creation which connects the intuition of binary trees with the concept of space partitioning.

Operation

Click on the left square to add points in a 2D projection of RGB space. After adding multiple points, click "Rebalance" on the right square to see how a newly constructed k-D tree would sort the given points.

Additional reading

http://pointclouds.org/documentation/tutorials/kdtree_search.php https://en.wikipedia.org/wiki/K-d_tree

<html>
<head>
<link rel='stylesheet' href='spectrum.css' />
<link rel='stylesheet' href='style.css' />
</head>
<body>
<div class="row">
<div id="kdtree"></div>
<div id="binarytree"></div>
</div>
<script src="//code.jquery.com/jquery-2.2.4.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/tinycolor/1.4.1/tinycolor.min.js"></script>
<script src="kdTree.js"></script>
<script>
var fast = 400; // ms
var slow = 2500; // ms
var data;
var colorSubset = [];
var tree = new kdTree(colorSubset, colorDistance, ["red", "green"]);
var bTree;
var rebalancing = false;
// Pretty good color distance from
// http://www.compuphase.com/cmetric.htm
function colorDistance(a, b) {
var dr = a.red - b.red;
var dg = a.green - b.green;
var db = a.blue - b.blue;
var redMean = (a.red + b.red)/2;
return (2+redMean/256)*dr*dr + 4*dg*dg + (2 + (255 - redMean)/256)*db*db;
}
$(function(){ // on document load
function update(color) {
// remove old search results
// nearest.forEach(function(d, i) {
// d[0].node.isSearchResult = false;
// });
$("#picked").css('background', color.toHex());
var rgb = color.toRgb();
var search = {red: rgb.r, green: rgb.g, blue: rgb.b};
var nearest = tree.nearest(search, 4);
nearest.sort(function(a,b){return a[1] - b[1]});
// nearest.forEach(function(d, i) {
// d[0].node.isSearchResult = true;
// });
var $list = $("#results ul");
$list.html("");
for(var i=0; i<nearest.length; i++) {
var c = nearest[i][0];
var $box = $("<div>")
.css('background', c.hex)
.css('display', 'inline-block')
.css('margin-right', '10px')
.height('30px')
.width('30px');
var $line = $("<li>").append($box).append(c.title);
$list.append($line);
}
updateSearchResults(nearest);
}
function setRGBfromHex (hex_color) {
var color = new tinycolor(hex_color.hex).toRgb();
hex_color.red = color.r;
hex_color.green = color.g;
hex_color.blue = color.b;
}
// tree = new kdTree(colorSubset, colorDistance, ["red", "green"]); //,"blue"]);
// data = tree.pointsBFS();
// D3 kdtree
var margin = {top: 0, right: 0, bottom: 0, left: 0},
width = 470 - margin.left - margin.right,
height = width - margin.top - margin.bottom,
pointRadius = 6;
var x = d3.scale.linear()
.range([0, width])
.domain([0, 255]);
var y = d3.scale.linear()
.range([height, 0])
.domain([0, 255]);
var hintText = d3.select("#kdtree").append("p")
.text("Click to add colors in 2D space to tree")
.attr("id", "hint-text")
var svg = d3.select("#kdtree").append("svg")
.attr("class", "kdtree")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
var reorder = d3.select("#binarytree").append("button")
.text("Rebalance tree")
.attr("id", "control-button")
.on("click", function() {
tree = new kdTree(colorSubset, colorDistance, ["red", "green"]);
data = tree.pointsBFS();
rebalancing = true;
drawDataSubset(data);
});
var graph = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("width", width)
.attr("height", height);
var linesG = graph.append("g")
.attr("class", "lines");
var pointsG = graph.append("g")
.attr("class", "points");
var tooltip = d3.select("body")
.append("div")
.attr("class", "tooltip")
.style("position", "absolute")
.style("z-index", "10")
.style("visibility", "hidden")
var swatch = tooltip.append("div")
.attr("class", "tooltip-swatch");
var label = tooltip.append("p")
.text("a simple tooltip");
// D3 binarytree
bTree = d3.layout.tree()
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.y, d.x]; });
var bSvg = d3.select("#binarytree").append("svg")
.attr("class", "binarytree")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
var bLinesG = bSvg.append("g")
.attr("class", "lines");
var bPointsG = bSvg.append("g")
.attr("class", "points");
svg.on('click', function() {
coordinates = d3.mouse(this);
var red = x.invert( coordinates[0] );
var green = y.invert( coordinates[1] );
var color = d3.rgb(red, green, 128);
var object = { title: "user defined", hex: color.toString() };
setRGBfromHex(object);
// check for very similar colors; don't insert to keep layouter sane
var potentialDuplicates = tree.nearest(object, 1);
if (potentialDuplicates.length > 0) {
// var potentialDuplicate = potentialDuplicates[0][0];
var distance = potentialDuplicates[0][1];
if (distance < 10) return;
}
colorSubset.push(object);
tree.insert(object);
data = tree.pointsBFS();
drawDataSubset(data);
});
data = tree.pointsBFS();
drawDataSubset(data);
var i = 1;
function drawIteratively(){
drawDataSubset(data.slice(0, i));
if (i < data.length) {
i++;
setTimeout(drawIteratively, 250);
} else {
i = 1;
}
}
function transitionEnd() {
rebalancing = false;
// console.log("no longer rebalancing");
}
function drawDataSubset(dataSubset) {
if (dataSubset.length > 0) {
hintText.style("opacity", 0.0);
} else {
hintText.style("opacity", 1.0);
}
if (dataSubset.length > 1) {
reorder.style("opacity", 1.0);
} else {
reorder.style("opacity", 0.0);
}
// points
{
var pointsSelection = pointsG.selectAll(".point")
.data(dataSubset, function(d){return d.hex + "point";});
pointsSelection.enter()
.append("circle")
.attr("class", function(d) { d.fresh = true; return "point" })
.attr("class", "point")
.attr("dimension", function(d) { return d.dimension; })
.attr("r", pointRadius)
.attr("cx", function(d) { return x(d.red); })
.attr("cy", function(d) { return y(d.green); })
.style("fill", function(d) { return d3.rgb(d.hex); })
.style("stroke", function(d) { return d3.rgb(d.hex); })
.on("mouseover", function(d) {
if (d.fresh) return;
var parent = d.node;
while(parent) {
parent.onAccessPath = true;
parent = parent.parent;
}
drawDataSubset(data);
})
.on("mouseout", function(d) {
if (d.fresh) {
d.fresh = false;
return;
}
var parent = d.node;
while(parent) {
parent.onAccessPath = false;
parent = parent.parent;
}
drawDataSubset(data);
})
pointsSelection
.style("stroke-width", function(d) { return d.node.onAccessPath || d.node.isSearchResult ? 2 : 0; });
// lines
var selection = linesG.selectAll(".point-line")
.data(dataSubset, function(d){return d.hex + "line";});
selection // update
.style("stroke-width", function(d) { return d.node.onAccessPath ? 2*height/400 : height/400; })
.transition().duration(function(){ return rebalancing ? slow : fast }).each("end", transitionEnd)
.attr("x1", function(d) { return x(d.x1); })
.attr("y1", function(d) { return y(d.y1); })
.attr("x2", function(d) { return x(d.x2); })
.attr("y2", function(d) { return y(d.y2); })
.attr("stroke",function(d) {
switch(d.dimension) {
case 0: return d3.rgb(d.red, 0, 0);
case 1: return d3.rgb(0, d.green, 0);
// case 2: return "green";
}
})
selection.enter()
.append("line")
.attr("class", "point-line")
// .attr("stroke-width", function(d) { return w(d.depth); })
.attr("stroke-width", height/400)
// .style("opacity", 0.8)
// start all animation from point
.attr("x1", function(d) { return x(d.red); })
.attr("y1", function(d) { return y(d.green); })
.attr("x2", function(d) { return x(d.red); })
.attr("y2", function(d) { return y(d.green); })
.attr("stroke",function(d) {
switch(d.dimension) {
case 0: return d3.rgb(d.red, 0, 0);
case 1: return d3.rgb(0, d.green, 0);
// case 2: return "green";
}
})
.transition()
.attr("x1", function(d) { return x(d.x1); })
.attr("y1", function(d) { return y(d.y1); })
.attr("x2", function(d) { return x(d.x2); })
.attr("y2", function(d) { return y(d.y2); })
selection // enter + update
// .transition()
selection.exit()
.remove();
}
// binary tree
{
// nodes
var depth = d3.max(dataSubset, function(d){return d.depth;})
var binaryTreePoints = bPointsG.selectAll(".binarytreepoint")
.data(dataSubset, function(d){return d.hex + "binarytreepoint";});
binaryTreePoints.enter()
.append("circle")
.attr("class", "binarytreepoint")
.attr("dimension", function(d) { return d.dimension; })
.attr("r", pointRadius)
.style("fill", function(d) { return d3.rgb(d.hex); })
.style("stroke", function(d) { return d3.rgb(d.hex); });
binaryTreePoints // enter + update
.style("stroke-width", function(d) { return d.node.onAccessPath ? 2 : 0; })
.transition().duration(function(){ return rebalancing ? slow : fast })
.attr("cx", function(d) {
if (!d.node.parent) {
d.node.bx = width / 2;
} else {
var inc = (width - 4*pointRadius) / Math.pow(2, d.depth + 1 );
if (d.node == d.node.parent.left) { // left child
d.node.bx = d.node.parent.bx - inc;
} else { // right child
d.node.bx = d.node.parent.bx + inc;
}
}
return d.node.bx;
})
.attr("cy", function(d) {
d.node.by = (d.depth * ((height - 4*pointRadius) / depth)) + (2 * pointRadius);
return d.node.by;
})
.style("fill", function(d) { return d3.rgb(d.hex); })
binaryTreePoints.exit()
.remove();
// edges
var selection = bLinesG.selectAll(".binarytreepoint-edge")
.data(dataSubset, function(d){return d.hex + "binarytreepoint-edge";});
selection // update
.style("stroke-width", function(d) { return d.node.onAccessPath ? 2*height/400 : height/400; })
.transition().duration(function(){ return rebalancing ? slow : fast })
.attr("x1", function(d) { return d.node.parent ? d.node.parent.bx : d.node.bx; })
.attr("y1", function(d) { return d.node.parent ? d.node.parent.by : d.node.by; })
.attr("x2", function(d) { return d.node.bx; })
.attr("y2", function(d) { return d.node.by; })
.attr("stroke",function(d) {
// return "gray";
switch(d.dimension) {
case 0: return d3.rgb(d.red, 0, 0);
case 1: return d3.rgb(0, d.green, 0);
// case 2: return "green";
}
})
selection.enter()
.append("line")
.attr("class", "binarytreepoint-edge")
// .attr("stroke-width", function(d) { return w(d.depth); })
.attr("stroke-dasharray", "1,3")
.attr("stroke-width", height/400)
.style("opacity", 0.8)
// start all animation from point
.attr("x1", function(d) { return d.node.parent ? d.node.parent.bx : d.node.bx; })
.attr("y1", function(d) { return d.node.parent ? d.node.parent.by : d.node.by; })
.attr("x2", function(d) { return d.node.parent ? d.node.parent.bx : d.node.bx; })
.attr("y2", function(d) { return d.node.parent ? d.node.parent.by : d.node.by; })
.attr("stroke",function(d) {
// return "gray";
switch(d.dimension) {
case 0: return d3.rgb(d.red, 0, 0);
case 1: return d3.rgb(0, d.green, 0);
// case 2: return "green";
}
})
.transition()
.attr("x1", function(d) { return d.node.parent ? d.node.parent.bx : d.node.bx; })
.attr("y1", function(d) { return d.node.parent ? d.node.parent.by : d.node.by; })
.attr("x2", function(d) { return d.node.bx; })
.attr("y2", function(d) { return d.node.by; })
selection // enter + update
// .transition()
selection.exit()
.remove();
}
}
});
</script>
</body>
</html>
/**
* k-d Tree JavaScript - V 1.01
*
* https://github.com/ubilabs/kd-tree-javascript
*
* @author Mircea Pricop <pricop@ubilabs.net>, 2012
* @author Martin Kleppe <kleppe@ubilabs.net>, 2012
* @author Ubilabs http://ubilabs.net, 2012
* @license MIT License <http://www.opensource.org/licenses/mit-license.php>
*/
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define(['exports'], factory);
} else if (typeof exports === 'object') {
factory(exports);
} else {
factory((root.commonJsStrict = {}));
}
}(this, function (exports) {
function Node(obj, dimension, parent) {
this.obj = obj;
this.left = null;
this.right = null;
this.parent = parent;
this.dimension = dimension;
}
function kdTree(points, metric, dimensions) {
var self = this;
function buildTree(points, depth, parent) {
var dim = depth % dimensions.length,
median,
node;
if (points.length === 0) {
return null;
}
if (points.length === 1) {
return new Node(points[0], dim, parent);
}
points.sort(function (a, b) {
return a[dimensions[dim]] - b[dimensions[dim]];
});
median = Math.floor(points.length / 2);
node = new Node(points[median], dim, parent);
node.left = buildTree(points.slice(0, median), depth + 1, node);
node.right = buildTree(points.slice(median + 1), depth + 1, node);
return node;
}
// Reloads a serialied tree
function loadTree (data) {
// Just need to restore the `parent` parameter
self.root = data;
function restoreParent (root) {
if (root.left) {
root.left.parent = root;
restoreParent(root.left);
}
if (root.right) {
root.right.parent = root;
restoreParent(root.right);
}
}
restoreParent(self.root);
}
// If points is not an array, assume we're loading a pre-built tree
if (!Array.isArray(points)) loadTree(points, metric, dimensions);
else this.root = buildTree(points, 0, null);
// Convert to a JSON serializable structure; this just requires removing
// the `parent` property
this.toJSON = function (src) {
if (!src) src = this.root;
var dest = new Node(src.obj, src.dimension, null);
if (src.left) dest.left = self.toJSON(src.left);
if (src.right) dest.right = self.toJSON(src.right);
// if (src.parent) dest.parent = self.toJSON(src.parent);
return dest;
};
this.insert = function (point) {
function innerSearch(node, parent) {
if (node === null) {
return parent;
}
var dimension = dimensions[node.dimension];
if (point[dimension] < node.obj[dimension]) {
return innerSearch(node.left, node);
} else {
return innerSearch(node.right, node);
}
}
var insertPosition = innerSearch(this.root, null),
newNode,
dimension;
if (insertPosition === null) {
this.root = new Node(point, 0, null);
return;
}
newNode = new Node(point, (insertPosition.dimension + 1) % dimensions.length, insertPosition);
dimension = dimensions[insertPosition.dimension];
if (point[dimension] < insertPosition.obj[dimension]) {
insertPosition.left = newNode;
} else {
insertPosition.right = newNode;
}
};
this.remove = function (point) {
var node;
function nodeSearch(node) {
if (node === null) {
return null;
}
if (node.obj === point) {
return node;
}
var dimension = dimensions[node.dimension];
if (point[dimension] < node.obj[dimension]) {
return nodeSearch(node.left, node);
} else {
return nodeSearch(node.right, node);
}
}
function removeNode(node) {
var nextNode,
nextObj,
pDimension;
function findMin(node, dim) {
var dimension,
own,
left,
right,
min;
if (node === null) {
return null;
}
dimension = dimensions[dim];
if (node.dimension === dim) {
if (node.left !== null) {
return findMin(node.left, dim);
}
return node;
}
own = node.obj[dimension];
left = findMin(node.left, dim);
right = findMin(node.right, dim);
min = node;
if (left !== null && left.obj[dimension] < own) {
min = left;
}
if (right !== null && right.obj[dimension] < min.obj[dimension]) {
min = right;
}
return min;
}
if (node.left === null && node.right === null) {
if (node.parent === null) {
self.root = null;
return;
}
pDimension = dimensions[node.parent.dimension];
if (node.obj[pDimension] < node.parent.obj[pDimension]) {
node.parent.left = null;
} else {
node.parent.right = null;
}
return;
}
// If the right subtree is not empty, swap with the minimum element on the
// node's dimension. If it is empty, we swap the left and right subtrees and
// do the same.
if (node.right !== null) {
nextNode = findMin(node.right, node.dimension);
nextObj = nextNode.obj;
removeNode(nextNode);
node.obj = nextObj;
} else {
nextNode = findMin(node.left, node.dimension);
nextObj = nextNode.obj;
removeNode(nextNode);
node.right = node.left;
node.left = null;
node.obj = nextObj;
}
}
node = nodeSearch(self.root);
if (node === null) { return; }
removeNode(node);
};
this.nearest = function (point, maxNodes, maxDistance) {
var i,
result,
bestNodes;
bestNodes = new BinaryHeap(
function (e) { return -e[1]; }
);
function nearestSearch(node) {
var bestChild,
dimension = dimensions[node.dimension],
ownDistance = metric(point, node.obj),
linearPoint = {},
linearDistance,
otherChild,
i;
function saveNode(node, distance) {
bestNodes.push([node, distance]);
if (bestNodes.size() > maxNodes) {
bestNodes.pop();
}
}
for (i = 0; i < dimensions.length; i += 1) {
if (i === node.dimension) {
linearPoint[dimensions[i]] = point[dimensions[i]];
} else {
linearPoint[dimensions[i]] = node.obj[dimensions[i]];
}
}
linearDistance = metric(linearPoint, node.obj);
if (node.right === null && node.left === null) {
if (bestNodes.size() < maxNodes || ownDistance < bestNodes.peek()[1]) {
saveNode(node, ownDistance);
}
return;
}
if (node.right === null) {
bestChild = node.left;
} else if (node.left === null) {
bestChild = node.right;
} else {
if (point[dimension] < node.obj[dimension]) {
bestChild = node.left;
} else {
bestChild = node.right;
}
}
nearestSearch(bestChild);
if (bestNodes.size() < maxNodes || ownDistance < bestNodes.peek()[1]) {
saveNode(node, ownDistance);
}
if (bestNodes.size() < maxNodes || Math.abs(linearDistance) < bestNodes.peek()[1]) {
if (bestChild === node.left) {
otherChild = node.right;
} else {
otherChild = node.left;
}
if (otherChild !== null) {
nearestSearch(otherChild);
}
}
}
if (maxDistance) {
for (i = 0; i < maxNodes; i += 1) {
bestNodes.push([null, maxDistance]);
}
}
if(self.root)
nearestSearch(self.root);
result = [];
for (i = 0; i < Math.min(maxNodes, bestNodes.content.length); i += 1) {
if (bestNodes.content[i][0]) {
result.push([bestNodes.content[i][0].obj, bestNodes.content[i][1]]);
}
}
return result;
};
this.pointsBFS = function () {
var result = [];
if (!this.root) return result;
var queue = [];
var bounds = { x1: 0, y1: 0, x2: 255, y2: 255 };
var bundle = { node: this.root, bounds: bounds, depth: 0 };
queue.push(bundle);
while(queue.length > 0) {
var bundle = queue.shift();
var node = bundle.node;
var bounds = bundle.bounds;
var depth = bundle.depth;
var object = node.obj;
object.dimension = node.dimension;
object.depth = depth;
node.children = [node.left, node.right];
object.node = node
var leftBounds, rightBounds;
switch(object.dimension) {
case 0:
object.x1 = object.x2 = object.red;
object.y1 = bounds.y1;
object.y2 = bounds.y2;
leftBounds = { x1: bounds.x1, y1: bounds.y1, x2: object.x2, y2: bounds.y2 };
rightBounds = { x1: object.x1, y1: bounds.y1, x2: bounds.x2, y2: bounds.y2 };
break;
case 1:
object.y1 = object.y2 = object.green;
object.x1 = bounds.x1;
object.x2 = bounds.x2;
leftBounds = { x1: bounds.x1, y1: bounds.y1, x2: bounds.x2, y2: object.y2 };
rightBounds = { x1: bounds.x1, y1: object.y1, x2: bounds.x2, y2: bounds.y2 };
break;
}
result.push(object);
//console.log(depth);
// Traverse the tree
if (node.left) {
queue.push({ node: node.left, bounds: leftBounds, depth: depth + 1 });
};
if (node.right) {
queue.push({ node: node.right, bounds: rightBounds, depth: depth + 1 });
};
} // while
return result;
};
this.d3tree = function () {
function dfs (node) {
if (!node) return "null"; // d3 expects JSON like nulls
var object = {};
object.name = node.obj.toString();
if (node.parent) {
object.parent = node.parent.obj.toString();
} else {
object.parent = "null"; // d3 expects JSON like nulls
}
object.children = [dfs(node.left), dfs(node.right)];
return object;
}
return dfs(this.root);
};
this.balanceFactor = function () {
function height(node) {
if (node === null) {
return 0;
}
return Math.max(height(node.left), height(node.right)) + 1;
}
function count(node) {
if (node === null) {
return 0;
}
return count(node.left) + count(node.right) + 1;
}
return height(self.root) / (Math.log(count(self.root)) / Math.log(2));
};
}
// Binary heap implementation from:
// http://eloquentjavascript.net/appendix2.html
function BinaryHeap(scoreFunction){
this.content = [];
this.scoreFunction = scoreFunction;
}
BinaryHeap.prototype = {
push: function(element) {
// Add the new element to the end of the array.
this.content.push(element);
// Allow it to bubble up.
this.bubbleUp(this.content.length - 1);
},
pop: function() {
// Store the first element so we can return it later.
var result = this.content[0];
// Get the element at the end of the array.
var end = this.content.pop();
// If there are any elements left, put the end element at the
// start, and let it sink down.
if (this.content.length > 0) {
this.content[0] = end;
this.sinkDown(0);
}
return result;
},
peek: function() {
return this.content[0];
},
remove: function(node) {
var len = this.content.length;
// To remove a value, we must search through the array to find
// it.
for (var i = 0; i < len; i++) {
if (this.content[i] == node) {
// When it is found, the process seen in 'pop' is repeated
// to fill up the hole.
var end = this.content.pop();
if (i != len - 1) {
this.content[i] = end;
if (this.scoreFunction(end) < this.scoreFunction(node))
this.bubbleUp(i);
else
this.sinkDown(i);
}
return;
}
}
throw new Error("Node not found.");
},
size: function() {
return this.content.length;
},
bubbleUp: function(n) {
// Fetch the element that has to be moved.
var element = this.content[n];
// When at 0, an element can not go up any further.
while (n > 0) {
// Compute the parent element's index, and fetch it.
var parentN = Math.floor((n + 1) / 2) - 1,
parent = this.content[parentN];
// Swap the elements if the parent is greater.
if (this.scoreFunction(element) < this.scoreFunction(parent)) {
this.content[parentN] = element;
this.content[n] = parent;
// Update 'n' to continue at the new position.
n = parentN;
}
// Found a parent that is less, no need to move it further.
else {
break;
}
}
},
sinkDown: function(n) {
// Look up the target element and its score.
var length = this.content.length,
element = this.content[n],
elemScore = this.scoreFunction(element);
while(true) {
// Compute the indices of the child elements.
var child2N = (n + 1) * 2, child1N = child2N - 1;
// This is used to store the new position of the element,
// if any.
var swap = null;
// If the first child exists (is inside the array)...
if (child1N < length) {
// Look it up and compute its score.
var child1 = this.content[child1N],
child1Score = this.scoreFunction(child1);
// If the score is less than our element's, we need to swap.
if (child1Score < elemScore)
swap = child1N;
}
// Do the same checks for the other child.
if (child2N < length) {
var child2 = this.content[child2N],
child2Score = this.scoreFunction(child2);
if (child2Score < (swap == null ? elemScore : child1Score)){
swap = child2N;
}
}
// If the element needs to be moved, swap it, and continue.
if (swap != null) {
this.content[n] = this.content[swap];
this.content[swap] = element;
n = swap;
}
// Otherwise, we are done.
else {
break;
}
}
}
};
this.kdTree = kdTree;
exports.kdTree = kdTree;
exports.BinaryHeap = BinaryHeap;
}));
#control-button {
position: absolute;
margin-top: 10px;
margin-left: 10px;
opacity: 0.0;
transition: opacity .5s ease-in-out;
}
#hint-text {
color: #bfbfbf;
text-align: center;
width: 460px;
position: absolute;
margin-top: 210px;
margin-left: 10px;
opacity: 1.0;
transition: opacity .25s ease-in-out;
}
#picked {
width: 50px;
height: 50px;
border: 1px solid #000;
}
#left {
float: left;
}
#results {
float: left;
margin-left: 10px;
}
body {
font-family: sans-serif;
}
li {
padding: 5px;
}
ul {
list-style: none;
padding-left: 0px;
}
.tooltip {
height: 26px;
color: #333;
background-color: rgba(255,255,255,1.0);
border: 1px rgba(0,0,0,0.66) solid;
border-radius: 13px;
/*padding: 15px;*/
/*padding-left: 15px;*/
padding-right: 13px;
/*padding-top: 10px;*/
}
.tooltip p {
display: inline-block;
margin-top: -10px;
margin-bottom: 0px;
}
.tooltip-swatch {
display: inline-block;
height: 26px;
width: 26px;
border-radius: 13px;
}
svg.kdtree {
border: 1px solid #DDD;
}
#kdtree {
display: inline-block;
}
/* binary tree */
#binarytree {
display: inline-block;
}
svg.binarytree {
border: 1px solid #DDD;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 3px;
}
.node text { font: 12px sans-serif; }
.link {
fill: none;
stroke: #ccc;
stroke-width: 2px;
}
.sp-picker-container {
margin: 20px;
}
.heading {
margin-left: 30px;
}
#results ul {
margin-top: 25px;
}
#results ul li div {
border-radius: 15px;
}
@jkcen
Copy link

jkcen commented Jul 2, 2018

Hi, is spectrum.css missing?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment