Original question:
I want to visual highlight attributes for each node. To do so I added another small-circle. Further I want to add a short string in each blue circle. I tried different approaches, the most common seems to be to .append("text") during the init straight away. Unfortunately I was not lucky yet.
How can I achieve that?
MODIFIED, as kikon mentioned:
I did as suggested, it works. But how can I assign the text to each node? Now it appears only on the first child circle.
var width = window.innerWidth,
height = window.innerHeight;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.call(d3.zoom().on("zoom", function(event) {
svg.attr("transform", event.transform)
}))
.append("g")
////////////////////////
// outer force layout
var data = {
"nodes":[
{ "id": "A", "shoes": [{"brand": "Nike"}, {"brand": "Adidas"}, {"brand": "Adidas"}]},
{ "id": "B", "shoes": [{"brand": "Nike"}, {"brand": "Adidas"}, {"brand": "Adidas"}, {"brand": "Adidas"}]},
{ "id": "C", "shoes": []},
],
"links": [
{ "source": "A", "target": "B"},
{ "source": "B", "target": "C"},
{ "source": "C", "target": "A"}
]
};
var simulation = d3.forceSimulation()
.force("size", d3.forceCenter(width / 2, height / 2))
.force("charge", d3.forceManyBody().strength(-1000))
.force("link", d3.forceLink().id(function (d) { return d.id }).distance(250))
linksContainer = svg.append("g").attr("class", "linkscontainer")
nodesContainer = svg.append("g").attr("class", "nodesContainer")
var links = linksContainer.selectAll("g")
.data(data.links)
.join("g")
.attr("fill", "transparent")
var linkLine = linksContainer.selectAll(".linkPath")
.data(data.links)
.join("path")
.attr("stroke", "red")
.attr("fill", "transparent")
.attr("stroke-width", 3)
nodes = nodesContainer.selectAll(".nodes")
.data(data.nodes, function (d) { return d.id; })
.join("g")
.attr("class", "nodes")
.attr("id", function (d) { return d.id; })
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
nodes.selectAll("circle")
.data(d => [d])
.join("circle")
.style("fill", "lightgrey")
.style("stroke", "blue")
.attr("r", 40)
var eventCircle = nodes.selectAll("g")
.data(data.nodes)
.enter()
.append("g")
eventCircle.append("g")
.selectAll("circle-small")
.data(x => x.shoes)
.enter()
.append('circle')
.attr('r', 15)
.attr("fill", "blue")
.attr('cx', function (d,i) {
const factor = (i / 40) * (15 / 2) * 5;
return 40 * Math.cos(factor - Math.PI * 0.5);
})
.attr('cy', function (d,i) {
const factor = (i / 40) * (15 / 2) * 5;
return 40 * Math.sin(factor - Math.PI * 0.5);
})
.attr("class", "circle-small")
eventCircle.append("text")
.attr("x", function(d) {
return -4
})
.attr("y", function(d) {
return -34
})
.attr("font-size", 15)
.attr("fill", "white")
.attr("pointer-events", "cursor")
.text(function (d) {
return "1"
})
simulation
.nodes(data.nodes)
.on("tick", tick)
simulation
.force("link")
.links(data.links)
function tick() {
linkLine.attr("d", function(d) {
var dx = (d.target.x - d.source.x),
dy = (d.target.y - d.source.y),
dr = Math.sqrt(dx * dx dy * dy)
return "M" d.source.x "," d.source.y "A" dr "," dr " 0 0,1 " d.target.x "," d.target.y;
})
nodes
.attr("transform", d => `translate(${d.x}, ${d.y})`);
}
function dragStarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragEnded(event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
body {
background: whitesmoke,´;
overflow: hidden;
margin: 0px;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>D3v7</title>
<!-- d3.js framework -->
<script src="https://d3js.org/d3.v7.js"></script>
</head>
<body>
</body>
</html>
CodePudding user response:
If you inspect your SVG you'll see you are appending several circles on top of each other.
Just append one <g>
for each brand, translating it accordingly...
var eventCircle = nodes.selectAll(null)
.data(d => d.shoes)
.enter()
.append("g")
.attr("transform", function(d, i) {
const factor = (i / 40) * (15 / 2) * 5;
return `translate(${40 * Math.cos(factor - Math.PI * 0.5)},${40 * Math.sin(factor - Math.PI * 0.5)})`;
});
...then append your circles and texts to those groups.
Here is your modified demo:
var width = window.innerWidth,
height = window.innerHeight;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.call(d3.zoom().on("zoom", function(event) {
svg.attr("transform", event.transform)
}))
.append("g")
////////////////////////
// outer force layout
var data = {
"nodes": [{
"id": "A",
"shoes": [{
"brand": "Nike"
}, {
"brand": "Adidas"
}, {
"brand": "Adidas"
}]
},
{
"id": "B",
"shoes": [{
"brand": "Nike"
}, {
"brand": "Adidas"
}, {
"brand": "Adidas"
}, {
"brand": "Adidas"
}]
},
{
"id": "C",
"shoes": []
},
],
"links": [{
"source": "A",
"target": "B"
},
{
"source": "B",
"target": "C"
},
{
"source": "C",
"target": "A"
}
]
};
var simulation = d3.forceSimulation()
.force("size", d3.forceCenter(width / 2, height / 2))
.force("charge", d3.forceManyBody().strength(-1000))
.force("link", d3.forceLink().id(function(d) {
return d.id
}).distance(250))
linksContainer = svg.append("g").attr("class", "linkscontainer")
nodesContainer = svg.append("g").attr("class", "nodesContainer")
var links = linksContainer.selectAll("g")
.data(data.links)
.join("g")
.attr("fill", "transparent")
var linkLine = linksContainer.selectAll(".linkPath")
.data(data.links)
.join("path")
.attr("stroke", "red")
.attr("fill", "transparent")
.attr("stroke-width", 3)
nodes = nodesContainer.selectAll(".nodes")
.data(data.nodes, function(d) {
return d.id;
})
.join("g")
.attr("class", "nodes")
.attr("id", function(d) {
return d.id;
})
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
nodes.selectAll("circle")
.data(d => [d])
.join("circle")
.style("fill", "lightgrey")
.style("stroke", "blue")
.attr("r", 40)
var eventCircle = nodes.selectAll(null)
.data(d => d.shoes)
.enter()
.append("g")
.attr("transform", function(d, i) {
const factor = (i / 40) * (15 / 2) * 5;
return `translate(${40 * Math.cos(factor - Math.PI * 0.5)},${40 * Math.sin(factor - Math.PI * 0.5)})`;
});
eventCircle.append('circle')
.attr('r', 15)
.attr("fill", "blue")
.attr("class", "circle-small")
eventCircle.append("text")
.attr("font-size", 10)
.attr("fill", "white")
.style("text-anchor", "middle")
.attr("pointer-events", "cursor")
.text(function(d) {
return d.brand;
})
simulation
.nodes(data.nodes)
.on("tick", tick)
simulation
.force("link")
.links(data.links)
function tick() {
linkLine.attr("d", function(d) {
var dx = (d.target.x - d.source.x),
dy = (d.target.y - d.source.y),
dr = Math.sqrt(dx * dx dy * dy)
return "M" d.source.x "," d.source.y "A" dr "," dr " 0 0,1 " d.target.x "," d.target.y;
})
nodes
.attr("transform", d => `translate(${d.x}, ${d.y})`);
}
function dragStarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragEnded(event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
body {
background: whitesmoke, ´;
overflow: hidden;
margin: 0px;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>D3v7</title>
<!-- d3.js framework -->
<script src="https://d3js.org/d3.v7.js"></script>
</head>
<body>
</body>
</html>