How to dynamically add items to a hierarchy

Hello, I’m working on a view of a data tree and I want to be able to dynamically update the contents of the tree according to return values of Ajax requests. I implemented this before using d3 about 6 years ago, but with the current d3 I get bugs. Here is an example of the data I’m using:

{
    "title": "root",
    "children": [
        {
            "title": "First node.",
            "children": [
                {
                    "title": "Link to second node.",
                    "to": 1
                }
            ]
        },
        {
            "title": "Second node.",
            "children": [
                {
                    "title": "Link to third node.",
                    "bla": "One.",
                    "to": 2
                }
            ]
        },
        {
            "title": "Third node.",
            "children": [
                {
                    "title": "Link to first node.",
                    "to": 0
                }
            ]
        }
    ]
}

These are nodes in a simple triangular graph where each node links to the next one in order, with node 2 looping around to link to node 0. I’m placing them in a collapsible tree similar to this one. When I click on the circle representing a link to another node, I want that node to open underneath it as one of its children. You can explore the graph endlessly by doing this.

In my old codebase, I accomplished this with code similar to the following:

item.on('click', function (d) {
    if (!d.children) {
        if (d.data.to && !d._children) {
            ajaxFetch(d.data.to, function (returned) {
                returned.parent = d;
                returned.depth = 1 + d.depth;
                d.children = [returned];
                // also recursively update depth of children
                return update(d);
            });
        } else {
            d.children = d._children;
            d._children = null;
            return update(d);
        };
    } else {
        d._children = d.children;
        d.children = null;
        return update(d);
    };
}

The update function above is similar to the one used in the collapsible tree example. When I do this in my current codebase, there are many bugs. For one, the new children are not initialized as members of the Qd class. To solve this, I tried calling d3.hierarchy() on each newly added node to make it a member of the Qd class. I then placed it in the children array for the link I wanted and I set its depth and parent ids. However, there are still lots of problems. When doing the source and target calculations for new nodes, the source node for newly added nodes always registers as id 0 and not the id of the parent node I set.

This results in a bunch of odd rendering behaviors, like link lines sliding from node to node. When exiting a node, its circle also slides straight down out of the viewport instead of sliding up to the parent node while it fades. I don’t know if the “height” property of nodes has anything to do with this. I don’t think it was there when I last used d3 and from what I can tell it’s the opposite of “depth,” it’s the distance of a node from the furthest leaf, with the root always having the highest height value. Needless to say I can’t accurately specify a node height without iterating over the entire tree, which I would like to avoid for efficiency’s sake.

Can anyone recommend a good approach to something like this? This example is the only thing I’ve found that does something similar but it doesn’t use a hierarchy and its approach involves change the entire chart rather than making the kind of partial changes I’m aiming for. Thanks for reading.

1 Like

Event management changed significantly in V6. In particular, the first argument to all event listeners is expected to be an event. You can find the details in the V6 migration guide.

2 Likes

I saw that had changed. The code I’m currently using is similar to this:

on('click', function (thisEvent, d) {
                if (d.data.to && !d._children) {
                    return fetcher(function (data) {
                        var ins = d3.hierarchy(data);
                        var ccount = root.descendants().length;
                        ins.parent = d;
                        ins.depth = d.depth + 1;
                        ins.id = ccount + 1;
                        ins.children[0]['depth'] = 2 + d.depth;
                        ins.children[0]['id'] = 2 + ccount;
                        ins.children[0]['children'] = null;
                        ins._children = ins.children;
                        ins.children = null;
                        d.children = [ins];
                        layoutTree(root);
                        return update(thisEvent, root);
                    }, { index : d.data.to });
                } else {
                    d.children = d.children ? null : d._children;
                    return update(thisEvent, d);
                };
            });

As far as I know these updates to the depth and count values should give the result I want, but I have all kinds of display bugs. Do I need to iterate over all nodes in the hierarchy and update their height values? Is anything more needed to make the hierarchy work right?

1 Like