user3560658 user3560658 - 1 month ago 24
Javascript Question

d3.js Force Directed Tree with Labels

I am completely stuck adding labels to the force directed tree graph found here http://bl.ocks.org/mbostock/1138500

I have attempted to synthesize the force directed tree with other examples that include labels as well as following the answer to Add text label to d3 node in Force directed Graph and resize on hover but the graph always seems to break.

This code works for the force directed graph with labels and pictures

<!DOCTYPE html>
<meta charset="utf-8">
<style>

.link {
stroke: #ccc;
}

.node text {
pointer-events: none;
font: 10px sans-serif;
}

</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>

var width = 960,
height = 500

var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);

var force = d3.layout.force()
.gravity(.05)
.distance(100)
.charge(-100)
.size([width, height]);

d3.json("graph.json", function(error, json) {
force
.nodes(json.nodes)
.links(json.links)
.start();

var link = svg.selectAll(".link")
.data(json.links)
.enter().append("line")
.attr("class", "link");

var node = svg.selectAll(".node")
.data(json.nodes)
.enter().append("g")
.attr("class", "node")
.call(force.drag);

node.append("image")
.attr("xlink:href", "https://github.com/favicon.ico")
.attr("x", -8)
.attr("y", -8)
.attr("width", 16)
.attr("height", 16);

node.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) { return d.name });

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("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
});
});

</script>


However when I attempt to modify this to form a tree structure from Mike's example my code looks like this but does not work.

<!DOCTYPE html>
<meta charset="utf-8">
<style>

.link {
stroke: #ccc;
}

.node text {
pointer-events: none;
font: 10px sans-serif;
}

</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>

var width = 960,
height = 500

var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);

var force = d3.layout.force()
.gravity(.05)
.distance(100)
.charge(-100)
.size([width, height]);

d3.json("test.json", function(error, json) {
force
.nodes(json.nodes)
.links(json.links)
.start();

var link = svg.selectAll(".link")
.data(json.links)
.enter().append("line")
.attr("class", "link");

var node = svg.selectAll(".node")
.data(json.nodes)
.enter().append("g")
.attr("class", "node")
.call(force.drag);

node.append("image")
.attr("xlink:href", "https://github.com/favicon.ico")
.attr("x", -8)
.attr("y", -8)
.attr("width", 16)
.attr("height", 16);

node.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) { return d.name });

force
.nodes(json.nodes)
.links(json.links)
.on("tick", tick)
.start();

function tick(e) {

// Push sources up and targets down to form a weak tree.
var k = 6 * e.alpha;
json.links.forEach(function(d, i) {
d.source.y -= k;
d.target.y += k;
});

node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return 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>


I have tried and tried to resolve this but cannot combine the labels with the force directed graph, any assistance would be greatly appreciated, I've been beating my head against the wall on this for some time now...

Thanks!

Answer

The part missing from your sample code is the addition to the tick callback that decreases the y value of the source, and increases that of the target by a small amount each time.

Here's a jsfiddle example which I think does what you're after.

The key portion is the addition of a parameter, called e here, to the tick function, along with the lines

var k = 6 * e.alpha;
json.links.forEach(function(d, i) {
  d.source.y -= k;
  d.target.y += k;
});

The result looks like this, once you also increase the magnitude of the charge to push the nodes a little further away from each other:

enter image description here

Comments