Search code examples
javascriptd3.jssvggraphtree

D3 v4 collapsible tree using the d3 link generator


I am trying the implementation of the collapsible tree in d3 v4. I was plying with this example and realized, it is using a custom function to create the link shape

// Creates a curved (diagonal) path from parent to the child nodes
function diagonal(s, d) {

    path = `M ${s.y} ${s.x}
            C ${(s.y + d.y) / 2} ${s.x},
            ${(s.y + d.y) / 2} ${d.x},
            ${d.y} ${d.x}`

    return path
}

Since d3 v 4.9 there is a built-in link generator and I wonder how can it be used in this example.

I have troubles understanding following calls

// Enter any new links at the parent's previous position.
var linkEnter = link.enter().insert('path', "g")
  .attr("class", "link")
  .attr('d', function(d){
    var o = {x: source.x0, y: source.y0}
    return diagonal(o, o)
});

....

// Remove any exiting links
var linkExit = link.exit().transition()
  .duration(duration)
  .attr('d', function(d) {
    var o = {x: source.x, y: source.y}
    return diagonal(o, o)
})
.remove();

I understand, that this is creating a curved line from point(x,y) to point (x,y) - so basically from and to the same point ?

Furthermore, I tried to update

// Transition back to the parent element position
linkUpdate.transition()
  .duration(duration)
  .attr('d', function(d){ return diagonal(d, d.parent) });

with following code

// Transition back to the parent element position linkUpdate.transition() .duration(duration) .attr('d', d3.linkHorizontal(d, d.parent)) but I got *Uncaught ReferenceError: d is not defined*
// Transition back to the parent element position
linkUpdate.transition()
    .duration(duration)
    .attr('d', d3.linkHorizontal()
        .source(function (d) {return d.parent})
        .target(function (d) {return d})
    );

but I got lot of errors in the console

d3.v4.min.js:2 Error: <path> attribute d: Expected number, "MNaN,NaNCNaN,NaN,…".

Could someone explain my mistake or point me to some working code ? Many thanks!


Solution

  • I think I figured out most of the confusion from my original answer.

    Creating the "zero length path" on link creation

    var linkEnter = link.enter().insert('path', "g")
      .attr("class", "link")
      .attr('d', function(d){
        var o = {x: source.x0, y: source.y0}
        return diagonal(o, o)
    });
    

    and on link removal

    var linkExit = link.exit().transition()
      .duration(duration)
      .attr('d', function(d) {
        var o = {x: source.x, y: source.y}
        return diagonal(o, o)
      })
     .remove();
    

    is used to create the animation, when the nodes, together with the related links slides-out/retrieves-back-in their parent nodes. The animation animates transformation of the path to/from their final shape from/to the "null" shape - thus the link starting and finishing at the same coordinates.

    The link generator can be then used like this

    // Enter any new links at the parent's previous position.
    var linkEnter = link.enter().insert('path', "g")
        .attr("class", "link")
        .attr('d', d3.linkHorizontal()
            .source(function(){ return [sourceNode.y0, sourceNode.x0]})
            .target(function(){ return [sourceNode.y0, sourceNode.x0]}));
    
     ...
    
    // Transition back to the parent element position
    linkUpdate.transition()
        .duration(duration)
        .attr('d', d3.linkHorizontal()
            .source(function (d) {return [d.parent.y, d.parent.x]})
            .target(function (d) {return [d.y, d.x]})
        );
    
     ...
    
    // Remove any exiting links
    var linkExit = link.exit().transition()
        .duration(duration)
        .attr('d', d3.linkHorizontal()
            .source(function(){ return [sourceNode.y, sourceNode.x]})
            .target(function(){ return [sourceNode.y, sourceNode.x]}))
        .remove();