Я боролся с этой проблемой уже несколько дней и не могу найти решение для правильного обновления узлов.
Поскольку я использую Threejs для визуализации графика, я не смог найти в Интернете достаточно информации о том, как правильно этого добиться.
При первоначальном рендеринге все работает как положено. Проблема в том, что я добавляю новые узлы с помощью функции update
.
Вновь добавленные узлы отсоединены от своих родителей, а созданным связям кажется, что им не хватает координат и сил.
В примере ниже я добавляю новый узел с помощью id: test
.
Это то, что я пробовал до сих пор:
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()
}
Спасибо!
Исправление в основном связано с тем, как были добавлены новые ссылки, и с тем фактом, что каждая новая ссылка не была правильно создана с их новыми источниками и целями.
async function update(newData, oldData) => {
// Create a map of existing nodes by their id
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) // Render new node
}
})
)
// Create a map of existing links by 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) // Render new link
}
})
)
// Restart the simulation with the updated nodes and links
simulation.nodes(nodes)
simulation.force('link').links(links)
simulation.alpha(0.2).restart()
}