Skip to content

Instantly share code, notes, and snippets.

@Fil
Last active November 20, 2016 12:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Fil/541b5b94a5419e408fcb51fb9d30861e to your computer and use it in GitHub Desktop.
Save Fil/541b5b94a5419e408fcb51fb9d30861e to your computer and use it in GitHub Desktop.
Vortex Emergence
license: gpl-3.0
border: no
scrolling: no
height: 500
<!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(4000).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(3).strength(0.7))
.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;
if (dx0 * dx1 < 0 && Math.abs(dy0) > aperture(alpha) ) {
node.vx *= -1;
}
}
})
.force("limit-speed", function (alpha) {
for (var i = 0, n = nodes.length, node; i < n; ++i) {
node = nodes[i];
var E = 1 + node.vx*node.vx + node.vy*node.vy,
mult = 1 + 0.03 * Math.log(6/E);
node.vx *= mult;
node.vy *= mult;
}
})
.on("tick", ticked)
.alphaDecay(0.01)
.alphaMin(0)
.velocityDecay(0);
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 speeds1 = [], speeds2 = [], ct = 0;
var y = d3.scaleLinear()
.range([100,0]);
var line = d3.line(),
speedline1 = svg.append('path')
.attr('fill','none')
.attr('stroke', 'red'),
speedline2 = svg.append('path')
.attr('fill','none')
.attr('stroke', 'green');
function ticked() {
var speed1 = d3.mean(nodes
.filter(function(n) {
return n.x> width/2;
})
.map(function(n){
return n.vx*n.vx + n.vy*n.vy;
})),
speed2 = d3.mean(nodes
.filter(function(n) {
return n.x < width/2;
})
.map(function(n){
return n.vx*n.vx + n.vy*n.vy;
}));
if (ct++ > width) {speeds1.shift();speeds2.shift();}
speeds1.push(speed1 || 0);
speeds2.push(speed2 || 0)
y.domain([0,d3.max(d3.merge([speeds1,speeds2]))]);
speedline1.attr('d', line(speeds1.map(function(d,i){
return [i,y(d)];
})));
speedline2.attr('d', line(speeds2.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.5, 0, τ);
}
context.fillStyle = "#644";
context.fill();
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment