Javascript 反应+;D3力布局:新链接的位置未定义
我相信我已经遵循了新React道具的一般更新模式。D3在收到新道具时进行数据计算和渲染,这样React就不必渲染每个刻度 D3在静态布局下运行良好。但当我在shouldComponentUpdate(nextProps)函数中收到新节点和链接时,这些节点缺少以下属性: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
- 索引-节点数组中节点的从零开始的索引李>
- 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;
}