|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="utf-8"> |
|
<title>Force Layout with edge-labels & responsive node labels</title> |
|
<style type="text/css"> |
|
@import url(https://fonts.googleapis.com/css?family=Open+Sans); |
|
body { |
|
background: #3b5998; |
|
} |
|
.textClass { |
|
font-family: 'Open Sans'; |
|
fill: #fff; |
|
} |
|
circle { |
|
stroke: #ccc; |
|
stroke-width: 2px; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script> |
|
<script type="text/javascript"> |
|
// Adapted from http://bl.ocks.org/jhb/5955887 |
|
|
|
var w = window.innerWidth - 20, |
|
h = window.innerHeight, |
|
middle = w/2; |
|
var linkDistance = 60; |
|
|
|
var colors = d3.scale.category20(); |
|
|
|
var dataset = { |
|
|
|
nodes: [ |
|
{name: "extraterrestrial"}, |
|
{name: "electrical"}, |
|
{name: "effortless"}, |
|
{name: "eons"}, |
|
{name: "energy"}, |
|
{name: "everyone"}, |
|
{name: "efficiently"}, |
|
{name: "epitomize"}, |
|
{name: "excellent"}, |
|
{name: "extravagant"}, |
|
{name: "elevate"}, |
|
{name: "essence"} |
|
], |
|
edges: [ |
|
{source: 0, target: 1}, |
|
{source: 0, target: 2}, |
|
{source: 0, target: 3}, |
|
{source: 0, target: 4}, |
|
{source: 0, target: 5}, |
|
{source: 0, target: 6}, |
|
{source: 0, target: 7}, |
|
{source: 0, target: 8}, |
|
{source: 0, target: 9}, |
|
{source: 0, target: 10}, |
|
{source: 0, target: 11} |
|
] |
|
}; |
|
|
|
var svg = d3.select("body").append("svg").attr({"width":w,"height":h}); |
|
|
|
var force = d3.layout.force() |
|
.size([w,h]) |
|
.nodes(dataset.nodes) |
|
.links(dataset.edges) |
|
.linkDistance(linkDistance) |
|
.charge(-2500) |
|
.start(); |
|
|
|
var edge = svg.selectAll("line") |
|
.data(dataset.edges) |
|
.enter() |
|
.append("line") |
|
.attr("id",function(d,i) {return 'edge'+i}) |
|
.style("stroke","#ccc") |
|
.style("pointer-events", "none"); |
|
|
|
var node = svg.selectAll("g.node") |
|
.data(dataset.nodes); |
|
|
|
var nodeEnter = node.enter().append("g") |
|
.attr("class", "node") |
|
.call(force.drag); |
|
|
|
nodeEnter.append("svg:circle") |
|
.attr({"r":8}) |
|
.style("fill",function(d,i){return colors(i);}) |
|
.call(force.drag); |
|
|
|
nodeEnter.append("svg:text") |
|
.attr("class", "textClass") |
|
.attr("x", 20) |
|
.attr("y", ".31em") |
|
.text(function (d) { |
|
return d.name; |
|
}); |
|
|
|
force.on("tick", function(e){ |
|
|
|
var q = d3.geom.quadtree(node), |
|
i = 0, |
|
n = node.length, |
|
k = .1 * e.alpha; |
|
|
|
while (++i < n) q.visit(collide(node[i])); |
|
|
|
edge.attr({"x1": function(d){return d.source.x;}, |
|
"y1": function(d){return d.source.y;}, |
|
"x2": function(d){return d.target.x;}, |
|
"y2": function(d){return d.target.y;} |
|
}); |
|
|
|
node.attr("transform", function (d) { |
|
return "translate(" + d.x + "," + d.y + ")"; |
|
}); |
|
|
|
svg.selectAll(".textClass") |
|
.transition() |
|
.duration(100) |
|
.attr("x", function(d) { |
|
return repositionNodeLabel(d); |
|
}); |
|
|
|
}); |
|
|
|
function repositionNodeLabel(d) { |
|
var nameLength = d.name.length; |
|
var nameOffset = (nameLength * 9) - (nameLength * 1.25); |
|
var initialX = 25; |
|
var offset; |
|
|
|
offset = d.x < middle ? (-initialX * 2) - nameOffset : 0; |
|
|
|
return initialX + offset; |
|
} |
|
|
|
function collide(node) { |
|
var r = node.radius + 16, |
|
nx1 = node.x - r, |
|
nx2 = node.x + r, |
|
ny1 = node.y - r, |
|
ny2 = node.y + r; |
|
return function(quad, x1, y1, x2, y2) { |
|
if (quad.point && (quad.point !== node)) { |
|
var x = node.x - quad.point.x, |
|
y = node.y - quad.point.y, |
|
l = Math.sqrt(x * x + y * y), |
|
r = node.radius + quad.point.radius; |
|
if (l < r) { |
|
l = (l - r) / l * .5; |
|
node.x -= x *= l; |
|
node.y -= y *= l; |
|
quad.point.x += x; |
|
quad.point.y += y; |
|
} |
|
} |
|
return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1; |
|
}; |
|
} |
|
|
|
</script> |
|
</body> |
|
</html> |