Mouseover to repel nodes. Adapted from mbostock's talk on force layouts and his related code.
-
-
Save bxt/30c8c331f98580a538def5806f8bdf94 to your computer and use it in GitHub Desktop.
Collision Detection
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
license: gpl-3.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"> | |
<title>BXTs kunterbuntes Bällebad</title> | |
<style> | |
* { padding: 0; margin: 0; } | |
body { color: #666; font-size: 8px; background: #000; font-family: 'Arial Black'; } | |
a { color: rgba(255,150,0,0.6); } | |
p { position: absolute; bottom: 10px; right: 10px; } | |
svg { display: block; } | |
</style> | |
</head> | |
<body> | |
<p>© 2017 by <a href="http://bernhardhaeussner.de/">BXT</a>. Code on <a href="https://gist.github.com/bxt/30c8c331f98580a538def5806f8bdf94">GitHub</a>.</p> | |
<script src="http://d3js.org/d3.v3.min.js"></script> | |
<script> | |
const COLOR_SCHEMES = { | |
mine: ['#e70','#A00','#d21','#fB0','#f90','#600','#d20','#ff0','#406'], | |
// http://www.colourlovers.com/palette/3282945/Happiness4 | |
happiness: ["#342D43","#F15C84","#F8A94A","#E5C44B","#72C980"], | |
// http://www.colourlovers.com/palette/2173182/The_Koi_Pond | |
koipond: ["#6E133F","#D24414","#FFB20E","#F5E7AA","#256C64"], | |
// http://www.colourlovers.com/palette/92095/Giant_Goldfish | |
goldfish: ["#69D2E7","#A7DBD8","#E0E4CC","#F38630","#FA6900"], | |
frost: ["#69D2E7","#A7DBD8","#E0E4CC"], | |
luv: ["#6E133F","#F15C84",'#600','#A00','#d20'], | |
}; | |
const USE_CIRCLES = false; | |
const TEXT_SCALE = USE_CIRCLES ? 1.8 : 2.1; | |
const TEXTS = ["BXT", "★", "O", "OÖØ", "☯", "❄", "❤", "!?", "FERD", "MULTIPLIKATION"]; | |
const COLORS = ["mine", "happiness", "goldfish", "goldfish", "koipond", "frost", "luv", "mine", "mine", "mine"]; | |
var current_text = 0; | |
const pick = (array, i) => array[i % array.length]; | |
function text(d, i) { | |
return pick(TEXTS[current_text], i); | |
} | |
function color(d, i) { | |
return pick(COLOR_SCHEMES[COLORS[current_text]], i); | |
} | |
const width = window.innerWidth; | |
const height = window.innerHeight; | |
const nodes = d3.range(200).map(() => ({radius: Math.random() * 12 + 4})); | |
const root = nodes[0]; | |
root.radius = 0; | |
root.fixed = true; | |
const force = d3.layout.force() | |
.gravity(0.05) | |
.charge((d, i) => i ? 0 : -2000) | |
.nodes(nodes) | |
.size([width, height]); | |
force.start(); | |
const svg = d3.select("body").append("svg") | |
.attr("width", width) | |
.attr("height", height); | |
if (USE_CIRCLES) { | |
svg.selectAll("circle") | |
.data(nodes.slice(1)) | |
.enter().append("circle") | |
.attr("r", d => d.radius * 0.9) | |
.style("fill", "#000") | |
.style("stroke", color) | |
.style("stroke-width", (d, i) => d.radius*0.2); | |
} | |
svg.selectAll("text") | |
.data(nodes.slice(1)) | |
.enter().append("text") | |
.attr("text-anchor", "middle") | |
.attr("font-size", d => d.radius * TEXT_SCALE) | |
.text(text) | |
.style("fill", color); | |
force.on("tick", function(e) { | |
const q = d3.geom.quadtree(nodes); | |
for (var i = 0, n = nodes.length; i < n; i++) { | |
q.visit(collide(nodes[i])); | |
} | |
if (USE_CIRCLES) { | |
svg.selectAll("circle") | |
.attr("cx", d => d.x) | |
.attr("cy", d => d.y); | |
} | |
svg.selectAll("text") | |
.attr("transform", (d, i) => ` | |
translate(${d.x} ${d.y}) | |
rotate(${(i * 457.3234) % 60 - 30}) | |
translate(0 ${d.radius*0.7}) | |
`); | |
}); | |
svg.on("mousemove", function() { | |
const p1 = d3.mouse(this); | |
root.px = p1[0]; | |
root.py = p1[1]; | |
force.resume(); | |
}); | |
svg.on("click", function() { | |
current_text = (current_text+1) % TEXTS.length; | |
svg.selectAll("text") | |
.data(nodes.slice(1)) | |
.text(text) | |
.style("fill", color); | |
}); | |
d3.select("body") | |
.on("touchstart", touch) | |
.on("touchmove", touch) | |
.on("touchend", touch); | |
function touch() { | |
d3.event.preventDefault(); | |
const p1 = d3.touches(svg.node()); | |
root.px = p1[0]; | |
root.py = p1[1]; | |
force.resume(); | |
} | |
function collide(node) { | |
const r = node.radius + 16; | |
const nx1 = node.x - r; | |
const nx2 = node.x + r; | |
const ny1 = node.y - r; | |
const ny2 = node.y + r; | |
return function(quad, x1, y1, x2, y2) { | |
if (quad.point && (quad.point !== node)) { | |
var x = node.x - quad.point.x; | |
var y = node.y - quad.point.y; | |
var l = Math.sqrt(x * x + y * y); | |
const r = node.radius + quad.point.radius; | |
if (l < r) { | |
l = (l - r) / l * 0.5; | |
x *= l; | |
y *= l; | |
node.x -= x; | |
node.y -= y; | |
quad.point.x += x; | |
quad.point.y += y; | |
} | |
} | |
return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1; | |
}; | |
} | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment