Links rendering on top of nodes in force graph following collapse/expand

495 views
Skip to first unread message

Wayne

unread,
Oct 12, 2018, 4:19:56 PM10/12/18
to d3-js
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:  
Capture.PNG


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>

HEATHER WALSH

unread,
Oct 13, 2018, 1:44:27 PM10/13/18
to d3...@googlegroups.com
hi there, 
I think you're right that the key is the problem with order, ie the lines are being painted after, so on top of, the nodes. There are a couple of helpful links about this including :


and  this bostock block:

You can see he uses insert() there to insert the links before the nodes, so if you do this:

  var linkEnter = d3link.enter().insert("line", ".node")
    .attr("class", "link");

hopefully it will do the trick. good luck!

--
You received this message because you are subscribed to the Google Groups "d3-js" group.
To unsubscribe from this group and stop receiving emails from it, send an email to d3-js+un...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Wayne

unread,
Oct 15, 2018, 9:40:01 AM10/15/18
to d3-js
Excellent!  That seems to have done the trick.  Thanks so much.  Though, I'm still a bit confused how the same piece of code can render the nodes/links in one order the first time, but render them in reverse order the second time.  I know, it works, I should just accept that and move on...  :-)

HEATHER WALSH

unread,
Oct 15, 2018, 11:02:14 AM10/15/18
to d3...@googlegroups.com
That’s great!
Maybe on click or such you are redrawing the lines/links but not redrawing the circles - so the lines are written after and so on top of the circles. usually when I run inexplicable things in the debugger with a ton of break points,  it annoyingly shows me it’s actually doing exactly what I told it to do ;-) 
Reply all
Reply to author
Forward
0 new messages