我正在尝试使用 D3 构建力导向图,但我使用 React/JSX 进行渲染,仅使用 d3 进行数学计算。
我在这里找到了一篇文章,解释了如何将节点添加到模拟中并进行绘制。 https://reactfordataviz.com/articles/force-directed-graphs-with-react-and-d3v7/
但是,我似乎无法按照文章末尾建议的那样对链接执行相同的操作。
我尝试fork文章中的示例,并注释掉代码中的“链接”部分,以避免代码沙箱崩溃:
这是代码(运行沙箱在这里:https://codesandbox.io/s/collision-force-forked-p1o9y9)
import "./styles.css";
import * as d3 from "d3";
import { useEffect, useMemo, useState } from "react";
function ForceGraph({ nodes, charge }) {
const [animatedNodes, setAnimatedNodes] = useState([]);
// const [animatedLinks, setAnimatedLinks] = useState([]);
// re-create animation every time nodes change
useEffect(() => {
const simulation = d3
.forceSimulation()
.force("x", d3.forceX(400))
.force("y", d3.forceY(300))
.force("charge", d3.forceManyBody().strength(charge))
.force("collision", d3.forceCollide(5));
// .force('links', d3.forceLink());
// alternatively: .force('link', d3.forceLink(links));
// update state on every frame
simulation.on("tick", () => {
setAnimatedNodes([...simulation.nodes()]);
// setAnimatedLinks([...simulation.links()]);
});
// copy nodes into simulation
simulation.nodes([...nodes]);
// simulation.links([...links]);
// slow down with a small alpha
simulation.alpha(0.1).restart();
// stop simulation on unmount
return () => simulation.stop();
}, [nodes, charge]);
return (
<g>
{animatedNodes.map((node) => (
<circle
cx={node.x}
cy={node.y}
r={node.r}
key={node.id}
stroke="black"
fill="transparent"
/>
))}
</g>
);
}
export default function App() {
const [charge, setCharge] = useState(-3);
// create nodes with unique ids
// radius: 5px
const nodes = useMemo(
() =>
d3.range(50).map((n) => {
return { id: n, r: 5 };
}),
[]
);
return (
<div className="App">
<h1>React & D3 force graph</h1>
<p>Current charge: {charge}</p>
<input
type="range"
min="-30"
max="30"
step="1"
value={charge}
onChange={(e) => setCharge(e.target.value)}
/>
<svg width="800" height="600">
<ForceGraph nodes={nodes} charge={charge} />
</svg>
</div>
);
}
我也尝试过像这样直接传递链接
.force('link', d3.forceLink(links));
如果我对链接进行一些转换,如下所示:
const obj: any = {};
nodes.forEach((d, i) => {
obj[d.id] = i; // create an object to look up a node's index by id
});
links?.forEach((d) => {
d.source = obj[d.source]; // look up the index of source
d.target = obj[d.target]; // look up the index of target
});
我收到另一条错误消息
当我编写simulation.links()尝试将它们取回并将它们设置为React状态时,我只是收到错误“simulation.links()不是函数”
但无论我做什么,我似乎都无法让它发挥作用。
我正在阅读同一篇文章,并且很困惑为什么它包含“simulation.links”,尽管我在 Observable 甚至 d3-force api 中的其他任何地方都找不到它。但我找到了解决方案!
既然我们能够获得“simulation.nodes”,我们就可以通过创建引用节点的 id 来为链接创建一个新的记忆函数。
const lineLinks = useMemo(() => {
const nodeMap = keyBy(beeData, 'id')
return beeLinks.map((link) => {
return {
...link,
source:
nodeMap[typeof link.source === 'object' ? link.source.id : ''],
target:
nodeMap[typeof link.target === 'object' ? link.target.id : ''],
}
})
}, [beeData, beeLinks])
我遵循了同一篇文章,所以你因为我们已经有了
setBeeData([...(simulation.nodes() as DirectionNode[])])
我们可以观看
beeData
,因为模拟至少为我们提供了本文也通过 useState 挂钩跟踪的节点。