|
<!DOCTYPE html> |
|
<meta charset="utf-8"> |
|
<body> |
|
<script src="//d3js.org/d3.v4.js"></script> |
|
<script> |
|
|
|
var width = 960, |
|
height = 300, |
|
τ = 2 * Math.PI; |
|
|
|
var nodes = d3.range(800).map(function () { |
|
var angle = Math.random() * 2 * Math.PI, radius = 60 * Math.sqrt(Math.random()); |
|
return { |
|
x: width/4 + radius * Math.sin(angle), |
|
y: height/2 + radius * Math.cos(angle), |
|
vx: Math.sin(angle) * Math.random(), |
|
vy: Math.cos(angle) * Math.random(), |
|
}; |
|
}); |
|
|
|
var force = d3.forceSimulation() |
|
.nodes(nodes) |
|
.force("collide", d3.forceCollide(2)) |
|
.force("bounce-on-container", function () { |
|
for (var i = 0, n = nodes.length, node; i < n; ++i) { |
|
node = nodes[i]; |
|
var dx = (node.x + node.vx) / width - 1 / 2, |
|
dy = (node.y + node.vy) / height - 1 / 2; |
|
if (dx*dx > 0.16) { |
|
node.vx *= -1; |
|
} |
|
if (dy*dy > 0.16) { |
|
node.vy *= -1; |
|
} |
|
|
|
} |
|
}) |
|
|
|
.force("internal-barrier", function (alpha) { |
|
for (var i = 0, n = nodes.length, node; i < n; ++i) { |
|
node = nodes[i]; |
|
var dx0 = (node.x) / width - 1 / 2, |
|
dy0 = (node.y) / height - 1 / 2; |
|
var dx1 = (node.x + node.vx) / width - 1 / 2, |
|
dy1 = (node.y + node.vy) / height - 1 / 2; |
|
|
|
if (dx0 * dx1 < 0 && Math.abs(dy0) > aperture(alpha) ) { |
|
node.vx *= -1; |
|
} |
|
} |
|
}) |
|
.on("tick", ticked) |
|
.alphaDecay(0.01) |
|
.alphaMin(0) |
|
.velocityDecay(0.00001); |
|
|
|
function aperture(alpha) { |
|
return 0.2 / (1-Math.log(alpha)); |
|
} |
|
|
|
var canvas = d3.select("body").append("canvas") |
|
.attr("width", width) |
|
.attr("height", height) |
|
.on('click', click); |
|
|
|
var svg = d3.select("body").append("svg") |
|
.attr("width", width) |
|
.attr("height", 100); |
|
|
|
var context = canvas.node().getContext("2d"); |
|
|
|
function click() { |
|
force.alpha(1).restart(); |
|
} |
|
|
|
|
|
var speeds = [], ct = 0; |
|
|
|
var y = d3.scaleLinear() |
|
.range([100,0]); |
|
|
|
var line = d3.area().y0(100), |
|
speedline = svg.append('path') |
|
.attr('fill','none') |
|
.attr('stroke', 'black'); |
|
|
|
|
|
function ticked() { |
|
|
|
var speed = d3.mean(nodes.map(function(n){ |
|
return n.vx*n.vx + n.vy*n.vy; |
|
})); |
|
if (ct++ > width) speeds.shift(); |
|
speeds.push(speed); |
|
y.domain([0,d3.max(speeds)]); |
|
speedline.attr('d', line(speeds.map(function(d,i){ |
|
return [i,y(d)]; |
|
}))); |
|
|
|
context.clearRect(0, 0, width, height); |
|
|
|
context.strokeStyle = "#444"; |
|
context.beginPath(); |
|
// rectangle |
|
context.moveTo(width * 0.1, height * 0.1); |
|
context.lineTo(width * 0.1, height * 0.9); |
|
context.lineTo(width * 0.9, height * 0.9); |
|
context.lineTo(width * 0.9, height * 0.1); |
|
context.lineTo(width * 0.1, height * 0.1); |
|
context.moveTo(width * 0.5, height * 0.1); |
|
// internal barrier |
|
context.lineTo(width * 0.5, height * (0.5 - aperture(force.alpha()))); |
|
context.moveTo(width * 0.5, height * 0.9); |
|
context.lineTo(width * 0.5, height * (0.5 + aperture(force.alpha()))); |
|
context.lineWidth = 1; |
|
context.stroke() |
|
|
|
context.beginPath(); |
|
for (var i = 0, n = nodes.length; i < n; ++i) { |
|
var node = nodes[i]; |
|
context.moveTo(node.x, node.y); |
|
context.arc(node.x, node.y, 1, 0, τ); |
|
} |
|
context.fillStyle = "#644"; |
|
context.fill(); |
|
|
|
} |
|
|
|
</script> |