A combination of Mike Bostock's NBody Problem using a Force Layout, combined with one of his point density contours examples. Here's a quick canvas mock up - and here's a trippier one when you forget to clean the canvas each tick.
Using contours might allow for some sort of alternative gooey effect with nodes when splitting or merging nodes.
xxxxxxxxxx
<meta charset="utf-8">
<style>
</style>
<svg width="960" height="500"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-contour.v1.min.js"></script>
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
<script>
var svg = d3.select("svg");
var width = svg.attr("width")
var height = svg.attr("height")
var nodes = d3.range(500).map(function() {
return {}
});
var color = d3.scaleSequential(d3.interpolateYlGnBu)
.domain([0, 0.016]); // Points per square pixel.
svg.append("g")
.attr("fill", "none")
.attr("stroke", "#000")
.attr("stroke-width", 0.5)
.attr("stroke-linejoin", "round")
.selectAll("path")
.data(d3.contourDensity()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.size([width, height])
.thresholds([0.02,0.06,0.08,0.16,0.32,0.64,1.28])
.bandwidth(10)
(nodes))
.enter().append("path")
.attr("fill", function(d) { return color(d.value); })
.attr("d", d3.geoPath());
var simulation = d3.forceSimulation()
.force("charge", d3.forceManyBody().strength(0.05))
.force("center", d3.forceCenter(width / 2, height / 2))
.alphaDecay(0)
.velocityDecay(0)
.nodes(nodes)
.on("tick", ticked);
function ticked() {
svg.selectAll("path")
.data(d3.contourDensity()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.size([width, 960])
.thresholds([0.02,0.06,0.08,0.16,0.32,0.64,1.28])
.bandwidth(10)
(nodes))
.attr("fill", function(d) { return color(d.value); })
.attr("d", d3.geoPath());
}
</script>
https://d3js.org/d3.v4.min.js
https://d3js.org/d3-contour.v1.min.js
https://d3js.org/d3-scale-chromatic.v1.min.js