如何只向 d3 力定向图添加新节点?
How to add only new nodes to d3 force directed graph?
我使用 d3
创建了一个力导向图,它使用 useEffect
挂钩在 react
组件中呈现。该图最初呈现正确,但如果它是 re-rendered/updated 通过输入表单提交的新节点,则现有节点将被复制。
我原以为 .enter()
之后会保留现有节点,只会创建新节点,这显然不会发生。对我出错的地方有帮助吗?
编辑 1:这是输入的数据示例
var nodesData = [
{"id": "Do Something", "type": "activity"},
{"id": "My Document", "type": "object"}
]
这是图表的代码:
import React, { useRef, useEffect } from 'react';
import * as d3 from 'd3';
import '../../custom_styles/bpForceDirected.css';
interface IProps {
data?: string;
linkData?: string;
}
/* Component */
export const BpForceDirectedGraph = (props: IProps) => {
const d3Container = useRef(null);
/* The useEffect Hook is for running side effects outside of React,
for instance inserting elements into the DOM using D3 */
useEffect(
() => {
if (props.data && d3Container.current) {
var w=500;
var h=500;
const svg = d3.select(d3Container.current)
.attr("viewBox", "0 0 " + w + " " + h )
.attr("preserveAspectRatio", "xMidYMid meet");
var simulation = d3.forceSimulation()
.nodes(props.data);
simulation
.force("charge_force", d3.forceManyBody())
.force("center_force", d3.forceCenter(w / 2, h / 2));
function circleColor(d){
if(d.type ==="activity"){
return "blue";
} else {
return "pink";
}
}
function linkColor(d){
console.log(d);
if(d.type === "Activity Output"){
return "green";
} else {
return "red";
}
}
//Create a node that will contain an object and text label
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("g")
.data(props.data)
.enter()
.append("g");
node.append("circle")
.attr("r", 10)
.attr("fill", circleColor);
node.append("text")
.attr("class", "nodelabel")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) { return d.activityname });
// The complete tickActions() function
function tickActions() {
//update circle positions each tick of the simulation
node.attr('transform', d => `translate(${d.x},${d.y})`);
//update link positions
//simply tells one end of the line to follow one node around
//and the other end of the line to follow the other node around
link
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
}
simulation.on("tick", tickActions );
//Create the link force
//We need the id accessor to use named sources and targets
var link_force = d3.forceLink(props.linkData)
.id(function(d) { return d.id; })
simulation.force("links",link_force)
//draw lines for the links
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(props.linkData)
.enter().append("line")
.attr("stroke-width", 2)
.style("stroke", linkColor);
// Remove old D3 elements
node.exit()
.remove();
}
},
[props.data, props.linkData, /*d3Container.current*/])
return (
<svg
className="d3-component"
ref={d3Container}
/>
);
}
export default BpForceDirectedGraph;
出现这个问题是因为生成图表的函数在每次更新时被调用,并且没有考虑现有的内容。
这是解决问题的一种方法:
当(重新)定义变量 svg
时,在每次执行 useEffect
开始时清空 SVG。如下图,.html('')
清空已有的SVG节点。
const svg = d3
.select(d3Container.current)
.html("")
.attr("viewBox", "0 0 " + w + " " + h)
.attr("preserveAspectRatio", "xMidYMid meet");
一种更优雅的方法是更新代码,以便一个函数初始化图表,第二个函数(重新)生成图表,我对 React 的理解是,这是使用 componentDidMount
和componentDidUpdate
.
我使用 d3
创建了一个力导向图,它使用 useEffect
挂钩在 react
组件中呈现。该图最初呈现正确,但如果它是 re-rendered/updated 通过输入表单提交的新节点,则现有节点将被复制。
我原以为 .enter()
之后会保留现有节点,只会创建新节点,这显然不会发生。对我出错的地方有帮助吗?
编辑 1:这是输入的数据示例
var nodesData = [
{"id": "Do Something", "type": "activity"},
{"id": "My Document", "type": "object"}
]
这是图表的代码:
import React, { useRef, useEffect } from 'react';
import * as d3 from 'd3';
import '../../custom_styles/bpForceDirected.css';
interface IProps {
data?: string;
linkData?: string;
}
/* Component */
export const BpForceDirectedGraph = (props: IProps) => {
const d3Container = useRef(null);
/* The useEffect Hook is for running side effects outside of React,
for instance inserting elements into the DOM using D3 */
useEffect(
() => {
if (props.data && d3Container.current) {
var w=500;
var h=500;
const svg = d3.select(d3Container.current)
.attr("viewBox", "0 0 " + w + " " + h )
.attr("preserveAspectRatio", "xMidYMid meet");
var simulation = d3.forceSimulation()
.nodes(props.data);
simulation
.force("charge_force", d3.forceManyBody())
.force("center_force", d3.forceCenter(w / 2, h / 2));
function circleColor(d){
if(d.type ==="activity"){
return "blue";
} else {
return "pink";
}
}
function linkColor(d){
console.log(d);
if(d.type === "Activity Output"){
return "green";
} else {
return "red";
}
}
//Create a node that will contain an object and text label
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("g")
.data(props.data)
.enter()
.append("g");
node.append("circle")
.attr("r", 10)
.attr("fill", circleColor);
node.append("text")
.attr("class", "nodelabel")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) { return d.activityname });
// The complete tickActions() function
function tickActions() {
//update circle positions each tick of the simulation
node.attr('transform', d => `translate(${d.x},${d.y})`);
//update link positions
//simply tells one end of the line to follow one node around
//and the other end of the line to follow the other node around
link
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
}
simulation.on("tick", tickActions );
//Create the link force
//We need the id accessor to use named sources and targets
var link_force = d3.forceLink(props.linkData)
.id(function(d) { return d.id; })
simulation.force("links",link_force)
//draw lines for the links
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(props.linkData)
.enter().append("line")
.attr("stroke-width", 2)
.style("stroke", linkColor);
// Remove old D3 elements
node.exit()
.remove();
}
},
[props.data, props.linkData, /*d3Container.current*/])
return (
<svg
className="d3-component"
ref={d3Container}
/>
);
}
export default BpForceDirectedGraph;
出现这个问题是因为生成图表的函数在每次更新时被调用,并且没有考虑现有的内容。
这是解决问题的一种方法:
当(重新)定义变量 svg
时,在每次执行 useEffect
开始时清空 SVG。如下图,.html('')
清空已有的SVG节点。
const svg = d3
.select(d3Container.current)
.html("")
.attr("viewBox", "0 0 " + w + " " + h)
.attr("preserveAspectRatio", "xMidYMid meet");
一种更优雅的方法是更新代码,以便一个函数初始化图表,第二个函数(重新)生成图表,我对 React 的理解是,这是使用 componentDidMount
和componentDidUpdate
.