Skip to content

Instantly share code, notes, and snippets.

@ludwigschubert
Last active July 2, 2018 22:17
Show Gist options
  • Select an option

  • Save ludwigschubert/0a300d8a41d9e6b49144fff8b62637f8 to your computer and use it in GitHub Desktop.

Select an option

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
Copy Markdown

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