Skip to content

Instantly share code, notes, and snippets.

@Fil
Last active Oct 31, 2018
Embed
What would you like to do?
Expand and fill force (work in progress)
license: gpl-3.0

A force that tries to expand to lower density regions.

<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="https://d3js.org/d3.v4.js"></script>
<script src="https://raw.githack.com/d3/d3-plugins/master/hexbin/hexbin.js"></script>
<script>
var width = 960,
height = 500,
τ = 2 * Math.PI,
maxLength = 10000,
maxLength2 = maxLength * maxLength;
var hexbin = d3.hexbin()
.size([width, height])
.radius(20);
var nodes = d3.range(500).map(function() {
return {
x: width * (5 + Math.random())/11,
y: height * (5 + Math.random())/11,
r: 2 + 6 * Math.random(),
c: d3.rgb(255*Math.random(), 255*Math.random(), 255*Math.random())
};
});
var step = 0;
var force = d3.forceSimulation()
.nodes(nodes)
.force('manybody', d3.forceManyBody().strength(-1))
.force('grid', function(alpha) {
var g = 40;
for(var i=0, n=nodes.length; i<n; i++) {
var d = nodes[i],
dx = d.x - g * Math.round(d.x/g),
dy = d.y - g * Math.round(d.y/g);
d.vx += -dx / 10;
d.vy += -dy / 10;
};
})
.force('expand', function(alpha) {
var radius = 10 + 40 * Math.random(),
hx = (Math.random() - 0.5)*radius,
hy = (Math.random() - 0.5)*radius,
angle = Math.random() * 2 * Math.PI,
s = Math.sin(angle),
c = Math.cos(angle),
e = 3; // alpha * 10
hexbin
.radius(radius)
.x(d => s * d.x + c * d.y + hx)
.y(d => c * d.x - s * d.y + hy);
var hex = hexbin(nodes)
.map(h => {
h.weight = d3.sum(h, d => d.r);
return h;
});
var hexmap = d3.map(hex, d => [d.i, d.j]);
hex
.forEach(function(h) {
var dest = [];
for (var i = -1; i < 2; i++){
for (var j = -1; j < 2; j++){
var k = [h.i + i, h.j + j]
if (!hexmap.has(k) || hexmap.get(k).weight < h.weight) {
dest.push([i,j]);
}
}
}
if (dest.length > 0)
h.forEach(function(d){
var l = dest[Math.floor(Math.random()*dest.length)];
d.vx += (s * l[0] + c * l[1]) * e;
d.vy += (c * l[0] - s * l[1]) * e;
});
});
})
.force('bounds', function(alpha) {
for(var i=0, n=nodes.length; i<n; i++) {
var d = nodes[i],
dx = d.x - width/2,
dy = d.y - height/2,
dist2 = Math.max(dx*dx , dy*dy),
over = Math.sqrt(dist2) - 200;
if (over > 0) {
d.vx = -dx / 200 * (over+1);
d.vy = -dy / 200 * (over+1);
}
};
})
.force('collide', d3.forceCollide(d => 1 + d.r || 1))
.on("tick", ticked);
var canvas = d3.select("body").append("canvas")
.attr("width", width)
.attr("height", height)
.on("ontouchstart" in document ? "touchmove" : "mousemove", moved);
var context = canvas.node().getContext("2d");
function moved() {
var p1 = d3.mouse(this);
nodes[0].fx = p1[0];
nodes[0].fy = p1[1];
force.alpha(0.1).restart();
}
function ticked() {
//var diagram = voronoi(nodes);
var links = [];//diagram.links();
context.clearRect(0, 0, width, height);
context.beginPath();
for (var i = 0, n = links.length; i < n; ++i) {
var link = links[i],
dx = link.source.x - link.target.x,
dy = link.source.y - link.target.y;
if (dx * dx + dy * dy < maxLength2) {
context.moveTo(link.source.x, link.source.y);
context.lineTo(link.target.x, link.target.y);
}
}
context.lineWidth = 1;
context.strokeStyle = "#bbb";
context.stroke();
for (var i = 0, n = nodes.length; i < n; ++i) {
var node = nodes[i];
context.beginPath();
context.moveTo(node.x, node.y);
context.arc(node.x, node.y, node.r, 0, τ);
context.fillStyle = node.c;
context.fill();
}
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment