Getting a parent's child to find parent's child text width

I need to get the width of the text in order to calculate the width of the rect which acts as the background. I am don't know how to traverse to the parent "nodeGroup" and then get the child text width, hoping to get some help. Pseudo code below.

Code using to try to get the width (doesn't work)

nodeGroups.selectAll('rect')
    .attr("width", function(d) {      
      let width = this.parentNode.select('text').getComputedTextLength; //??     
      console.log(width);
      return width;
    })

With the error: this.parentNode.select is not a function

Here is the basic structure of the nodeGroup:

  <g class="nodeGroup">
      <circle class="node" r="15" fill="#67b14f" cx="163.66683334043782" cy="25.26573226107303">
      </circle>
      <rect fill="#676767" text-anchor="middle" class="labelBack" height="20" x="179.66683334043782" y="15.26573226107303">
      </rect>
      <text class="label" x="187.66683334043782" y="29.26573226107303">
        GET MY WIDTH <--- ?????
      </text>
  </g>

1 answer

  • answered 2018-01-11 21:23 Andrew Reid

    You will probably want to use the bbox approach for this as suggested in the comments.

    parentNode returns the DOM element, not a D3 selection, so

    this.parentNode.select('text')
    

    won't work, you need to use:

    d3.select(this.parentNode)
    

    Then, you can select the text element, and get its bbox from the DOM element itself (not the d3 selection):

    d3.select(this.parentNode).select(text).node().getBBox();
    

    Then from there you can get the width with bbox.width. Of course, you don't need to use d3 selections for the intermediate steps, but as you've started down this path, I've continued with it for the first snippet below:

    var data = ["Short String","A Longer String"];
    
    var svg = d3.select("body")
      .append("svg");
      
    var g = svg.selectAll("g")
      .data(data)
      .enter()
      .append("g")
      .attr("transform",function(d,i) { 
         return "translate("+[20,i*20+20]+")";
      });
      
    g.append("rect")
      .attr("height", 20)
      .attr("fill","yellow");
      
    g.append("text")
     .text(function(d) { return d; })
     .attr("y", 18);
    
    g.selectAll("rect")
      .attr("width",function() {
         let parentSelection = d3.select(this.parentNode)
         
         let bbox = parentSelection.select("text").node().getBBox();
      
         let width = bbox.width;
         
         return width;
      })
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>

    Better Approach

    But, things get easier (and faster) without d3 for the intermediate steps. If your rectangle is the nearest sibling before the text, then you can use nextSibling (as this refers to the rectangle, the next element is the text):

    var data = ["Short String","A Longer String"];
    
    var svg = d3.select("body")
      .append("svg");
      
    var g = svg.selectAll("g")
      .data(data)
      .enter()
      .append("g")
      .attr("transform",function(d,i) { 
         return "translate("+[20,i*20+20]+")";
      });
      
    g.append("rect")
      .attr("height", 20)
      .attr("fill","yellow");
      
    g.append("text")
     .text(function(d) { return d; })
     .attr("y", 18);
    
    g.selectAll("rect")
      .attr("width",function() {
         return this.nextSibling.getBBox().width;
      })
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>