Built with blockbuilder.org
Last active
March 6, 2017 22:13
Modifying a Force Layout with Voronoi Polygons for Dragging and Mouseover of Grouped Elements
This file contains hidden or 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: mit |
This file contains hidden or 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> | |
<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; } | |
path { | |
pointer-events: all; | |
fill: none; | |
stroke: #666; | |
stroke-opacity: 0.2; | |
} | |
.active path { | |
fill: #111; | |
opacity: 0.05; | |
} | |
.active text { | |
visibility: visible; | |
} | |
.active circle { | |
stroke: #000; | |
stroke-width: 1.5px; | |
} | |
svg { | |
border: 1px solid #888; | |
} | |
.links { | |
stroke: #000; | |
stroke-width: 1.5; | |
} | |
.nodes { | |
stroke-width: 1.5; | |
} | |
text { | |
pointer-events: none; | |
font: 1.8em sans-serif; | |
visibility: hidden; | |
} | |
</style> | |
</head> | |
<body> | |
<script> | |
var svg = d3.select("body").append("svg") | |
.attr("width", 960) | |
.attr("height", 500); | |
var width = +svg.attr("width"), | |
height = +svg.attr("height"), | |
color = d3.scaleOrdinal(d3.schemeCategory10); | |
var a = {id: "a"}, | |
b = {id: "b"}, | |
c = {id: "c"}, | |
data = [a, b, c], | |
links = []; | |
var simulation = d3.forceSimulation(data) | |
.force("charge", d3.forceManyBody().strength(-10)) | |
.force("link", d3.forceLink(links).distance(200)) | |
.force("center", d3.forceCenter(width / 2, height / 2)) | |
.alphaTarget(1) | |
.on("tick", ticked); | |
var link = svg.append("g").attr("class", "links").selectAll(".link"), | |
node = svg.append("g").attr("class", "nodes").selectAll(".node"); | |
var voronoi = d3.voronoi() | |
.x(function(d) { return d.x; }) | |
.y(function(d) { return d.y; }) | |
.extent([[-1, 1], [width + 1, height + 1]]); | |
update(); | |
d3.timeout(function() { | |
links.push({source: a, target: b}); // Add a-b. | |
links.push({source: b, target: c}); // Add b-c. | |
links.push({source: c, target: a}); // Add c-a. | |
update(); | |
}, 1000); | |
d3.interval(function() { | |
data.pop(); // Remove c. | |
links.pop(); // Remove c-a. | |
links.pop(); // Remove b-c. | |
update(); | |
}, 5000, d3.now()); | |
d3.interval(function() { | |
data.push(c); // Re-add c. | |
links.push({source: b, target: c}); // Re-add b-c. | |
links.push({source: c, target: a}); // Re-add c-a. | |
update(); | |
}, 5000, d3.now() + 1000); | |
function update() { | |
node = node.data(data, function(d) { return d.id; }); | |
node.exit().remove(); | |
var nodeEnter = node.enter().append("g") | |
.attr("class", "node") | |
.on("mouseover", mouseover) | |
.on("mouseout", mouseout); | |
nodeEnter.append("circle").attr("fill", function(d) { return color(d.id); }).attr("r", 8); | |
nodeEnter.append("text") | |
.attr("dx", 12) | |
.attr("dy", ".35em") | |
.text(function(d) { return d.id; }); | |
nodeEnter.append("path").attr("class", "path"); | |
nodeEnter.call(d3.drag() | |
.on("start", dragstarted) | |
.on("drag", dragged) | |
.on("end", dragended)); | |
node = node.merge(nodeEnter); | |
// Apply the general update pattern to the links. | |
link = link.data(links, function(d) { return d.source.id + "-" + d.target.id; }); | |
link.exit().remove(); | |
link = link.enter().append("line").merge(link); | |
// Update and restart the simulation. | |
simulation.nodes(data); | |
simulation.force("link").links(links); | |
simulation.alpha(1).restart(); | |
} | |
function mouseover(d) { | |
d3.select(this).raise().classed("active", true); | |
} | |
function mouseout(d) { | |
d3.select(this).raise().classed("active", false); | |
} | |
function dragstarted(d) { | |
if (!d3.event.active) simulation.alphaTarget(0.3).restart(); | |
d.fx = d.x; | |
d.fy = d.y; | |
} | |
function dragged(d) { | |
d3.select(this).select("circle").attr("cx", d.fx = d3.event.x).attr("cy", d.fy = d3.event.y); | |
} | |
function dragended(d) { | |
if (!d3.event.active) simulation.alphaTarget(0); | |
d.fx = null; | |
d.fy = null; | |
} | |
function ticked() { | |
node.select("circle") | |
.attr("cx", function(d) { return d.x; }) | |
.attr("cy", function(d) { return d.y; }); | |
node.select("path") | |
.data(voronoi.polygons(data)) | |
.attr("d", function(d) { return d == null ? null : "M" + d.join("L") + "Z"; }); | |
node.select("text") | |
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")" }); | |
link.attr("x1", function(d) { return d.source.x; }) | |
.attr("y1", function(d) { return d.source.y; }) | |
.attr("x2", function(d) { return d.target.x; }) | |
.attr("y2", function(d) { return d.target.y; }); | |
} | |
</script> | |
</body> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment