D3 text wrap with text-anchor: middle

放肆的年华 提交于 2021-01-28 09:17:38

问题


In D3 I would like to wrap texts which have a middle text anchor. I looked at this example from Mike Bostok but I cannot wrap my head around the settings.

I would like to see the text to be in the center (horizontal and vertical) of the red box.

var plot = d3.select(container)
  .insert("svg")
  .attr('width', 100)
  .attr('height', 200);

plot.append("text")
  .attr("x", 50)
  .attr("y", 100)
  .attr("text-anchor", "middle")
  .attr("alignment-baseline", "alphabetic")
  .text("The brown fox jumps!")
  .call(wrap, 100);


function wrap(text, width) {
  text.each(function() {
    var text = d3.select(this),
      words = text.text().split(/\s+/).reverse(),
      word,
      line = [],
      lineNumber = 0,
      lineHeight = 1.1, // ems
      y = text.attr("y"),
      dy = 1,
      tspan = text.text(null).append("tspan").attr("x", 0).attr("y", y).attr("dy", dy + "em");
    while (word = words.pop()) {
      line.push(word);
      tspan.text(line.join(" "));
      if (tspan.node().getComputedTextLength() > width) {
        line.pop();
        tspan.text(line.join(" "));
        line = [word];
        tspan = text.append("tspan").attr("x", 0).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
      }
    }
  });
}

Given the pointers from Shashank I was now able to position the text central with the translate method. Just setting the y of the text didn't seem to change anything. I also added a yTrack variable to be able to continue drawing below the wrapped text.

let yTrack = 100,
  plot = d3.select(container)
  .insert("svg")
  .attr('width', 100)
  .attr('height', 200);

plot.append("text")
  .attr("x", 50)
  .attr("y", yTrack)
  .attr("text-anchor", "middle")
  .attr("font-family", "sans-serif")
  .text("The quick brown fox jumps over the lazy dog.")
  .call(wrap, 100);

let height = parseInt(plot.select('text').node().getBoundingClientRect().height);

plot.select("text").attr('transform', 'translate(0, ' + (-height / 2) + ')');

//plot.select("text").attr('y', 0);

yTrack += (parseInt(height / 2) + 10);

plot.append('rect')
  .attr("x", 0)
  .attr("y", yTrack)
  .attr("width", 100)
  .attr("height", 10)
  .style('fill', '#999');

function wrap(text, width) {
  text.each(function() {
    let text = d3.select(this),
      words = text.text().split(/\s+/).reverse(),
      word,
      line = [],
      lineNumber = 0,
      lineHeight = 1.1, // ems
      x = text.attr("x"),
      y = text.attr("y"),
      dy = 1.1,
      tspan = text.text(null).append("tspan").attr("x", x).attr("y", y).attr("dy", dy + "em");
    while (word = words.pop()) {
      line.push(word);
      tspan.text(line.join(" "));
      if (tspan.node().getComputedTextLength() > width) {
        line.pop();
        tspan.text(line.join(" "));
        line = [word];
        tspan = text.append("tspan").attr("x", x).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
      }
    }
  });
}
#container {
  height: 200px;
  width: 100px;
  border: 1px solid red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="container"></div>

回答1:


From the docs for text-anchor, middle will result in the following:

The rendered characters are aligned such that the middle of the text string is at the current text position.

and in your case, as you are breaking down the text into tspans with x equal to 0 -- the current text position would be the start of the svg (i.e. at point 0). Also, the x:50 applied to the text element wouldn't matter at all.

  1. One approach would be to apply the center value i.e. 50 to the tspans:

    tspan.attr("x", 50)
    

    Here's a snippet doing that:

    var plot = d3.select(container)
      .insert("svg")
      .attr('width', 100)
      .attr('height', 200);
    
    plot.append("text")
      .attr("text-anchor", "middle")
      .attr("alignment-baseline", "alphabetic")
      .text("The brown fox jumps!")
      .call(wrap, 100);
    
    var height = plot.select('text').node().getBoundingClientRect().height;
    plot.select('text').attr('y', 100-height/2);
    
    function wrap(text, width) {
      text.each(function() {
        var text = d3.select(this),
          words = text.text().split(/\s+/).reverse(),
          word,
          line = [],
          lineNumber = 0,
          lineHeight = 0.1, // ems
          y = text.attr("y"),
          dy = 1,
          tspan = text.text(null).append("tspan").attr("x", 50).attr("y", y).attr("dy", dy + "em");
        while (word = words.pop()) {
          line.push(word);
          tspan.text(line.join(" "));
          if (tspan.node().getComputedTextLength() > width) {
            line.pop();
            tspan.text(line.join(" "));
            line = [word];
            tspan = text.append("tspan").attr("x", 50).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
          }
        }
      });
    }
    #container {
      height: 200px;
      width: 100px;
      border: 1px solid red;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
    <div id="container"></div>
  2. Another approach would be to apply 'transform' property to the text in the following way:

    plot.select('text').attr('transform', 'translate(50, ' + (100-height/2)+')');
    

    Here's a snippet with this method:

    var plot = d3.select(container)
      .insert("svg")
      .attr('width', 100)
      .attr('height', 200);
    
    plot.append("text")
      .attr("text-anchor", "middle")
      .attr("alignment-baseline", "alphabetic")
      .text("The brown fox jumps!")
      .call(wrap, 100);
    
    var height = plot.select('text').node().getBoundingClientRect().height;
    plot.select('text').attr('transform', 'translate(50, ' + (100-height/2)+')');
    
    function wrap(text, width) {
      text.each(function() {
        var text = d3.select(this),
          words = text.text().split(/\s+/).reverse(),
          word,
          line = [],
          lineNumber = 0,
          lineHeight = 0.1, // ems
          y = text.attr("y"),
          dy = 1,
          tspan = text.text(null).append("tspan").attr("x", 0).attr("y", y).attr("dy", dy + "em");
        while (word = words.pop()) {
          line.push(word);
          tspan.text(line.join(" "));
          if (tspan.node().getComputedTextLength() > width) {
            line.pop();
            tspan.text(line.join(" "));
            line = [word];
            tspan = text.append("tspan").attr("x", 0).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
          }
        }
      });
    }
    #container {
      height: 200px;
      width: 100px;
      border: 1px solid red;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
    <div id="container"></div>

In both of the above approaches, I'm not using the y-center value i.e. 100 instead I'm calculating the mid-point based on the text height as well using getBoundingClientRect() function.

var height = plot.select('text').node().getBoundingClientRect().height;
plot.select('text').attr('y', 100-height/2);

Hope this helps. :)



来源:https://stackoverflow.com/questions/48429014/d3-text-wrap-with-text-anchor-middle

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!