如何在d3js中的forceSimulmation中更平滑地插入新节点?

时间:2019-04-30 08:59:21

标签: javascript d3.js simulation

我正在尝试构建一个图形,其中节点彼此之间插入。这是使用d3js的react组件。问题在于,在每个新插入的节点上,图似乎“碰撞”。

为了尽可能平滑插入,我将新节点插入其已连接的现有节点附近,其距离等于链接距离。我减少了许多身体力量。 我试图在插入新节点时添加过渡,但出现错误。 该过渡将新节点转换为

    this.newnodes = this.nodes
    .enter()
    .append('g')
    .attr("class", "nodesGroups")
    .transition().delay(1000).duration(3500)

但是我得到一个错误:

Uncaught Error
    at Transition._default [as merge] (merge.js:4)
    at Graph.componentDidUpdate (graph.js:146)
    at commitLifeCycles (react-dom.development.js:17349)
    at commitAllLifeCycles (react-dom.development.js:18736)
    at HTMLUnknownElement.callCallback (react-dom.development.js:149)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:199)
    at invokeGuardedCallback (react-dom.development.js:256)
    at commitRoot (react-dom.development.js:18948)
    at react-dom.development.js:20418
    at Object.unstable_runWithPriority (scheduler.development.js:255)

基本上,这就是我要达到的目标:节点的平滑插入。 我注意到删除forceCenter时,效果几乎消失了,但是当我插入新节点时,链接的样式闪烁。 我想保留forceCenter并避免眨眼。

我写了一个例子,它显然很容易碰到

import React, { Component } from 'react';
import * as d3 from "d3";

const styles = {
  svg:{
    width: "100%",
    height: "100%",

  }
}

class Graph extends Component {
  state = {
    "simulationTime": 10000,
    "insertionDelay": 1000,
    "target": 0,
    "linkDistance": 60,
    "id": 0,
    "nodes": [{"id": 0, "weight": 0}],
    "nodesWeighted": [0],
    "links": [],
    "position":{
      "x": 0,
      "y": 0
    }
  }

  updateGraph = () => {
    var id = this.state.id+1
    var target = this.chooseTarget()
    var nodes = [...this.state.nodes]
    var links = [...this.state.links]
    var nodesWeighted = [...this.state.nodesWeighted]

    const {linkDistance} = this.state

    nodes[target].weight += 1
    var newNode = {"id": id, "weight": 1, 
                   "x":nodes[target].x+linkDistance*Math.cos(2*3.1415*Math.random()), 
                   "y":nodes[target].y+linkDistance*Math.sin(2*3.1415*Math.random())
                  }
    var newLink = {"source": id, "target": target}

    nodes.push(newNode)
    links.push(newLink)
    nodesWeighted.push(id, target)
    this.setState({
      "target": target,
      "id": id,
      "nodes": nodes,
      "nodesWeighted": nodesWeighted,
      "links": links,
      "position": nodes[target]
    })

  }

  chooseTarget = () => {
    return this.state.nodesWeighted[Math.floor((Math.random() * this.state.nodesWeighted.length))]; 
  }

  componentDidMount() {
    const height = document.getElementById('landing-page-graph-id').clientHeight
    const width = document.getElementById('landing-page-graph-id').clientWidth
    const radius = 20

    const ticked = () => {
      this.links

      .attr('x1', link => link.source.x)
      .attr('y1', link => link.source.y)
      .attr('x2', link => link.target.x)
      .attr('y2', link => link.target.y)

      this.nodes
      .attr('transform', d => {
        d.x = Math.max(radius, Math.min(width  - radius, d.x))
        d.y = Math.max(radius, Math.min(height - radius, d.y))
        // console.log("data : ", d)
        return `translate(${d.x}, ${d.y})`
      })
    }

    this.graph = d3.select(this.refs["landing-page-svg-ref"])

    this.simulation = d3.forceSimulation()
    .force("link", d3.forceLink().id(d => d.id).distance(d => {return this.state.linkDistance}))
    .force('charge', d3.forceManyBody().strength(-10))
    .force('center', d3.forceCenter(width / 2, height / 2))
    .on("tick", ticked);


    this.links = this.graph
    .append('g')
    .attr("class", "links")
    .selectAll('line')

    this.nodes = this.graph
    .append('g')
    .attr("class", "nodes")
    .selectAll("g")

    var interval = d3.interval( elapsed => {
      if(elapsed > this.state.simulationTime){
        interval.stop()
        return
      }
      this.updateGraph()
    }, this.state.insertionDelay)
  }




  componentDidUpdate(){
    const {nodes, links} = this.state

    this.links = this.links
    .data(links, d => d.source + d.target)
    this.links.exit()
    .remove()

    this.newlinks = this.links
    .enter()
    .append('line')

    this.links = this.newlinks
    .merge(this.links)

    this.newlinks
    .attr('stroke-width', 1.0)
    .attr('stroke', '#000000')

    this.nodes = this.nodes
    .data(nodes, d => d.id)
    this.nodes.exit()
    .remove()

    this.newnodes = this.nodes
    .enter()
    .append('g')
    .attr("class", "nodesGroups")

    this.nodes = this.newnodes
    .merge(this.nodes)

    this.newnodes
    .append("circle")
    .transition().delay(1000).duration(3000)
    .attr("r", d => {return 5})
    .attr("id", d => "node-circle-by-id-"+d.id)

    this.newnodes
    .call(d3.drag()
    .on("start", this.dragstarted)
    .on("drag", this.dragged)
    .on("end", this.dragended)
    )

    this.targetCircle = d3.select("#node-circle-by-id-"+this.state.target)
    this.targetCircle
    .transition().duration(3000)
    .attr("r",  d => d3.scalePow().exponent(0.5).domain([0,10])
                      .range([1,15])(d.weight))

    this.simulation.nodes(nodes)
    this.simulation.force("link").links(links);
    this.simulation.alpha(0.8).restart()
  }


  dragstarted = d => {
    if (!d3.event.active) this.simulation.alphaTarget(0.3).restart();
    d.fx = d3.event.x;
    d.fy = d3.event.y;
  }
  dragged = d => {
    d.fx = d3.event.x;
    d.fy = d3.event.y;
  }
  dragended = d => {
    if (!d3.event.active) this.simulation.alphaTarget(0);
    d.fx = null;
    d.fy = null;
  }


  render(){
    return (
      <div id="landing-page-graph-id" ref="landing-page-graph-ref" style={styles.svg}>
        <svg id="landing-page-svg-id" ref="landing-page-svg-ref" style={styles.svg}></svg>
      </div>
    )
  }
}

export default Graph;

您可以像这样实例化: <Graph/>

编辑 我解决了链接闪烁的问题,这是链接插入的id方法中的一个问题,好的方法是:

    this.links = this.links
    .data(links, d => d.source.id + d.target.id)

仍在寻找forceCenter的平滑插入

0 个答案:

没有答案