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 using exit().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
const node = zoomContainer
.selectAll('.node')
.data(this.nodes, function (d) {
return d.id;
});
zoomContainer.selectAll('g').data(node).exit().remove();