Search code examples
javascriptd3.jsd3-force-directed

D3 forceRadial() with nested data centered around parent


I am trying to get d3.forceRadial() to distribute nodes hierarchically around their respective parents, but when I set the the x and y parameters on the force all the nodes shoot off towards the bottom right corner and beyond with x and y values ending up in the tenthousands..

My code looks like this:

let simulation = d3.forceSimulation()
    .force('radial', d3.forceRadial((d) => (d.distance * 100))
        .x((d) => ((d.parent && !isNaN(d.parent.x)) ? d.parent.x + d.parent.vx : 0))
        .y((d) => ((d.parent && !isNaN(d.parent.y)) ? d.parent.y + d.parent.vy : 0))
        .strength(3))
    .force('link', d3.forceLink().id((d) => (d.entity_id)).strength(0))
    .nodes(data.nodes);

Where parent is a reference to the parent node and d.distance is the number of steps away from the root node. I have tried setting all node's x and y to 0 beforehand but to no avail..


Solution

  • Unfortunately, in the current version (4.13.0 at the time of the writing), d3.forceRadial does not accept a function for the x() and y() methods, despite the fact that it does accept a function for the radius() method.

    We can see this inspecting the source code.

    This is the code for radius():

    force.radius = function(_) {
        return arguments.length ? (radius = typeof _ === "function" ? _ :
            constant(+_), initialize(), force) : radius;
    };
    

    As you can see, it accepts a function.

    But this is the code for x() and y():

    force.x = function(_) {
        return arguments.length ? (x = +_, force) : x;
    };
    
    force.y = function(_) {
        return arguments.length ? (y = +_, force) : y;
    };
    

    As you can see, it does not accept a function.

    You can also see that the API only talks about numbers, not functions. Regarding radial.x:

    If x is specified, sets the x-coordinate of the circle center to the specified number and returns this force. (emphasis mine)

    The same goes for radial.y.