'Make d3.js v4 force directed graph simulate in hierarchical fashion
I have a json graph with nodes and links. I am using d3.js v4 library to generate a graph from this json. But the graph nodes are not arranged in some fashion. I would like them to appear in a tree like order. So, I was following this example -> d3 force directed graph downward force simulation
But I am unable to understand what would the exact changes be for my code. Since the above is done for a tree and that too for d3.js v3. I am also new to javascript and d3.js and hence need your help to understand what would my code be if I wanted my graph to look something like in the above link.
Above example considers one root. But my json can have multiple roots so having a tree by definition is not possible I guess, but I want them just to be ordered in hierarchy. Following is my json:
{
"nodes": [
{
"hostname": "seroics08140",
"level": 1,
"type": "Server",
"position": "Baremetal",
"cabinet": "SERO01-01-00-0011/03*019",
"position_in_cabinet": "2"
},
{
"hostname": "seroinmsw11048",
"level": 2,
"type": "Switch",
"position": "Top Rack",
"cabinet": "SERO01-01-00-0011/04*019",
"position_in_cabinet": "40"
},
{
"hostname": "serointsw00989",
"level": 2,
"type": "Switch",
"position": "Top Rack",
"cabinet": "SERO01-01-00-0011/04*019",
"position_in_cabinet": "41"
},
{
"hostname": "serointsw00996",
"level": 3,
"type": "Switch",
"position": "Spine",
"cabinet": "SERO01-01-00-0011/04*016",
"position_in_cabinet": "39"
},
{
"hostname": "serointsw00971",
"level": 4,
"type": "Switch",
"position": "Super Spine",
"cabinet": "SERO01-01-00-0011/10*007",
"position_in_cabinet": "35"
},
{
"hostname": "serointrt00110",
"level": 5,
"type": "Router",
"position": "Border Leaf",
"cabinet": "SERO01-01-00-0011/10*007",
"position_in_cabinet": "34"
},
{
"hostname": "serointrt00204",
"level": 5,
"type": "Router",
"position": "Border Leaf",
"cabinet": "SERO01-01-00-0011/10*023",
"position_in_cabinet": "42"
},
{
"hostname": "serointsw00972",
"level": 4,
"type": "Switch",
"position": "Super Spine",
"cabinet": "SERO01-01-00-0011/10*023",
"position_in_cabinet": "40"
},
{
"hostname": "serointsw00997",
"level": 3,
"type": "Switch",
"position": "Spine",
"cabinet": "SERO01-01-00-0011/04*016",
"position_in_cabinet": "40"
},
{
"hostname": "serointsw01009",
"level": 2,
"type": "Switch",
"position": "Top Rack",
"cabinet": "SERO01-01-00-0011/04*019",
"position_in_cabinet": "42"
},
{
"hostname": "serointsw01010",
"level": 2,
"type": "Switch",
"position": "Top Rack",
"cabinet": "SERO01-01-00-0011/04*019",
"position_in_cabinet": "43"
},
{
"hostname": "serointsw00998",
"level": 3,
"type": "Switch",
"position": "Spine",
"cabinet": "SERO01-01-00-0011/04*016",
"position_in_cabinet": "41"
},
{
"hostname": "serointsw00999",
"level": 3,
"type": "Switch",
"position": "Spine",
"cabinet": "SERO01-01-00-0011/04*016",
"position_in_cabinet": "42"
},
{
"hostname": "serointsw01001",
"level": 3,
"type": "Switch",
"position": "Spine",
"cabinet": "SERO01-01-00-0011/04*016",
"position_in_cabinet": "43"
},
{
"hostname": "serointsw01002",
"level": 3,
"type": "Switch",
"position": "Spine",
"cabinet": "SERO01-01-00-0011/04*016",
"position_in_cabinet": "44"
},
{
"hostname": "serointsw01011",
"level": 2,
"type": "Switch",
"position": "Top Rack",
"cabinet": "SERO01-01-00-0011/04*019",
"position_in_cabinet": "44"
}
],
"links": [
{
"source": "seroics08140",
"target": "seroinmsw11048",
"strength": 1
},
{
"source": "seroics08140",
"target": "serointsw00989",
"strength": 1
},
{
"source": "serointsw00989",
"target": "serointsw00996",
"strength": 2
},
{
"source": "serointsw00996",
"target": "serointsw00971",
"strength": 3
},
{
"source": "serointsw00971",
"target": "serointrt00110",
"strength": 4
},
{
"source": "serointsw00971",
"target": "serointrt00204",
"strength": 4
},
{
"source": "serointrt00204",
"target": "serointrt00110",
"strength": 5
},
{
"source": "serointsw00996",
"target": "serointsw00972",
"strength": 3
},
{
"source": "serointsw00972",
"target": "serointrt00110",
"strength": 4
},
{
"source": "serointsw00972",
"target": "serointrt00204",
"strength": 4
},
{
"source": "serointsw00989",
"target": "serointsw00997",
"strength": 2
},
{
"source": "serointsw00997",
"target": "serointsw00971",
"strength": 3
},
{
"source": "serointsw00997",
"target": "serointsw00972",
"strength": 3
},
{
"source": "serointsw00989",
"target": "serointsw01009",
"strength": 2
},
{
"source": "seroics08140",
"target": "serointsw01009",
"strength": 1
},
{
"source": "serointsw01009",
"target": "serointsw00996",
"strength": 2
},
{
"source": "serointsw01009",
"target": "serointsw00997",
"strength": 2
},
{
"source": "serointsw01009",
"target": "serointsw00989",
"strength": 2
},
{
"source": "seroics08140",
"target": "serointsw01010",
"strength": 1
},
{
"source": "serointsw01010",
"target": "serointsw00998",
"strength": 2
},
{
"source": "serointsw00998",
"target": "serointsw00971",
"strength": 3
},
{
"source": "serointsw00998",
"target": "serointsw00972",
"strength": 3
},
{
"source": "serointsw01010",
"target": "serointsw00999",
"strength": 2
},
{
"source": "serointsw00999",
"target": "serointsw00971",
"strength": 3
},
{
"source": "serointsw00999",
"target": "serointsw00972",
"strength": 3
},
{
"source": "serointsw01010",
"target": "serointsw01001",
"strength": 2
},
{
"source": "serointsw01001",
"target": "serointsw00971",
"strength": 3
},
{
"source": "serointsw01001",
"target": "serointsw00972",
"strength": 3
},
{
"source": "serointsw01010",
"target": "serointsw01002",
"strength": 2
},
{
"source": "serointsw01002",
"target": "serointsw00971",
"strength": 3
},
{
"source": "serointsw01002",
"target": "serointsw00972",
"strength": 3
},
{
"source": "serointsw01010",
"target": "serointsw01011",
"strength": 2
},
{
"source": "seroics08140",
"target": "serointsw01011",
"strength": 1
},
{
"source": "serointsw01011",
"target": "serointsw00998",
"strength": 2
},
{
"source": "serointsw01011",
"target": "serointsw00999",
"strength": 2
},
{
"source": "serointsw01011",
"target": "serointsw01001",
"strength": 2
},
{
"source": "serointsw01011",
"target": "serointsw01002",
"strength": 2
},
{
"source": "serointsw01011",
"target": "serointsw01010",
"strength": 2
}
]
}
As you can see my json has levels that shows what nodes should be in what level. The below is my code:
d3.select("svg").remove();
var graph;
width = 1000;
height = 1000;
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().distance(function (d) { return 100; }).id(function (d) { return d.hostname; }))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2))
var zoom = d3.zoom()
.scaleExtent([0.5, 8])
.on("zoom", zoom);
var svg = d3.select("body")
.append("svg")
.attr("width", "100%")
.attr("height", height)
var g = svg.append("g");
zoom(svg);
var color = d3.scaleOrdinal(d3.schemeCategory20);
var linkColor = d3.scaleOrdinal(d3.schemeCategory10);
d3.json(d3dataURL, function(error, graph){
if(error) throw error
var link = g.append("g")
.attr("class", "links")
.selectAll("line")
.data(graph.links)
.enter().append("line")
.style("stroke", function(d){ return linkColor(d.strength); })
.attr("stroke-width", function (d) { return Math.sqrt(d.strength); })
var node = g.append("g")
.attr("class", "nodes")
.selectAll("g")
.data(graph.nodes)
.enter().append("g")
var circles = node.append("circle")
.attr("r", 7)
.attr("fill", function (d) { return color(d.level); });
// Create a drag handler and append it to the node object instead
var drag_handler = d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended);
drag_handler(node);
var lables = node.append("text")
.text(function (d) { return titleCase(d.hostname) + '[' + titleCase(d.type) + ']'; })
.attr('x', 6)
.attr('y', 3)
.attr("class", "label")
.attr("text-anchor", "top")
.style("fill", "#555")
.style("font-family", "Arial")
.style("font-size", "10px");
node.append("title")
.text(function (d) {
var hover_info ;
hover_info = d.position + ' ' + d.type + '\nCabinet: ' + d.cabinet + '\nPosition in Cabinet: ' + d.position_in_cabinet;
return hover_info;
});
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation.force("link")
.links(graph.links);
simulation.alpha(1);
simulation.restart();
function ticked() {
node
.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
})
link
.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; });
}
});
function zoom() {
g.attr("transform", d3.event.transform);
}
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.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;
d.fy = null;
}
function titleCase(str) {
str = str.charAt(0).toUpperCase() + str.substring(1);
return str;
}
}
Following is the link for a fiddle -> https://jsfiddle.net/24kMagicInTheAir/rs3kj0ym/
It might appear that the server is a valid root but it is not since there can be many more server for my requirement. Infact the green nodes (routers) are perfect candidate for root nodes.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|
