在我们通过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
将文本截断为固定空间答案 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);
});