D3.js Auto font-sizing based on nodes individual radius/diameter

后端 未结 3 518
猫巷女王i
猫巷女王i 2020-12-16 00:24

How can I have D3.js automatically adjust font-size for each node based on their individual radius/diameter?

I use a style that allows automatic inc

相关标签:
3条回答
  • 2020-12-16 01:03

    You can do this by dynamically setting the text size based on the size of the container. For this, you have to add the text, get its bounding box, get the bounding box of the container element and derive the correct font size based on the current font size and those bounding boxes.

    The code would look something like this.

    // ...
      .append("text")
      .text("text")
      .style("font-size", "1px")
      .each(getSize)
      .style("font-size", function(d) { return d.scale + "px"; });
    
    function getSize(d) {
      var bbox = this.getBBox(),
          cbbox = this.parentNode.getBBox(),
          scale = Math.min(cbbox.width/bbox.width, cbbox.height/bbox.height);
      d.scale = scale;
    }
    
    0 讨论(0)
  • 2020-12-16 01:05

    To offset text within a circle, rather than running along the diameter, I implement this differently:

    dy shifts a text node up or down within the circle and is used to calculate the width or the chord to size the text.

    The scale is then stored in a data attribute on the text element, rather than modifying the source data.

    jsfiddle

    function appendScaledText(parentGroup, textVal, dyShift) {
      parentGroup
        .append("text")
        .attr("dy", dyShift)
        .attr("text-anchor", "middle")
        .attr("dominant-baseline", "central")
        .attr("font-family", "sans-serif")
        .attr("fill", "white")
        .text(textVal)
        .style("font-size", "1px")
        .each(getSize)
        .style("font-size", function() {
          return d3.select(this).attr("data-scale") + "px";
        });
    }
    
    function getSize() {
      var d3text = d3.select(this);
    
      // in other cases could be parentElement or nextElementSibling
      var circ = d3.select(this.previousElementSibling); 
      var radius = Number(circ.attr("r"));
      var offset = Number(d3text.attr("dy"));
    
      // TODO: this could be bounding box instead
      var textWidth = this.getComputedTextLength();
    
      // TODO: could adjust based on ratio of dy to radius 
      var availWidth = chordWidth(Math.abs(offset), radius); 
    
      // fixed 15% 'padding', could be more dynamic/precise based on above TODOs
      availWidth = availWidth * 0.85;
    
      d3text.attr("data-scale", availWidth / textWidth); 
    }
    
    function chordWidth(dFromCenter, radius) {
      if (dFromCenter > radius) return Number.NaN;
      if (dFromCenter === radius) return 0;
      if (dFromCenter === 0) return radius * 2;
    
      // a^2 + b^2 = c^2
      var a = dFromCenter;
      var c = radius;
      var b = Math.sqrt(Math.pow(c, 2) - Math.pow(a, 2)); // 1/2 of chord length
    
      return b * 2;
    }
    
    0 讨论(0)
  • 2020-12-16 01:19

    Alternatively, you can create text label embedded with each node as follows:

    this.g.append("g")
      .attr("class", "labels")
      .selectAll(".mytext")
      .data(NODE_DATA)
      .enter()
      .append("text")
      .text(function (d) {
         return d.LabelText; // Here label text is the text that you want to show in the node
      })
      .style("font-size", "1px")
      .attr("dy", ".35em") // You can adjust it
      .each(function (d) {
          var r = Number(d.Size), a = this.getComputedTextLength(),
              c=0.35, // Same as dy attribute value
              b = 2*Math.sqrt(r*r-c*c), s = Math.min(r, b/a);
          d.fs = s;
      })
      .style("font-size", function (d) {
         return d.fs + "px";
      })
    
    0 讨论(0)
提交回复
热议问题