如何在定向d3.js力布局上动态更改方向箭头的位置和大小?

时间:2013-08-06 01:39:32

标签: javascript svg d3.js force-layout

我目前正在我的力布局中实现箭头,就像在此示例中所做的那样(http://bl.ocks.org/mbostock/1153292)并且完美无缺。但是,人们会很快意识到箭头的位置和大小是硬编码的,因为节点永远不会改变大小。

我有一个图表,我动态更改节点大小,因此我希望箭头相应地更新,否则它们被节点覆盖或覆盖节点或者只是没有连接到节点。

我发现只有一篇帖子(linking nodes of variable radius with arrows)谈到了这个问题。然而,它没有得到回答,并且一张海报给出的边缘在节点的半径而不是中心处结束的答案不是我想做的事情。它需要不断重新计算边缘位置,因为我所拥有的边数不实用。

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

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

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/)。这是一个基本的力布局,我试图让动态箭头尺寸起作用。

1 个答案:

答案 0 :(得分:3)

这是一个老问题,但这是我的解决方案。我们的想法是绘制连接节点的路径,使得端点位于节点上。边缘而不是节点中心。从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");