Here is a demo
When new data hits my d3 service it loads the new data set but the old isn't removed. Therefore I have duplicate nodes inside its parent node 'g' element. New to d3, however I've done lots of reading around selection.join() instead of enter().append(). I've also read up on ways to add node.exit().remove(); and node.merge(node); at specific points.
As you can see from the dom, all new node properties are in the <g class="node"> element, duplicated, not replacing the original data. Therefore I get a overlapping of content.
Here is the way my nodes are built...
const zoomContainer = d3.select('svg g');
const node = zoomContainer.selectAll('g').data(nodes, function (d) {
return d.id;
});
//zoomContainer.selectAll('.node').data(node).exit().remove();
const nodeEnter = node
.join('g')
.attr('class', 'node')
.call(
d3
.drag()
.on('start', (d) => this.dragended(d3, d, simulation))
.on('drag', function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
})
.on('end', (d) => this.dragended(d3, d, simulation))
);
nodeEnter
.append('circle')
.style('fill', '#fff')
.style('cursor', 'pointer')
.style('fill-opacity', '1')
.style('stroke-opacity', '0.5')
.attr('id', (d, i) => d.id)
.attr('r', 28);
nodeEnter
.append('image')
.attr('xlink:href', 'https://github.com/favicon.ico')
.attr('x', -15)
.attr('y', -60)
.attr('width', 16)
.attr('class', 'image')
.style('cursor', 'pointer')
.attr('height', 16);
const nodeText = nodeEnter
.data(nodes)
.append('text')
.style('text-anchor', 'middle')
.style('cursor', 'pointer')
.attr('dy', -3)
.attr('y', -25)
.attr('class', 'nodeText')
.attr('id', 'nodeText');
nodeText
.selectAll('tspan')
.data((d, i) => d.label)
.join('tspan')
.attr('class', 'nodeTextTspan')
.text((d) => d)
.style('font-size', '12px')
.attr('x', -10)
.attr('dx', 10)
.attr('dy', 15);
I probably could clear the graph by force but I like and need the way .join() can compare what's changed and the options to use enter().append().exit(). If anybody can see why duplicates are not being removed/merged I would appreciate it.
UPDATE:
If I use enter().append('g') instead of join('g') I then get a better result. I can use zoomContainer.selectAll('.node').data(node).exit().remove(); before hand and my nodes do get updated but only after clicking update twice. If I use join('g') they duplicate and I am unable to use zoomContainer.selectAll('.node').data(node).exit().remove();
Here is a demo

Targeting the
<g>element rather than the class and then usingexit().remove()seemed to have done the trick... I was adding a class attribute at the.enter()level in.join()and then doing the exit on that. Demo here