在版本4中动态添加节点到D3 Force布局

Adding nodes dynamically to D3 Force Layout in version 4

本文关键字:D3 Force 布局 节点 添加 版本 动态      更新时间:2023-09-26

我试图实现一个简单的力布局,其中节点(没有链接)可以动态添加和删除。我成功地在D3版本3中实现了这个概念,但我无法将其转化为版本4。在添加和更新节点后,模拟冻结,并在svg的左上角绘制传入的圆圈。有人知道为什么会这样吗?谢谢你的帮助:)

我的概念是基于这个解决方案:向强制布局添加新节点

JSFiddle:在d3 v3中的工作代码

/* Define class */
class Planet {
  constructor(selector) {
    this.w = $(selector).innerWidth();
    this.h = $(selector).innerHeight();
    this.svg = d3.select(selector)
      .append('svg')
      .attr('width', this.w)
      .attr('height', this.h);
    this.force = d3.layout.force()
      .gravity(0.05)
      .charge(-100)
      .size([this.w, this.h]);
    this.nodes = this.force.nodes();
  }
  /* Methods (are called on object) */
  update() {
    /* Join selection to data array -> results in three new selections enter, update and exit */
    const circles = this.svg.selectAll('circle')
      .data(this.nodes, d => d.id); // arrow function, function(d) { return d.y;}
    /* Add missing elements by calling append on enter selection */
    circles.enter()
      .append('circle')
      .attr('r', 10)
      .style('fill', 'steelblue')
      .call(this.force.drag);
    /* Remove surplus elements from exit selection */
    circles.exit()
      .remove();
    this.force.on('tick', () => {
      circles.attr('cx', d => d.x)
        .attr('cy', d => d.y);
    });
    /* Restart the force layout */
    this.force.start();
  }
  addThought(content) {
    this.nodes.push({ id: content });
    this.update();
  }
  findThoughtIndex(content) {
    return this.nodes.findIndex(node => node.id === content);
  }
  removeThought(content) {
    const index = this.findThoughtIndex(content);
    if (index !== -1) {
      this.nodes.splice(index, 1);
      this.update();
    }
  }
}
/* Instantiate class planet with selector and initial data*/
const planet = new Planet('.planet');
planet.addThought('Hallo');
planet.addThought('Ballo');
planet.addThought('Yallo');

这是我将代码翻译成v4的意图:

/* Define class */
class Planet {
  constructor(selector) {
    this.w = $(selector).innerWidth();
    this.h = $(selector).innerHeight();
    this.svg = d3.select(selector)
      .append('svg')
      .attr('width', this.w)
      .attr('height', this.h);
    this.simulation = d3.forceSimulation()
      .force('charge', d3.forceManyBody())
      .force('center', d3.forceCenter(this.w / 2, this.h / 2));
    this.nodes = this.simulation.nodes();
  }
  /* Methods (are called on object) */
  update() {
    /* Join selection to data array -> results in three new selections enter, update and exit */
    let circles = this.svg.selectAll('circle')
      .data(this.nodes, d => d.id); // arrow function, function(d) { return d.y;}
    /* Add missing elements by calling append on enter selection */
    const circlesEnter = circles.enter()
      .append('circle')
      .attr('r', 10)
      .style('fill', 'steelblue');
    circles = circlesEnter.merge(circles);
    /* Remove surplus elements from exit selection */
    circles.exit()
      .remove();
    this.simulation.on('tick', () => {
      circles.attr('cx', d => d.x)
        .attr('cy', d => d.y);
    });
    /* Assign nodes to simulation */
    this.simulation.nodes(this.nodes);
    /* Restart the force layout */
    this.simulation.restart();
  }
  addThought(content) {
    this.nodes.push({ id: content });
    this.update();
  }
  findThoughtIndex(content) {
    return this.nodes.findIndex(node => node.id === content);
  }
  removeThought(content) {
    const index = this.findThoughtIndex(content);
    if (index !== -1) {
      this.nodes.splice(index, 1);
      this.update();
    }
  }
}

请参见plunkr示例

我使用画布,但理论是一样的:

你必须先给你的新的数组的节点和链接到D3的核心功能,然后再添加到原来的数组。

drawData: function(graph){
  var countExtent = d3.extent(graph.nodes,function(d){return d.connections}),
      radiusScale = d3.scalePow().exponent(2).domain(countExtent).range(this.nodes.sizeRange);
      // Let D3 figure out the forces
      for(var i=0,ii=graph.nodes.length;i<ii;i++) {
        var node = graph.nodes[i];
        node.r = radiusScale(node.connections);
        node.force = this.forceScale(node);
        };
    // Concat new and old data
    this.graph.nodes = this.graph.nodes.concat(graph.nodes);
    this.graph.links = this.graph.links.concat(graph.links);
    // Feed to simulation
    this.simulation
        .nodes(this.graph.nodes);
    this.simulation.force("link")
        .links(this.graph.links);
    this.simulation.alpha(0.3).restart();
}

之后,告诉D3用新数据重启。

当D3调用您的tick()函数时,它已经知道您需要将什么坐标应用于SVG元素。

ticked: function(){
     if(!this.graph) {
        return false;
    }
    this.context.clearRect(0,0,this.width,this.height);
    this.context.save();
    this.context.translate(this.width / 2, this.height / 2);
    this.context.beginPath();
    this.graph.links.forEach((d)=>{
        this.context.moveTo(d.source.x, d.source.y);
        this.context.lineTo(d.target.x, d.target.y);
    });
    this.context.strokeStyle = this.lines.stroke.color;
    this.context.lineWidth = this.lines.stroke.thickness;
    this.context.stroke();
    this.graph.nodes.forEach((d)=>{
        this.context.beginPath();
        this.context.moveTo(d.x + d.r, d.y);
        this.context.arc(d.x, d.y, d.r, 0, 2 * Math.PI);
        this.context.fillStyle = d.colour;
        this.context.strokeStyle =this.nodes.stroke.color;
        this.context.lineWidth = this.nodes.stroke.thickness;
        this.context.fill();
        this.context.stroke();
    });
    this.context.restore();
}

Plunkr示例