首页 > 解决方案 > React + d3 力图不更新最后添加节点的位置

问题描述

我有一个 React 组件,可以让我将节点添加到 d3 力图。每当我添加一个节点时,它在基础数据中的位置都会被模拟更新,但它的位置不会在视觉上更新,直到添加下一个节点。有人能够发现为什么吗?

我制作了一个简化的示例来演示该问题,也可以在此代码框中看到:

import React from "react";
import shortid from "shortid";
import * as d3 from "d3";

const WIDTH = 500;
const HEIGHT = 500;

class Graph extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      nodes: [],
      value: shortid(),
    };
  }

  handleChange = event => {
    this.setState({ value: event.target.value });
  };

  handleAddNode = event => {
    event.preventDefault();

    const newNodes = [...this.state.nodes, { id: this.state.value }];

    this.setState({ nodes: newNodes, value: shortid() });
  };

  componentDidMount() {
    this.initialise(this.state.nodes);
    this.draw(this.state.nodes);
  }

  componentDidUpdate() {
    this.draw(this.state.nodes);
  }

  initialise = nodes => {
    const container = d3
      .select(this.svg)
      .attr("width", WIDTH)
      .attr("height", HEIGHT)
      .append("g")
      .attr("class", "container")
      .attr("transform", "translate(" + WIDTH / 2 + "," + HEIGHT / 2 + ")");

    container.append("g").attr("class", "nodes");

    this.simulation = d3
      .forceSimulation(nodes)
      .alphaDecay(0.2)
      .force("charge", d3.forceManyBody())
      .force("collide", d3.forceCollide().strength(1))
      .on("tick", this.ticked);
  };

  draw = nodes => {
    this.nodesSelection = d3
      .select(".nodes")
      .selectAll(".node")
      .data(nodes);

    this.nodesSelection
      .enter()
      .append("circle")
      .attr("class", "node")
      .attr("data-id", d => d.id)
      .style("fill", "red")
      .attr("r", 5);

    this.simulation.nodes(nodes);
    this.simulation.alpha(1).restart();

    console.log(nodes);
  };

  ticked = () => {
    this.nodesSelection.attr("cx", d => d.x).attr("cy", d => d.y);
  };

  render() {
    return (
      <div className="c-graph-container">
        <form className="c-node-creator">
          <div>
            <input
              type="text"
              value={this.state.value}
              onChange={this.handleChange}
            />
          </div>
          <button onClick={this.handleAddNode}>Add</button>
        </form>
        <svg ref={svg => (this.svg = svg)} className=".c-graph" />
      </div>
    );
  }
}

export default Graph;

标签: javascriptreactjsd3.js

解决方案


正如 Andrew 在评论中正确指出的那样,更新选择 (this.nodesSelection) 不包括输入选择 (this.nodesSelection.enter()) 中的节点。由于选择是不可变的,因此更新选择在输入后不包含输入的节点,除非您重新选择或使用合并。

因此,在我的情况下,解决方法是将选择行更改为:

this.nodesSelection = this.nodesSelection
  .enter()
  .append("circle")
  .attr("class", "node")
  .attr("data-id", d => d.id)
  .style("fill", "red")
  .attr("r", 5)
  .merge(this.nodesSelection);

推荐阅读