d3.js - 如何在 d3 力导向图布局中可视化节点组

标签 d3.js force-layout

我正在寻找一种方法将组插入到我的力导向图可视化中。到目前为止,我发现了三个相关的例子:

  • Cola.js这将需要添加另一个库并可能改造我的代码以适应这个不同的库。
  • This block ,这很难解开。
  • This slide来自 mbostock 的幻灯片,这不是我想要的,但在正确的道路上...

  • 我最想要的是一种从第一个链接中添加非常接近结构的东西的简单方法,但没有太多开销。

    现在我有一个非常标准的设置:
    var link = g.selectAll(".link")
                .data(graph.links)
                .enter().append("line")
                .attr("class", "link")
                .style(...
    
    var node = g.selectAll(".node")
                .data(graph.nodes)
                .enter().append("g")
                .attr("class", "node")
                .attr("id", function(d) { return d.id; })
    

    我希望只是从 cola.js 中获取 d3 代码并对其进行处理,但该库似乎相当复杂,因此不会太容易。我希望在直接 d3 中得到这样的东西并不太难:

    enter image description here

    谢谢!

    最佳答案

    我比建议的图片更多地关注标题“可视化节点组”,但我认为调整我的答案以显示图像中的边界框并不难

    可能只有几个 d3 解决方案,几乎可以肯定所有这些解决方案都需要手动调整节点位置以保持节点正确分组。最终结果不会严格地是典型的力布局,因为除了连接之外,还必须操纵链接和节点位置以显示分组——因此,最终结果将是每个力之间的折衷——节点电荷、长度强度和长度, 和组。

    实现目标的最简单方法可能是:

  • 链接链接不同组时削弱链接强度
  • 在每个刻度上,计算每个组的质心
  • 调整每个节点的位置,使其更靠近组的质心
  • 使用 voronoi 图显示分组

  • 对于我这里的示例,我将使用 Mike 的规范 force layout .

    当链接链接不同组时弱化链接

    使用链接示例,当链接目标和链接源具有不同的组时,我们可以抑制链接强度。指定的强度可能需要根据部队布局的性质进行更改 - 更多相互关联的群体可能需要更弱的群体间链接强度。

    要根据我们是否有组间链接来更改链接强度,我们可以使用:
    var simulation = d3.forceSimulation()
        .force("link", d3.forceLink().id(function(d) { return d.id; }).strength(function(link) {   
          if (link.source.group == link.source.target) {
            return 1; // stronger link for links within a group
          }
          else {
            return 0.1; // weaker links for links across groups
          }   
          }) )
        .force("charge", d3.forceManyBody().strength(-20))
        .force("center", d3.forceCenter(width / 2, height / 2));
    

    在每个刻度上,计算组质心

    我们想强制组节点在一起,为此我们需要知道组的质心。 simulation.nodes()的数据结构不是最适合计算质心的,所以我们需要做一些工作:
    var nodes = this.nodes();
    var coords ={};
    var groups = [];
    
    // sort the nodes into groups:  
    node.each(function(d) {
        if (groups.indexOf(d.group) == -1 ) {
            groups.push(d.group);
            coords[d.group] = [];
        }
        coords[d.group].push({x:d.x,y:d.y});    
    })
    
    // get the centroid of each group:
    var centroids = {};
    
    for (var group in coords) {
        var groupNodes = coords[group];
        var n = groupNodes.length;
        var cx = 0;
        var tx = 0;
        var cy = 0;
        var ty = 0;
    
        groupNodes.forEach(function(d) {
            tx += d.x;
            ty += d.y;
        })
    
        cx = tx/n;
        cy = ty/n;
    
        centroids[group] = {x: cx, y: cy}   
    }
    

    调整每个节点的位置以使其更靠近其组的质心:

    我们不需要调整每个节点 - 只是那些偏离其质心相当远的节点。对于足够远的那些,我们可以使用质心和节点当前位置的加权平均值将它们推得更近。

    我修改了用于确定是否应该在可视化冷却时调整节点的最小距离。在可视化处于事件状态的大部分时间,当 alpha 高时,优先级是分组,因此大多数节点将被强制向分组质心。随着 alpha 下降到零,节点应该已经分组,并且强制其位置的需要不太重要:
    // don't modify points close the the group centroid:
    var minDistance = 10;
    
    // modify the min distance as the force cools:
    if (alpha < 0.1) {
        minDistance = 10 + (1000 * (0.1-alpha))
    }
    
    // adjust each point if needed towards group centroid:
    node.each(function(d) {
        var cx = centroids[d.group].x;
        var cy = centroids[d.group].y;
        var x = d.x;
        var y = d.y;
        var dx = cx - x;
        var dy = cy - y;
    
        var r = Math.sqrt(dx*dx+dy*dy)
    
        if (r>minDistance) {
            d.x = x * 0.9 + cx * 0.1;
            d.y = y * 0.9 + cy * 0.1;
        }
    })
    

    使用 Voronoi 图

    这允许最简单的节点分组 - 它确保组 shell 之间没有重叠。我没有内置任何验证来确保一个节点或一组节点不会与其组的其余部分隔离 - 根据可视化的复杂性,您可能需要这样做。

    我最初的想法是使用隐藏的 Canvas 来计算 shell 是否重叠,但是使用 Voronoi,您可能会计算出每个组是否使用相邻单元格进行合并。在非合并组的情况下,您可以对杂散节点使用更强的强制。

    应用 voronoi 非常简单:
      // append voronoi  
      var cells = svg.selectAll()
      .data(simulation.nodes())
      .enter().append("g")
      .attr("fill",function(d) { return color(d.group); })
      .attr("class",function(d) { return d.group })
    
      var cell = cells.append("path") 
        .data(voronoi.polygons(simulation.nodes()))
    

    并在每个刻度上更新:
    // update voronoi:
    cell = cell.data(voronoi.polygons(simulation.nodes())).attr("d", renderCell);
    

    结果

    总而言之,在分组阶段,这看起来像这样:

    enter image description here

    随着可视化终于停止:

    enter image description here

    如果第一个图像更可取,则删除更改 minDistance 的部分随着阿尔法冷却。

    这是a block使用上述方法。

    进一步修改

    我们可以使用另一个力图来定位每个组的理想质心,而不是使用每个组节点的质心。该力图将为每个组有一个节点,每个组之间的链接强度将对应于组节点之间的链接数。使用这个力图,我们可以将原始节点强制向我们理想化的质心——第二个力布局的节点。

    这种方法在某些情况下可能具有优势,例如通过更大的数量分隔组。这种方法可能会给你类似的东西:

    enter image description here

    我在这里包含了一个示例,但希望代码被充分注释以理解,而不会像上面的代码那样出现故障。

    second example块.

    voronoi 很容易,但并不总是最美观的,您可以使用剪辑路径将多边形保持为某种椭圆形,或者使用渐变叠加使多边形在到达边缘时淡出。根据图的复杂性,一种可能的选择是使用最小凸多边形,尽管这不适用于少于三个节点的组。在大多数情况下,边界框可能不起作用,除非您确实将强制因子保持在较高水平(例如:始终保持 minDistance 非常低)。权衡将始终是您想要显示更多内容:连接或分组。

    关于d3.js - 如何在 d3 力导向图布局中可视化节点组,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47544041/

    相关文章:

    javascript - 如何判断 D3 强制布局何时停止

    javascript - 力导向图 - 基于节点间连接数(体积)的链接宽度

    javascript - 无法生成网格线

    r - 使用 plotly 或 Network 3D 将 igraph 转换为交互式绘图

    javascript - 添加文本标签以强制 d3.js 中的定向图链接

    javascript - 使用 json 的 d3.js 中的不完整图表

    javascript - 遍历函数调用

    javascript - 根据过滤器下拉列表在 d3 中的力图中添加和删除节点和链接

    javascript - 在 D3 力导向布局中,如何在不拖动的情况下以编程方式定位固定节点?

    d3.js - 在 D3 强制布局节点标签中插入换行符