Add a Toggle function to d3 tree chart v4

283 Views Asked by At

i'm new to d3 and i'm trying to render this d3 tree chart with left and right nodes. the initial tree render is working fine and i have added a toggle function that will make the children null and call the render method, but it's not working. please help.

var data = {
  "name": "Root",
  "children": [{
      "name": "Branch 1"
    }, {
      "name": "Branch 2",
      "children": [{
        "name": "Branch 2.1"
      }, {
        "name": "Branch 2.2",
        "children": [{
          "name": "Branch 2.2.1"
        }, {
          "name": "Branch 2.2.2"
        }]
      }]
    }, {
      "name": "Branch 3"
    }, {
      "name": "Branch 4",
      "children": [{
        "name": "Branch 4.1"
      }, {
        "name": "Branch 4.2"
      }]
    }, {
      "name": "Branch 5"
    },


    {
      "name": "Branch 6"
    }, {
      "name": "Branch 7",
      "children": [{
        "name": "Branch 7.1"
      }, {
        "name": "Branch 7.2",
        "children": [{
          "name": "Branch 7.2.1"
        }, {
          "name": "Branch 7.2.2"
        }]
      }]
    }, {
      "name": "Branch 8"
    }, {
      "name": "Branch 9",
      "children": [{
        "name": "Branch 9.1"
      }, {
        "name": "Branch 9.2"
      }]
    }, {
      "name": "Branch 10"
    }
  ]
};

var split_index = Math.round(data.children.length / 2)

// Left data
var data1 = {
  "name": data.name,
  "children": JSON.parse(JSON.stringify(data.children.slice(0, split_index)))
};

// Right data
var data2 = {
  "name": data.name,
  "children": JSON.parse(JSON.stringify(data.children.slice(split_index)))
};

// Create d3 hierarchies
var right = d3.hierarchy(data1);
var left = d3.hierarchy(data2);

// Render both trees
drawTree(right, "right")
drawTree(left, "left")

// draw single tree
function drawTree(root, pos) {

  var SWITCH_CONST = 1;
  if (pos === "left") {
    SWITCH_CONST = -1;
  }

  var svg = d3.select("svg"),
    width = +svg.attr("width"),
    height = +svg.attr("height")

  // Shift the entire tree by half it's width
  var g = svg.append("g").attr("transform", "translate(" + width / 2 + ",0)");

  // Create new default tree layout
  var tree = d3.tree()
    // Set the size
    // Remember the tree is rotated
    // so the height is used as the width
    // and the width as the height
    .size([height, SWITCH_CONST * (width - 150) / 2]);

  tree(root)

  var nodes = root.descendants();
  var links = root.links();
  // Set both root nodes to be dead center vertically
  nodes[0].x = height / 2

  // Create links
  var link = g.selectAll(".link")
    .data(links)
    .enter()

  link.append("path")
    .attr("class", "link")
    .attr("d", function(d) {
      return "M" + d.target.y + "," + d.target.x + "C" + (d.target.y + d.source.y) / 2.5 + "," + d.target.x + " " + (d.target.y + d.source.y) / 2 + "," + d.source.x + " " + d.source.y + "," + d.source.x;
    });

  // Create nodes
  var node = g.selectAll(".node")
    .data(nodes)
    .enter()
    .append("g")
    .attr("class", function(d) {
      return "node" + (d.children ? " node--internal" : " node--leaf");
    })
    .attr("transform", function(d) {
      return "translate(" + d.y + "," + d.x + ")";
    }).on("click", function(e, d) {
      toggle(d, pos);
    })

  node.append("circle")
    .attr("r", function(d, i) {
      return 2.5
    });

  node.append("text")
    .attr("y", -10)
    .style("text-anchor", "middle")
    .text(function(d) {
      return d.data.name
    });
}

function toggle(d, pos) {
  console.log(d, pos);
  if (d.children) {
    d._children = d.children;
    d.children = null;
  } else {
    d.children = d._children;
    d._children = null;
  }
  this.drawTree(d, pos);
}
.node circle {
  fill: #999;
}

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

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

.link {
  fill: none;
  stroke: #555;
  stroke-opacity: 0.4;
  stroke-width: 1.5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js"></script>
<svg width="900" height="600"></svg>

1

There are 1 best solutions below

0
On BEST ANSWER

You only appended new nodes, you never removed them. That way, the old nodes would stay there after a click. You can fix that by using .join().

Also, you used the clicked node as the new root of the tree, but that's invalid. Instead, pass the true root and just redraw that part of the tree entirely. Otherwise, the rest of the tree is completely forgotten.

var data = {
  "name": "Root",
  "children": [{
      "name": "Branch 1"
    }, {
      "name": "Branch 2",
      "children": [{
        "name": "Branch 2.1"
      }, {
        "name": "Branch 2.2",
        "children": [{
          "name": "Branch 2.2.1"
        }, {
          "name": "Branch 2.2.2"
        }]
      }]
    }, {
      "name": "Branch 3"
    }, {
      "name": "Branch 4",
      "children": [{
        "name": "Branch 4.1"
      }, {
        "name": "Branch 4.2"
      }]
    }, {
      "name": "Branch 5"
    },


    {
      "name": "Branch 6"
    }, {
      "name": "Branch 7",
      "children": [{
        "name": "Branch 7.1"
      }, {
        "name": "Branch 7.2",
        "children": [{
          "name": "Branch 7.2.1"
        }, {
          "name": "Branch 7.2.2"
        }]
      }]
    }, {
      "name": "Branch 8"
    }, {
      "name": "Branch 9",
      "children": [{
        "name": "Branch 9.1"
      }, {
        "name": "Branch 9.2"
      }]
    }, {
      "name": "Branch 10"
    }
  ]
};

var split_index = Math.round(data.children.length / 2)

// Left data
var data1 = {
  "name": data.name,
  "children": JSON.parse(JSON.stringify(data.children.slice(0, split_index)))
};

// Right data
var data2 = {
  "name": data.name,
  "children": JSON.parse(JSON.stringify(data.children.slice(split_index)))
};

// Create d3 hierarchies
var right = d3.hierarchy(data1);
var left = d3.hierarchy(data2);

var svg = d3.select("svg"),
  width = +svg.attr("width"),
  height = +svg.attr("height");

// Shift the entire tree by half it's width
var g = svg.append("g")
  .attr("transform", "translate(" + width / 2 + ",0)");

// Render both trees
drawTree(right, "right")
drawTree(left, "left")

// draw single tree
function drawTree(root, pos) {

  var SWITCH_CONST = 1;
  if (pos === "left") {
    SWITCH_CONST = -1;
  }

  // Create new default tree layout
  var tree = d3.tree()
    // Set the size
    // Remember the tree is rotated
    // so the height is used as the width
    // and the width as the height
    .size([height, SWITCH_CONST * (width - 150) / 2]);

  tree(root)

  var nodes = root.descendants();
  var links = root.links();
  // Set both root nodes to be dead center vertically
  nodes[0].x = height / 2

  // Create links
  var link = g.selectAll(".link." + pos)
    .data(links)
    .join(
      enter => enter.append("path"),
      update => update,
      exit => exit.remove()
    )
    .attr("class", "link " + pos)
    .attr("d", function(d) {
      return "M" + d.target.y + "," + d.target.x + "C" + (d.target.y + d.source.y) / 2.5 + "," + d.target.x + " " + (d.target.y + d.source.y) / 2 + "," + d.source.x + " " + d.source.y + "," + d.source.x;
    });

  // Create nodes
  var node = g.selectAll(".node." + pos)
    .data(nodes)
    .join(
      enter => {
        const n = enter
          .append("g")
          .on("click", (e, d) => toggle(d, pos, pos === "left" ? left : right));

        n.append("circle").attr("r", 2.5);
        n.append("text").attr("y", -10).style("text-anchor", "middle");
        return n;
      },
      update => update,
      exit => exit.remove()
    )
    .attr("class", function(d) {
      return "node " + pos + (d.children ? " node--internal" : " node--leaf");
    })
    .attr("transform", function(d) {
      return "translate(" + d.y + "," + d.x + ")";
    })
    .select("text")
    .text(function(d) {
      return d.data.name
    });
}

function toggle(d, pos, root) {
  if (d.children) {
    d._children = d.children;
    d.children = null;
  } else {
    d.children = d._children;
    d._children = null;
  }
  this.drawTree(root, pos);
}
.node circle {
  fill: #999;
}

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

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

.link {
  fill: none;
  stroke: #555;
  stroke-opacity: 0.4;
  stroke-width: 1.5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js"></script>
<svg width="900" height="600"></svg>