parameterized D3js force directed graph node positioning

729 views Asked by At

I am planning on creating a tree like structure for nodes for a dynamically created dataset, and would like to set the y-axis coordinates of all nodes for the force directed graph. In other words, I would like nodes to sit in levels (like a tree structure), while the nodes are free to move and optimise themselves in the x-axis.

The end result should be a tree where the nodes sit in allocated levels instead of radially as they would be by default. Sibling links should not overlap, and be as short as possible (logical grouping, hence wanting to do this via force graph).
If someone has a solution that is simpler than this, then I am all ears.

code extract:

ldLinks = [
{source: "root", target: "1", source_level: 0, target_level: 1},
{source: "root", target: "2", source_level: 0, target_level: 1},
{source: "root", target: "3", source_level: 0, target_level: 1},
{source: "root", target: "4", source_level: 0, target_level: 1},
{source: "1", target: "1.1", source_level: 1, target_level: 2},
{source: "1", target: "1.2", source_level: 1, target_level: 2},
{source: "2", target: "2.1", source_level: 1, target_level: 3},
{source: "2", target: "2.2", source_level: 1, target_level: 3},
{source: "3", target: "3.1", source_level: 1, target_level: 3},
{source: "4", target: "3.2", source_level: 1, target_level: 3},
{source: "4", target: "3.3", source_level: 1, target_level: 3},
{source: "3", target: "3.2", source_level: 2, target_level: 3},
{source: "3", target: "4.1", source_level: 2, target_level: 3},
{source: "2.1", target: "5.1", source_level: 3, target_level: 4},
{source: "3.1", target: "5.1", source_level: 3, target_level: 4},
{source: "1", target: "5", source_level: 1, target_level: 4},
];

var dNodes = {};

// Compute the distinct dNodes from the ldLinks.
ldLinks.forEach(function(link) {
    link.source = dNodes[link.source] || (dNodes[link.source] = {name: link.source, level: link.source_level});
    link.target = dNodes[link.target] || (dNodes[link.target] = {name: link.target,  level: link.target_level});
});

//parameterize nodes
for (var sNodeName in dNodes) {
    //dNodes[sNodeName].fixed = (only fixed along y-axis)
    dNodes[sNodeName].y = (dNodes[sNodeName].level * 50)
}
1

There are 1 answers

0
Roman On BEST ANSWER

Huzzah!

Thank you to Limin's comment from this answer!

I set force.gravity(0), and then added boundaries (.attr("cx/cy")) to node in tick() function:

function tick() {
    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; });

    node
        .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
        .attr("cx", function(d) { return d.x = Math.max(8, Math.min(width - 8, d.x)); })
        .attr("cy", function(d) { return d.y = Math.max(100*d.level, Math.min(100*d.level, d.y)); });
}