D3 Node Labeling

d3 Node Labeling

There are lots of examples showing how to add labels to graph and tree visualizations, but I'd probably start with this one as the simplest:

  • http://bl.ocks.org/950642

You haven’t posted a link to your code, but I'm guessing that node refers to a selection of SVG circle elements. You can’t add text elements to circle elements because circle elements are not containers; adding a text element to a circle will be ignored.

Typically you use a G element to group a circle element (or an image element, as above) and a text element for each node. The resulting structure looks like this:

<g class="node" transform="translate(130,492)">
<circle r="4.5"/>
<text dx="12" dy=".35em">Gavroche</text>
</g>

Use a data-join to create the G elements for each node, and then use selection.append to add a circle and a text element for each. Something like this:

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

node.append("circle")
.attr("r", 4.5);

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

One downside of this approach is that you may want the labels to be drawn on top of the circles. Since SVG does not yet support z-index, elements are drawn in document order; so, the above approach causes a label to be drawn above its circle, but it may be drawn under other circles. You can fix this by using two data-joins and creating separate groups for circles and labels, like so:

<g class="nodes">
<circle transform="translate(130,492)" r="4.5"/>
<circle transform="translate(110,249)" r="4.5"/>

</g>
<g class="labels">
<text transform="translate(130,492)" dx="12" dy=".35em">Gavroche</text>
<text transform="translate(110,249)" dx="12" dy=".35em">Valjean</text>

</g>

And the corresponding JavaScript:

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

var text = svg.append("g")
.attr("class", "labels")
.selectAll("text")
.data(nodes)
.enter().append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) { return d.name });

This technique is used in the Mobile Patent Suits example (with an additional text element used to create a white shadow).

Add text label to d3 node in Force layout

Right now, you are appending the text elements to the circle elements, and that simply won't work.

When you write...

var label = nodes.append("svg:text")

You're appending the texts to the nodesselection. However, you have to keep in mind what nodes is:

var nodes = svg.selectAll("circle")
.data(dataset.nodes)
.enter()
.append("circle")

Thus, you are appending the texts to circles, and that doesn't work. They show up when you inspect the page (as <circle><text></text></circle>), but nothing will actually show up in the SVG.

Solution: just change to:

var label = svg.selectAll(null)
.data(dataset.nodes)
.enter()
.append("text")
.text(function (d) { return d.name; })
.style("text-anchor", "middle")
.style("fill", "#555")
.style("font-family", "Arial")
.style("font-size", 12);

Here is the fiddle: https://jsfiddle.net/gerardofurtado/7pvhxfzg/1/

How can i put label to my node in d3 directed graph

Approach:

So my approach to that would to to create group elements g with circle and text inside them, and then we can use force layout to position them with transform. This way we only care about group element position and circle with text should follow that position. So the node structure might look like that:

<g transform='translate(30,70)'>
<circle r='3'>/<circle>
<text>sample text</text>
</g>

Code:

You have quite a lot of code in your sample so I'll just focus on having persistent labels over circles. This might break some other things I'm not focusing on but should give you direction and hopefully you'll be able to fix the rest if not fell free to ask.

Using your plunker first thing you have to do it so associate data with g elements instead of circle :

node = vis.selectAll('g.node')
.data(nodes, function(d) { return d.filename })
.enter().append('g');

Then you can append circles to those g elements, and in this case there is no need to define cx and cy as this is taken care of by transform applied to g elements, so all you need to specify is r and any other styles you wan to apply:

node.append('circle')
.attr('class', 'node')
.attr('r', function(d) {
return scale * (d.root ? 8 : d.module && !d.native ? 5 : 3)
})
(...)

Now you can add your labels:

node.append('text')
.text(function(d){return d.id})
.style('text-anchor',middle); // this will center label horizontally

One last step is to update on 'tick' function as before it was updating circle position and now we want it to update g position, so instead of:

node.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; });

you should do:

node.attr('transform', function(d) { 
return 'translate('+ d.x + ',' + d.y + ')';
});

And here you have your plunker with my changes, hope thats what you asked for.
http://plnkr.co/edit/Q69DkEH5Z9aOe92UwlOB?p=preview

Fell free to ask any more questions if you have any :)

d3 node labeling: how to display JSON data formatted with HTML?

If you can modify the structure of your dataset, then I would suggest using a text element with multiple tspan elements to style each part of the label differently. Below is a simplified example (if your label styling is more complex than the example you provided, then you would need to modify the below approach to appending the tspans, but this should point you in the right direction).

const nodes = [{  "group": "3",  "x": "30",  "y": "30",  "id": "école féminin ou masculin",  "tspans": [    {"text": "école ", "style": "font-weight: normal;"},    {"text": "féminin ou masculin", "style": "font-weight: bold;"}  ]}];
const svg = d3.select('body') .append('svg') .attr('width', 300) .attr('height', 300);
const circles = svg.selectAll('circle') .data(nodes) .enter() .append('circle'); const labels = svg.selectAll('text') .data(nodes) .enter() .append('text');
circles .attr('r', 25) .attr('cx', d => d.x) .attr('cy', d => d.y) .attr('style', 'fill: rgb(102, 193, 163);'); labels .attr('x', d => d.x) .attr('y', d => d.y) .append('tspan') .text(d => d.tspans[0].text) .attr('style', d => d.tspans[0].style); labels .append('tspan') .text(d => d.tspans[1].text) .attr('style', d => d.tspans[1].style);
<body>  <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script></body>

Creating force layout node labels in d3.js

There is a confusion between "labels" and "tooltips". Traditionally, we name "labels" the texts that show up next to the nodes without user interaction, and we name "tooltips" the texts that show up when the user interacts with the nodes (for instance, hovering the nodes).

So, if you mean "labels", this is a solution: append the nodes as groups...

var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.call(d3.drag().on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));

And append the circles and the labels (as <text> elements) to them:

node.append("circle")
.attr("r", 5)
.attr("fill", function(d) { return color(d.group); });

node.append("text")
.attr("dx", 6)
.text(function(d) { return d.id; });

Here is a demo using (most of) your code, with a made up data:

var graph = {nodes:[ {"id": "A", "group": 1}, {"id": "B", "group": 2}, {"id": "C", "group": 2}, {"id": "D", "group": 2}, {"id": "E", "group": 2}, {"id": "F", "group": 3}, {"id": "G", "group": 3}, {"id": "H", "group": 3}, {"id": "I", "group": 3}],links:[{"source": "A", "target": "B", "value": 1},{"source": "B", "target": "C", "value": 1},{"source": "A", "target": "D", "value": 1},{"source": "H", "target": "E", "value": 1},{"source": "I", "target": "F", "value": 1},{"source": "A", "target": "G", "value": 1},{"source": "B", "target": "H", "value": 1},{"source": "A", "target": "I", "value": 1},]};
var svg = d3.select("svg"), width = +svg.attr("width"), height = +svg.attr("height");
var color = d3.scaleOrdinal(d3.schemeCategory20);
var simulation = d3.forceSimulation() .force("link", d3.forceLink().id(function(d) { return d.id; }).distance(40)) .force("charge", d3.forceManyBody()) .force("center", d3.forceCenter(width / 2, height / 2));
var link = svg.append("g") .attr("class", "links") .selectAll("line") .data(graph.links) .enter().append("line") .attr("stroke-width", function(d) { return Math.sqrt(d.value); });

var node = svg.selectAll(".node") .data(graph.nodes) .enter().append("g") .attr("class", "node") .call(d3.drag() .on("start", dragstarted) .on("drag", dragged) .on("end", dragended)); node.append("circle") .attr("r", 5) .attr("fill", function(d) { return color(d.group); });
node.append("text") .attr("dx", 6) .text(function(d) { return d.id; });
simulation .nodes(graph.nodes) .on("tick", ticked);
simulation.force("link") .links(graph.links);
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("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; }); }
function dragstarted(d) { if (!d3.event.active) simulation.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; }
function dragged(d) { d.fx = d3.event.x; d.fy = d3.event.y; }
function dragended(d) { if (!d3.event.active) simulation.alphaTarget(0); d.fx = null; d.fy = null; }
.links line {  stroke: #999;  stroke-opacity: 0.6;}
.node circle { stroke: #fff; stroke-width: 1.5px;}
.node text{ fill: #666; font-family: Helvetica}
<svg width="400" height="300"></svg><script src="https://d3js.org/d3.v4.min.js"></script>

How to label a force directed Graph on d3?

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 nodesvar 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>

Colored text and line-breaks for D3 node labels

Not directly related to the question, but to use the <pre> element to hold your data, you have to use:

var text = d3.select("pre").text();

Instead of d3.text().

Back to the question:

For printing those values, you just need:

node.append("text")
.attr("dy", "1.3em")
.style("text-anchor", "middle")
.style("fill", "white")
.text(function(d) {
return d.size;
});

Adjusting dy the way you want. However, there is an additional problem: you're not populating size in the data array. Therefore, add this in the create_nodes function:

size: data[node_counter].size,

Here is the code with those changes:

<!DOCTYPE html><meta charset="utf-8"><style type="text/css">  text {    font: 10px sans-serif;  }    pre {    display: none;  }    circle {    stroke: #565352;    stroke-width: 1;  }</style>
<body> <pre id="data">Toyota Motor,61.84,Asia,239Volkswagen,44.54,Europe,124Daimler,40.79,Europe,104BMW,35.78,Europe,80Ford Motor,31.75,America,63General Motors,30.98,America,60</pre>
<script src="https://d3js.org/d3.v3.min.js"></script> <script> Array.prototype.contains = function(v) { for (var i = 0; i < this.length; i++) { if (this[i] === v) return true; } return false; };

var width = 500, height = 500, padding = 1.5, // separation between same-color nodes clusterPadding = 6, // separation between different-color nodes maxRadius = 12;
var color = d3.scale.ordinal() .range(["#0033cc", "#33cc66", "#990033"]);


var text = d3.select("pre").text(); var colNames = "text,size,group,revenue\n" + text; var data = d3.csv.parse(colNames);
data.forEach(function(d) { d.size = +d.size; });

//unique cluster/group id's var cs = []; data.forEach(function(d) { if (!cs.contains(d.group)) { cs.push(d.group); } });
var n = data.length, // total number of nodes m = cs.length; // number of distinct clusters
//create clusters and nodes var clusters = new Array(m); var nodes = []; for (var i = 0; i < n; i++) { nodes.push(create_nodes(data, i)); }
var force = d3.layout.force() .nodes(nodes) .size([width, height]) .gravity(.02) .charge(0) .on("tick", tick) .start();
var svg = d3.select("body").append("svg") .attr("width", width) .attr("height", height);

var node = svg.selectAll("circle") .data(nodes) .enter().append("g").call(force.drag);

node.append("circle") .style("fill", function(d) { return color(d.cluster); }) .attr("r", function(d) { return d.radius })

node.append("text") .attr("dy", ".3em") .style("text-anchor", "middle") .style("fill", "white") .text(function(d) { return d.text; });
node.append("text") .attr("dy", "1.3em") .style("text-anchor", "middle") .style("fill", "white") .text(function(d) { return d.size; });
function create_nodes(data, node_counter) { var i = cs.indexOf(data[node_counter].group), r = Math.sqrt((i + 1) / m * -Math.log(Math.random())) * maxRadius, d = { cluster: i, radius: data[node_counter].size * 1.5, text: data[node_counter].text, size: data[node_counter].size, revenue: data[node_counter].revenue, x: Math.cos(i / m * 2 * Math.PI) * 200 + width / 2 + Math.random(), y: Math.sin(i / m * 2 * Math.PI) * 200 + height / 2 + Math.random() }; if (!clusters[i] || (r > clusters[i].radius)) clusters[i] = d; return d; };


function tick(e) { node.each(cluster(10 * e.alpha * e.alpha)) .each(collide(.5)) .attr("transform", function(d) { var k = "translate(" + d.x + "," + d.y + ")"; return k; })
}
// Move d to be adjacent to the cluster node. function cluster(alpha) { return function(d) { var cluster = clusters[d.cluster]; if (cluster === d) return; var x = d.x - cluster.x, y = d.y - cluster.y, l = Math.sqrt(x * x + y * y), r = d.radius + cluster.radius; if (l != r) { l = (l - r) / l * alpha; d.x -= x *= l; d.y -= y *= l; cluster.x += x; cluster.y += y; } }; }
// Resolves collisions between d and all other circles. function collide(alpha) { var quadtree = d3.geom.quadtree(nodes); return function(d) { var r = d.radius + maxRadius + Math.max(padding, clusterPadding), nx1 = d.x - r, nx2 = d.x + r, ny1 = d.y - r, ny2 = d.y + r; 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), r = d.radius + quad.point.radius + (d.cluster === quad.point.cluster ? padding : clusterPadding); if (l < r) { l = (l - r) / 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; }); }; } </script>

D3 assigning nodes labels

Group circle and text inside a group element. You can use marker-end property to add end markers to the line.

There are marker-mid and marker-start configs too.

var graph = JSON.parse('{"nodes":[{"id":"0","color":"#4444E5"},{"id":"1","color":"#4444E5"},{"id":"2","color":"#CCCCCC"},{"id":"3","color":"#CCCCCC"},{"id":"4","color":"#CCCCCC"},{"id":"5","color":"#CCCCCC"},{"id":"6","color":"#CCCCCC"},{"id":"7","color":"#CCCCCC"},{"id":"8","color":"#CCCCCC"},{"id":"9","color":"#CCCCCC"},{"id":"10","color":"#cc0000"},{"id":"11","color":"#CCCCCC"},{"id":"12","color":"#CCCCCC"},{"id":"13","color":"#CCCCCC"},{"id":"14","color":"#CCCCCC"},{"id":"15","color":"#cc0000"}],"links":[{"source":1,"target":15,"weight":-0.7582412523901727},{"source":0,"target":6,"weight":-0.08179822732917646},{"source":6,"target":10,"weight":0.0939757437427291},{"source":3,"target":4,"weight":0.436380368086641},{"source":4,"target":5,"weight":-0.5385567058738547},{"source":1,"target":2,"weight":-0.277729004201837},{"source":2,"target":3,"weight":0.7675128737907505},{"source":13,"target":14,"weight":0.5519067984674436},{"source":14,"target":15,"weight":0.832660697502495},{"source":5,"target":7,"weight":0.8607887414383458},{"source":7,"target":8,"weight":-0.8965760877371078},{"source":8,"target":9,"weight":-0.2488791800975232},{"source":9,"target":10,"weight":-0.646075500567235},{"source":12,"target":13,"weight":0.40622804770087395},{"source":0,"target":11,"weight":0.24806004723386413},{"source":11,"target":12,"weight":0.8035303834469385}]}');
var svg = d3.select("svg"), width = +svg.attr("width"), height = +svg.attr("height");
var defs = svg.append("defs")
var marker = defs
.append("svg:marker") .attr("id", "arrow") .attr("viewBox", "0 -5 10 10") .attr("refX", 15) .attr("refY", -1.5) .attr("markerWidth", 6) .attr("markerHeight", 6) .attr("orient", "auto")
marker.append("path") .attr("d", "M0,-5L10,0L0,5") .attr("class", "arrowHead");
var simulation = d3.forceSimulation() .force("link", d3.forceLink().id(function(d) { return d.id; })) .force("charge", d3.forceManyBody()) .force("center", d3.forceCenter(width / 2, height / 2));

var link = svg.append("g") .attr("class", "links") .selectAll("line") .data(graph.links) .enter().append("line") .attr("stroke-width", function(d) { return Math.sqrt(d.weight); }).attr("marker-end", "url(#arrow)")
var node = svg.append("g") .attr("class", "nodes") .selectAll("circle") .data(graph.nodes) .enter().append("g");
node.append("circle") .attr("r", 5) .attr("fill", function(d) { return d3.color(d.color); }) .call(d3.drag() .on("start", dragstarted) .on("drag", dragged) .on("end", dragended));
var label = node.append("text") .attr("class", "label") .text("test")
node.append("title") .text(function(d) { return d.id; });
simulation .nodes(graph.nodes) .on("tick", ticked);
simulation.force("link") .links(graph.links);
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("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });}
function dragstarted(d) { if (!d3.event.active) simulation.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y;}
function dragged(d) { d.fx = d3.event.x; d.fy = d3.event.y;}
function dragended(d) { if (!d3.event.active) simulation.alphaTarget(0); d.fx = null; d.fy = null;}
line {  stroke: black;}
<script src="https://d3js.org/d3.v4.min.js"></script><svg width="960" height="600"></svg>

styling node label not working in d3 force directed graph

Your text is filled grey (#555) but in your updateDisplay() function you appear to be adding a red (or blue) stroke to your text nodes which hides the fill.

If you don't want a stroke on your text you can give it a stroke-width of 0 like so.

node.append('text')
.text(function(d) {return d.id})
.style('text-anchor', 'middle')
.style('cursor', 'pointer')
.style("fill", "#555555")
.style("font-family", "Arial")
.style("font-size", 12)
.attr("stroke-width", 0)

Plunker



Related Topics



Leave a reply



Submit