D3 force directed graph update nodes with new data

  Kiến thức lập trình

I’ve been struggling with this issue for days now and cannot find a solution to update the nodes properly.

Since I’m using threejs to render the graph I couldn’t find enough info on the web on how to properly achieve this.

On initial render everything works as expected. The problem is when I add new nodes with the update function.

Newly added nodes are disconnected from their parents and the created links seem to lack coordinates and forces.

In the example below I am adding a new node with id: test.

This is what I’ve tried so far:

  let root = d3
    .stratify()
    .id((d) => d.id)
    .parentId((d) => d.linkedTo)(data)

  let nodes = root.descendants()
  let links = root.links()

  simulation = d3
    .forceSimulation(nodes)
    .force('charge', d3.forceManyBody().strength(-1000))
    .force('center', d3.forceCenter(0, 0))
    .force('collide', d3.forceCollide().radius(50).strength(0.9))
    .on('tick', ticked)

  simulation.force(
    'link',
    d3
      .forceLink(links)
      .id((d) => {
        return d.data._id
      })
      .distance(10)
      .strength(0.9)
  )

  // Render nodes and links three scene 
  links.forEach(renderLink)
  nodes.forEach(renderNode)

  function update(newData, oldData) {
    simulation.stop()

    const newRoot = d3
      .stratify()
      .id((d) => d.id)
      .parentId((d) => d.linkedTo)(newData)

    const newNodes = newRoot.descendants()
    const newLinks = newRoot.links()

    // Find nodes to remove and remove them
    const nodesToRemove = nodes.filter((node) => !newNodes.some((newNode) => newNode.id === node.id))

    // remove nodes from the three scene
    removeNodes(nodesToRemove)

    // Find links to remove and remove them
    const linksToRemove = links.filter(
      (link) =>
        !newLinks.some(
          (newLink) => newLink.source.id === link.source.id && newLink.target.id === link.target.id
        )
    )

    // remove links from the three scene
    removeLinks(linksToRemove)

    // Find new nodes
    const nodesToAdd = newNodes.filter((newNode) => !nodes.some((node) => node.id === newNode.id))

    nodes = [...nodes, ...nodesToAdd]

    // Find links to add and add them
    const linksToAdd = newLinks.filter(
      (newLink) =>
        !links.some((link) => {
          const sourceMathces = link.source.id === newLink.source.id
          const targetMathces = link.target.id === newLink.target.id

          return sourceMathces && targetMathces
        })
    )

    links = [...links, ...linksToAdd]

    simulation.nodes(nodes).force('link').links(links)
    simulation.alpha(0.5).restart()
  }

Thank you!

LEAVE A COMMENT