Javascript 如何在d3中正确实现force node链接图的更新模式?
我在React应用程序中实现节点链接图的强制布局的更新模式时遇到问题。根据我的理解,模式是加入、退出、更新、输入,但显然我没有正确地实现它。下面是一个指向沙箱的链接,其中有一个复制问题的最小示例:一些链接消失,但没有新节点进入,也没有节点退出 基本上,每隔三秒钟,我就要交换数据源,并用适当的节点链接更新svg 下面是一个图表包装器,它允许我在React应用程序中编写纯d3:Javascript 如何在d3中正确实现force node链接图的更新模式?,javascript,reactjs,d3.js,force-layout,Javascript,Reactjs,D3.js,Force Layout,我在React应用程序中实现节点链接图的强制布局的更新模式时遇到问题。根据我的理解,模式是加入、退出、更新、输入,但显然我没有正确地实现它。下面是一个指向沙箱的链接,其中有一个复制问题的最小示例:一些链接消失,但没有新节点进入,也没有节点退出 基本上,每隔三秒钟,我就要交换数据源,并用适当的节点链接更新svg 下面是一个图表包装器,它允许我在React应用程序中编写纯d3: import React, { useEffect, useRef, useState } from 'react'; i
import React, { useEffect, useRef, useState } from 'react';
import data from "./products";
import data2 from "./products2";
import Chart from './Chart';
import * as d3 from 'd3';
const ChartWrapper = () => {
const chartArea = useRef(null);
const [chart, setChart] = useState(null);
const [nodes, setNodes] = useState(data);
let flag = true;
useEffect(() => {
if (!chart)
setChart(new Chart(chartArea.current, nodes))
else {
chart.update(nodes);
}
}, [nodes]);
d3.interval(() => {
let newNodes = flag ? data : data2
setNodes(newNodes);
flag = !flag
}, 3000)
return (<div className="chart-area" ref={chartArea}></div>)
}
export default ChartWrapper;
import * as d3 from "d3";
import "./Chart.css";
const MARGIN = { TOP: 20, RIGHT: 20, BOTTOM: 20, LEFT: 20 };
const WIDTH = 400 - MARGIN.LEFT - MARGIN.RIGHT;
const HEIGHT = 400 - MARGIN.TOP - MARGIN.BOTTOM;
const AA_RED = "#cb3327";
const AA_BLUE = "#184485";
export default class Chart {
constructor(chartarea, data) {
const vis = this;
vis.data = data;
vis.svg = d3
.select(chartarea)
.append("svg")
.attr("width", WIDTH + MARGIN.LEFT + MARGIN.RIGHT)
.attr("height", HEIGHT + MARGIN.BOTTOM + MARGIN.RIGHT)
.append("g")
.attr("transfrom", `translate(${MARGIN.LEFT}, ${MARGIN.RIGHT})`);
vis.link = vis.svg
.selectAll(".link")
.data(vis.data.links)
.enter()
.append("line")
.attr("class", "link");
vis.force = d3
.forceSimulation(vis.data.nodes)
.force(
"link",
d3
.forceLink()
.id(function (d) {
return d.name;
})
.strength(0.4)
.links(vis.data.links)
)
.force("charge", d3.forceManyBody().strength(-500))
.force("x", d3.forceX())
.force("y", d3.forceY())
.force("center", d3.forceCenter(WIDTH / 2, HEIGHT / 2))
.force(
"collision",
d3
.forceCollide()
.radius((node) => {
return 50;
})
.iterations(3)
)
.on("tick", function () {
vis.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;
});
vis.node.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
});
vis.node = vis.svg
.selectAll(".node")
.data(vis.data.nodes)
.enter()
.append("g")
.attr("class", "node")
.call(
d3
.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended)
);
vis.node
.append("circle")
.attr("r", (d) => {
return d.type === "application" ? 30 : 20;
})
.attr("fill", (d) => {
return d.type === "application" ? AA_RED : AA_BLUE;
});
vis.node
.append("text")
.attr("dx", (d) => {
return d.type === "application" ? -8 : 24;
})
.attr("dy", ".35em")
.text(function (d) {
return d.name;
});
function dragstarted(event, d) {
if (!event.active) vis.force.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragended(event, d) {
if (!event.active) vis.force.alphaTarget(0);
d.fx = null;
d.fy = null;
}
}
update(data) {
const vis = this;
vis.data = data;
function dragstarted(event, d) {
if (!event.active) vis.force.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragended(event, d) {
if (!event.active) vis.force.alphaTarget(0);
d.fx = null;
d.fy = null;
}
//join
vis.node = vis.svg.selectAll(".node").data(vis.data.nodes);
vis.link = vis.svg.selectAll(".link").data(vis.data.links);
//exit
vis.node.exit().remove();
vis.link.exit().remove();
//update
vis.node
.append("circle")
.attr("r", (d) => {
return d.type === "application" ? 30 : 20;
})
.attr("fill", (d) => {
return d.type === "application" ? AA_RED : AA_BLUE;
});
vis.link.append("line").attr("class", "link");
//enter
vis.node = vis.node
.enter()
.append("g")
.attr("class", "node")
.call(
d3
.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended)
);
vis.link = vis.link.enter().append("line").attr("class", "link");
//restart simulation
vis.force
.nodes(vis.data.nodes)
.force(
"link",
d3
.forceLink()
.id(function (d) {
return d.name;
})
.strength(0.4)
.links(vis.data.links)
)
.force("charge", d3.forceManyBody().strength(-500))
.force("x", d3.forceX())
.force("y", d3.forceY())
.force("center", d3.forceCenter(WIDTH / 2, HEIGHT / 2))
.force(
"collision",
d3
.forceCollide()
.radius((node) => {
return 50;
})
.iterations(3)
)
.restart();
}
}