Update the nodes in a directed graph with fresh data

For the past few days, I've been facing a persistent issue that I just can't seem to find a solution for when it comes to updating nodes properly.

Using three.js to render the graph adds an extra layer of complexity, as the information available online is scarce on how to achieve this effectively.

Initially, everything works smoothly upon rendering. However, the problem arises when attempting to add new nodes using the update function.

The newly added nodes end up disconnected from their parent nodes, and the created links appear to be lacking coordinates and forces.

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

https://i.sstatic.net/82qt6dAT.png

https://i.sstatic.net/2fbe3TQM.png

Here are the approaches I have experimented with 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 on the 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()

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

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

    // Identify links to remove and discard 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)

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

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

    // Identify links to add and introduce 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()
  }

Your assistance is greatly appreciated!

Answer №1

The issue lies primarily in the way the new links were incorporated, as well as the incorrect instantiation of each new link with their respective sources and targets.

async function update(newData, oldData) => {
    // Establish a map of current nodes based on their ids
    const existingNodesMap = new Map(nodes.map((node) => [node.id, node]))
    // Create new root, nodes, and links
    const newRoot = d3
      .stratify()
      .id((d) => d.id)
      .parentId((d) => d.linkedTo)(newData)

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

    await Promise.all(
      newNodes.slice().map((newNode) => {
        if (!existingNodesMap.has(newNode.id)) {
          nodes.push(newNode)
          existingNodesMap.set(newNode.id, newNode)

          return renderNode(newNode) // Display new node
        }
      })
    )

    // Establish a set of existing links based on their source and target ids
    const existingLinksSet = new Set(links.map((link) => `${link.source.id}-${link.target.id}`))

    // Update the links array, avoiding duplicates and adding valid links
    await Promise.all(
      newLinks.slice().map((newLink) => {
        const source = existingNodesMap.get(newLink.source.id)
        const target = existingNodesMap.get(newLink.target.id)
        const linkId = `${newLink.source.id}-${newLink.target.id}`

        if (source && target && !existingLinksSet.has(linkId)) {
          newLink.source = source
          newLink.target = target
          links.push(newLink)
          existingLinksSet.add(linkId)

          return renderLink(newLink) // Display new link
        }
      })
    )

    // Restart the simulation with the updated nodes and links
    simulation.nodes(nodes)
    simulation.force('link').links(links)
    simulation.alpha(0.2).restart()
  }

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

How can I generate a .json file that includes PHP variables?

How can I create a customized JSON file called "file.json.php" using PHP variables such as $_POST['foo']? This file will produce different results based on the value of the post variable passed through an ajax call. What settings do I need to imp ...

Is there a way to display one of the divs in sequence each time the page is reloaded?

Is there a way to display the divs sequentially when the page is refreshed? For instance, on the initial load, only div 1 should appear, and with each refresh it should cycle through divs 2, 3, 4, and 5 before starting over. Below is an example I'm c ...

What could be causing the issue with my Mongoose One-To-Many Relationships not linking correctly?

Can anyone shed light on why the connection between "users" and "posts" (where users can have multiple posts) is failing to work properly? Despite setting up my mongoose associations correctly, when a new post is made, it doesn't get assigned to a use ...

Steps to open and configure a Mobile-Angular modal from another controller

Is it possible to use a modal in Angular JS for mobile devices without compromising the layout? I'm having trouble setting up the controller for my modal inside my main controller and passing parameters. Should I consider using ui-bootstrap for this p ...

What is an alternative way to show the contents of a JSON file without directly accessing it

Recently, I stumbled upon an amazing website - where I have been exploring to learn something new. The website prominently features the use of Ajax and some fascinating javascript without any additional libraries. Within a single javascript file on this ...

Encountered a runtime error while processing 400 requests

Current Situation: When authenticating the username and password in my Ionic 2 project using WebApi 2 token authentication, a token is returned if the credentials are correct. However, a 400 bad request error is returned if the credentials are incorrect. ...

Tips for Accessing the TextInput Content in React Native

My code is currently experiencing a bug where I am getting an undefined object type when trying to console log user input. Below is the code snippet I am working with. Can you help me identify and correct the issue? export default function App() { con ...

Django compressor throwing minification hiccup

I'm currently utilizing a tool for automatic minification known as django compressor. Unfortunately, it seems that the minification process with django compressor is causing errors to be introduced. Updated script with semicolons: Before: var ap ...

Is it possible to integrate ng-repeat with ng-model in Angular?

Is it possible to link the ng-model of a button with the ng-repeat loop? <a ng-repeat="x in [1,2,3,4]" ng-model="myButton[x]">{{myButton[x]}}</a> In the Controller: var id = 4; $scope.myButton[id] = ' :( '; I am interested in crea ...

Error: Uncaught ReferenceError: d3 is undefined. The script is not properly referenced

Entering the world of web development, I usually find solutions on Stack Overflow. However, this time I'm facing a challenge. I am using Firefox 32 with Firebug as my debugger. The webpage I have locally runs with the following HTML Code <!DOCTYP ...

In a responsive design, rearrange a 3-column layout so that the 3rd column is shifted onto or above the

My question is concise and to the point. Despite searching online, I have not been able to find any information on this topic. Currently, my layout consists of 3 columns: ---------------------------- |left|the-main-column|right| ------------------------- ...

What could be causing the input field state to remain static even as I type in the MUI textField?

In my React.js component, I am facing an issue where the textField is not updating when I try to type anything. Upon debugging, I discovered that when the first character is entered, the component re-renders and loses its previous state, causing the textF ...

What is the best way to trigger an action in response to changes in state within Vuex

Are there more sophisticated methods to trigger actions in vuex when a state changes, rather than using a watcher on that state? I want to ensure the most efficient approach is being utilized. ...

When using the `array.join` method with nested arrays, it does not automatically include spaces in the output

function convertArrayToString(arr) { // Your solution goes here. let str = arr.join(", "); return str; } const nestedValues = [5, 6, [7], ["8", ["9", ["10" ]]]]; const convertedString = convertArrayToString(nestedValues); console.log(convertedString ...

Is there a way to show an element on a website only when the height of the HTML content exceeds the height of the viewport?

My webpage serves as a dynamic to-do list, allowing users to add an endless number of tasks of all types. As the list grows in size, the page height increases and a scrollbar appears. Positioned at the bottom right corner of the page is a fixed button that ...

Ensuring radio button validation prior to redirecting to contact form

I created a survey form using HTML and CSS. I am looking to add validation to the questions before redirecting to the Contact form page. Currently, when a user clicks on the submit button in the Survey form, the page redirects to the contact form. Howeve ...

A numerical input field that removes non-numeric characters without removing existing numbers?

Currently, I have implemented the following code: <input type="text" onkeyup="this.value = this.value.replace(/\D/g, '')"> This code restricts users from entering anything other than numbers into a field on my webpage. However, I hav ...

absence of data in ajax response (or HTTP status code 206 Partial Content)

Feeling frustrated because I've wasted two hours trying to solve a simple task that I've done numerous times before. I'm not even sure where to start looking now. Struggling to retrieve static content using ajax from local servers (Apache a ...

At what point are DOMs erased from memory?

Recently, I've been working on an application that involves continuous creation and removal of DOM elements. One thing I noticed is that the process memory for the browser tab keeps increasing even though the javascript heap memory remains steady. To ...

Managing multiple Socket.io connections upon page reload

I am currently developing a real-time application and utilizing Socket.io for its functionality. At the moment, my setup involves receiving user-posted messages through the socket server, saving this data to a MySQL database via the controller, and then b ...