I am trying to add label to the nodes in a D3 force layout, but there seems to be some issue with it. All the text just shows up on top of the screen, overlapping on each other. Here is the code snippet:
var link = g.append("g")
.attr("class", "links")
.selectAll("line")
.data(graph.links)
.enter().append("line");
var node = g.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("r", 2.5)
.on('click', clicked);
var text = g.append("g")
.attr("class", "labels")
.selectAll("text")
.data(graph.nodes)
.enter().append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) { return d.name });
Elements piling up on the top left corner of the SVG (which is the origin) is a symptom of several problems, among them:
NaN for the positions
Not setting the positions
Not translating the element
Not including the element in the tickfunction
As you have a force directed chart, the last one is the obvious reason.
Solution: You have to include the text in your ticked function.
function ticked() {
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; });
text.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; });
}
Related
I am visualising a graph of relationships between people with d3. All nodes are connected to a single central node, and then have relationships with other nodes. I've got the basics working, but I'm struggling to work out how to set the parameters like linkDistance, linkStrength, gravity and charge.
Each edge has a rating from 0-5, which I'm then using to compute linkDistance using an inverse linear scale. The main problem is getting the relationships to be represented properly. The central node seems to be much further away than any other node, even though it has the shortest linkDistance with some other nodes. I'm also finding it difficult to find the right settings to get nodes to be an appropriate distance apart.
var h = 500, w = 1000
var color = d3.scale.category20()
var svg = d3.select("body")
.append("svg")
.attr({ height: h, width: w })
queue()
.defer(d3.json, "nodes.json")
.defer(d3.json, "links.json")
.await(makeDiag);
function makeDiag(error, nodes, links, table) {
links = links.filter(function(link) {
if (link.value) return true
})
var scale = d3.scale.linear().domain([0,5]).range([20,0])
var edges = svg.selectAll("line")
.data(links)
.enter()
.append("line")
.style("stroke", "#ccc")
.style("stroke-width", 1)
/* Establish the dynamic force behavor of the nodes */
var force = d3.layout.force()
.nodes(nodes)
.links(links)
.size([w,h])
.linkDistance(function(d) {
if (d.value == 0) return null
console.log('in',d.value,'out',scale(d.value))
return scale(d.value)
})
.charge(-1400)
.start();
/* Draw the edges/links between the nodes */
var texts = svg.selectAll("text")
.data(nodes)
.enter()
.append("text")
.attr("fill", "black")
.attr("font-family", "sans-serif")
.attr("font-size", "10px")
.text(function(d) { return d.name; });
var nodes = svg.selectAll("circle")
.data(nodes)
.enter()
.append("circle")
.attr("r", function(d,i) { return 20 })
.attr("opacity", 0.7)
.style("fill", function(d,i) { return color(i); })
.call(force.drag);
/* Draw the nodes themselves */
/* Run the Force effect */
force.on("tick", function() {
edges.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; });
nodes.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
texts.attr("transform", function(d) {
return "translate(" + (d.x - 12.5) + "," + (d.y + 5) + ")";
});
});
};
jsFiddle
full screen result
I am having trouble adding a text to the links that connect the nodes in the following D3JS connected node graph: http://jsfiddle.net/rqa0nvv2/1/
Could anyone please explain to me what is the process to add them?
var link = svg.selectAll(".link")
.data(links)
.enter().append("line")
.attr("class", function(d) { if(d.value == "visible") {return "link";} else return "" })
.style("stroke-width", function(d) { return Math.sqrt(d.stroke); });
link.append("svg:title").text(function(d) { return "Unit: " + ", Formula: ";});
Thanks!
Just a couple changes are needed to get the text attached to the edges.
First, the problem with your current method is that you are shoving the <text> inside the <line>. This just can't be done in SVG, so you need to create a <g> (group) to contain both the line and the text:
var link = svg.selectAll(".link")
.data(links)
.enter()
.append("g")
.attr("class", "link-group")
.append("line")
.attr("class", function(d) { return d.value == "visible" ? "link" : ""; })
.style("stroke-width", function(d) { return Math.sqrt(d.stroke); });
Ok, just adding this won't change anything visually, but it does create the groups that we need. So we need to add the text with something like:
var linkText = svg.selectAll(".link-group")
.append("text")
.data(force.links())
.text(function(d) { return d.value == "visible" ? "edge" : ""; })
.attr("x", function(d) { return (d.source.x + (d.target.x - d.source.x) * 0.5); })
.attr("y", function(d) { return (d.source.y + (d.target.y - d.source.y) * 0.5); })
.attr("dy", ".25em")
.attr("text-anchor", "middle");
This adds the text and initially positions it. Feel free (please!) style the text and change the content according to how you want it to look. Finally, we need to make it move with the force-directed graph:
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; });
linkText
.attr("x", function(d) { return (d.source.x + (d.target.x - d.source.x) * 0.5); })
.attr("y", function(d) { return (d.source.y + (d.target.y - d.source.y) * 0.5); });
});
Which is simple of course since it's the same thing that we already did!
I want to display one <circle> and <text> for each node. My code looks like this, having added the suggested code from the answer below. Please note the different
var width = 960,
height = 500;
var color = d3.scale.category10();
var nodes = [],
links = [];
var force = d3.layout.force()
.nodes(nodes)
.links(links)
.charge(-250)
.linkDistance(25)
.size([width, height])
.on("tick", tick);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var node = svg.selectAll(".node")
.data(force.nodes(), function(d) { return d.id;}),
link = svg.selectAll(".link");
var text=svg.selectAll("text") //simply add text to svg
.data(force.nodes())
.enter()
.append("text")
.attr("class", "nodeText")
.attr("font-family", "sans-serif")
.attr("font-size", "10px")
.attr("fill", "red");
function start() {
link = link.data(force.links(), function(d) { return d.source.id + "-" + d.target.id; });
link.enter().insert("line", ".node")
.attr("class", function(d) { return "link " + d.edgeType; })
.attr("id", function(d) { return d.source.id + "-" + d.target.id; });
link.exit().remove();
v1: <line>s exist and are displayed, no <circle>s or <text> exist
var g = node.enter().append("g");
g.append("circle")
.attr("class", function(d) { return "node " + d.id; })
.attr("id", function(d) { return d.id; })
.attr("r", 8)
.on("click", nodeClick);
g.append("text")
.text(function(d) {return d.id; });
/v1
v2: <line>s and <circle>s exist and are displayed. <text>s exist within <circle>s but aren't displayed
node = node.data(force.nodes(), function(d) { return d.id;});
node.enter()
.append("circle")
.attr("class", function(d) { return "node " + d.id; })
.attr("id", function(d) { return d.id; })
.attr("r", 8)
.on("click", nodeClick);
node.append("text")
.text(function(d) {return d.id; });
/v2
node.exit().remove();
force.start();
}
function nodeClick() {
var node_id = event.target.id;
handleClick(node_id, "node");
}
function tick() {
text.attr("dx", function(d) { return d.x+5; })
.attr("dy", function(d) { return d.y+5; })
.text(function(d){return d.id});
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; });
}
Simply add text element as following in your js
var text=svg.selectAll("text") //simply add text to svg
.data(Your_nodes_array)
.enter()
.append("text")
.attr("class", "nodeText")
.attr("font-family", "sans-serif")
.attr("font-size", "10px")
.attr("fill", "red");
And inside tick do this :
text.attr("dx", function(d) { return d.x+5;}) //to keep it away from node
.attr("dy", function(d) { return d.y+5; })
.text(function(d){return d.id});
This will solve circle and text problems as this time we are adding text directly now
I have a force layout built using d3.js with nodes entering at interval of 15 seconds. When the nodes enter, they enter from any random direction before settling in the center. I want the node to enter always from left top (0,0) and then go to the center to settle.
that.force = d3.layout.force()
.charge(-8.6)
.linkDistance(2)
.size([600, 600]);
that.svg = d3.select(that.selector).append("svg")
.attr("width", that.width)
.attr("height", that.height);
d3.json("data/tweets.json", function(error, graph) {
that.graph = graph;
setInterval(function () {
d3.json("data/tweets.json", function(error, graph) {
that.graph = graph;
that.render();
});
},15000);
that.force
.nodes(that.graph.nodes)
.links(that.graph.links)
.start(0);
that.nodes = that.svg.selectAll("g")
.data(that.graph.nodes)
.enter()
.append("g")
.call(that.force.drag);
that.nodes
.append("rect")
.attr("class", "node")
.attr("width", that.rectw)
.attr("height", that.recth)
.style("fill","white")
.style("stroke", function(d) { return d.COLOR; })
.style("stroke-width", "1px")
that.nodes.append("title")
.text(function(d) { return d.SCREEN_NAME; });
that.nodes.append("image")
.attr("class", "node-image")
.attr("transform", "translate(1,1)")
.attr("xlink:href", function(d) { return "img/"+d.PROFILE_PIC;})
.attr("height", that.rectw-2 + "px")
.attr("width", that.recth-2 + "px");
that.link = that.svg.selectAll(".link")
.data(that.graph.links)
.enter()
.append("line")
.attr("class", "link")
.style("stroke-width", function(d) { return Math.sqrt(d.value); });
that.force.on("tick", function() {
that.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; });
that.nodes.attr("transform", function(d) {
return "translate("+d.x+","+d.y+")";
})
});
});
Any suggestion will be helpful.
Thanks.
You can pre-set the position of the nodes before giving them to the force layout by setting the x and y attributes of the data. So in your case, all you need to do is initialise x and y of all nodes to 0.
Actually I've integrated collapsible feature inside bounded force directed graph. But when I tried to put a label on each node, I got unexpected output.
I used bellow code to append labels on node:
node.enter().append("text")
.attr("class","node")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.on("click",click)
.text(function(d){return d.name})
.call(force.drag);
And below code I've written inside a tick function:
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
What I might be doing wrong?
I need to append the g tag and then circle and text:
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.on("click", click)
.call(force.drag);
nodeEnter.append("circle")
.attr("r", function(d) { return Math.sqrt(d.size) / 10 || 8.5; });
nodeEnter.append("text")
.attr("dy", ".35em")
.text(function(d) { return d.name; });
node.select("circle")
.style("fill", color);