以 React / JSX 方式构建力导向图时如何将链接添加到 D3 模拟中

问题描述 投票:0回答:2

我正在尝试使用 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()不是函数”

但无论我做什么,我似乎都无法让它发挥作用。

reactjs d3.js
2个回答
0
投票

我正在阅读同一篇文章,并且很困惑为什么它包含“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 挂钩跟踪的节点。


0
投票

据我了解

https://dev.to/gilfink/creating-a-force-graph-using-react-and-d3-76c

是一个更好的起点。链接开箱即用。

© www.soinside.com 2019 - 2024. All rights reserved.