I'm quite new in D3.
I would like to draw a graph using the data received from JSON (the graph contains nodes connected with each other by links with a given percentage of occupancy). After the first query from the database, the graph looks correct. After a few seconds, I send another query (to update the data on the use of links) and unfortunately, instead of updating only the text (linkOccup), the whole graph is deleted and redrawn (with correct updated data).
I would like only the information about occupancy of links to be updated instead of redrawing the entire graph. Does anyone know the solution to the problem?
import React, { useEffect, useRef } from 'react';
import useResizeObserver from './useResizeObserver';
import * as d3 from 'd3';
import { select } from 'd3';
function ForceGraph({ linksData, nodesData }) {
const svgRef = useRef();
const wrapperRef = useRef();
const dimensions = useResizeObserver(wrapperRef);
const links = linksData;
const nodes = nodesData;
useEffect(() => {
const svg = select(svgRef.current);
if (!dimensions) return;
svg.attr('viewBox', [
-dimensions.width / 2,
-dimensions.height / 2,
dimensions.width,
dimensions.height,
]);
const drag = simulation => {
const dragstarted = d => {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
};
const dragged = d => {
d.fx = d3.event.x;
d.fy = d3.event.y;
};
const dragended = d => {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
};
return d3
.drag()
.on('start', dragstarted)
.on('drag', dragged)
.on('end', dragended);
};
const simulation = d3
.forceSimulation(nodes)
.force(
'link',
d3.forceLink(links).id(d => d.dpid)
)
.force('charge', d3.forceManyBody().strength(-1200));
const link = svg
.selectAll('line')
.data(links)
.join(
enter => enter.append('line').attr('class', 'new-element'),
update => update.attr('class', 'updated-element')
)
.attr('stroke-width', 2)
.attr('stroke', '#000')
.call(drag(simulation));
const node = svg
.selectAll('circle')
.data(nodes)
.join(
enter => enter.append('circle').attr('class', 'new-element'),
update => update.attr('class', 'updated-element')
)
.attr('r', 18)
.call(drag(simulation));
const linkOccup = svg
.selectAll('text')
.data(links)
.join(
enter =>
enter
.append('text')
.text(d => {
if (typeof d.max_link_occupancy !== 'undefined') {
return d.max_link_occupancy + '%';
} else {
return d.bw + ' Mb/s';
}
})
.attr('class', 'new-element'),
update =>
update
.text(d => {
if (typeof d.max_link_occupancy !== 'undefined') {
return d.max_link_occupancy + '%';
} else {
return d.bw + ' Mb/s';
}
})
.attr('class', 'updated-element')
)
.attr('text-anchor', 'middle')
.attr('dominant-baseline', 'central')
.attr('font-size', 18)
.call(drag(simulation));
const label = svg
.selectAll('.label')
.data(nodes)
.join(
enter => enter.append('text').attr('class', 'new-element'),
update => update.attr('class', 'updated-element')
)
.text(d => {
return d.dpid;
})
.attr('class', 'label')
.attr('text-anchor', 'middle')
.attr('dominant-baseline', 'central')
.attr('font-size', 18)
.attr('fill', 'white');
simulation.on('tick', () => {
link
.attr('x1', d => d.source.x)
.attr('y1', d => d.source.y)
.attr('x2', d => d.target.x)
.attr('y2', d => d.target.y);
node.attr('cx', d => d.x).attr('cy', d => d.y);
label
.attr('x', d => {
return d.x;
})
.attr('y', d => {
return d.y;
});
linkOccup
.attr('x', d => (d.source.x + d.target.x) / 2)
.attr('y', d => (d.source.y + d.target.y) / 2);
});
}, [linksData, dimensions]);
return (
<div className="rootNew" ref={wrapperRef} style={{ marginBottom: '2rem' }}>
<svg className="svgNew" ref={svgRef}></svg>
</div>
);
}
export default ForceGraph;
question from:
https://stackoverflow.com/questions/66066271/d3-react-the-whole-force-graph-is-redrawn-when-updated-data-arrives-instead