我已经在链接上构建了一个带有文本标签的D3力导向可视化.我遇到的一个问题是,当链接位于其源节点的左侧时,这些标签会颠倒显示.这里的例子:
我定位路径和文本的代码如下所示:
var nodes = flatten(data); var links = d3.layout.tree().links(nodes); var path = vis.selectAll('path.link') .data(links, function(d) { return d.target.id; }); path.enter().insert('svg:path') .attr({ class: 'link', id: function(d) { return 'text-path-' + d.target.id; }, 'marker-end': 'url(#end)' }) .style('stroke', '#ccc'); var linkText = vis.selectAll('g.link-text').data(links); linkText.enter() .append('text') .append('textPath') .attr('xlink:href', function(d) { return '#text-path-' + d.target.id; }) .style('text-anchor', 'middle') .attr('startOffset', '50%') .text(function(d) {return d.target.customerId});
我知道我需要以某种方式确定每个路径的当前角度,然后相应地设置文本位置,但我不知道如何.
以下是基于此问题的块的链接:http://blockbuilder.org/MattDionis/5f966a5230079d9eb9f4
下面的答案让我大约90%的方式.以下是我的原始可视化效果,文本长度超过几位数:
......以下是利用以下答案中的提示:
因此,虽然文本现在是"正面向上",但它不再遵循弧形.
您绘制的弧线使得它们在中间的切线正好是文本基线的方向,并且它也与用于分隔两个树节点的矢量共线.
我们可以用它来解决问题.
需要一点数学.首先,让我们定义一个函数,该函数返回向量v
相对于水平轴的角度:
function xAngle(v) { return Math.atan(v.y/v.x) + (v.x < 0 ? Math.PI : 0); }
然后,在每个刻度线处,让我们通过减去其基线的角度来旋转文本.首先,一些实用功能:
function isFiniteNumber(x) { return typeof x === 'number' && (Math.abs(x) < Infinity); } function isVector(v) { return isFiniteNumber(v.x) && isFiniteNumber(v.y); }
然后,在你的tick
函数中,添加
linkText.attr('transform', function (d) { // Checks just in case, especially useful at the start of the sim if (!(isVector(d.source) && isVector(d.target))) { return ''; } // Get the geometric center of the text element var box = this.getBBox(); var center = { x: box.x + box.width/2, y: box.y + box.height/2 }; // Get the tangent vector var delta = { x: d.target.x - d.source.x, y: d.target.y - d.source.y }; // Rotate about the center return 'rotate(' + (-180/Math.PI*xAngle(delta)) + ' ' + center.x + ' ' + center.y + ')'; }); });
编辑:添加图片:
编辑2使用直线而不是弯曲的弧线(只是
在
内部而不是内部
),您可以替换与此相关的tick
函数部分linkText
:
linkText.attr('transform', function(d) { if (!(isVector(d.source) && isVector(d.target))) { return ''; } // Get the geometric center of this element var box = this.getBBox(); var center = { x: box.x + box.width / 2, y: box.y + box.height / 2 }; // Get the direction of the link along the X axis var dx = d.target.x - d.source.x; // Flip the text if the link goes towards the left return dx < 0 ? ('rotate(180 ' + center.x + ' ' + center.y + ')') : ''; });
这就是你得到的:
注意当链接从更多指向右边指向更多指向左边时,文本如何被翻转.
这个问题是文本最终在链接下面.这可以修复如下:
linkText.attr('transform', function(d) { if (!(isVector(d.source) && isVector(d.target))) { return ''; } // Get the geometric center of this element var box = this.getBBox(); var center = { x: box.x + box.width / 2, y: box.y + box.height / 2 }; // Get the vector of the link var delta = { x: d.target.x - d.source.x, y: d.target.y - d.source.y }; // Get a unitary vector orthogonal to delta var norm = Math.sqrt(delta.x * delta.x + delta.y * delta.y); var orth = { x: delta.y/norm, y: -delta.x/norm }; // Replace this with your ACTUAL font size var fontSize = 14; // Flip the text and translate it beyond the link line // if the link goes towards the left return delta.x < 0 ? ('rotate(180 ' + center.x + ' ' + center.y + ') translate(' + (orth.x * fontSize) + ' ' + (orth.y * fontSize) + ')') : ''; });
现在结果如下:
正如您所看到的,即使链接指向左侧,文本也能很好地位于行顶部.
最后,为了解决问题,同时保持弧线并使文本右侧向上弯曲,我认为你需要构建两个
元素.一个从去source
到target
,一个是要以相反的方式.当链接向右(delta.x >= 0
)时你会使用第一个,当链接向左()时你会使用第二个delta.x < 0
,我认为结果看起来更好,代码不一定比原来复杂,只是添加了更多的逻辑.