【问题标题】:How can I dynamically change the position and the size of direction arrows on a directed d3.js force layout?如何在定向 d3.js 强制布局上动态更改方向箭头的位置和大小?
【发布时间】:2013-08-06 01:39:31
【问题描述】:

我目前正在我的力布局中实现箭头,如本示例 (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/)。这是我试图让动态箭头大小起作用的基本力布局。

【问题讨论】:

  • 你能把这段代码 sn-p 变成一个有效的fiddle吗?看起来没有多少遗漏,并且可以让其他人更容易尝试找出解决方案。
  • 我创建了一个。检查帖子中的更新。这是一个非常基本的力布局,我只是想向其中添加动态大小的箭头。
  • 控制台抱怨的语法错误在上一行,您用单引号打开一个字符串并用双引号关闭它。关于您的问题,我注意到您提出了几个关于丢弃潜在解决方案的问题,因为在数百个节点上运行的潜在性能问题。您可能应该尝试最简单的解决方案并在丢弃它之前对其进行测试。你会惊讶于你可以在一瞬间对几百个点做多少。如今的浏览器速度很快(相对而言,即使是移动设备)。
  • 感谢您的发现。我很欣赏尝试最简单的解决方案,而且我实际上相信我正在尝试的解决方案(即使我不知道该怎么做)就是那个解决方案。老实说,可视化对我来说已经有点慢了,所以我一直在努力寻找提高速度的方法。我希望添加这个新功能不会产生什么影响。不过我会试一试的!

标签: javascript svg d3.js force-layout


【解决方案1】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-11-17
    • 2015-09-17
    • 1970-01-01
    • 2016-08-15
    • 1970-01-01
    • 2013-06-25
    • 2018-12-24
    相关资源
    最近更新 更多