D3.js d3力贴图在重新渲染后不正确地渲染链接
我用react和Force布局制作了一个组织层次结构图。Org对象有一个用户,定义用户与工作中的其他人(如老板、同事、下属)的关系。可以在组织地图中动态添加一个新的人员,从而使用新信息重新呈现地图 但是,重新渲染后,贴图会错误地显示链接和关系文本。即使与节点关联的数据正确,节点上的名称也会被错误分配。通过调试,我发现链接、节点和链接标签对象都是正确的。但进出似乎有点古怪,可能是问题的根源 我有一个程序来模拟这个bug 最初渲染具有四个节点的组织映射。Joe是用户,他有一个老板John、同事Shelley和下属Maria 我创建了一个按钮来模拟动态添加一个新人。单击按钮将添加(数据为错误模拟硬编码)Kelly作为Maria的同事,并重新渲染地图。您会注意到渲染后,所有链接和标签都不正确。但是,当我在调试模式下查看与节点关联的数据时,它是正确的 我花了很多时间试图弄明白这一点,但似乎没有抓住错误 jsfiddle是用react编写的。如果您不熟悉react,请忽略react代码,只关注d3代码 JSFIDLE代码粘贴在此处: Javascript:D3.js d3力贴图在重新渲染后不正确地渲染链接,d3.js,D3.js,我用react和Force布局制作了一个组织层次结构图。Org对象有一个用户,定义用户与工作中的其他人(如老板、同事、下属)的关系。可以在组织地图中动态添加一个新的人员,从而使用新信息重新呈现地图 但是,重新渲染后,贴图会错误地显示链接和关系文本。即使与节点关联的数据正确,节点上的名称也会被错误分配。通过调试,我发现链接、节点和链接标签对象都是正确的。但进出似乎有点古怪,可能是问题的根源 我有一个程序来模拟这个bug 最初渲染具有四个节点的组织映射。Joe是用户,他有一个老板John、同事She
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>