在 D3 中环绕文本
Posted
技术标签:
【中文标题】在 D3 中环绕文本【英文标题】:Wrapping Text in D3 【发布时间】:2014-09-07 04:32:28 【问题描述】:我想让文本在下面的 D3 树上换行,而不是
Foo is not a long word
每一行都换成
Foo is
not a
long word
我尝试将文本设置为“foreignObject”而不是文本对象,文本确实换行了,但它不会在树动画上移动,并且全部分组在左上角。
代码位于
http://jsfiddle.net/mikeyai/X43X5/1/
var width = 960,
height = 500;
var tree = d3.layout.tree()
.size([width - 20, height - 20]);
var root = ,
nodes = tree(root);
root.parent = root;
root.px = root.x;
root.py = root.y;
var diagonal = d3.svg.diagonal();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(10,10)");
var node = svg.selectAll(".node"),
link = svg.selectAll(".link");
var duration = 750,
timer = setInterval(update, duration);
function update()
if (nodes.length >= 500) return clearInterval(timer);
// Add a new node to a random parent.
var n = id: nodes.length,
p = nodes[Math.random() * nodes.length | 0];
if (p.children) p.children.push(n); else p.children = [n];
nodes.push(n);
// Recompute the layout and data join.
node = node.data(tree.nodes(root), function(d) return d.id; );
link = link.data(tree.links(nodes), function(d) return d.source.id + "-" + d.target.id; );
// Add entering nodes in the parent’s old position.
node.enter().append("text")
.attr("class", "node")
.attr("x", function(d) return d.parent.px; )
.attr("y", function(d) return d.parent.py; )
.text('Foo is not a long word');
// Add entering links in the parent’s old position.
link.enter().insert("path", ".node")
.attr("class", "link")
.attr("d", function(d)
var o = x: d.source.px, y: d.source.py;
return diagonal(source: o, target: o);
);
// Transition nodes and links to their new positions.
var t = svg.transition()
.duration(duration);
t.selectAll(".link")
.attr("d", diagonal);
t.selectAll(".node")
.attr("x", function(d) return d.px = d.x; )
.attr("y", function(d) return d.py = d.y; );
【问题讨论】:
我昨天回答了非常相似的问题***.com/questions/24755321/… 我实际上已经看到了您在答案中发布的两种资源,不幸的是,没有人给我太多的见解 - 我不明白 tspan 元素如何与 D3 结合使用。 mbostock 示例可能有一些用处,但我不知道 x.rangeBand() 是什么以及如何用简单的文本调用实现的“换行”函数。 我以前曾与这样的事情作斗争,发现最简单的方法就是翻转我的树jsfiddle.net/robschmuecker/5nHGz x.rangeBand() 只是一个宽度。因此,您可以将其替换为另一个整数。 我修改了我的示例以显示我的树可以多么容易地更新jsfiddle.net/robschmuecker/5nHGz/1 只是在第 1222 行添加了“wrap”函数和对其的调用,并添加了一个很长的第一个节点名称用于测试。看过你的,但无法立即使用。 【参考方案1】:您可以修改Mike Bostock's "Wrapping Long Labels" example 以将<tspan>
元素添加到您的<text>
节点。将包装文本添加到节点需要进行两项主要更改。我没有深入研究让文本在过渡期间更新其位置,但添加起来应该不会太难。
首先是添加一个函数wrap
,基于上面示例中的函数。 wrap
将负责添加 <tspan>
元素以使您的文本适合特定宽度:
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
x = text.attr("x"),
y = text.attr("y"),
dy = 0, //parseFloat(text.attr("dy")),
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);
);
第二个变化是不再设置每个节点的文本,而是需要为每个节点调用wrap
:
// Add entering nodes in the parent’s old position.
node.enter().append("text")
.attr("class", "node")
.attr("x", function (d) return d.parent.px; )
.attr("y", function (d) return d.parent.py; )
.text("Foo is not a long word")
.call(wrap, 30); // wrap the text in <= 30 pixels
【讨论】:
不幸的是,我无法让我的示例使用它来处理转换,但谢谢你,因为这是朝着正确方向迈出的一步 我想通了,我必须在转换之后添加 t.selectAll("tspan")...。这与您的有用帖子一起回答了我的问题! @user235236:太好了,很高兴为您提供帮助! 答案很有帮助。谢谢@mdml 我唯一不知道的是如何将动画应用于包装的文本。在.call(wrap, 30)
之前设置的.transition()
将不起作用。在将文本设置为tspan
之前,在 wrap 函数中插入一个转换似乎也不起作用。 @user235236 你是怎么解决的?
这很有帮助,谢谢 :) 我查看了 blocks.org 示例,但不知道他在哪里附加了文本元素。 d3 blocks.org 示例应该更简单【参考方案2】:
如果您使用的是 React,那么您可以使用的库是 @visx/text。它公开了一个更强大的 SVG 文本元素,支持 width
参数。
import Text from '@visx/text';
const App = () => (
<svg>
<Text width=20>Foo is not a long word</Text>
</svg>
);
【讨论】:
【参考方案3】:如果您愿意添加另一个 JS 库,另一种选择是使用 D3 插件 D3plus。它具有内置的text wrapping 功能。它甚至支持填充和调整文本大小以填充可用空间。
d3plus.textwrap()
.container(d3.select("#rectWrap"))
.draw();
我用过。这肯定比自己计算包装要好。
有 another d3 plugin 可用于文本换行,但我从未使用过它,所以我无法说出它的用处。
【讨论】:
【参考方案4】:这是一种使用 d3 plus 进行文本换行的方法。这对我来说真的很容易,并且现在可以在所有浏览器中使用
d3plus.textwrap()
.container(d3.select("#intellectual"))
.shape('square')
.width(370)
.height(55)
.resize(true)
.draw();
【讨论】:
【参考方案5】:您还可以通过 foreignObject
在 SVG 中使用纯 html 元素。
例如,我使用以下内容将 HTML div
附加到我的 svg 对象。
svg.append("foreignObject")
.attr("width", blockWidth)
.attr("height", blockHeight)
.append("xhtml:div")
.style("color", "#000")
.style("text-align", "center")
.style("width", "100%")
.style("height", "100%")
.style("padding", "5px")
.style("font-size", `$fontSizepx`)
.style("overflow-y", "auto")
.html("The text to display")
结果:
<foreignObject >
<div style="color: rgb(0, 0, 0); text-align: center; width: 100%; height: 100%; padding: 5px; font-size: 12px; overflow-y: auto;">
The text to display
</div>
</foreignObject>
如果您的样式是静态的,您也可以使用.attr('class', 'classname')
来代替所有.style(...)
调用并通过样式表插入样式。
来源(以及更多信息/选项)from this SO answer。
【讨论】:
以上是关于在 D3 中环绕文本的主要内容,如果未能解决你的问题,请参考以下文章