【问题标题】:D3.js nodes jump when restarting simulation to add or remove nodesD3.js 重新启动模拟时节点跳转添加或删除节点
【发布时间】:2021-05-26 00:24:52
【问题描述】:

我正在使用带有强制布局的 d3.js v6 来表示网络图。 我正在添加和删除节点,但是当我重新启动模拟时,所有节点都会跳到左上角,然后又回到原来的位置。

我有以下代码 sn-p 可以准确显示我的意思,我在网上看到了其他运行良好但无法找到我做错的示例,非常感谢任何帮助。

var dataset = {
  nodes: [
    {
      id: 1
    }, 
    {
      id: 2
    }
  ],
  links: [{
    id: 1,
    source: 1,
    target: 2
  }]
};

let switchBool = false;

let svg = d3.select('svg')
           .attr('width', '100%')
           .attr('height', '100%');

const width = svg.node()
  .getBoundingClientRect().width;
const height = svg.node()
  .getBoundingClientRect().height;

console.log(`${width}, ${height}`);

svg = svg.append('g');

svg.append('g')
  .attr('class', 'links');

svg.append('g')
  .attr('class', 'nodes');

const simulation = d3.forceSimulation();
initSimulation();

let link = svg.select('.links')
    .selectAll('line');
  
loadLinks();

let node = svg.select('.nodes')
    .selectAll('.node');
  
loadNodes();
restartSimulation();

function initSimulation() {
    simulation
  .force('link', d3.forceLink())
  .force('charge', d3.forceManyBody())
  .force('collide', d3.forceCollide())
  .force('center', d3.forceCenter())
  .force('forceX', d3.forceX())
  .force('forceY', d3.forceY());

  simulation.force('center')
    .x(width * 0.5)
    .y(height * 0.5);

  simulation.force('link')
    .id((d) => d.id)
    .distance(100)
    .iterations(1);

  simulation.force('collide')
    .radius(10);

  simulation.force('charge')
    .strength(-100);
}

function loadLinks() {
    link = svg.select('.links')
    .selectAll('line')
    .data(dataset.links, (d) => d.id)
    .join(
      enter => enter.append('line').attr('stroke', '#000000'),
    );
}

function loadNodes() {
    node = svg.select('.nodes')
    .selectAll('.node')
    .data(dataset.nodes, (d) => d.id)
    .join(
      enter => {
        const nodes = enter.append('g')
          .attr('class', 'node')
        nodes.append('circle').attr('r', 10);
        return nodes;
      },
    );
}

function restartSimulation() {
  simulation.nodes(dataset.nodes);
  simulation.force('link').links(dataset.links);
  simulation.alpha(1).restart();
  simulation.on('tick', ticked);
}

function ticked() {
  link
    .attr('x1', (d) => d.source.x)
    .attr('y1', (d) => d.source.y)
    .attr('x2', (d) => d.target.x)
    .attr('y2', (d) => d.target.y);

  node.attr('transform', (d) => `translate(${d.x},${d.y})`);
}

function updateData() {
    switchBool = !switchBool;
  if (switchBool) {
    dataset.nodes.push({id: 3});
    dataset.links.push({id: 2, source: 1, target: 3});
  } else {
    dataset.nodes.pop();
    dataset.links.pop();
  }
  
  loadLinks();
  loadNodes();
    restartSimulation();
}
<script src="https://d3js.org/d3.v6.min.js"></script>
<div>
  <button onclick="updateData()">Add/Remove</button>
  <svg></svg>
</div>

【问题讨论】:

    标签: javascript d3.js force-layout


    【解决方案1】:

    我实际上刚刚找到了问题的解决方案。

    forceXforceY 都有默认参数,这意味着有一个力将节点推向 (0,0),更改了这段代码我能够修复它:

    .force('x', d3.forceX().x(width * 0.5))
    .force('y', d3.forceY().y(height * 0.5));
    

    【讨论】:

      【解决方案2】:

      这是因为您使用了d3.forceCenter(),它不会将节点强制到一个中心点:

      中心力均匀地平移节点,使得均值 所有节点的位置(如果所有节点都相等,则为质心 weight) 在给定的位置⟨x,y⟩。 (docs)

      因此,如果您的两个节点直接位于 d3.forceCenter 的中心点下方/上方,则质量是平衡的。引入一个新节点并且必须转换整个力以使质心成为中心。这个翻译就是你看到的跳跃。

      移除 forceCenter 并使用 d3.forceX 和 d3.forceY 指定中心值,这会将节点推向指定的 x 和 y 值:

      var dataset = {
        nodes: [
          {
            id: 1
          }, 
          {
            id: 2
          }
        ],
        links: [{
          id: 1,
          source: 1,
          target: 2
        }]
      };
      
      let switchBool = false;
      
      let svg = d3.select('svg')
                 .attr('width', '100%')
                 .attr('height', '100%');
      
      const width = svg.node()
        .getBoundingClientRect().width;
      const height = svg.node()
        .getBoundingClientRect().height;
      
      console.log(`${width}, ${height}`);
      
      svg = svg.append('g');
      
      svg.append('g')
        .attr('class', 'links');
      
      svg.append('g')
        .attr('class', 'nodes');
      
      const simulation = d3.forceSimulation();
      initSimulation();
      
      let link = svg.select('.links')
          .selectAll('line');
        
      loadLinks();
      
      let node = svg.select('.nodes')
          .selectAll('.node');
        
      loadNodes();
      restartSimulation();
      
      function initSimulation() {
          simulation
        .force('link', d3.forceLink())
        .force('charge', d3.forceManyBody())
        .force('collide', d3.forceCollide())
        .force('forceX', d3.forceX().x(width/2))
        .force('forceY', d3.forceY().y(height/2));
      
      
      
        simulation.force('link')
          .id((d) => d.id)
          .distance(100)
          .iterations(1);
      
        simulation.force('collide')
          .radius(10);
      
        simulation.force('charge')
          .strength(-100);
      }
      
      function loadLinks() {
          link = svg.select('.links')
          .selectAll('line')
          .data(dataset.links, (d) => d.id)
          .join(
            enter => enter.append('line').attr('stroke', '#000000'),
          );
      }
      
      function loadNodes() {
          node = svg.select('.nodes')
          .selectAll('.node')
          .data(dataset.nodes, (d) => d.id)
          .join(
            enter => {
              const nodes = enter.append('g')
                .attr('class', 'node')
              nodes.append('circle').attr('r', 10);
              return nodes;
            },
          );
      }
      
      function restartSimulation() {
        simulation.nodes(dataset.nodes);
        simulation.force('link').links(dataset.links);
        simulation.alpha(1).restart();
        simulation.on('tick', ticked);
      }
      
      function ticked() {
        link
          .attr('x1', (d) => d.source.x)
          .attr('y1', (d) => d.source.y)
          .attr('x2', (d) => d.target.x)
          .attr('y2', (d) => d.target.y);
      
        node.attr('transform', (d) => `translate(${d.x},${d.y})`);
      }
      
      function updateData() {
          switchBool = !switchBool;
        if (switchBool) {
          dataset.nodes.push({id: 3});
          dataset.links.push({id: 2, source: 1, target: 3});
        } else {
          dataset.nodes.pop();
          dataset.links.pop();
        }
        
        loadLinks();
        loadNodes();
          restartSimulation();
      }
      <script src="https://d3js.org/d3.v6.min.js"></script>
      <div>
        <button onclick="updateData()">Add/Remove</button>
        <svg></svg>
      </div>

      【讨论】:

        猜你喜欢
        • 2014-11-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-12-25
        • 1970-01-01
        • 1970-01-01
        • 2017-05-17
        • 1970-01-01
        相关资源
        最近更新 更多