I am creating a force-directed graph with d3.js and can't get passed the point of labelling the nodes with text. I have tried countless answers here on StackOverflow and online tutorials, yet I believe that the problems lie in my fundamental Javascript understanding.
I have tried different combinations of .attr/.append/.text to get the text from the source and target to appear, but nothing ever happens.
This is the area in question:
node.append("title")
.text(function (d) {return d.target});
node.append("text")
.attr("dy", -3)
.text(function (d) {return d.source})
.attr("class", "font");
This is a simplified excerpt of the style:
<style>
.node {
fill: #ccc; /* Fill of the circles*/
stroke: #ffffff;
stroke-width: 2px;
}
.font {
font: 10px;
font-family: sans-serif;
}
.link {
stroke: #777; /* Colour of the lines*/
stroke-width: 2px;
}
</style>
This is a simplified excerpt of the script:
var width = 640,
height = 480;
var links = [
//this is an array
{source: "Germany", target: "name1"},
{source: "Germany", target: "name2"},
{source: "Nigeria", target: "name3"},
{source: "Environment", target: "name4"},
];
//setting up the nodes:
var nodes = {};
links.forEach(function(link){
link.source = nodes[link.source] ||
(nodes[link.source] = {name: link.source});
link.target = nodes[link.target] ||
(nodes[link.target] = {name: link.target});
});
//add svg to the body, this is where the actual d3 starts
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var force = d3.layout.force() //Here we specify the paramaters
.size([width,height])
.nodes(d3.values(nodes)) //this is where we pass the nodes of our dataset
.links(links) // source of links
.on("tick", tick) //on click of the nodes
.linkDistance(300) //How far apart the nodes are
.start(); //Start to render
//add link and nodes
var link = svg.selectAll(".link")
.data(links) //get the data
.enter().append('line') //binds the data in the links array to the svg
.attr("class", "link") //css styling
var node = svg.selectAll(".node")
.data(force.nodes()) //way to reference the nodes in the force layout
.enter().append("circle")
.attr("class", "node") //attribute CSS styling
.attr("r", width * 0.03); //radius of the circle
//text element
node.append("title")
.text(function (d) {return d.target});
node.append("text")
.attr("dy", -3)
.text(function (d) {return d.source})
.attr("class", "font");
//creating the tick function from the force variable
//the "e" paramater can be used for positioning
function tick(e) {
node.attr("cx", function(d) {return d.x;})
.attr("cy", function(d) {return d.y;})
.call(force.drag); //the relative location will activate a drag once the node is clicked
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'm currently not getting any error messages which is making it hard for me to debug the file. Any help is appreciated. Thank you!
There are two problems in your code.
The first problem is your node
selection:
var node = svg.selectAll(".node")
.data(force.nodes())
.enter().append("circle")
//etc...
As you can see, this is a selection of circles. Later, when you try...
node.append("text")
... it won't work because you cannot append a <text>
element to a <circle>
element.
The most common solution is making node
a group (<g>
) selection, to which you append both circles and texts.
The second problem is the data for the nodes. You have this in your texts:
node.append("text")
.text(function (d) {return d.source})
However, there is no property named source
in the data. The only property you have is name
.
Here is your code with those changes:
var width = 640,
height = 480;
var links = [
//this is an array
{
source: "Germany",
target: "name1"
},
{
source: "Germany",
target: "name2"
},
{
source: "Nigeria",
target: "name3"
},
{
source: "Environment",
target: "name4"
},
];
//setting up the nodes:
var nodes = {};
links.forEach(function(link) {
link.source = nodes[link.source] ||
(nodes[link.source] = {
name: link.source
});
link.target = nodes[link.target] ||
(nodes[link.target] = {
name: link.target
});
});
//add svg to the body, this is where the actual d3 starts
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var force = d3.layout.force() //Here we specify the paramaters
.size([width, height])
.nodes(d3.values(nodes)) //this is where we pass the nodes of our dataset
.links(links) // source of links
.on("tick", tick) //on click of the nodes
.linkDistance(300) //How far apart the nodes are
.start(); //Start to render
//add link and nodes
var link = svg.selectAll(".link")
.data(links) //get the data
.enter().append('line') //binds the data in the links array to the svg
.attr("class", "link") //css styling
var node = svg.selectAll(".node")
.data(force.nodes()) //way to reference the nodes in the force layout
.enter().append("g");
node.append("circle")
.attr("class", "node")
.attr("r", width * 0.03); //radius of the circle
node.append("text")
.attr("dy", -3)
.text(function(d) {
return d.name
})
.attr("class", "font");
//creating the tick function from the force variable
//the "e" paramater can be used for positioning
function tick(e) {
node.attr("transform", function(d) {
return "translate(" + [d.x, d.y] + ")"
})
.call(force.drag); //the relative location will activate a drag once the node is clicked
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 {
fill: #ccc;
/* Fill of the circles*/
stroke: #ffffff;
stroke-width: 2px;
}
.font {
font: 10px;
font-family: sans-serif;
}
.link {
stroke: #777;
/* Colour of the lines*/
stroke-width: 2px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>