title: D3 tree with boxes
body: <p>With my limited D3 knowledge, I've combined some tree layout examples to make a collapsible tree with branch labels. Here is a functional extract:</p>
  
  <p><div class="snippet" data-lang="js" data-hide="false" data-console="true" data-babel="false">
  <div class="snippet-code">
  <pre class="snippet-code-js lang-js prettyprint-override"><code>var root = {
      children:[
          {
              title:"Node title",
              children:[
                  {
                      type:"end",
                      items:[],
                      optionTitle:"Branch 1"
                  },
                  {
                      type:"end",
                      items:[],
                      optionTitle:"Branch 2"
                  }
              ]
          }
      ]
  }
  
  var maxLabelLength = 23;
  var i = 0;
  var duration = 750;
  
  // Define the root
  root.x0 = viewerHeight / 2;
  root.y0 = 0;
  
  var viewerWidth = 800;
  var viewerHeight = 300;
  
  var tree = d3.layout.tree();
  
  var diagonal = d3.svg.diagonal()
      .projection(function(d) {
          return [d.y, d.x];
      });
  
  function visit(parent, visitFn, childrenFn) {
      if (!parent) return;
  
      visitFn(parent);
  
      var children = childrenFn(parent);
      if (children) {
          var count = children.length;
          for (var i = 0; i &lt; count; i++) {
              visit(children[i], visitFn, childrenFn);
          }
      }
  }
  
  var baseSvg = d3.select('.tree').append("svg")
      .attr("width", viewerWidth)
      .attr("height", viewerHeight)
      .attr("class", "tree-container");
  
  
  // Helper functions for collapsing and expanding nodes.
  function collapse(d) {
      if (d.children) {
          d._children = d.children;
          d._children.forEach(collapse);
          d.children = null;
      }
  }
  
  function centerNode(source) {
      var scale = 1;
      var x = 20;
      var y = -source.x0;
      y = y * scale + viewerHeight / 2;
      d3.select('g').transition()
          .duration(duration)
          .attr("transform", "translate(" + x + "," + y + ")scale(" + scale + ")");
  }
  
  function toggleChildren(d) {
      if (d.children) {
          d._children = d.children;
          d.children = null;
      } else if (d._children) {
          expand(d);
      }
      return d;
  }
  
  function expand(d) {
      if (d._children) {
          d.children = d._children;
          d._children = null;
          if (d.children.length == 1) {
              expand(d.children[0])
          }
      }
  }
  
  function click(d) {
      if (d._children) {
          if (d.type!='end') {
              expandCollapse(d);
          }
      } else {
          expandCollapse(d);
      }
  }
  
  function expandCollapse(d) {
      d = toggleChildren(d);
      update(d);
      centerNode(d);
  }
  
  function update(source) {
      var levelWidth = [1];
      var childCount = function(level, n) {
  
          if (n.children &amp;&amp; n.children.length &gt; 0) {
              if (levelWidth.length &lt;= level + 1) levelWidth.push(0);
  
              levelWidth[level + 1] += n.children.length;
              n.children.forEach(function(d) {
                  childCount(level + 1, d);
              });
          }
      };
      childCount(0, root);
      var newHeight = d3.max(levelWidth) * 25;
      tree = tree.size([newHeight, viewerWidth]);
  
      // Compute the new tree layout.
      var nodes = tree.nodes(root).reverse(),
          links = tree.links(nodes);
  
      // Set widths between levels based on maxLabelLength.
      nodes.forEach(function(d) {
          d.y = (d.depth * (maxLabelLength * 8)); 
  
          if (d.x&lt;root.x) {
              d.x -= (root.x-d.x)*3;
          } else if (d.x&gt;root.x) {
              d.x += (d.x-root.x)*3;
          }
      });
  
      // Update the nodes…
      var node = svgGroup.selectAll("g.node")
          .data(nodes, function(d) {
              return d.id || (d.id = ++i);
          });
  
      // Enter any new nodes at the parent's previous position.
      var nodeEnter = node.enter().append("g")
          .attr("class", "node")
          .attr("transform", function(d) {
              return "translate(" + source.y0 + "," + source.x0 + ")";
          })
          .on('click', click);
  
      nodeEnter.append("circle")
          .attr('class', 'nodeCircle');
          
      // Change the circle fill depending on whether it has children and is collapsed
      node.select("circle.nodeCircle")
          .attr("r", 6)
          .style("fill", function(d) {
              return getNodeFill(d);
          });
  
      nodeEnter.append("text")
          .attr("x", function(d) {
              return d.children || d._children ? -10 : 10;
          })
          .attr("dy", ".35em")
          .attr('class', 'nodeText')
          .attr("text-anchor", function(d) {
              return d.children || d._children ? "end" : "start";
          })
          .text(function(d) {
              return d.title;
          })
          .style("fill-opacity", 0);
  
  
      // Update the text to reflect whether node has children or not.
      node.select('text')
          .attr("x", function(d) {
              return d.children || d._children ? -10 : 10;
          })
          .text(function(d) {
              if (d.type!='end') {
                  return d.title
              } else {
                  return 'End node'
              }
          });
  
      // Transition nodes to their new position.
      var nodeUpdate = node.transition()
          .duration(duration)
          .attr("transform", function(d) {
              return "translate(" + d.y + "," + d.x + ")";
          });
  
      // Fade the text in
      nodeUpdate.select("text")
          .style("fill-opacity", 1);
  
      // Transition exiting nodes to the parent's new position.
      var nodeExit = node.exit().transition()
          .duration(duration)
          .attr("transform", function(d) {
              return "translate(" + source.y + "," + source.x + ")";
          })
          .remove();
  
      nodeExit.select("circle")
          .attr("r", 0);
  
      nodeExit.select("text")
          .style("fill-opacity", 0);
  
      // Update the links…
      var link = svgGroup.selectAll("path.link")
          .data(links, function(d) {
              return d.target.id;
          });
  
      // Enter any new links at the parent's previous position.
      link.enter().insert("path", "g")
          .attr("class", "link")
          .attr("d", function(d) {
              var o = {
                  x: source.x0,
                  y: source.y0
              };
              return diagonal({
                  source: o,
                  target: o
              });
          });
  
      // Transition links to their new position.
      link.transition()
          .duration(duration)
          .attr("d", diagonal);
  
      // Transition exiting nodes to the parent's new position.
      link.exit().transition()
          .duration(duration)
          .attr("d", function(d) {
              var o = {
                  x: source.x,
                  y: source.y
              };
              return diagonal({
                  source: o,
                  target: o
              });
          })
          .remove();
  
      // Update the link text
      var linktext = svgGroup.selectAll("g.link")
          .data(links, function (d) {
              return d.target.id;
          });
  
      linktext.enter()
          .insert("g")
          .attr("class", "link")
          .append("text")
          .attr("dy", ".35em")
          .attr("text-anchor", "middle")
          .text(function (d) {
              return d.target.optionTitle;
          });
  
      // Transition link text to their new positions
  
      linktext.transition()
          .duration(duration)
          .attr("transform", function (d) {
              return "translate(" + ((d.source.y + d.target.y) / 2) + "," + ((d.source.x + d.target.x) / 2) + ")";
          })
  
      //Transition exiting link text to the parent's new position.
      linktext.exit().transition().remove();
  
  
      // Stash the old positions for transition.
      nodes.forEach(function(d) {
          d.x0 = d.x;
          d.y0 = d.y;
      });
  
  
  }
  
  var svgGroup = baseSvg.append("g");
  
  // Layout the tree initially and center on the root node.
  update(root);
  centerNode(root);
  
  svgGroup
      .append('defs')
      .append('pattern')
      .attr('id', function(d,i){
          return 'pic_plus';
      })
      .attr('height',60)
      .attr('width',60)
      .attr('x',0)
      .attr('y',0)
      .append('image')
      .attr('xlink:href',function(d,i){
          return 'https://s3-eu-west-1.amazonaws.com/eq-static/app/images/common/plus.png';
      })
      .attr('height',12)
      .attr('width',12)
      .attr('x',0)
      .attr('y',0);
  
  function getNodeFill(d) {
      if (isFinal(d)) {
          return '#0f0';
      } else if (d._children || (!d._children&amp;&amp;!d.children)) {
          return 'url(#pic_plus)'
      } else {
          return '#fff'
      }
  }
  
  function isFinal(node) {
      return node.type=='end';
  }</code></pre>
  <pre class="snippet-code-css lang-css prettyprint-override"><code>body {
      background-color: #ddd;
  }
  
  .tree-custom,
  .tree {
      width:100%;
      height: 100%;
      background-color: #fff;
  }
  
  .holder {
      margin: 0 auto;
      width: 1000px;
      height: 800px;
      background-color: #fff;
  }
  
  .node {
      cursor: pointer;
  }
  
  .node circle {
      fill: #fff;
      stroke: steelblue;
      stroke-width: 1.5px;
  }
  
  .node text {
      font: 10px sans-serif;
  }
  
  path.link {
      fill: none;
      stroke: #ccc;
      stroke-width: 1.5px;
  }
  
  .link text {
      font: 10px sans-serif;
      fill: #666;
  }</code></pre>
  <pre class="snippet-code-html lang-html prettyprint-override"><code>&lt;html&gt;
      &lt;head&gt;
          &lt;script src="https://d3js.org/d3.v3.min.js"&gt;&lt;/script&gt;
      &lt;/head&gt;
      &lt;body&gt;
    &lt;div class="tree"&gt;&lt;/div&gt;
    &lt;script src="code.js"&gt;&lt;/script&gt;
      &lt;/body&gt;
  &lt;/html&gt;</code></pre>
  </div>
  </div>
  </p>
  
  <p>In my code, the nodes are circles with the node labels next to them:</p>
  
  <p><a href="https://i.stack.imgur.com/AFv2t.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/AFv2t.png" alt="Expanded tree example"></a></p>
  
  <p>When I collapse a node, a <code>plus</code>sign shows up.</p>
  
  <p><a href="https://i.stack.imgur.com/4Ays6.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/4Ays6.png" alt="Collapsed tree example"></a></p>
  
  <p>Now I'm trying to put the node labels in boxes as it's shown in this <a href="https://bl.ocks.org/swayvil/b86f8d4941bdfcbfff8f69619cd2f460" rel="nofollow noreferrer">example code</a>. </p>
  
  <p><a href="https://i.stack.imgur.com/7Xyfj.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/7Xyfj.png" alt="Boxed labels"></a></p>
  
  <p>I know I have to change the <code>circles</code> by <code>foreignObjects</code>as in the example code but when I do it, paths aren't adjusted to the boxes.  </p>
  
  <p>How could I change the <code>circles</code> by <code>foreignObjects</code> and maintain the same functionality expand/collapse/plus? </p>