React + D3力布局 - 渲染后几秒钟内圆圈不再可拖动

Vin*_*ent 4 javascript d3.js reactjs

我一直试图在React中制作一个可拖动的d3力布局一段时间了.React必须能够与图中的节点进行交互.例如,当您单击节点时,React应该能够返回节点的id onClick.

我根据Shirley Wu的一个例子制作了4个组件.一个App组件,用于保存图形数据的状态并呈现Graph组件.图形组件呈现节点和链接组件.这样,可点击节点部分就可以解决了.

当页面呈现时,节点将只能拖动几秒钟.渲染页面后,您可以立即拖动节点,然后突然,被拖动的节点完全停在一个位置.此时,其他节点也不能再被拖动.我希望能够随时拖动节点.

我可以在网上找到一些关于在图形后面创建画布,设置填充和指针事件的提示.关于letting或d3或React进行渲染和计算的讨论也很多.我尝试使用React的所有生命周期方法,但我无法使用它.

你可以在这里找到一个实时样本:https://codepen.io/vialito/pen/WMKwEr

请记住,圈子只能点击几秒钟.然后他们会留在同一个地方.所有浏览器和每次刷新后的行为都是相同的.当您记录拖动功能时,您会看到它在拖动时会分配新的坐标,但圆圈不会显示在它的新位置.

我非常渴望了解这个问题的原因,如果你甚至可以提出解决方案,那将是非常酷的.

App.js

class App extends React.Component {
  constructor(props){
    super(props)
    this.state = {
      data : {"nodes":
        [
          {"name": "fruit", "id": 1},
          {"name": "apple", "id": 2},
          {"name": "orange", "id": 3},
          {"name": "banana", "id": 4}
        ],
      "links": 
        [
          {"source": 1, "target": 2},
          {"source": 1, "target": 3}
        ]
      }
    }
  }

  render() {
    return (
            <div className="graphContainer">
                <Graph data={this.state.data} />
            </div>
        )
    }
}

class Graph extends React.Component {

    componentDidMount() {
        this.d3Graph = d3.select(ReactDOM.findDOMNode(this));
        var force = d3.forceSimulation(this.props.data.nodes);
        force.on('tick', () => {
            force
            .force("charge", d3.forceManyBody().strength(-50))
            .force("link", d3.forceLink(this.props.data.links).distance(90))
            .force("center", d3.forceCenter().x(width / 2).y(height / 2))
            .force("collide", d3.forceCollide([5]).iterations([5]))

            const node = d3.selectAll('g')
                .call(drag)

            this.d3Graph.call(updateGraph)
        });
    }

    render() {
        var nodes = this.props.data.nodes.map( (node) => {
            return (
            <Node
                data={node}
                name={node.name}
                key={node.id}
            />);
        });
        var links = this.props.data.links.map( (link,i) => {
            return (
                <Link
                    key={link.target+i}
                    data={link}
                />);
        });
        return (
            <svg className="graph" width={width} height={height}>
                <g>
                    {nodes}
                </g>
                <g>
                    {links}
                </g>
            </svg>
        );
    }
}
Run Code Online (Sandbox Code Playgroud)

Node.js的

    class Node extends React.Component {

    componentDidMount() {
        this.d3Node = d3.select(ReactDOM.findDOMNode(this))
            .datum(this.props.data)
            .call(enterNode)
    }

    componentDidUpdate() {
        this.d3Node.datum(this.props.data)
            .call(updateNode)
    }

    handle(e){
        console.log(this.props.data.id + ' been clicked')
    }

    render() {
        return (
            <g className='node'>
                <circle ref="dragMe" onClick={this.handle.bind(this)}/>
                <text>{this.props.data.name}</text>
            </g>
        );
    }
}
Run Code Online (Sandbox Code Playgroud)

Link.js

    class Link extends React.Component {

    componentDidMount() {
        this.d3Link = d3.select(ReactDOM.findDOMNode(this))
            .datum(this.props.data)
            .call(enterLink);
    }

    componentDidUpdate() {
        this.d3Link.datum(this.props.data)
            .call(updateLink);
    }

    render() {
        return (
                <line className='link' />
        );
    }
}
Run Code Online (Sandbox Code Playgroud)

D3Functions.js

const width = 1080;
const height = 250;
const color = d3.scaleOrdinal(d3.schemeCategory10);
const force = d3.forceSimulation();

const drag = () => {
    d3.selectAll('g')
        .call(d3.drag()
            .on("start", dragStarted)
            .on("drag", dragging)
            .on("end", dragEnded));
};

function dragStarted(d) {
    if (!d3.event.active) force.alphaTarget(0.3).restart()
    d.fx = d.x
    d.fy = d.y

}

function dragging(d) {
    d.fx = d3.event.x
    d.fy = d3.event.y
}

function dragEnded(d) {
    if (!d3.event.active) force.alphaTarget(0)
    d.fx = null
    d.fy = null
}

const enterNode = (selection) => {
    selection.select('circle')
        .attr("r", 30)
        .style("fill", function(d) { return color(d.name) })


    selection.select('text')
        .attr("dy", ".35em")
        .style("transform", "translateX(-50%,-50%")
};

const updateNode = (selection) => {
    selection.attr("transform", (d) => "translate(" + d.x + "," + d.y + ")")

};

const enterLink = (selection) => {
    selection.attr("stroke-width", 2)
    .style("stroke","yellow")
        .style("opacity",".2")
};

const updateLink = (selection) => {
    selection
        .attr("x1", (d) => d.source.x)
        .attr("y1", (d) => d.source.y)
        .attr("x2", (d) => d.target.x)
        .attr("y2", (d) => d.target.y);
};

const updateGraph = (selection) => {
    selection.selectAll('.node')
        .call(updateNode)
        .call(drag);
    selection.selectAll('.link')
        .call(updateLink);
};
Run Code Online (Sandbox Code Playgroud)

Mik*_*kov 6

您可以在代码中定义两次力模拟.第一次 - 你的codepen中的字符串7和第二次 - 字符串113.你的dragStarteddragEnded函数(全局定义)使用来自字符串7的强制模拟,但没有指定(你没有将节点,链接和其他参数传递给它).

在此输入图像描述

您应该在定义和指定力模拟时将这些函数移动到方法中,因此组件的componentDidMount方法Graph应该如下所示(您还应该重写您的tick处理函数,并且只设置一次force params(现在您在每个tick上执行),检查我的笔叉):

componentDidMount() {
  this.d3Graph = d3.select(ReactDOM.findDOMNode(this));

  var force = d3.forceSimulation(this.props.data.nodes)
    .force("charge", d3.forceManyBody().strength(-50))
    .force("link", d3.forceLink(this.props.data.links).distance(90))
    .force("center", d3.forceCenter().x(width / 2).y(height / 2))
    .force("collide", d3.forceCollide([5]).iterations([5]))

  function dragStarted(d) {
      if (!d3.event.active) force.alphaTarget(0.3).restart()
      d.fx = d.x
      d.fy = d.y

  }

  function dragging(d) {
      d.fx = d3.event.x
      d.fy = d3.event.y
  }

  function dragEnded(d) {
      if (!d3.event.active) force.alphaTarget(0)
      d.fx = null
      d.fy = null
  }

  const node = d3.selectAll('g.node')
    .call(d3.drag()
              .on("start", dragStarted)
              .on("drag", dragging)
              .on("end", dragEnded)
         );

    force.on('tick', () => {
        this.d3Graph.call(updateGraph)
    });
}
Run Code Online (Sandbox Code Playgroud)