Minu
Minu

Reputation: 242

Collapsible Radial Tree in d3 v4

I am new to d3 and I am trying to create a collapsible version of https://bl.ocks.org/mbostock/4063550 in v4. There are various examples that explain the same in v3, but I couldn't find a proper one for v4. I implemented the onClick function in which the children opens up, but the issue is that the links get misplaced after the click. they shift to the right, and the nodes remain at the same position. Please find below my code for the update function:

<!DOCTYPE html>
<meta charset="utf-8">
<style>
<!--
.node circle {
  //fill: #999;
  fill: #fff;
  stroke: steelblue;
  stroke-width: 3px;
}



.node text {
  font: 10px sans-serif;
}

.node--internal circle {
  fill: #555;
}

.node--internal text {
  text-shadow: 0 1px 0 #fff, 0 -1px 0 #fff, 1px 0 0 #fff, -1px 0 0 #fff;
}

.link {
  fill: none;
  stroke: #555;
  stroke-opacity: 0.4;
  stroke-width: 1.5px;
}
-->
.node {
  cursor: pointer;
}

.node circle {
  fill: #999;
  stroke: steelblue;
  stroke-width: 1.5px;
}

.node text {
  font: 10px sans-serif;
}

.link {
  fill: none;
  stroke: #ccc;
  stroke-width: 1.5px;
</style>
<body>
<script src="//d3js.org/d3.v4.min.js"></script>
<script>



var width = 960,
height = 1000,
duration = 750;

var nodes,links;
    var i = 0;


    var svg = d3.select("body").append("svg")
                .attr("width",width)
                .attr("height",height);
    var g = svg.append("g").attr("transform", "translate(" + (width / 2 + 40) + "," + (height / 2 + 90) + ")");

    function connector(d) {
        return "M" + project(d.x, d.y)
                                     + "C" + project(d.x, (d.y + d.parent.y) / 2)
                                     + " " + project(d.parent.x, (d.y + d.parent.y) / 2)
                                     + " " + project(d.parent.x, d.parent.y)
    /*
  return "M" + d.y + "," + d.x +
    "C" + (d.y + d.parent.y) / 2 + "," + d.x +
    " " + (d.y + d.parent.y) / 2 + "," + d.parent.x +
    " " + d.parent.y + "," + d.parent.x;  */
}



    var treeMap = d3.tree()
    .size([360,250]),
    root;
    var nodeSvg, linkSvg, nodeEnter, linkEnter ;



    d3.json("treeData.json",function(error,treeData){
        if(error) throw error;

        root = d3.hierarchy(treeData,function(d){
            return d.children;
        });

         root.each(function (d) {
         console.log(d);
                d.name = d.data.name; //transferring name to a name variable
                d.id = i; //Assigning numerical Ids
                i += i;
            });

         root.x0 = height / 2;
            root.y0 = 0;

            function collapse(d) {
    if (d.children) {
      d._children = d.children;
      d._children.forEach(collapse);
      d.children = null;
    }
  }
         //root.children.forEach(collapse);
            update(root);
    });


function update(source) {


        //root = treeMap(root);
        nodes = treeMap(root).descendants();
        //console.log(nodes);
        //links = root.descendants().slice(1);
        links = nodes.slice(1);
        //console.log(links);
        var nodeUpdate;
                var nodeExit;

// Normalize for fixed-depth.
  nodes.forEach(function(d) { d.y = d.depth * 180; });

  nodeSvg = g.selectAll(".node")
                    .data(nodes,function(d) { return d.id || (d.id = ++i); });


        //nodeSvg.exit().remove();

        var nodeEnter = nodeSvg.enter()
                    .append("g")
                    //.attr("class", function(d) { return "node" + (d.children ? " node--internal" : " node--leaf"); })
                    .attr("class", "node")
                    .attr("transform", function(d) { return "translate(" + project(d.x, d.y) + ")"; })
                    //.attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; })
                    .on("click",click)
                    .on("mouseover", function(d) { return "minu"; });



        nodeEnter.append("circle")
            .attr("r", 5)
            .style("fill", color);



      nodeEnter.append("text")
            .attr("dy", ".31em")
            //.attr("x", function(d) { return d.x < 180 === !d.children ? 6 : -6; })
            .attr("x", function(d) { return d.children || d._children ? -10 : 10; })
            .style("text-anchor", function(d) { return d.x < 180 === !d.children ? "start" : "end"; })
            //.attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
            .attr("transform", function(d) { return "rotate(" + (d.x < 180 ? d.x - 90 : d.x + 90) + ")"; })
            .text(function(d) {  return d.data.name; });

            // Transition nodes to their new position.
  var nodeUpdate = nodeSvg.merge(nodeEnter).transition()
      .duration(duration);
     // .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });


    nodeSvg.select("circle")
      .style("fill", color);


  nodeUpdate.select("text")
      .style("fill-opacity", 1);

      // Transition exiting nodes to the parent's new position.
  var nodeExit = nodeSvg.exit().transition()
      .duration(duration)
      .attr("transform", function(d) { return "translate(" + source.y + "," + source.x + ")"; }) //for the animation to either go off there itself or come to centre
      .remove();

  nodeExit.select("circle")
      .attr("r", 1e-6);

  nodeExit.select("text")
      .style("fill-opacity", 1e-6);

      nodes.forEach(function(d) {
    d.x0 = d.x;
    d.y0 = d.y;
  });


        linkSvg = g.selectAll(".link")
                    .data(links, function(link) { var id = link.id + '->' + link.parent.id; return id; });



        // Transition links to their new position.
                linkSvg.transition()
                    .duration(duration);
                   // .attr('d', connector);

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



                    // Transition links to their new position.
                linkSvg.merge(linkEnter).transition()
                    .duration(duration)
                    .attr("d", connector);


                    // Transition exiting nodes to the parent's new position.
                linkSvg.exit().transition()
                    .duration(duration)
                    .attr("d", /*function (d) {
                        var o = {x: source.x, y: source.y, parent: {x: source.x, y: source.y}};
                        return connector(o);
                    })*/function(d) {
                                return "M" + project(d.x, d.y)
                                     + "C" + project(d.x, (d.y + d.parent.y) / 2)
                                     + " " + project(d.parent.x, (d.y + d.parent.y) / 2)
                                     + " " + project(d.parent.x, d.parent.y);
                            })
                    .remove();

                    // Stash the old positions for transition.


}

function click(d) {
  if (d.children) {
    d._children = d.children;
    d.children = null;
  } else {
    d.children = d._children;
    d._children = null;
  }
  update(d);
}


function color(d) {
  return d._children ? "#3182bd" // collapsed package
      : d.children ? "#c6dbef" // expanded package
      : "#fd8d3c"; // leaf node
}


function flatten (root) {
  // hierarchical data to flat data for force layout
  var nodes = [];
  function recurse(node) {
    if (node.children) node.children.forEach(recurse);
    if (!node.id) node.id = ++i;
    else ++i;
    nodes.push(node);
  }
  recurse(root);
  return nodes;
}


function project(x, y) {
  var angle = (x - 90) / 180 * Math.PI, radius = y;
  return [radius * Math.cos(angle), radius * Math.sin(angle)];
}

</script>
</body>
</html>

treeData.json:

{
  "name": "United States",
  "children": [
    {
        "name": "Arizona",
        "children":[
        {   "name" : "Arizona Airport", "size": 13}
      ]
    },
    {
        "name": "California",
        "children":[
        {"name": "San Francisco","size":15},
        {"name": "San Jose","size":25},
        {"name": "Los Angeles","size":17}
        ]
    },
    { 
      "name": "Illinois",
      "children":[
      { "name" : "Chicago O'Hare", "size": 13},
      { "name" : "Midway", "size": 18 }
      ]
    },
    {
        "name": "Colorado",
        "children" : [
        { "name": "Denver","size": 7}
        ]
    },
    {
        "name": "Florida","size":2
    },
    {
        "name": "Georgia", "size": 25
    },
    {
        "name": "Kentucky","size":2
    },
    {
        "name": "Massachussets", "size": 25
    },
    {
        "name": "Michigan","size":2
    },
    {
        "name": "Minnesota", "size": 25
    },
    {
        "name": "Missouri","size":2
    },
    {
        "name": "North Carolina", "size": 25
    },
    {
        "name": "Nevada","size":2
    },
    { 
        "name": "Newyork", "size": 12 
    },
    {
        "name": "Oregon","size":2
    },
    {
        "name": "Pennsylvania", "size": 25
    },
    {
        "name": "Washington",
        "children": [
        { "name" : "Seattle","size" : 13}
        ]
    },
    {
        "name": "Hawaii", "size": 25
    },
    {
        "name": "Texas",
        "children" : [
        { "name": "Dallas" ,"size": 9},
        { "name": "Houston" ,"size": 13},
        { "name": "Austin" ,"size": 17}
        ]
    },
    {
        "name": "Utah", "size": 25
    },
    {
        "name": "Virginia", "size": 25
    }
  ]
}

Upvotes: 2

Views: 2374

Answers (1)

pwebmedia
pwebmedia

Reputation: 31

@Minu didn't update the code in the question, so here's what I did to get it to work:

Just change:

// Transition nodes to their new position.
var nodeUpdate = nodeSvg.merge(nodeEnter).transition()
.duration(duration);

to this:

// Transition nodes to their new position.
var nodeUpdate = nodeSvg.merge(nodeEnter).transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + project(d.x, d.y) + ")"; });

Hopefully this is helpful to someone else who wants to make the radial tree collapsible.

Upvotes: 3

Related Questions