I have the following code and I would like to add text in the circles. How could I make it possible; I have looked at these possible duplicates
Insert text inside Circle in D3 chart
const link = svg.append("g")
.attr("stroke", "#999")
.attr("stroke-opacity", 0.6)
.selectAll("line")
.data(links)
.enter().append("line")
.attr("stroke-width", d => Math.sqrt(d.value));
// link.append("title").text(d => d.value);
const node = svg.append("g")
.attr("stroke", "#fff")
.attr("stroke-width", 1.5)
.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("r", 5)
.attr("fill", d => color(d.group))
.call(drag(simulation));
node.append("title").text(d => "Node: " + d.id);
function ticked() {
link
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);
node
.attr("cx", d => d.x)
.attr("cy", d => d.y);
}
There are numerous ways to add text labels to circles. In your code, you've added title elements to the circles, which usually show up as tooltips when you hover over the element. Unlike title elements, text elements cannot be added as children of the circle elements. One common way to handle them is to use g elements to group together the circle and the related text, so that you can then perform any transformations (or removals, etc.) on the g instead of having to apply them separately to text and circle.
To convert your code sample, first, I've altered the selectAll / enter code to act on g elements instead of circles:
const node = svg
[...]
.selectAll('.circle')
.data(nodes)
.enter()
.append('g') // for each item in nodes, add <g class='circle'>
.classed('circle', true)
You can then append the circle and text elements to node:
node
.append("circle")
.attr('r', 5)
node
.append("text")
.text(d => "Node: " + d.id)
See the code snippet for a full example.
var nodes = [{
"x": 80,
"y": 60,
id: 'foo'
}, {
"x": 20,
"y": 70,
id: 'bar'
}, {
"x": 40,
"y": 40,
id: 'baz'
}, {
"x": 50,
"y": 90,
id: 'bop'
}];
const svg = d3.select('svg')
const node = svg
.append("g")
.attr("stroke", "#fff")
.attr("stroke-width", 1.5)
.selectAll(".circle")
.data(nodes)
.enter()
.append('g')
.classed('circle', true)
.attr('transform', d => 'translate(' + d.x + ',' + d.y + ')');
node
.append("circle")
.attr("r", 5)
.attr("fill", 'deepskyblue')
// .call(drag(simulation));
node
.append("text")
.classed('circleText', true)
.attr('dy', '0.35em')
.attr('dx', 5)
.text(d => "Node: " + d.id);
svg .circleText {
font-family: Helvetica, Arial, sans-serif;
font-size: 12px;
fill: black;
stroke-width: 0;
}
<script src="http://d3js.org/d3.v5.js"></script>
<svg width="200" height="200"></svg>
Related
I want to create a scatterplot of my data which consists of three columns of Year, Men and Women. I want to draw circles for both Men and Women columns that correspond to the year. Here's the code I've written, but only the first set of circles are added to the SVG. I'm using d3 v5.
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", d => xScale(d.Year))
.attr("cy", d => yScale(d.Men))
.attr("r", d => aScale(d.Men))
.attr("id", "men")
.attr("fill", function(d) {
return "rgb(0, 0, " + Math.round((d.Men) * 20) + ")"
});
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", d => xScale(d.Year))
.attr("cy", d => yScale(d.Women))
.attr("r", d => aScale(d.Women))
.attr("id", "women")
.attr("fill", "green");
You are using wrong the enter() method.
The enter evaluates witch data is newer, using the selector before. In your case, the selector is circle. Like you are using again the same selector, data and enter for the second case, the enter doesn't detect new changes. Your must to store the enter in a variable and use it for both:
const circleEnter = svg.selectAll("circle")
.data(dataset)
.enter();
circleEnter.append("circle")
.attr("cx", d => xScale(d.Year))
.attr("cy", d => yScale(d.Men))
.attr("r", d => aScale(d.Men))
.attr("id", "men")
.attr("fill", function(d) {
return "rgb(0, 0, " + Math.round((d.Men) * 20) + ")"
});
circleEnter.append("circle")
.attr("cx", d => xScale(d.Year))
.attr("cy", d => yScale(d.Women))
.attr("r", d => aScale(d.Women))
.attr("id", "women")
.attr("fill", "green");
Good luck!
This question already has an answer here:
reverse how vertical bar chart is drawn
(1 answer)
Closed 2 years ago.
This is a d3 bar graph.
I am trying to animate bars grow from bottom to upwards. how to achieve it?
also, how to make labels ie text of bars stay at the top of the bars and move along with them?
currently, bars are starting at top and going down to zero.
d3.select("body")
.append("h2")
.text("BAR GRAPH");
const dataset = [12, 31, 22, 17, 25, 18, 29, 14, 9];
const w = 500;
const h = 120;
const svg = d3
.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
var bars = svg
.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", (d, i) => i * 30)
.attr("y", (d, i) => h - 3 * d)
.attr("width", 25)
.attr("height", (d, i) => 3 * d)
.attr("fill", "navy");
bars
.transition()
.duration(400)
.attr("y", h)
.attr("height", (d, i) => 3 * d);
svg
.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(d => d)
.attr("x", (d, i) => i * 30)
.attr("y", (d, i) => h - 3 * d - 3)
.style("font-size", "25px")
.style("fill", "red")
.append("title")
.text(d => d);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
Your code was almost on point for the bars, you just need to change the order of the affectation to the y attribute of bars and set the height attribute of bars to 0 at the start.
I just modified the three lines in your example and marked them with comments to show what to change to achieve your desired effect.
To make the labels follow the bars you can just add a transition on them with the same duration ! I added some lines in the last block and modified one to achieve the effect.
Hope it helps :)
d3.select("body")
.append("h2")
.text("BAR GRAPH");
const dataset = [12, 31, 22, 17, 25, 18, 29, 14, 9];
const w = 500;
const h = 120;
const svg = d3
.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
var bars = svg
.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", (d, i) => i * 30)
.attr("y", h) // modified here
.attr("width", 25)
.attr("height", 0) // modified here
.attr("fill", "navy");
bars
.transition()
.duration(400)
.attr("y", (d, i) => h - 3 * d) // modified here
.attr("height", (d, i) => 3 * d);
// modified a bit here
var labels = svg
.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(d => d)
.attr("x", (d, i) => i * 30)
.attr("y", h) // modified here
.style("font-size", "25px")
.style("fill", "red");
labels
.append("title")
.text(d => d);
labels
.transition() // added here
.duration(400) // added here
.attr("y", (d, i) => h - 3 * d - 3) // added here
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
I've been using the sample code from this d3 project to learn how to display d3 graphs and I can't seem to get text to show up in the middle of the circles (similar to this example and this example). I've looked at other examples and have tried adding
node.append("title").text("Node Name To Display")
and
node.append("text")
.attr("text-anchor", "middle")
.attr("dy", ".3em").text("Node Name To Display")
right after node is defined but the only results I see is "Node Name To Display" is showing up when I hover over each node. It's not showing up as text inside the circle. Do I have to write my own svg text object and determine the coordinates of that it needs to be placed at based on the coordinates of radius of the circle? From the other two examples, it would seem like d3 already takes cares of this somehow. I just don't know the right attribute to call/set.
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).
I found this guide very useful in trying to accomplish something similar :
https://www.dashingd3js.com/svg-text-element
Based on above link this code will generate circle labels :
<!DOCTYPE html>
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</head>
<body style="overflow: hidden;">
<div id="canvas" style="overflow: hidden;"></div>
<script type="text/javascript">
var graph = {
"nodes": [
{name: "1", "group": 1, x: 100, y: 90, r: 10 , connected : "2"},
{name: "2", "group": 1, x: 200, y: 50, r: 15, connected : "1"},
{name: "3", "group": 2, x: 200, y: 130, r: 25, connected : "1"}
]
}
$( document ).ready(function() {
var width = 2000;
var height = 2000;
var svg = d3.select("#canvas").append("svg")
.attr("width", width)
.attr("height", height)
.append("g");
var lines = svg.attr("class", "line")
.selectAll("line").data(graph.nodes)
.enter().append("line")
.style("stroke", "gray") // <<<<< Add a color
.attr("x1", function (d, i) {
return d.x
})
.attr("y1", function (d) {
return d.y
})
.attr("x2", function (d) {
return findAttribute(d.connected).x
})
.attr("y2", function (d) {
return findAttribute(d.connected).y
})
var circles = svg.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.style("stroke", "gray")
.style("fill", "white")
.attr("r", function (d, i) {
return d.r
})
.attr("cx", function (d, i) {
return d.x
})
.attr("cy", function (d, i) {
return d.y
});
var text = svg.selectAll("text")
.data(graph.nodes)
.enter()
.append("text");
var textLabels = text
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; })
.text( function (d) { return d.name })
.attr("font-family", "sans-serif")
.attr("font-size", "10px")
.attr("fill", "red");
});
function findAttribute(name) {
for (var i = 0, len = graph.nodes.length; i < len; i++) {
if (graph.nodes[i].name === name)
return graph.nodes[i]; // Return as soon as the object is found
}
return null; // The object was not found
}
</script>
</body>
</html>
If you want to grow the nodes to fit large labels, you can use the getBBox property of an SVG text node after you've drawn it. Here's how I did it, for a list of nodes with fixed coordinates, and two possible shapes:
nodes.forEach(function(v) {
var nd;
var cx = v.coord[0];
var cy = v.coord[1];
switch (v.shape) {
case "circle":
nd = svg.append("circle");
break;
case "rectangle":
nd = svg.append("rect");
break;
}
var w = 10;
var h = 10;
if (v.label != "") {
var lText = svg.append("text");
lText.attr("x", cx)
.attr("y", cy + 5)
.attr("class", "labelText")
.text(v.label);
var bbox = lText.node().getBBox();
w = Math.max(w,bbox.width);
h = Math.max(h,bbox.height);
}
var pad = 4;
switch (v.shape) {
case "circle":
nd.attr("cx", cx)
.attr("cy", cy)
.attr("r", Math.sqrt(w*w + h*h)/2 + pad);
break;
case "rectangle":
nd.attr("x", cx - w/2 - pad)
.attr("y", cy - h/2 - pad)
.attr("width", w + 2*pad)
.attr("height", h + 2*pad);
break;
}
});
Note that the shape is added, the text is added, then the shape is positioned, in order to get the text to show on top.
I've been using the sample code from this d3 project to learn how to display d3 graphs and I can't seem to get text to show up in the middle of the circles (similar to this example and this example). I've looked at other examples and have tried adding
node.append("title").text("Node Name To Display")
and
node.append("text")
.attr("text-anchor", "middle")
.attr("dy", ".3em").text("Node Name To Display")
right after node is defined but the only results I see is "Node Name To Display" is showing up when I hover over each node. It's not showing up as text inside the circle. Do I have to write my own svg text object and determine the coordinates of that it needs to be placed at based on the coordinates of radius of the circle? From the other two examples, it would seem like d3 already takes cares of this somehow. I just don't know the right attribute to call/set.
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).
I found this guide very useful in trying to accomplish something similar :
https://www.dashingd3js.com/svg-text-element
Based on above link this code will generate circle labels :
<!DOCTYPE html>
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</head>
<body style="overflow: hidden;">
<div id="canvas" style="overflow: hidden;"></div>
<script type="text/javascript">
var graph = {
"nodes": [
{name: "1", "group": 1, x: 100, y: 90, r: 10 , connected : "2"},
{name: "2", "group": 1, x: 200, y: 50, r: 15, connected : "1"},
{name: "3", "group": 2, x: 200, y: 130, r: 25, connected : "1"}
]
}
$( document ).ready(function() {
var width = 2000;
var height = 2000;
var svg = d3.select("#canvas").append("svg")
.attr("width", width)
.attr("height", height)
.append("g");
var lines = svg.attr("class", "line")
.selectAll("line").data(graph.nodes)
.enter().append("line")
.style("stroke", "gray") // <<<<< Add a color
.attr("x1", function (d, i) {
return d.x
})
.attr("y1", function (d) {
return d.y
})
.attr("x2", function (d) {
return findAttribute(d.connected).x
})
.attr("y2", function (d) {
return findAttribute(d.connected).y
})
var circles = svg.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.style("stroke", "gray")
.style("fill", "white")
.attr("r", function (d, i) {
return d.r
})
.attr("cx", function (d, i) {
return d.x
})
.attr("cy", function (d, i) {
return d.y
});
var text = svg.selectAll("text")
.data(graph.nodes)
.enter()
.append("text");
var textLabels = text
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; })
.text( function (d) { return d.name })
.attr("font-family", "sans-serif")
.attr("font-size", "10px")
.attr("fill", "red");
});
function findAttribute(name) {
for (var i = 0, len = graph.nodes.length; i < len; i++) {
if (graph.nodes[i].name === name)
return graph.nodes[i]; // Return as soon as the object is found
}
return null; // The object was not found
}
</script>
</body>
</html>
If you want to grow the nodes to fit large labels, you can use the getBBox property of an SVG text node after you've drawn it. Here's how I did it, for a list of nodes with fixed coordinates, and two possible shapes:
nodes.forEach(function(v) {
var nd;
var cx = v.coord[0];
var cy = v.coord[1];
switch (v.shape) {
case "circle":
nd = svg.append("circle");
break;
case "rectangle":
nd = svg.append("rect");
break;
}
var w = 10;
var h = 10;
if (v.label != "") {
var lText = svg.append("text");
lText.attr("x", cx)
.attr("y", cy + 5)
.attr("class", "labelText")
.text(v.label);
var bbox = lText.node().getBBox();
w = Math.max(w,bbox.width);
h = Math.max(h,bbox.height);
}
var pad = 4;
switch (v.shape) {
case "circle":
nd.attr("cx", cx)
.attr("cy", cy)
.attr("r", Math.sqrt(w*w + h*h)/2 + pad);
break;
case "rectangle":
nd.attr("x", cx - w/2 - pad)
.attr("y", cy - h/2 - pad)
.attr("width", w + 2*pad)
.attr("height", h + 2*pad);
break;
}
});
Note that the shape is added, the text is added, then the shape is positioned, in order to get the text to show on top.
I want to create a circle inside a g element. I can create the groups and translate the element just fine. However, I haven't figure out how to create the circle into the g elements.
With this code I'm getting the element g and circles but the circles are outside the g:
var data = [
{"x": 10, "y": 10},
{"x": 20, "y": 20},
{"x": 30, "y": 30},
];
var svg = d3.select('svg');
var t = d3.transition().duration(750);
var group = svg
.selectAll(".groups")
.data(data) // JOIN
group.exit() // EXIT
.remove();
var groups = group.enter() // ENTER
.append("g")
.attr('class', 'groups')
.merge(group)
.attr('transform', function(d) { return 'translate(' + d.x + ',' + d.y + ')'; });
var circles = svg.selectAll('.circles')
.data(data)
.style("fill", "blue");
circles.exit()
.style("fill", "brown")
.transition(t)
.style("fill-opacity", 1e-6)
.remove();
circles.enter()
.append('circle')
.attr('r', 2.5)
.attr('class', 'circles')
.style("fill", "green")
.merge(circles)
.attr("r", (d) => d.y/5)
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
How can I fix it?
Here is the snippet: https://plnkr.co/edit/pnoSGSEv7pDo28HxRifd
You need to append to the groups by selecting the groups and not the svg:
var circles = groups.selectAll('.circles')
.data(data)
.style("fill", "blue");
Here is the updated code:https://plnkr.co/edit/4WtXqUrDAdCkOYSdqori