【问题标题】:How to change the distance between nodes in a force layout?如何更改力布局中节点之间的距离?
【发布时间】:2018-12-29 11:54:19
【问题描述】:

我是 D3 的新手,并尝试学习力布局。我想改变节点之间的链接距离并保持原点形状。我发现更改链接的距离后,布局发生了变化。

图一是原始布局,然后我用代码distance([150])(第80行)更改了链接距离以使节点更远,但布局的变化超出了我的预期。

我预计布局会随着形状与原始形状保持一致而成长,但布局发生了很大变化。我现在不确定链接距离的含义。 任何人都可以帮忙吗?谢谢! My code on codepen

【问题讨论】:

    标签: d3.js data-visualization force-layout


    【解决方案1】:

    根据其定义,力导向图是动态的:您不能预先设置节点的位置,或者期望节点会落在给定的位置。

    D3 力模拟有很多参数,其中一个的微小变化可以使布局完全不同。这实际上是预期的。

    所以,就你而言,这里没有什么奇怪的。您可以尝试几种不同的解决方案。在我看来,保持(大约)你想要的结构并在节点之间有更大间隔的那个是使用forceCollide,为中心节点(橙色那个)设置不同的半径:

    .force('collide', d3.forceCollide(function(d){
        return d.id === "j" ? 100 : 50
    }));
    

    这里是更新后的 CodePen:https://codepen.io/anon/pen/xJgYmP?editors=0010

    这里是堆栈 sn-p 中的相同代码:

    graph = {
      nodes: [{
          id: 'a',
          group: 1
        },
        {
          id: 'b',
          group: 1
        },
        {
          id: 'c',
          group: 1
        },
        {
          id: 'd',
          group: 2
        },
        {
          id: 'e',
          group: 2
        },
        {
          id: 'f',
          group: 2
        },
        {
          id: 'g',
          group: 3
        },
        {
          id: 'h',
          group: 3
        },
        {
          id: 'i',
          group: 3
        },
        {
          id: 'j',
          group: 4
        }
      ],
      links: [{
          source: 'a',
          target: 'b',
          value: 2
        },
        {
          source: 'a',
          target: 'c',
          value: 2
        },
        {
          source: 'b',
          target: 'c',
          value: 1
        },
        {
          source: 'd',
          target: 'e',
          value: 2
        },
        {
          source: 'd',
          target: 'f',
          value: 2
        },
        {
          source: 'e',
          target: 'f',
          value: 1
        },
        {
          source: 'g',
          target: 'h',
          value: 1
        },
        {
          source: 'g',
          target: 'i',
          value: 2
        },
        {
          source: 'i',
          target: 'h',
          value: 2
        },
        {
          source: 'a',
          target: 'j',
          value: 2
        },
        {
          source: 'b',
          target: 'j',
          value: 1
        },
        {
          source: 'c',
          target: 'j',
          value: 1
        },
        {
          source: 'd',
          target: 'j',
          value: 1
        },
        {
          source: 'e',
          target: 'j',
          value: 1
        },
        {
          source: 'f',
          target: 'j',
          value: 1
        },
        {
          source: 'g',
          target: 'j',
          value: 1
        },
        {
          source: 'h',
          target: 'j',
          value: 2
        },
        {
          source: 'i',
          target: 'j',
          value: 2
        },
      ]
    }
    var graph = this.graph
    
    var svg = d3.select('svg')
    var width = +svg.attr('width')
    var height = +svg.attr('height')
    
    var color = d3.scaleOrdinal(d3.schemeCategory20)
    
    var simulation = d3.forceSimulation()
      .force('link', d3.forceLink().id(function(d) {
        return d.id
      }))
      .force('charge', d3.forceManyBody())
      .force('collide', d3.forceCollide(function(d) {
        return d.id === "j" ? 100 : 50
      }))
      .force('center', d3.forceCenter(width / 2, height / 2))
    
    var link = svg.append('g')
      .attr('class', 'links')
      .selectAll('line')
      .data(graph.links)
      .enter().append('line')
      .attr('stroke-width', function(d) {
        return Math.sqrt(d.value)
      })
    
    var node = svg.append('g')
      .attr('class', 'nodes')
      .selectAll('circle')
      .data(graph.nodes)
      .enter().append('circle')
      .attr('r', 5)
      .attr('fill', function(d) {
        return color(d.group)
      })
      .call(d3.drag()
        .on('start', dragstarted)
        .on('drag', dragged)
        .on('end', dragended))
    
    node.append('title')
      .text(function(d) {
        return d.id
      })
    
    simulation
      .nodes(graph.nodes)
      .on('tick', ticked)
    
    simulation.force('link')
      .links(graph.links)
    
    function ticked() {
      link
        .attr('x1', function(d) {
          return d.source.x
        })
        .attr('y1', function(d) {
          return d.source.y
        })
        .attr('x2', function(d) {
          return d.target.x
        })
        .attr('y2', function(d) {
          return d.target.y
        })
    
      node
        .attr('cx', function(d) {
          return d.x
        })
        .attr('cy', function(d) {
          return d.y
        })
    }
    
    function dragstarted(d) {
      if (!d3.event.active) simulation.alphaTarget(0.3).restart()
      d.fx = d.x
      d.fy = d.y
    }
    
    function dragged(d) {
      d.fx = d3.event.x
      d.fy = d3.event.y
    }
    
    function dragended(d) {
      if (!d3.event.active) simulation.alphaTarget(0)
      d.fx = null
      d.fy = null
    }
    .links line {
      stroke: #999;
      stroke-opacity: 0.6;
    }
    
    .nodes circle {
      stroke: #fff;
      stroke-width: 1.5px;
    }
    <script src="https://d3js.org/d3.v4.min.js"></script>
    <div class="container">
      <svg width="960" height="600"></svg>
    </div>

    【讨论】:

    • 谢谢!结果正是我所期望的。但我想知道距离可能与身份无关。我给每个节点一个值,并希望与这样的值相关的距离code with node has value。我使用 value * constant 来计算距离,不同的距离使布局不同。有什么技巧可以选择合适的常数吗?非常感谢!
    • 这很复杂。正如我所说,如果您想要特定的布局,请不要使用强制。
    • 好的,看来我应该花更多时间学习它。非常感谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-07-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-13
    • 2020-05-29
    相关资源
    最近更新 更多