D3.js d3力贴图在重新渲染后不正确地渲染链接

D3.js d3力贴图在重新渲染后不正确地渲染链接,d3.js,D3.js,我用react和Force布局制作了一个组织层次结构图。Org对象有一个用户,定义用户与工作中的其他人(如老板、同事、下属)的关系。可以在组织地图中动态添加一个新的人员,从而使用新信息重新呈现地图 但是,重新渲染后,贴图会错误地显示链接和关系文本。即使与节点关联的数据正确,节点上的名称也会被错误分配。通过调试,我发现链接、节点和链接标签对象都是正确的。但进出似乎有点古怪,可能是问题的根源 我有一个程序来模拟这个bug 最初渲染具有四个节点的组织映射。Joe是用户,他有一个老板John、同事She

我用react和Force布局制作了一个组织层次结构图。Org对象有一个用户,定义用户与工作中的其他人(如老板、同事、下属)的关系。可以在组织地图中动态添加一个新的人员,从而使用新信息重新呈现地图

但是,重新渲染后,贴图会错误地显示链接和关系文本。即使与节点关联的数据正确,节点上的名称也会被错误分配。通过调试,我发现链接、节点和链接标签对象都是正确的。但进出似乎有点古怪,可能是问题的根源

我有一个程序来模拟这个bug

最初渲染具有四个节点的组织映射。Joe是用户,他有一个老板John、同事Shelley和下属Maria

我创建了一个按钮来模拟动态添加一个新人。单击按钮将添加(数据为错误模拟硬编码)Kelly作为Maria的同事,并重新渲染地图。您会注意到渲染后,所有链接和标签都不正确。但是,当我在调试模式下查看与节点关联的数据时,它是正确的

我花了很多时间试图弄明白这一点,但似乎没有抓住错误

jsfiddle是用react编写的。如果您不熟悉react,请忽略react代码,只关注d3代码

JSFIDLE代码粘贴在此处:

Javascript:

const ForceMap = React.createClass({
  propTypes: {
   data: React.PropTypes.object,
     width: React.PropTypes.number,
     height: React.PropTypes.number
  },


  componentDidMount(){

    let {width,height} = this.props;

    this.forceLayout = d3.layout.force()
                         .linkDistance(100)
                         .charge(-400)
                         .gravity(.02)
                         .size([width, height])

    this.svg = d3.select("#graph")
                .append("svg")
                  .attr({id:'#org-map',width:width,height:height,backgroundColor:'white'})


    let container = this.svg.append("g").attr('class','container');
    let rect = container.append("rect")
                           .attr({width:width,height:height})
                           .style({fill:"white","pointer-events":"all"})


    this.org = this.props.data;
    this.org.x = width / 2;
    this.org.y = height / 2;
    this.org.fixed = true;

    console.log('Initial Org:',this.org);

    this.d3render(this.org);

  }, //componentDidMount

  d3render(org) {

    let container = d3.selectAll('g.container')  
    let nodes = this.flatten(org);
    let links = d3.layout.tree().links(nodes);

    let force = this.forceLayout.on("tick", tick);

    force.nodes(nodes)       // Restart the force layout.
         .links(links)
         .start();

    debugger;

    // Links line that connects two org members together
    let link = container.selectAll(".link").data(links);
    link.exit().remove()
    link.enter().append("line")
                  .attr('class',"link")
                  .attr('id', (d)=> d.source.name + '-' +d.target.name)

    console.log('link:',link);

    //Relationship label for every link
    let linkLabel =  container.selectAll(".linklabelholder").data(links);
    linkLabel.exit().remove();
    linkLabel.enter()
             .append("g")
               .attr("class", "linklabelholder")
               .attr('id', (d) => `linklabel-${d.source.name}-${d.target.name}`)
            .append("text")
               .attr({dx:1, dy: ".35em", "text-anchor": "middle"})
               .text((d) => d.target.relation)
               .style("font-size",12);

     console.log('link Labels: ',linkLabel);

    // Update nodes. Each node represents one person
    let node = container.selectAll(".node").data(nodes);
    node.exit().remove();
    let nodeEnter = node.enter()
                        .append("g")
                          .attr("class", "node")
                          .attr('id', (d) => `node-${d.name}`)

      nodeEnter.append('circle')
                  .attr('r',25)
                  .attr('id',(d) => d.name)
                  .style('fill', 'steelblue')                       

      nodeEnter.append("text")
                  .attr("dy", ".35em")
                  .text((d) => d.name)
                  .attr('id', (d) => d.name)
                  .style("font-size",12);

        console.log('Nodes: ',node);

    function tick() {
      node.attr("cx", function(d) { return d.x = Math.max(25, Math.min(475, d.x)); })
           .attr("cy", function(d) { return d.y = Math.max(25, Math.min(275, d.y)); });

      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)

      linkLabel.attr("transform", (d) => `translate(${(d.source.x+d.target.x)/2},${(d.source.y+d.target.y)/2})`);
      node.attr("transform", (d) => `translate(${d.x},${d.y})`)

    } //tick

  }, //d3 render



  addNewPerson() {

    let newPerson = {_id: "5",name: 'Kelly' ,relation:'coworker'};
        let addTo =  {_id:"4", name: "Maria"};

    add(this.org);

    console.log('RE-RENDER AFTER ADDING NEW PERSON');
    console.log('Org after addding new person: ', this.org);

    this.d3render(this.org);

    function add(node) {
    if (node.children) node.children.forEach(add);
      if (node._id === addTo._id) {
        if (!node.children) node.children = [];
          node.children.push(newPerson);
       }
    }
  },

 flatten(org) {
  var nodes = [], i = 0;
    recurse(org);
    return nodes;

  function recurse(node) {
    if (node.children) node.children.forEach(recurse);
    if (!node.id) node.id = ++i;
    nodes.push(node);
  }

}, //flatten

render() {
    return (
         <div>
      <div  id="graph"></div>
            <button className='btnClass' onClick={this.addNewPerson}  type="submit">Add new person
   </button>

</div>
    );
  },


});

var user = {
              name: 'Joe',
              _id: "1",
              children:[
                {_id:"2", name: "John", relation:"boss"},
                { _id:"3", name: "Shelley", relation:"coworker"},
                {_id:"4", name: "Maria", relation:"subordinate"}
              ]
        }

ReactDOM.render(
  <ForceMap
    data={user}
    width={500}
    height={300}
    />,
  document.getElementById('container')
);
const ForceMap=React.createClass({
道具类型:{
数据:React.PropTypes.object,
宽度:React.PropTypes.number,
高度:React.PropTypes.number
},
componentDidMount(){
设{width,height}=this.props;
this.forceLayout=d3.layout.force()
.linkDistance(100)
。收费(-400)
重力(.02)
.尺寸([宽度、高度])
this.svg=d3.select(#图形)
.append(“svg”)
.attr({id:'#org map',width:width,height:height,backgroundColor:'white'})
让container=this.svg.append(“g”).attr('class','container');
让rect=container.append(“rect”)
.attr({width:width,height:height})
.style({fill:“white”,“指针事件”:“all”})
this.org=this.props.data;
this.org.x=宽度/2;
this.org.y=高度/2;
this.org.fixed=true;
log('初始组织:',this.Org);
this.d3render(this.org);
},//组件安装
d3render(组织){
让container=d3.selectAll('g.container'))
让节点=this.flatte(org);
让links=d3.layout.tree().links(节点);
设force=this.forceLayout.on(“勾选”,勾选);
force.nodes(nodes)//重新启动force布局。
.链接(links)
.start();
调试器;
//链接将两个组织成员连接在一起的行
让link=container.selectAll(“.link”)。数据(links);
link.exit().remove()
link.enter().append(“行”)
.attr(“类”,“链接”)
.attr('id',(d)=>d.source.name+'-'+d.target.name)
log('link:',link);
//每个链接的关系标签
让linkLabel=container.selectAll(“.linklabelholder”).data(links);
linkLabel.exit().remove();
linkLabel.enter()
.附加(“g”)
.attr(“类别”、“链接标签持有人”)
.attr('id',(d)=>`linklabel-${d.source.name}-${d.target.name}`)
.append(“文本”)
.attr({dx:1,dy:“.35em”,“文本锚定”:“中间”})
.text((d)=>d.target.relation)
.style(“字体大小”,12);
log('link Labels:',linkLabel);
//更新节点。每个节点代表一个人
让节点=容器。选择所有(“.node”)。数据(节点);
node.exit().remove();
让nodeEnter=node.enter()
.附加(“g”)
.attr(“类”、“节点”)
.attr('id',(d)=>`node-${d.name}`)
nodeEnter.append('circle'))
.attr('r',25)
.attr('id',(d)=>d.name)
.style('fill'、'steelblue')
nodeEnter.append(“文本”)
.attr(“dy”,“.35em”)
.text((d)=>d.name)
.attr('id',(d)=>d.name)
.style(“字体大小”,12);
log('节点:',节点);
函数tick(){
attr(“cx”,函数(d){返回d.x=Math.max(25,Math.min(475,d.x));})
.attr(“cy”,函数(d){返回d.y=Math.max(25,Math.min(275,d.y));};
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)
linkLabel.attr(“transform”,(d)=>`translate(${(d.source.x+d.target.x)/2},${(d.source.y+d.target.y)/2});
attr(“transform”,(d)=>`translate(${d.x},${d.y})`)
}//勾选
},//d3渲染
addNewPerson(){
让newPerson={u id:“5”,名字:'Kelly',关系:'coworker'};
让addTo={u id:“4”,name:“Maria”};
添加(this.org);
console.log(“添加新人员后重新呈现”);
log('添加新人员后的组织:',this.Org);
this.d3render(this.org);
功能添加(节点){
if(node.children)node.children.forEach(add);
if(node.\u id===addTo.\u id){
如果(!node.children)node.children=[];
node.children.push(newPerson);
}
}
},
扁平化(组织){
var节点=[],i=0;
递归(org);
返回节点;
函数递归(节点){
if(node.children)node.children.forEach(recurse);
如果(!node.id)node.id=++i;
nodes.push(节点);
}
},//展平
render(){
返回(
添加新人员
);
},
});
变量用户={
名字:'乔',
_id:“1”,
儿童:[
{u id:
let link = container.selectAll(".link").data(links, function(d) { return d.source_id+"-"+d.target._id+"-"+d.target.relation; });

...

let linkLabel =  container.selectAll(".linklabelholder").data(links, function(d) {
    return d.source._id+"-"+d.target._id+"-"+d.target.relation;
});

...

let node = container.selectAll(".node").data(nodes, function(d) { return d._id; });
<old links>
<old nodes>
<new links>
<new nodes>
<g>
<old links>
<new links>
</g>
<g>
<old nodes>
<new nodes>
</g>