Hi all,
I've been banging my head against the wall trying to figure out what I'm doing wrong here. I'm new to D3, but learning quickly. I've included a simple html page below as an example of what I'm doing.
In a nutshell, I have a force graph in which I am allowing the user to click on a parent node to hide the children below it and click again to re-show them. I started out with an example I found online that used d3 version 3. It works fine, but other features I want to add required me to move up to the newer version of D3. So I set about porting the code and I have everything working, except that when I re-expand a node, all of the links get redrawn on TOP of the nodes, like this:

The graph renders correctly when the page initially loads, it's only after the node is collapsed then re-expanded that the links render incorrectly. I can see in the HTML markup that the <line> tags are rendered after the <g> (node) tags following a re-expand, but they're render before the <g> tags when the page loads. As you can see in my example code below, it's using the exact same function (update) to render the nodes upon loading the page and upon collapsing/expanding the nodes. For the life of me, I have no idea what's wrong. Any help would be greatly appreciated. Thanks!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<style>
.node circle {
cursor: pointer;
stroke: #3182bd;
stroke-width: 1.5px;
}
line.link {
fill: none;
stroke: #9ecae1;
stroke-width: 1.5px;
}
</style>
</head>
<body>
<script src='https://d3js.org/d3.v5.min.js' ></script>
<script>
var d3width = 960,
d3height = 500,
d3root;
var svg = d3.select("body").append("svg")
.attr("width", d3width)
.attr("height", d3height)
.append("g")
.attr("transform", "translate(40,0)");
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(d3width / 2, d3height / 2))
.on("tick", tick);
var d3link = svg.selectAll(".link");
var d3node = svg.selectAll(".node");
var json = {
"name": "Vince Thompson",
"root": "true",
"children": [
{
"name": "Affiliations",
"children": [
{
"name": "Peter Parker"
},
{
"name": "Carol Danvers"
},
{
"name": "Bruce Banner"
}
]
},
{
"name": "Hosp",
"children": [
{
"name": "Jim"
},
{
"name": "Bob"
},
{
"name": "Tony"
},
{
"name": "Tim"
},
{
"name": "Bill"
},
{
"name": "Wayne"
}
]
},
{
"name": "Soft Affiliations",
"img": "Soft.png",
"children": [
{
"type": "soft",
"name": "Victor von Doom",
"img": "prescriber.png",
"size": 40000
},
{
"type": "soft",
"name": "Unrevealed",
"img": "prescriber.png",
"size": 40000
},
{
"type": "soft",
"name": "Johann Shmidt",
"img": "prescriber.png",
"size": 40000
},
{
"type": "soft",
"name": "Ronan",
"img": "prescriber.png",
"size": 40000
},
{
"type": "soft",
"name": "Max Eisenhardt",
"img": "prescriber.png",
"size": 40000
}
]
}
]
};
d3root = d3.hierarchy(json);
update();
function update()
{
var nodes = flatten(d3root),
links = d3root.links();
simulation
.nodes(nodes)
.force("link").links(links);
d3link = d3link.data(links, function(d) { return d.target.id; });
d3link.exit().remove();
var linkEnter = d3link.enter()
.append("line")
.attr("class", "link");
d3link = linkEnter.merge(d3link);
d3node = d3node.data(nodes, function(d) { return d.id; });
d3node.exit().remove();
var nodeEnter = d3node.enter()
.append("g")
.attr("class", "node")
.on("click", click)
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
nodeEnter.append("circle")
.attr("r", function(d) { return d.data.root ? 25 : 15; })
.style("fill", color);
d3node = nodeEnter.merge(d3node);
}
function dragstarted(d)
{
if (!d3.event.active)
simulation.alphaTarget(0.3).restart();
d.fx = d3.event.x;
d.fy = d3.event.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; //d3.event.x;
d.fy = d3.event.y;
}
function tick()
{
d3link.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; });
d3node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
}
function color(d)
{
return d.data.root ? "#00ff00" // root node
: d._children ? "#3182bd" // collapsed
: d.children ? "#c6dbef" // expanded
: "#efdbef"; // leaf node
}
function click(d)
{
if (d3.event.defaultPrevented)
return; // ignore drag
if (d.children)
{
d._children = d.children;
d.children = null;
}
else
{
d.children = d._children;
d._children = null;
}
update();
}
function flatten(root)
{
var nodes = [], i = 0;
function recurse(node)
{
if (node.children)
node.children.forEach(recurse);
if (!node.id)
node.id = ++i;
nodes.push(node);
}
recurse(root);
return nodes;
}
</script>
</body>
</html>