|
<!doctype html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<title>Collision Detection</title> |
|
<link rel="stylesheet" href="collision-detection.css"> |
|
<script src="http://d3js.org/d3.v3.min.js"></script> |
|
</head> |
|
<body> |
|
<div class="controls"> |
|
<!-- <label>Gravity: <input id="gravitySlider" type="range" min=0 max=2 step=0.1> <span id="gravityValue"></span></label> --> |
|
<label>Charge Scaling: <input id="chargeSlider" type="range" min=0 max=400 step=10> <span id="chargeValue"></span></label> |
|
<label>Node Scaling: <input id="nodeSlider" type="range" min=0 max=4 step=0.1> <span id="nodeValue"></span></label> |
|
|
|
</div> |
|
<script> |
|
var width = 800; |
|
var height = 400; |
|
var numPoints = 200; |
|
var initGravity = 1; |
|
var initChargeScaling = 0; |
|
var initNodeScaling = 2; |
|
var svg = d3.select("body").append("svg") |
|
.attr("width", width).attr("height", height) |
|
.attr("viewBox", "0 0 " + width + " " + height) |
|
.attr("preserveAspectRatio", "xMidYMid"); |
|
|
|
// Buncha random nodes |
|
var force = d3.layout.force().size([width,height]); |
|
var nodes = d3.range(numPoints).map(function(d,i){ |
|
return { name: "Node " + i, radius: 5 }; |
|
}); |
|
|
|
// Buncha random links between nodes |
|
var links = d3.range(numPoints).map(function(d,i){ |
|
var src = Math.floor(Math.random() * numPoints); |
|
var tar = Math.floor(Math.random() * numPoints); |
|
if (tar === src) { |
|
(src === 0) ? tar = 1 : tar = src - 1; |
|
} |
|
return { source: src, target: tar }; |
|
}); |
|
|
|
// Push links first so links get drawn *behind* the nodes |
|
force.links(links); |
|
force.nodes(nodes); |
|
force.gravity(initGravity); |
|
|
|
var linkSelection = svg.selectAll("line.link").data(links); |
|
linkSelection.enter() |
|
.insert("line") |
|
.classed("link", true); |
|
|
|
var nodeSelection = svg.selectAll("circle.node").data(nodes); |
|
nodeSelection.enter() |
|
.append("circle") |
|
.classed("node", true); |
|
|
|
force.on("start", function(){ |
|
force.charge(function(node,i){ |
|
var chargeScale = d3.select("#chargeSlider").property("valueAsNumber"); |
|
return -1*(weightedValue(chargeScale, node.weight, 30)); |
|
}); |
|
}); |
|
|
|
force.on("tick", function(e){ |
|
var nodeScaling = d3.select("#nodeSlider").property("valueAsNumber"); |
|
var q = d3.geom.quadtree(nodes), |
|
i = 0, |
|
n = nodes.length; |
|
|
|
while (++i < n) q.visit(collide(nodes[i], nodeScaling)); |
|
|
|
linkSelection.attr("x1", function(d){return d.source.x;}) |
|
.attr("y1", function(d){return d.source.y;}) |
|
.attr("x2", function(d){return d.target.x;}) |
|
.attr("y2", function(d){return d.target.y;}); |
|
|
|
nodeSelection.attr("cx", function(d){return d.x;}) |
|
.attr("cy", function(d) {return d.y}) |
|
.attr("r", function(d){return weightedValue(d.weight, nodeScaling, 2);}); |
|
}); |
|
|
|
d3.select("#chargeSlider").on("change", function(){ |
|
var val = this.value; |
|
d3.select("#chargeValue").text(val); |
|
force.start(); |
|
}); |
|
d3.select("#nodeSlider").on("change", function(){ |
|
var val = this.value; |
|
d3.select("#nodeValue").text(val); |
|
force.resume(); |
|
}); |
|
|
|
function collide(node, nodeScaling) { |
|
var radius = weightedValue(node.weight,nodeScaling,2); |
|
var r = radius,// + 16, |
|
nx1 = node.x - r, |
|
nx2 = node.x + r, |
|
ny1 = node.y - r, |
|
ny2 = node.y + r; |
|
return function(quad, x1, y1, x2, y2) { |
|
if (quad.point && (quad.point !== node)) { |
|
var x = node.x - quad.point.x, |
|
y = node.y - quad.point.y, |
|
l = Math.sqrt(x * x + y * y), |
|
r = radius + weightedValue(quad.point.weight,nodeScaling,2); |
|
if (l < r) { |
|
l = (l - r) / l * .5; |
|
node.x -= x *= l; |
|
node.y -= y *= l; |
|
quad.point.x += x; |
|
quad.point.y += y; |
|
} |
|
} |
|
return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1; |
|
}; |
|
} |
|
|
|
function weightedValue(weight,scalingFactor,minimum) { |
|
return weight * scalingFactor + minimum; |
|
} |
|
|
|
// Main |
|
d3.select("#chargeSlider").property("value", initChargeScaling); |
|
d3.select("#chargeValue").text(initChargeScaling); |
|
d3.select("#nodeSlider").property("value", initNodeScaling); |
|
d3.select("#nodeValue").text(initNodeScaling); |
|
force.start(); |
|
|
|
</script> |
|
</body> |
|
</html> |