Collapsible Network(Graph) Diagram with D3

treeData = {
name: 'parent',
value: 5,
children: [
{name: 'child_1', value: 10},
{name: 'child_2', value: 15},
{name: 'child_3', value: 20},
]
}
// Collapse the node and all it's children
function collapse(d) {
if(d.children) {
d._children = d.children
d._children.forEach(collapse)
d.children = null
}
}
graph={
"nodes": [
{"name": "A"},
{"name": "B"},
{"name": "C"},
{"name": "D"},
{"name": "E"},
],
"links": [
{"source": "A", "target": "B"},
{"source": "E", "target": "D"},
{"source": "C", "target": "E"},
{"source": "B", "target": "E"},
{"source": "A", "target": "D"},

]
}
graph={
"nodes": [
{"name": "A", show:true, showChildren:true, hasChildren:true},
{"name": "B", show:true, showChildren:true, hasChildren:true},
{"name": "C", show:true, showChildren:true, hasChildren:true},
{"name": "D", show:true, showChildren:false, hasChildren:false},
{"name": "E", show:true, showChildren:true, hasChildren:true},
],
"links": [
{"source": "A", "target": "B", show:true},
{"source": "E", "target": "D", show:true},
{"source": "C", "target": "E", show:true},
{"source": "B", "target": "E", show:true},
{"source": "A", "target": "D", show:true},

]
}
  • show: The boolean value will help us determine whether to show or hide the node or link.
  • showChildren: This will elaborate our code whether to show the children of the node or not.
  • hasChildren: This will help us to make our code efficient by determining the nodes that have children.
drawNetwork = () => {/* ... PLEASE FILL IN THE REMAINING CODE BY YOURSELF ... */// making nodes and link part, functions below
const nodes = drawNodes(graph);
const links = drawLinks(graph);
// the simulation part
const simulation = d3.forceSimulation(data.nodes.filter(n => n.show))
.force("link", d3.forceLink(data.links.filter(n => n.show)).id(function(d) { return d.id; }))
.force("charge", d3.forceManyBody().strength(-7000))
.force("center", d3.forceCenter((width - margin.right - margin.left)/2.5, (height - margin.top - margin.bottom)/2.5))
.on("tick", ticked);
// please set width and height by yourself as well
node = svg.append("g")
.selectAll("text")
.data(data.nodes.filter(n => n.show))
.enter()
.append("text")
.attr("text-anchor", 'middle')
.attr("class", "fas cmdb_element")
.attr('font-size', '20px')
.attr('cursor', 'move')
.attr('fill', function(d) { return determineColor(d, ICON_COLOR, CIRCLE_COLOR) })
.text(function(d) { return String.fromCodePoint(parseInt(d.icon, 16))})
.on("mouseover", function() {
d3.selectAll(`#relation_${this.__data__.id}`).style('visibility', 'visible');
d3.selectAll(`#line_${this.__data__.id}`).style('stroke',
TEXT_COLOR)
})
.on("mouseout", function(){
nodeRel.style("visibility", "hidden");
d3.selectAll(`#line_${this.__data__.id}`).style('stroke', ICON_COLOR);
})
.on('click', function(d){ toggleChildren(this.__data__)});
// Toggle children on click.function toggleChildren(d) {
if (d.showChildren){
toggleVisibilityOfNodesAndLinks(d.id, false);
d.showChildren = false;
}
else{
if(d.hasChildren && !d.cache){
fetch_relationships(d.id); // Api Call to fetch further nodes
d.cache = true; // Using cache to extra avoid api call
}
toggleVisibilityOfNodesAndLinks(d.id, true);
d.showChildren = true;
}
updateNetwork();
}
function toggleVisibilityOfNodesAndLinks(d, visibility){
const parents = [d];
let loopCount = 0;
let visited = []; // to track visited nodes
while (parents.length > 0) {
const parent = parents.shift();
visited.push(parent);
data.links.forEach(data_link => {
if ((data_link.source.id || data_link.source) === parent) {
data_link.show = visibility;
if(data_link.target && data_link.target.hasChildren && !data_link.target.showChildren) data_link.target.show = visibility;
if (!visited.includes(data_link.target.id || data_link.target) && (data_link.target.show !== visibility))
{
parents.push(data_link.target.id || data_link.target);
}
}
});
data.nodes.forEach(node => {
if (node.id === parent && loopCount !== 0) {
node.show = visibility;
data.links.forEach(data_link => ((data_link.target.id || data_link.target) == node.id && data_link.source.showChildren == true) ? data_link.show = visibility : data_link.show);
}
});
loopCount += 1;
}
};
// Remove and Reset everythingfunction updateNetwork(){
svg.selectAll('circle').remove();
svg.selectAll('line').remove();
svg.selectAll('text').remove();
svg.selectAll('rect').remove();
svg.selectAll('marker').remove();
this.drawNetwork(); // Create Network Again
}

Code Explanation:

  • toggleChildren: This function is a wrapper method to call the toggleVisibilityOfNodesAndLinks in case showChildren of a node is true. After then, we set the showChildren to false. In case if showChildren is set to false then we check if it hasChildren. If yes we call the API to get the further children.
  • toggleVisibilityOfNodesAndLinks: This method is the brain of the whole code. This method is based on the algorithmic logic of BFS(Breath First Search). It hides/shows the link originated from the parent itself. It adds the nodes that the link points to as a parent. It hides/shows the nodes that are the parent (except for the first one). It repeats until we have no parent. The visited collection is used to track the visited nodes so that our code doesn’t go into the infinite loop. The (data_link.target.id || data_link.target) is used because when new node is added via API we do not have target.id. The visibility of node_link is changed only when showChildren of the node is set to true otherwise it is unnecessary to change the visibility of the link.
  • updateNetwork: This method is re-rendering the whole network to display the updated graph data.

Demo:

--

--

--

Today I am wise, so I am changing myself.

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Try Fold First!

Install Oracle Java JDK in Ubuntu 20.04 LTS

Exporting Firestore Collection CSV into Cloud Storage on Demand, the easy way

Goodbye, Slack. Hello, Spectrum!

Create Custom Domain on Xampp Localhost

An example Image

WCGW using fake bookshelves during live meetings

Learning Unity — Dynamic GameObject Movement

Microsoft Azure Kubernetes Service (AKS)

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Mujeeb Arshad

Mujeeb Arshad

Today I am wise, so I am changing myself.

More from Medium

Rebuilding with Eleventy

External Sensor using ESP32 with BMP280

How to use Custom Theme Colors in German MS Office for Mac 365

Automatic “Alt+Tab” input with AutoHotKey