D3.js v4 +:截断文本以适应固定空间

时间:2018-05-29 08:19:16

标签: javascript d3.js

在我们通过d3操作的SVG中要求文本元素并不罕见,例如分类刻度标签。这有点不幸,因为SVG中的<text>元素并不是最好的......渲染的字体大小通常略大于字体应该采用的字体大小。例如,如果选择宽度/高度比为0.6的单空格字体(例如,如果字体大小为12px,则字符的宽度应为7.2px),则元素的计算边界矩形可能为14.2px n * 8px其中 n 是字符数。

问题更加复杂的是,人们常常使用非等宽字体。

通过

截断“太长”的字符串很容易
string.slice(0, numChars-3)+'...'

但知道适合固定宽度的正确字符数似乎并非易事。

function truncateText(t, text, space) {
  // make sure it is a string
  text = String(text)
  // set text
  t.text(text)
  // get space it takes up
  var rect = t.node().getBoundingClientRect()

  while (Math.max(rect.width, rect.height) > space) {
    text = text.slice(0, text.length - 1)
    t.text(text + '...')
    rect = t.node().getBoundingClientRect()
    if (text.length == 0) break
  }
}

上面的函数采用了d3.selection,文本和文本应该适合的空间。通过不断地操作DOM,我们可以完美契合,但这在计算上非常昂贵。

为了澄清,为了在修复空间中拟合文本,我的意思是如果我有一个字符串var string = "this is my very long string",我想要呈现字符串的方向(从左到右,即我们正在查看字符串长度)适合固定的空间(例如var fixedSpace = 100 //px

上面的truncate文本函数只适用于几个字符串,但如果有很多字符串调用此函数,则会出现滞后现象。

当然,我们可以通过选择最长的字符串进行优化,计算该字符串上的truncateText,然后获取字符数(尽管这仍然有些错误,因为并非所有字符都具有相同的宽度)。

是否有更有效的方法使用d3

将文本截断为固定空间

2 个答案:

答案 0 :(得分:1)

我同意你提出的方法计算成本很高,但这是我能想到的唯一方法。但是,如果你只是偶尔运行它(即只是在页面加载,而不是鼠标悬停),那么它应该不会太糟糕,这取决于你应用它的文本元素的数量。

您可能希望尝试将您的方法的效果与this example by Mike Bostock中的方法进行比较,后者使用node().getComputedTextLength()代替node().getBoundingClientRect(),并按字词分解文字:

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 = parseFloat(text.attr("dy")),
        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);
      }
    }
  });
}

PS /有一个CSS technique for truncating text with an ellipsis,但不幸的是it doesn't work in SVG :(

答案 1 :(得分:1)

另一种选择是使用100px宽rect作为text元素的clipPath - 类似这样的内容:

d3.selectAll("text.label")
  .attr("x", function(t) {
    return Math.max(0, 100-this.textLength.baseVal.value);
  });
#text-box {
  stroke: green;
  fill: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg>
  <defs>
    <rect id="text-box" x="0" y="0" width="100" height="1.2em" />
    <clipPath id="clip-box">
      <use href="#text-box" />
    </clipPath>
  </defs>

  <g transform="translate(0, 0)">
    <use x="0" y="0" href="#text-box" />
    <text x="0" y="1em" class="label" clip-path="url(#clip-box)">Long text that should be clipped</text>
  </g>

  <g transform="translate(0, 50)">
    <use x="0" y="0" href="#text-box" />
    <text x="0" y="1em" class="label" clip-path="url(#clip-box)">Another long string</text>
  </g>

  <g transform="translate(0, 100)">
    <use x="0" y="0" href="#text-box" />
    <text x="0" y="1em" class="label" clip-path="url(#clip-box)">Short text</text>
  </g>
</svg>

更新:渲染标签后,使用此函数获取长度,如果文本短于裁剪宽度,则调整“x”属性:

d3.selectAll("text.label")
  .attr("x", function(t) {
    return Math.max(0, 100-this.textLength.baseVal.value);
  });