D3
OG
Old school D3 from simpler times
All examples
By author
By category
About
tonyhschu
Full window
Github gist
Boid Sort of Works
Built with
blockbuilder.org
<!DOCTYPE html> <head> <meta charset="utf-8"> <script src="https://d3js.org/d3.v4.min.js"></script> <style> body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; } </style> </head> <body> <script> console.clear() // Feel free to change or delete any of the code you see in this editor! var NUM_OF_NODES = 130 var WIDTH = 960 var HEIGHT = 500 var TICKS = 0 var LOCAL_DIST = 30 var TAU = 2 * Math.PI var mouse = [0, 0] var forceX = d3.forceX().strength(0.01) var forceY = d3.forceY().strength(0.01) function sigmoid(t) { return 1/(1+Math.pow(Math.E, -t)); } function normalize(theta) { var t = theta % TAU if (t > Math.PI) { return t - TAU } if (t < -Math.PI) { return t + TAU } return t } var test = Math.PI function subtractAngle(a, b) { return normalize(normalize(a) - normalize(b)) } var forceSpiral = function(alpha) { for (var i = 0, n = nodes.length, node, k = alpha * 0.1; i < n; ++i) { node = nodes[i]; var dx = node.x - mouse[0] var dy = node.y - mouse[1] var dist = Math.sqrt(dx * dx + dy * dy) var dForce = (sigmoid((dist - 80) / 60) - 0.5) //var dForce = (sigmoid((dist - 150) / 20) - 0.5) * -2 var backgroundTheta = Math.atan2(dy, dx) + Math.PI / 2 + Math.PI * dForce var dt = subtractAngle(node.theta, backgroundTheta) node.theta = normalize(node.theta + dt * 0.1 * Math.abs(dForce)) node.vx += Math.cos(node.theta) * -node.velocity * k node.vy += Math.sin(node.theta) * -node.velocity * k var r = Math.round(123 - dForce * 123) var g = Math.round(123 + dForce * 60) node.color = 'rgb('+ r + ',' + g + ', 0)'; } } var forceB = function(alpha) { for (var i = 0, n = nodes.length, node, k = alpha * 0.1; i < n; ++i) { node = nodes[i]; var local = nodes.filter(function(n) { var sightX = node.x + Math.cos(node.theta) * 12 var sightY = node.y + Math.sin(node.theta) * 12 var dx = n.x - sightX var dy = n.y - sightY var dist = Math.sqrt(dx * dx + dy * dy) return dist < LOCAL_DIST }) if (local.length > 0) { var localCentroid = local.reduce(function(prev, curr) { return [prev[0] + curr.x, prev[1] + curr.y] }, [0, 0]) .map(function(sum) { return sum / local.length }) var localAlignment = local.reduce(function(prev, curr) { return prev + curr.theta }, 0) / local.length var ld = subtractAngle(node.theta, localAlignment) // cohesion delta var cx = node.x - localCentroid[0] var cy = node.y - localCentroid[1] var distFromCentroid = Math.sqrt(cx * cx + cy * cy) var cohesion = Math.atan2(node.y - localCentroid[1], node.x - localCentroid[0]) var cd = subtractAngle(node.theta, cohesion) // cohesion delta var cohesionPower = sigmoid(distFromCentroid - 120) / 2 node.theta += cd * cohesionPower // node.theta += ld * 0.005 } } } var simulation = d3.forceSimulation() .force("collide",d3.forceCollide(function(d){ return d.r }).iterations(16)) .force("boid", forceB) .force("spiral", forceSpiral) .alphaDecay(0) var svg = d3.select("body").append("svg") .attr("style", "background: #333") .attr("width", WIDTH) .attr("height", HEIGHT) .on("mousemove", function() { mouse = d3.mouse(this); forceX.x(mouse[0]) forceY.y(mouse[1]) simulation.alpha(1) simulation.restart(); }) var nodes = d3.range(NUM_OF_NODES).map(function(key) { return { key: key, x: WIDTH / 2, y: HEIGHT / 2, r: 5, theta: Math.random() * Math.PI * 2, velocity: 12 } }) var triangles = svg.append("g") .attr("class", "circles") .selectAll(".triangle") .data(nodes) .enter().append("g") .attr("class", "triangle") .each(function(d) { var layer = d3.select(this) layer.append('path') .attr('d', 'M -7, 0 L 5, 5 L 5, -5 Z') }) var paths = svg.selectAll("path") var ticked = function() { TICKS += 1 triangles .attr("transform", function(d) { return "translate(" + d.x + ", " + d.y + ") rotate(" + (d.theta / Math.PI * 180) + ")"; }) paths.each(function(d, i) { d3.select(this).attr('fill', nodes[i].color) }) } simulation .nodes(nodes) .on("tick", ticked); </script> </body>
https://d3js.org/d3.v4.min.js