2

我目前正在我的力布局中实现箭头,如本示例 ( http://bl.ocks.org/mbostock/1153292 ) 中所做的那样,效果很好。然而,人们很快就会意识到箭头的位置和大小在这里是硬编码的,因为节点永远不会改变大小。

我有一个图表,如果我动态更改节点大小,因此我希望箭头相应更新,否则它们会被节点覆盖或覆盖节点,或者只是不附加到节点。

我发现只有一篇文章(用箭头链接可变半径的节点)讨论了这个问题。但是,它没有得到回答,并且一张海报给出的答案是让边缘在节点的半径而不是中心结束,这不是我想做的事情。它需要不断地重新计算边缘位置,考虑到我拥有的边缘数量是不切实际的。

我认为这会相对简单,但无法弄清楚。我目前正在进行的更改是将标记创建移动到节点的生成之下,否则除非我想运行我正在使用的大小方法,否则无法获取节点大小数据,这将大量浪费处理能力(我有数百个节点)。

我正在尝试什么(粗略的例子,我的代码有点复杂)

var path = svg.append("svg:g").selectAll("path")
    .data(force.links())
  .enter().append("svg:path")
    .attr("class", function(d) { return d.target.nodeID; });

var circle = svg.append("svg:g").selectAll("circle")
    .data(force.nodes())
      .enter().append("svg:circle")
    .attr("r", nodeSize) //Dynamically determine size
    .call(force.drag);

// Per-node markers, as each node could potentially have a unique size
svg.append("svg:defs").selectAll("marker")
    .data(nodes, function(d) { return d.nodeID; })
  .enter().append("svg:marker")
    .attr("id", function(d) { return d.nodeID; })
    .attr("viewBox", "0 -5 10 10")
    .attr("refX", function(d) { return d.r; }) //Offset by the radius of the node
    .attr("refY", 0)
    .attr("markerWidth", 6)
    .attr("markerHeight", 6)
    .attr("orient", "auto")
  .append("svg:path")
    .attr("d", "M0,-5L10,0L0,5");

//Now that we know radius data for nodes, add the arrows in
path.each(function(d) { 
    d.attr("marker-end", function(d) { return "url(#" + d.target.nodeID + ")"; })
});

有没有人知道解决这个问题的最佳方法?提前致谢!

更新:根据请求,我创建了一个 jsfiddle ( http://jsfiddle.net/herbstmb/j5wJ7/ )。这是我试图让动态箭头大小起作用的基本力布局。

4

1 回答 1

4

这是一个老问题,但这是我的解决方案。这个想法是绘制连接节点的路径,使得端点位于节点的边缘而不是节点的中心。从 Mobile Patent Suits 示例 ( http://bl.ocks.org/mbostock/1153292 ) 开始,我将 linkArc 方法替换为:

function drawCurve(d) {
    var sourceX = d.source.x;
    var sourceY = d.source.y;
    var targetX = d.target.x;
    var targetY = d.target.y;

    var theta = Math.atan((targetX - sourceX) / (targetY - sourceY));
    var phi = Math.atan((targetY - sourceY) / (targetX - sourceX));

    var sinTheta = d.source.r * Math.sin(theta);
    var cosTheta = d.source.r * Math.cos(theta);
    var sinPhi = d.target.r * Math.sin(phi);
    var cosPhi = d.target.r * Math.cos(phi);

    // Set the position of the link's end point at the source node
    // such that it is on the edge closest to the target node
    if (d.target.y > d.source.y) {
        sourceX = sourceX + sinTheta;
        sourceY = sourceY + cosTheta;
    }
    else {
        sourceX = sourceX - sinTheta;
        sourceY = sourceY - cosTheta;
    }

    // Set the position of the link's end point at the target node
    // such that it is on the edge closest to the source node
    if (d.source.x > d.target.x) {
        targetX = targetX + cosPhi;
        targetY = targetY + sinPhi;    
    }
    else {
        targetX = targetX - cosPhi;
        targetY = targetY - sinPhi;   
    }

    // Draw an arc between the two calculated points
    var dx = targetX - sourceX,
        dy = targetY - sourceY,
        dr = Math.sqrt(dx * dx + dy * dy);
    return "M" + sourceX + "," + sourceY + "A" + dr + "," + dr + " 0 0,1 " + targetX + "," + targetY;
}

请注意,此代码要求节点数据中包含“r”或半径属性。这将等效于您的 jsfiddle 中的 size 属性。如果要将链接绘制为直线,则可以返回此字符串:

return "M" + sourceX + "," + sourceY + "L" + targetX + "," + targetY;

为了将箭头的点放置在正确的位置,我更改了 refX 和 refY 属性,使箭头的点位于节点的边缘:

svg.append("defs").selectAll("marker")
    .data(["suit", "licensing", "resolved"])
  .enter().append("marker")
    .attr("id", function(d) { return d; })
    .attr("viewBox", "0 -5 10 10")
    .attr("refX", 10)
    .attr("refY", 0)
    .attr("markerWidth", 6)
    .attr("markerHeight", 6)
    .attr("orient", "auto")
  .append("path")
    .attr("d", "M0,-5L10,0L0,5");
于 2014-10-28T18:57:30.967 回答