1

How can I add additional nodes to a D3.js pack layout when it has already been created and is being displayed in an SVG element? Recalculating the entire pack is an option, but I would like to do the insertation much more seamless.

What I am looking for is a pack().addNode() function which allows me to insert additional children nodes into an existing leaf node. When the user zoomes in to that leaf node, the leaf node is supposed to turn into a parent node and display the inserted nodes as its children.

Please take a look at my fiddle here. The node called subnode_jane should receive the children in subnode_subnodes as its new children.

var subnode_subnodes = [{
    "name": "JanesDaughter",
    "size": 1030
}, {
    "name": "JanesSon",
    "size": 12000
}];

function zoom(d) {
    var focus0 = focus;
    focus = d;

    if (d.name === "subnode_jane") {
        alert("Oh I see subnode_jane has unpaginated children; insert them now!");
    }

/* ... */
}

Optional: While doing so, it would be nice if the overall appearance of the original nodes would remain quite the same. Thanks!

Related: update a layout.pack in d3.js

Community
  • 1
  • 1
nilsole
  • 1,663
  • 2
  • 12
  • 28
  • 1
    http://bl.ocks.org/mbostock/1095795, http://stackoverflow.com/questions/11606214/adding-and-removing-nodes-in-d3js-force-graph, http://stackoverflow.com/questions/21070899/d3-js-how-to-remove-nodes-when-link-data-updates-in-a-force-layout – AJ_91 Apr 01 '15 at 10:04

1 Answers1

1

Please find my workaround solution here. (Workaround because I was not able to code an extension function of the D3 pack layout)

My solution is to create a second "virtual" pack and to use its circle coordinates for integration in the original pack. Works for me.

// hard-coded function
var virtualNodesByParentNode = function (d3NodeParentElement, nodeChildrenElementArray) {
    root.children[0].children[0].children = subnode_subnodes;
    // we need to do this because otherwise, the parent node object will be changed
    var d3NodeParentElementClone = clone(d3NodeParentElement);
    var pack = d3.layout.pack()
        .padding(2)
         // -1 is important to avoid edge overlap
        .size([d3NodeParentElementClone.r * 2 - 1, d3NodeParentElementClone.r * 2 - 1])
        .value(function (d) {
    return d.size;
        });
    d3NodeParentElementClone.children = nodeChildrenElementArray;
    var nodes = pack.nodes(d3NodeParentElementClone)
    // absolute x,y coordinates calculation
    var curChildnode;
    for (var i = 1; i < nodes.length; i++) {
        curChildnode = nodes[i];
        curChildnode.x = curChildnode.x - nodes[0].x + d3NodeParentElement.x;
        curChildnode.y = curChildnode.y - nodes[0].y + d3NodeParentElement.y;
        curChildnode.depth = d3NodeParentElement.depth + 1;
        curChildnode.parent = d3NodeParentElement;
    }
    nodes.splice(0, 1);
    return nodes;
};

The if condition within the zoom function:

if (d.name === "subnode_jane" && done === 0) {
    done = 1;
    var virtualNodes = virtualNodesByParentNode(d, subnode_subnodes);
    d.children = virtualNodes;
    // http://stackoverflow.com/a/5081471/2381339
    nodes.push.apply(nodes, virtualNodes);
    circle = svg.selectAll("circle")
        .data(nodes)
        .enter().append("circle")
        .attr("class", function (d) {
        return d.parent ? d.children ? "node" : "node node--leaf" : "node node--root";
    })
        .style("fill", function (d) {
        return "red";
    })
        .on("click", function (d) {
        if (focus !== d) zoom(d), d3.event.stopPropagation();
    });
    text.remove();
    text = svg.selectAll("text")
        .data(nodes)
        .enter().append("text")
        .attr("class", "label")
        .style("fill-opacity", function (d) {
        return d.parent === root ? 1 : 0;
    })
        .style("display", function (d) {
        return d.parent === root ? null : "none";
    })
        .text(function (d) {
        return d.name;
    });
    circle = svg.selectAll("circle");
    node = svg.selectAll("circle,text");
    // zoom to current focus again (do the transformation of the updated elements)
    zoomTo(view);
    return zoom(d);
}
nilsole
  • 1,663
  • 2
  • 12
  • 28