Javascript 反应+;D3力布局:新链接的位置未定义

Javascript 反应+;D3力布局:新链接的位置未定义,javascript,node.js,reactjs,d3.js,force-layout,Javascript,Node.js,Reactjs,D3.js,Force Layout,我相信我已经遵循了新React道具的一般更新模式。D3在收到新道具时进行数据计算和渲染,这样React就不必渲染每个刻度 D3在静态布局下运行良好。但当我在shouldComponentUpdate(nextProps)函数中收到新节点和链接时,这些节点缺少以下属性: 索引-节点数组中节点的从零开始的索引 x—当前节点位置的x坐标 y—当前节点位置的y坐标 因此,所有新节点都具有,并聚集在左上角 我更新道具的方法是将新对象推送到节点数组和链接数组。我不明白为什么D3没有像在component

我相信我已经遵循了新React道具的一般更新模式。D3在收到新道具时进行数据计算和渲染,这样React就不必渲染每个刻度

D3在静态布局下运行良好。但当我在shouldComponentUpdate(nextProps)函数中收到新节点和链接时,这些节点缺少以下属性:

  • 索引-节点数组中节点的从零开始的索引
  • x—当前节点位置的x坐标
  • y—当前节点位置的y坐标
因此,所有新节点都具有
,并聚集在左上角

我更新道具的方法是将新对象推送到节点数组和链接数组。我不明白为什么D3没有像在componentDidMount()的初始设置那样分配d.x和d.y。我已经为这个问题挣扎了好几天了。希望有人能帮我

以下是ForceLayout.jsx:

//React for structure - D3 for data calculation - D3 for rendering

import React from 'react';
import * as d3 from 'd3';

export default class ForceLayout extends React.Component{
  constructor(props){
    super(props);
  }

  componentDidMount(){ //only find the ref graph after rendering
    const nodes = this.props.nodes;
    const links = this.props.links;
    const width = this.props.width;
    const height = this.props.height;

    this.simulation = d3.forceSimulation(nodes)
      .force("link", d3.forceLink(links).distance(50))
      .force("charge", d3.forceManyBody().strength(-120))
      .force('center', d3.forceCenter(width / 2, height / 2));

    this.graph = d3.select(this.refs.graph);

    this.svg = d3.select('svg');
    this.svg.call(d3.zoom().on(
      "zoom", () => {
        this.graph.attr("transform", d3.event.transform)
      })
    );

    var node = this.graph.selectAll('.node')
      .data(nodes)
      .enter()
      .append('g')
      .attr("class", "node")
      .call(enterNode);

    var link = this.graph.selectAll('.link')
      .data(links)
      .enter()
      .call(enterLink);

    this.simulation.on('tick', () => {
      this.graph.call(updateGraph);
    });
  }

  shouldComponentUpdate(nextProps){
    //only allow d3 to re-render if the nodes and links props are different
    if(nextProps.nodes !== this.props.nodes || nextProps.links !== this.props.links){
      console.log('should only appear when updating graph');

      this.simulation.stop();
      this.graph = d3.select(this.refs.graph);

      var d3Nodes = this.graph.selectAll('.node')
        .data(nextProps.nodes);
      d3Nodes
        .enter() 
        .append('g')
        .attr("class", "node")
      .call(enterNode);
      d3Nodes.exit().remove(); //get nodes to be removed
      // d3Nodes.call(updateNode);

      var d3Links = this.graph.selectAll('.link')
        .data(nextProps.links);
      d3Links
        .enter()
        .call(enterLink);
      d3Links.exit().remove();
      // d3Links.call(updateLink);

      const newNodes = nextProps.nodes.slice(); //originally Object.assign({}, nextProps.nodes)
      const newLinks = nextProps.links.slice(); //originally Object.assign({}, nextProps.links)
      this.simulation.nodes(newNodes);
      this.simulation.force("link").links(newLinks);

      this.simulation.alpha(1).restart();

      this.simulation.on('tick', () => {
        this.graph.call(updateGraph);
      });
    }
    return false;
  }

  render(){
    return(
      <svg
        width={this.props.width}
        height={this.props.height}
        style={this.props.style}>
        <g ref='graph' />
      </svg>
    );
    }
  }

  /** d3 functions to manipulate attributes **/
  var enterNode = (selection) => {
    selection.append('circle')
      .attr('r', 10)
      .style('fill', '#888888')
      .style('stroke', '#fff')
      .style('stroke-width', 1.5);

    selection.append("text")
      .attr("x", function(d){return 20}) //
      .attr("dy", ".35em") // vertically centre text regardless of font size
      .text(function(d) { return d.word });
   };

   var enterLink = (selection) => {
      selection.append('line')
        .attr("class", "link")
        .style('stroke', '#999999')
        .style('stroke-opacity', 0.6);
   };

   var updateNode = (selection) => {
       selection.attr("transform", (d) => "translate(" + d.x + "," + d.y + ")");
    };

    var updateLink = (selection) => {
      selection.attr("x1", (d) => d.source.x)
        .attr("y1", (d) => d.source.y)
        .attr("x2", (d) => d.target.x)
        .attr("y2", (d) => d.target.y);
    };

    var updateGraph = (selection) => {
       selection.selectAll('.node')
          .call(updateNode);
       selection.selectAll('.link')
          .call(updateLink);
    };
//对结构作出反应-D3进行数据计算-D3进行渲染
从“React”导入React;
从“d3”导入*作为d3;
导出默认类ForceLayout扩展React.Component{
建造师(道具){
超级(道具);
}
componentDidMount(){//仅在呈现后查找ref图
const nodes=this.props.nodes;
const links=this.props.links;
const width=this.props.width;
const height=this.props.height;
this.simulation=d3.forceSimulation(节点)
.力(“链接”,d3.力链接(链接).距离(50))
.力(“电荷”,d3.力人体().力(-120))
.力(“中心”,d3.力中心(宽度/2,高度/2));
this.graph=d3.select(this.refs.graph);
this.svg=d3.select('svg');
调用(d3.zoom().on(
“缩放”,()=>{
this.graph.attr(“transform”,d3.event.transform)
})
);
var node=this.graph.selectAll(“.node”)
.数据(节点)
.输入()
.append('g')
.attr(“类”、“节点”)
.呼叫(enterNode);
var link=this.graph.selectAll('.link')
.数据(链接)
.输入()
。致电(enterLink);
this.simulation.on('tick',()=>{
this.graph.call(updateGraph);
});
}
应更新组件(下一步){
//仅当节点和链接道具不同时才允许d3重新渲染
if(nextrops.nodes!==this.props.nodes | | nextrops.links!==this.props.links){
log('应仅在更新图形时出现');
this.simulation.stop();
this.graph=d3.select(this.refs.graph);
var d3Nodes=this.graph.selectAll(“.node”)
.data(nextrops.nodes);
D3节点
.输入()
.append('g')
.attr(“类”、“节点”)
.呼叫(enterNode);
d3Nodes.exit().remove();//获取要删除的节点
//d3Nodes.call(updateNode);
var d3Links=this.graph.selectAll('.link')
.数据(nextrops.links);
d3Links
.输入()
。致电(enterLink);
d3Links.exit().remove();
//d3Links.call(updateLink);
const newNodes=nextrops.nodes.slice();//最初的Object.assign({},nextrops.nodes)
const newLinks=nextrops.links.slice();//原始对象.assign({},nextrops.links)
这个.simulation.nodes(newNodes);
这个.simulation.force(“link”).links(newLinks);
this.simulation.alpha(1.restart();
this.simulation.on('tick',()=>{
this.graph.call(updateGraph);
});
}
返回false;
}
render(){
返回(
);
}
}
/**d3函数用于操作属性**/
var enterNode=(选择)=>{
selection.append('circle'))
.attr('r',10)
.style('fill','#8888888')
.style('stroke','#fff')
.样式(“笔划宽度”,1.5);
选择。追加(“文本”)
.attr(“x”,函数(d){return 20})//
.attr(“dy”,“.35em”)//垂直居中文本,不考虑字体大小
.text(函数(d){return d.word});
};
var enterLink=(选择)=>{
selection.append('行')
.attr(“类”、“链接”)
.style('stroke','#99999')
.样式(“笔划不透明度”,0.6);
};
变量updateNode=(选择)=>{
selection.attr(“transform”(转换),(d)=>“translate”(+d.x+),“+d.y+”);
};
var updateLink=(选择)=>{
selection.attr(“x1”,(d)=>d.source.x)
.attr(“y1”,(d)=>d.source.y)
.attr(“x2”,(d)=>d.target.x)
.attr(“y2”,(d)=>d.target.y);
};
var updateGraph=(选择)=>{
selection.selectAll(“.node”)
.呼叫(更新节点);
selection.selectAll(“.link”)
.呼叫(更新链接);
};
我尝试在shouldComponentUpdate()函数中将新节点推入节点数组,而不是修改服务器中的数组。但新节点仍显示在左上角,位置未定义。所以我猜我的问题出在shouldComponentUpdate()。非常感谢您的帮助


编辑:找到该对象。分配(…)不返回数组。将其改为array.slice()。现在,所有节点都使用一个位置进行渲染,但根本没有链接。旧节点也从原始位置磨损

我不明白为什么链接上的位置与节点不对应。

forceLink
中,默认情况下使用对象引用来指向
目标

您不会展示如何构造
链接
节点
道具,但您可以通过调用并设置id访问器指向节点逻辑id来绕过它,因此假设您的节点具有id属性,可以这样写:

.force("link", d3.forceLink(links).id(d => d.id).distance(50))
var nodes = updatePositions(newProps.nodes, this.simulation.nodes())
或者,您可以使用节点的索引作为访问器:

.force("link", d3.forceLink(links).id(d => d.index).distance(50))

--编辑--

另一个可能有帮助的措施是将当前节点的属性与新节点合并,这将使它们保留位置:

const updatePositions = (newNodes = [], currentNodes = []) => {
  const positionMap = currentNodes.reduce((result, node) => {
    result[node.id] = {
      x: node.x,
      y: node.y,
    };
    return result
  }, {});
  return newNodes.map(node => ({...node, ...positionMap[node.id]}))
}
<
var nodes = updatePositions(newProps.nodes, this.simulation.nodes())
this.graph.selectAll('.node')
    .data(nextProps.nodes, d => d.id) // again assuming id property 
removeD3Extras: function(currentGraph) {
  currentGraph.nodes.forEach(function(d){
    delete d.index;
    delete d.x;
    delete d.y;
    delete d.vx;
    delete d.vy;
  });

  currentGraph.links.forEach(function(d){
    d.source = d.source.id;
    d.target = d.target.id;
    delete d.index;
  });

  return currentGraph;
}