我正在尝试构建一个图形,其中节点彼此之间插入。这是使用d3js的react组件。问题在于,在每个新插入的节点上,图似乎“碰撞”。
为了尽可能平滑插入,我将新节点插入其已连接的现有节点附近,其距离等于链接距离。我减少了许多身体力量。 我试图在插入新节点时添加过渡,但出现错误。 该过渡将新节点转换为
this.newnodes = this.nodes
.enter()
.append('g')
.attr("class", "nodesGroups")
.transition().delay(1000).duration(3500)
但是我得到一个错误:
Uncaught Error
at Transition._default [as merge] (merge.js:4)
at Graph.componentDidUpdate (graph.js:146)
at commitLifeCycles (react-dom.development.js:17349)
at commitAllLifeCycles (react-dom.development.js:18736)
at HTMLUnknownElement.callCallback (react-dom.development.js:149)
at Object.invokeGuardedCallbackDev (react-dom.development.js:199)
at invokeGuardedCallback (react-dom.development.js:256)
at commitRoot (react-dom.development.js:18948)
at react-dom.development.js:20418
at Object.unstable_runWithPriority (scheduler.development.js:255)
基本上,这就是我要达到的目标:节点的平滑插入。
我注意到删除forceCenter
时,效果几乎消失了,但是当我插入新节点时,链接的样式闪烁。
我想保留forceCenter
并避免眨眼。
我写了一个例子,它显然很容易碰到
:import React, { Component } from 'react';
import * as d3 from "d3";
const styles = {
svg:{
width: "100%",
height: "100%",
}
}
class Graph extends Component {
state = {
"simulationTime": 10000,
"insertionDelay": 1000,
"target": 0,
"linkDistance": 60,
"id": 0,
"nodes": [{"id": 0, "weight": 0}],
"nodesWeighted": [0],
"links": [],
"position":{
"x": 0,
"y": 0
}
}
updateGraph = () => {
var id = this.state.id+1
var target = this.chooseTarget()
var nodes = [...this.state.nodes]
var links = [...this.state.links]
var nodesWeighted = [...this.state.nodesWeighted]
const {linkDistance} = this.state
nodes[target].weight += 1
var newNode = {"id": id, "weight": 1,
"x":nodes[target].x+linkDistance*Math.cos(2*3.1415*Math.random()),
"y":nodes[target].y+linkDistance*Math.sin(2*3.1415*Math.random())
}
var newLink = {"source": id, "target": target}
nodes.push(newNode)
links.push(newLink)
nodesWeighted.push(id, target)
this.setState({
"target": target,
"id": id,
"nodes": nodes,
"nodesWeighted": nodesWeighted,
"links": links,
"position": nodes[target]
})
}
chooseTarget = () => {
return this.state.nodesWeighted[Math.floor((Math.random() * this.state.nodesWeighted.length))];
}
componentDidMount() {
const height = document.getElementById('landing-page-graph-id').clientHeight
const width = document.getElementById('landing-page-graph-id').clientWidth
const radius = 20
const ticked = () => {
this.links
.attr('x1', link => link.source.x)
.attr('y1', link => link.source.y)
.attr('x2', link => link.target.x)
.attr('y2', link => link.target.y)
this.nodes
.attr('transform', d => {
d.x = Math.max(radius, Math.min(width - radius, d.x))
d.y = Math.max(radius, Math.min(height - radius, d.y))
// console.log("data : ", d)
return `translate(${d.x}, ${d.y})`
})
}
this.graph = d3.select(this.refs["landing-page-svg-ref"])
this.simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(d => d.id).distance(d => {return this.state.linkDistance}))
.force('charge', d3.forceManyBody().strength(-10))
.force('center', d3.forceCenter(width / 2, height / 2))
.on("tick", ticked);
this.links = this.graph
.append('g')
.attr("class", "links")
.selectAll('line')
this.nodes = this.graph
.append('g')
.attr("class", "nodes")
.selectAll("g")
var interval = d3.interval( elapsed => {
if(elapsed > this.state.simulationTime){
interval.stop()
return
}
this.updateGraph()
}, this.state.insertionDelay)
}
componentDidUpdate(){
const {nodes, links} = this.state
this.links = this.links
.data(links, d => d.source + d.target)
this.links.exit()
.remove()
this.newlinks = this.links
.enter()
.append('line')
this.links = this.newlinks
.merge(this.links)
this.newlinks
.attr('stroke-width', 1.0)
.attr('stroke', '#000000')
this.nodes = this.nodes
.data(nodes, d => d.id)
this.nodes.exit()
.remove()
this.newnodes = this.nodes
.enter()
.append('g')
.attr("class", "nodesGroups")
this.nodes = this.newnodes
.merge(this.nodes)
this.newnodes
.append("circle")
.transition().delay(1000).duration(3000)
.attr("r", d => {return 5})
.attr("id", d => "node-circle-by-id-"+d.id)
this.newnodes
.call(d3.drag()
.on("start", this.dragstarted)
.on("drag", this.dragged)
.on("end", this.dragended)
)
this.targetCircle = d3.select("#node-circle-by-id-"+this.state.target)
this.targetCircle
.transition().duration(3000)
.attr("r", d => d3.scalePow().exponent(0.5).domain([0,10])
.range([1,15])(d.weight))
this.simulation.nodes(nodes)
this.simulation.force("link").links(links);
this.simulation.alpha(0.8).restart()
}
dragstarted = d => {
if (!d3.event.active) this.simulation.alphaTarget(0.3).restart();
d.fx = d3.event.x;
d.fy = d3.event.y;
}
dragged = d => {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
dragended = d => {
if (!d3.event.active) this.simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
render(){
return (
<div id="landing-page-graph-id" ref="landing-page-graph-ref" style={styles.svg}>
<svg id="landing-page-svg-id" ref="landing-page-svg-ref" style={styles.svg}></svg>
</div>
)
}
}
export default Graph;
您可以像这样实例化:
<Graph/>
编辑 我解决了链接闪烁的问题,这是链接插入的id方法中的一个问题,好的方法是:
this.links = this.links
.data(links, d => d.source.id + d.target.id)
仍在寻找forceCenter
的平滑插入