React + D3 + Force-Directed Tree + Adjustable Link 强度

React + D3 + Force-Directed Tree + Adjustable Link Strength

我正在尝试使用 React 制作 Force-Directed Tree 并且它有效。但是如果我通过 props 将它传递到组件之外,我无法修改 "link strength"。

老实说,我可以更改 "strength",但我需要将 d3 svg 附加到我的 react ref div 之后才能看到更改。整个图将被重新绘制。

我找到了example by Mike Bostock。他建议通过使用 simulation.alpha 和 simulation.restart 重新加热模拟来修改力导向图的参数。但我不能让它与反应一起工作。没有任何反应。

这是我的代码:

export default function Hierarchy(props) {
  const {
    strength,
    lineColor,
    lineStroke,
    width,
    height,
    nodeSize,
    nodeColor,
  } = props;

  const root = d3.hierarchy(data);
  const links = root.links();
  const nodes = root.descendants();

  const svg = d3.create("svg");

  const link = svg
    .append("g")
    .selectAll("line")
    .data(links)
    .join("line");

  const node = svg
    .append("g")
    .selectAll("circle")
    .data(nodes)
    .join("circle");

  function applyStyle(selectionSVG) {
    selectionSVG
      .attr("width", width)
      .attr("height", height)
      .attr("viewBox", [-width / 2, -height / 2, width, height]);

    selectionSVG
      .selectAll("circle")
      .attr("r", nodeSize)
      .attr("fill", nodeColor)

    selectionSVG
      .selectAll("line")
      .attr("stroke", lineColor)
      .attr("stroke-width", lineStroke);
  }

  applyStyle(svg);

  const divRef = React.useRef(null);

  const linkForce = d3
    .forceLink(links)
    .id(d => d.id)
    .distance(0)
    .strength(strength);

  const simulation = d3
    .forceSimulation(nodes)
    .force("link", linkForce)
    .force("charge", d3.forceManyBody().strength(-500))
    .force("x", d3.forceX())
    .force("y", d3.forceY());

  simulation.on("tick", () => {
    link
      .attr("x1", d => d.source.x)
      .attr("y1", d => d.source.y)
      .attr("x2", d => d.target.x)
      .attr("y2", d => d.target.y);

    node.attr("cx", d => d.x).attr("cy", d => d.y);
  });

  //ComponentDidMount
  useEffect(() => {
    //Append d3 svg to ref div
    var div = d3.select(divRef.current);
    if (div.node().firstChild) {
      div.node().removeChild(div.node().firstChild);
    }
    div.node().appendChild(svg.node());
  }, []);

  //ComponentDidUpdate
  useEffect(() => {
    simulation.force("link").strength(strength);
    simulation.alpha(1).restart();
  }, [strength]);

  //ComponentDidUpdate
  useEffect(() => {
    var div = d3.select(divRef.current);
    applyStyle(div.select("svg"));
  });

  //Render
  return <div id="hierarchyTree" ref={divRef} />;
}

这里是Sandbox.

如果有人感兴趣,我会找到解决方案。

事实是更新组件时没有保存模拟。所以我为它创建了 ref。

const simulationRef = React.useRef(simulation)

并在useEffect部分替换它

//ComponentDidUpdate
useEffect(() => {
    simulationRef.current.force("link").strength(strength)
    simulationRef.current.alpha(1).restart()
    console.log(simulationRef.current)
}, [strength])

之后一切正常。