一段时间以来,我一直在尝试在 React 中制作可拖动的 d3 力布局。React 必须能够与图中的节点进行交互。例如,当你点击一个节点时,React 应该能够返回节点的 id onClick。
我根据 Shirley Wu 的示例之一制作了 4 个组件。一个 App 组件,它保存处于其状态的图形数据并呈现 Graph 组件。图组件呈现一个节点和一个链接组件。这样,可点击的节点部分就解决了。
当页面呈现时,节点将只能拖动几秒钟。渲染页面后,您可以立即拖动节点,然后突然,被拖动的节点完全停在一个位置。此时也不能再拖动其他节点。我希望能够始终拖动节点。
我可以在网上找到一些关于在图形后面创建画布、设置填充和指针事件的提示。也有很多关于让或 d3 或 React 进行渲染和计算的讨论。我尝试使用 React 的所有生命周期方法,但我无法让它工作。
您可以在此处找到实时示例:https : //codepen.io/vialito/pen/WMKwEr
请记住,圆圈只能点击几秒钟。然后他们会留在同一个地方。所有浏览器和每次页面刷新后的行为都是相同的。当您记录拖动功能时,您会看到它在拖动时确实分配了新坐标,但是圆圈不会显示在它的新位置。
我非常渴望了解此问题的原因,如果您甚至可以提出解决方案,那将是非常酷的。
应用程序.js
class App extends React.Component {
constructor(props){
super(props)
this.state = {
data : {"nodes":
[
{"name": "fruit", "id": 1},
{"name": "apple", "id": 2},
{"name": "orange", "id": 3},
{"name": "banana", "id": 4}
],
"links":
[
{"source": 1, "target": 2},
{"source": 1, "target": 3}
]
}
}
}
render() {
return (
<div className="graphContainer">
<Graph data={this.state.data} />
</div>
)
}
}
class Graph extends React.Component {
componentDidMount() {
this.d3Graph = d3.select(ReactDOM.findDOMNode(this));
var force = d3.forceSimulation(this.props.data.nodes);
force.on('tick', () => {
force
.force("charge", d3.forceManyBody().strength(-50))
.force("link", d3.forceLink(this.props.data.links).distance(90))
.force("center", d3.forceCenter().x(width / 2).y(height / 2))
.force("collide", d3.forceCollide([5]).iterations([5]))
const node = d3.selectAll('g')
.call(drag)
this.d3Graph.call(updateGraph)
});
}
render() {
var nodes = this.props.data.nodes.map( (node) => {
return (
<Node
data={node}
name={node.name}
key={node.id}
/>);
});
var links = this.props.data.links.map( (link,i) => {
return (
<Link
key={link.target+i}
data={link}
/>);
});
return (
<svg className="graph" width={width} height={height}>
<g>
{nodes}
</g>
<g>
{links}
</g>
</svg>
);
}
}
节点.js
class Node extends React.Component {
componentDidMount() {
this.d3Node = d3.select(ReactDOM.findDOMNode(this))
.datum(this.props.data)
.call(enterNode)
}
componentDidUpdate() {
this.d3Node.datum(this.props.data)
.call(updateNode)
}
handle(e){
console.log(this.props.data.id + ' been clicked')
}
render() {
return (
<g className='node'>
<circle ref="dragMe" onClick={this.handle.bind(this)}/>
<text>{this.props.data.name}</text>
</g>
);
}
}
链接.js
class Link extends React.Component {
componentDidMount() {
this.d3Link = d3.select(ReactDOM.findDOMNode(this))
.datum(this.props.data)
.call(enterLink);
}
componentDidUpdate() {
this.d3Link.datum(this.props.data)
.call(updateLink);
}
render() {
return (
<line className='link' />
);
}
}
D3Functions.js
const width = 1080;
const height = 250;
const color = d3.scaleOrdinal(d3.schemeCategory10);
const force = d3.forceSimulation();
const drag = () => {
d3.selectAll('g')
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragging)
.on("end", dragEnded));
};
function dragStarted(d) {
if (!d3.event.active) force.alphaTarget(0.3).restart()
d.fx = d.x
d.fy = d.y
}
function dragging(d) {
d.fx = d3.event.x
d.fy = d3.event.y
}
function dragEnded(d) {
if (!d3.event.active) force.alphaTarget(0)
d.fx = null
d.fy = null
}
const enterNode = (selection) => {
selection.select('circle')
.attr("r", 30)
.style("fill", function(d) { return color(d.name) })
selection.select('text')
.attr("dy", ".35em")
.style("transform", "translateX(-50%,-50%")
};
const updateNode = (selection) => {
selection.attr("transform", (d) => "translate(" + d.x + "," + d.y + ")")
};
const enterLink = (selection) => {
selection.attr("stroke-width", 2)
.style("stroke","yellow")
.style("opacity",".2")
};
const 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);
};
const updateGraph = (selection) => {
selection.selectAll('.node')
.call(updateNode)
.call(drag);
selection.selectAll('.link')
.call(updateLink);
};