D3 Force layout graph
Created
October 21, 2016 20:18
-
-
Save amejiarosario/e50c22396a1d27344202693194e45a8c to your computer and use it in GitHub Desktop.
Force Graph
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> | |
<meta charset="utf-8"> | |
<style> | |
.node { | |
stroke-width: 5; | |
stroke: #049FD9; | |
fill: white; | |
} | |
.node text { | |
stroke: none; | |
fill: black; | |
font: 16px helvetica; | |
} | |
.link { | |
stroke-width: 5; | |
stroke: #049FD9; | |
} | |
body { | |
background-color: #E8EBF1; | |
} | |
</style> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script> | |
<script> | |
const graph = { | |
"nodes": [ | |
{"id": "ACMECorp"}, | |
{"id": "Device 1"}, | |
{"id": "Device 2"}, | |
{"id": "Device 3"}, | |
{"id": "Device 4"}, | |
{"id": "Device 5"}, | |
{"id": "Device 6"}, | |
], | |
"links": [ | |
{"source": 0, "target": 1}, | |
{"source": 0, "target": 2}, | |
{"source": 0, "target": 3}, | |
{"source": 0, "target": 4}, | |
{"source": 0, "target": 5}, | |
{"source": 0, "target": 6}, | |
] | |
}; | |
//Constants for the SVG | |
var width = 960, | |
height = 900; | |
//Set up the colour scale | |
var color = d3.scale.category20(); | |
//Set up the force layout | |
var force = d3.layout.force() | |
.charge(-1e4) | |
.linkDistance(100) | |
.size([width, height]); | |
//Append a SVG to the body of the html page. Assign this SVG as an object to svg | |
var svg = d3.select("body").append("svg") | |
.attr("width", width) | |
.attr("height", height); | |
//Creates the graph data structure out of the json data | |
force.nodes(graph.nodes) | |
.links(graph.links) | |
.start(); | |
//Create all the line svgs but without locations yet | |
var link = svg.selectAll(".link") | |
.data(graph.links) | |
.enter().append("line") | |
.attr("class", "link"); | |
//Do the same with the circles for the nodes - no | |
var node = svg.selectAll(".node") | |
.data(graph.nodes) | |
.enter().append("g") | |
.attr("class", "node") | |
.call(force.drag) | |
// .on('mouseover', connectedNodes) | |
// .on('mouseout', connectedNodes); | |
node.append("circle") | |
.attr("r", 58) | |
node.append("text") | |
.attr("dx", 10) | |
.attr("dy", ".35em") | |
.text(function(d) { return d.id }); | |
//Now we are giving the SVGs co-ordinates - the force layout is generating the co-ordinates which this code is using to update the attributes of the SVG elements | |
force.on("tick", function () { | |
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; | |
}); | |
node.attr("cx", function (d) { | |
return d.x; | |
}) | |
.attr("cy", function (d) { | |
return d.y; | |
}); | |
node.each(collide(0.5)); | |
//Changed | |
d3.selectAll("circle").attr("cx", function (d) { | |
return d.x; | |
}).attr("cy", function (d) { | |
return d.y; | |
}); | |
d3.selectAll("text").attr("x", function (d) { | |
return d.x - 40; | |
}).attr("y", function (d) { | |
return d.y; | |
}); | |
//End Changed | |
}); | |
// Collision | |
var padding = 25, // separation between circles | |
radius=58; | |
function collide(alpha) { | |
var quadtree = d3.geom.quadtree(graph.nodes); | |
return function(d) { | |
var rb = 2*radius + padding, | |
nx1 = d.x - rb, | |
nx2 = d.x + rb, | |
ny1 = d.y - rb, | |
ny2 = d.y + rb; | |
quadtree.visit(function(quad, x1, y1, x2, y2) { | |
if (quad.point && (quad.point !== d)) { | |
var x = d.x - quad.point.x, | |
y = d.y - quad.point.y, | |
l = Math.sqrt(x * x + y * y); | |
if (l < rb) { | |
l = (l - rb) / l * alpha; | |
d.x -= x *= l; | |
d.y -= y *= l; | |
quad.point.x += x; | |
quad.point.y += y; | |
} | |
} | |
return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1; | |
}); | |
}; | |
} | |
// Selection | |
//Toggle stores whether the highlighting is on | |
var toggle = 0; | |
//Create an array logging what is connected to what | |
var linkedByIndex = {}; | |
for (i = 0; i < graph.nodes.length; i++) { | |
linkedByIndex[i + "," + i] = 1; | |
}; | |
graph.links.forEach(function (d) { | |
linkedByIndex[d.source.index + "," + d.target.index] = 1; | |
}); | |
//This function looks up whether a pair are neighbours | |
function neighboring(a, b) { | |
return linkedByIndex[a.index + "," + b.index]; | |
} | |
function connectedNodes() { | |
if (toggle == 0) { | |
//Reduce the opacity of all but the neighbouring nodes | |
d = d3.select(this).node().__data__; | |
node.style("opacity", function (o) { | |
return neighboring(d, o) | neighboring(o, d) ? 1 : 0.1; | |
}); | |
link.style("opacity", function (o) { | |
return d.index==o.source.index | d.index==o.target.index ? 1 : 0.1; | |
}); | |
//Reduce the op | |
toggle = 1; | |
} else { | |
//Put them back to opacity=1 | |
node.style("opacity", 1); | |
link.style("opacity", 1); | |
toggle = 0; | |
} | |
} | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment