Я пытаюсь построить график, в котором узлы вставляются один за другим.Это реактивный компонент, использующий d3js.Проблема в том, что на каждом новом вставленном узле график кажется «выпуклым».
Чтобы максимально сгладить вставку, я вставляю новый узел рядом с его уже существующим узлом, на расстоянии, равномна расстояние ссылки.Я уменьшил многие силы тела.Я попытался добавить переход при вставке новых узлов, но получаю ошибку.Этот переход преобразует новые узлы следующим образом:
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