Skip to content

Instantly share code, notes, and snippets.

@RemiFaure
Last active February 9, 2018 14:05
Show Gist options
  • Save RemiFaure/bfcfdabe65b2759cc3030be332d9464f to your computer and use it in GitHub Desktop.
Save RemiFaure/bfcfdabe65b2759cc3030be332d9464f to your computer and use it in GitHub Desktop.
TP layout
license: mit
<!DOCTYPE html>
<html>
<head>
<title>Force-Directed Layout with Convex Hull</title>
<script src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<style>
line {
fill: white;
stroke: black;
stroke-width: 1;
}
.background {
fill: none;
stroke: none;
}
.polygons {
fill: none;
stroke: #000;
}
</style>
<script type="text/javascript">
// parameters
var w = 960,
h = 500,
n = 100,
nb_groups = 36,
nb_simulation = 120,
fill = d3.scaleOrdinal(d3.schemeCategory20),
graph = {};
// we generate n random nodes in nb_groups
graph.nodes = d3.range(n).map(function(d, i) {
return {id: i, group: i % nb_groups, r: 10*Math.random()}
});
// we don't generate links, yet
graph.links = [
{source: graph.nodes[0], target: graph.nodes[1]}
];
// main svg canvas
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h);
// we separate ndoes using 3 arbitrary columns
var cols = Math.ceil(Math.sqrt(nb_groups));
// horizontal scale for the columns
var x = d3.scaleLinear()
.domain([0, cols])
.range([w/4, 3*w/4]);
// MAIN LOOP
// sets positions for each node based on index
// -we store the pixel values
// -those are calculated only once
graph.nodes.forEach(function(d, i) {
var col = d.group % cols;
d.x = x(col);
d.y = Math.trunc(d.group/cols);
});
// simulation of positions
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }))
.force("charge", d3.forceManyBody())
.force("x", d3.forceX(function(d, i) {
return d.x;
}))
.force("y", d3.forceY(function(d, i) {
return d.y;
}))
.force("collide", d3.forceCollide(12).radius(function(d) { return d.r + 0.5; }).iterations(2))
.force("center", d3.forceCenter(w / 2, h / 2))
.stop();
// apply the simulation to nodes
simulation
.nodes(graph.nodes);
// apply the simulation to links
simulation.force("link")
.links(graph.links);
// we run the simulation nb_simulation times
for (var i = 0; i < nb_simulation; ++i) simulation.tick();
// we create a Voronoi partition based on nodes positions
var voronoi = d3.voronoi()
.extent([[0, 0], [w, h]])
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
// we draw the voronoi partition using paths
var polygon = svg.append("g")
.attr("class", "polygons")
.selectAll("path")
.data(voronoi.polygons(graph.nodes))
.enter()
.append("path")
.attr("id", function(d) { return "_" + d.data.id; })
.style("fill", "white")
.on("mouseenter", function(d) {
// highlight nodes in the current partition
d3.select(this).style("fill", "red").transition().style("fill", "white")
d3.selectAll("circle#" + d3.select(this).attr("id"))
.attr("r", 10)
.transition()
.attr("r", function(d) { return d.r; })
})
.on("mouseleave", function(d) {
})
.call(redrawPolygon);
// create nodes using the graph.nodes object
var node = svg.selectAll("circle.node")
.data(graph.nodes)
.enter()
.append("circle")
.attr("class", "node")
.attr("r", function(d) { return d.r; })
.attr("id", function(d) { return "_" + d.id; })
.style("fill", function(d, i) { return fill(i); })
.style("stroke", function(d, i) { return d3.rgb(fill(i)).darker(2); })
.style("stroke-width", 1.5);
// draw the nodes
node
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
// create links using the graph.links object
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(graph.links)
.enter()
.append("line")
.attr("stroke-width", function(d) { return Math.sqrt(d.value); });
// draw links
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; });
// function to draw the vornoi partition
function redrawPolygon(polygon) {
polygon
.attr("d", function(d) { return d ? "M" + d.join("L") + "Z" : null; });
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment