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 nodes
selection. 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
Why Is My Grid Element'S Height Not Being Calculated Correctly
How to Style Part of an Input Field'S Value
Access Iframe Content from a Chrome'S Extension Content Script
CSS Background Image on Top of <Img>
What's the Difference Between "&Nbsp;" and " "
Floating an Image to the Bottom Right with Text Wrapping Around
How to Rotate and Postion an Element on the Top Left or Top Right Corner
How to Get Rid of Extra Space Below Svg in Div Element
CSS Data Attribute New Line Character & Pseudo-Element Content Value
Why Is There a Vertical Scroll Bar If Parent and Child Have the Same Height
Adding HTML Class Tag Under ≪Option≫ in Html.Dropdownlist
Setting the Cursor in the Element's Default Styles, or in Element:Hover
Required Attribute Not Work in Safari Browser
Prevent Scroll-Bar from Adding-Up to the Width of Page on Chrome
What Is the Meaning of ? (Question Mark) in a Url String
Best Way to Synchronize Local HTML5 Db (Websql Storage, SQLite) with a Server (2 Way Sync)