|
<meta charset="utf-8"> |
|
<style> |
|
|
|
.layer { |
|
opacity: 0.5; |
|
} |
|
|
|
.particle .seed { |
|
fill: black; |
|
} |
|
|
|
.particle .influence-zone { |
|
fill: url("#radial-gradient"); |
|
stroke: none; |
|
} |
|
|
|
</style> |
|
<body> |
|
<svg> |
|
<defs> |
|
<radialGradient id="radial-gradient" cx="50%" cy="50%" r="50%"> |
|
<stop offset="25%" stop-color="white"></stop> |
|
<stop offset="100%" stop-color="black"></stop> |
|
</radialGradient> |
|
</defs> |
|
</svg> |
|
<script src="https://d3js.org/d3.v4.min.js"></script> |
|
<script src="https://raw.githack.com/Kcnarf/d3-distanceLimitedVoronoi/master/distance-limited-voronoi.js"></script> |
|
<script> |
|
var width = 960, |
|
height = 500, |
|
influenceRadius = 20; |
|
|
|
var layerCount = 2, |
|
particleCountPerLayer = 25, |
|
particles = new Array(layerCount); |
|
|
|
for (var l = 0; l < layerCount; ++l) { |
|
particles[l] = new Array(particleCountPerLayer); |
|
for (var i = 0; i < particleCountPerLayer; ++i) { |
|
particles[l][i] = { |
|
x: Math.random() * width, |
|
y: Math.random() * height, |
|
vx: 0, |
|
vy: 0 |
|
}; |
|
} |
|
} |
|
|
|
var limitedVoronoi = d3.distanceLimitedVoronoi() |
|
.limit(influenceRadius) |
|
.extent([[-influenceRadius,-influenceRadius],[width+influenceRadius,height+influenceRadius]]) |
|
.x( function(d) { return d.x }) |
|
.y( function(d) { return d.y }); |
|
|
|
var svg = d3.select("svg"); |
|
svg.attr("width", width) |
|
.attr("height", height); |
|
|
|
svg.selectAll("g").data(d3.range(layerCount)) |
|
.enter() |
|
.append("g") |
|
.classed("layer", true) |
|
.attr("id", function(d) { return "layer-"+d; }); |
|
|
|
for (var l = 0; l < layerCount; l++) { |
|
var layer = svg.select("#layer-"+l); |
|
var clipPathName = "clip-"+l+"-"; |
|
var drawnParticles = layer.selectAll(".particle").data(particles[0]); |
|
enteringParticles = drawnParticles.enter() |
|
.append("g") |
|
.classed("particle", true); |
|
enteringParticles |
|
.append("clipPath") |
|
.attr("id", function(d,i){ return clipPathName+i; }) |
|
.append("path") |
|
.classed("clip", true); |
|
enteringParticles.append("circle") |
|
.classed("influence-zone", true) |
|
.attr("r", influenceRadius) |
|
.attr("clip-path", function(d, i){ return "url(#"+clipPathName+i+")"; }); |
|
} |
|
|
|
d3.timer(function(elapsed) { |
|
for (var l = 0; l < layerCount; l++) { |
|
var layer = svg.select("#layer-"+l); |
|
for (var i = 0; i < particleCountPerLayer; ++i) { |
|
var p = particles[l][i]; |
|
p.x += p.vx; |
|
p.y += p.vy; |
|
if (p.x < -influenceRadius) { |
|
p.x += (width+2*influenceRadius); |
|
} else if (p.x > width+influenceRadius) { |
|
p.x -= (width+2*influenceRadius); |
|
} |
|
if (p.y < -influenceRadius) { |
|
p.y += (height+2*influenceRadius); |
|
} else if (p.y > height+influenceRadius) { |
|
p.y -= (height+2*influenceRadius); |
|
} |
|
p.vx += 0.2 * (Math.random() - .5) - 0.01 * p.vx; |
|
p.vy += 0.2 * (Math.random() - .5) - 0.01 * p.vy; |
|
} |
|
|
|
var limitedCells = limitedVoronoi(particles[l]); |
|
|
|
layer.selectAll(".particle .influence-zone").data(limitedCells) |
|
.attr("cx", function(d) { return d.datum.x; }) |
|
.attr("cy", function(d) { return d.datum.y; }); |
|
layer.selectAll(".particle .clip").data(limitedCells) |
|
.attr("d", function(d) { return d.path; }); |
|
} |
|
}); |
|
|
|
</script> |
|
</body> |