Creis
Creis

Reputation: 96

Old nodes in d3 chart aren't removed during update

I'm trying to make a force directed graph, where nodes and links are added and removed as needed. However, while the chart correctly updates with added/removed links and nodes, all the old nodes are still visible.

Screenshot

Here is the update function. I've tried various tutorials, re-arranged the code, double checked the data being update is correct (i.e. this.dataNodes is being mutated, but replacing the object completely doesn't work either), etc. Honestly don't know what I should be looking for anymore.

 // ADDING LINKS
        this.link = this.linkGroup.selectAll("path")
            .data(this.dataLinks, (link) => {
              return link.target.id + link.source.id
            });

        this.link.exit().remove();

        const linkEnter = this.link.enter()
            .append("path")
            .attr("stroke-width", 2)
            .style("stroke", "#ccc")
            .style('marker-start', (d) => d.sync ? 'url(#start-arrow)' : '')
            .style('marker-end', (d) => 'url(#end-arrow)');


        this.link = linkEnter.merge(this.link);

        // ADDING NODES
        this.node = this.nodeGroup.selectAll(".nodes")
            .data(this.dataNodes, function (node) { return node.id });

        this.node.exit().remove();

        const nodeEnter = this.node.enter()
            .append("g")
            .call(this.dragAndDrop);

        // Main circle
        nodeEnter.append("circle")
            .attr("r", 10)
            .attr("fill", "grey")
        // ADDING CHARACTER NAMES
        nodeEnter.append("text")
            .attr("x", 12)
            .attr("dy", ".35em")
            .text(function (d) {return d.title;});

        this.node = nodeEnter.merge(this.node);

this.simulation.nodes(this.dataNodes).on("tick", this.tickActions );
this.simulation.force('link').links(this.dataLinks);
this.simulation.alphaTarget(1).restart();

EDIT: This code is called when the force graph is first created. this.updateSimulation is the function above and renders with no problems. Calling it again, all previously created nodes remain in the graph.

this.svg = d3.select('#relationship-chart')
            .append('svg')
            .attr('width', this.width)
            .attr('height', this.height);


        // GROUPS
        this.linkGroup = this.svg.append("g").attr("class", "links");
        this.nodeGroup = this.svg.append("g").attr("class", "nodes");


        // MAIN SIMULATION
        this.link_force =  d3.forceLink()
            .id(function(d) { return d.id; })
            .distance(100);

        this.simulation = d3.forceSimulation()
            .force("link", this.link_force)
            .force("charge", d3.forceManyBody().strength(-200))
            .force('center', d3.forceCenter(this.width / 2, this.height / 2))
            //.force('collide', d3.forceCollide(25))
            .force("x", d3.forceX())
            .force("y", d3.forceY())
            .alphaTarget(1);

          // MISC DEFINTIONS
        this.dragAndDrop = d3.drag()
            .on("start", this.dragstarted)
            .on("drag", this.dragged)
            .on("end", this.dragended);

        // ADDING ARROWS
        this.svg.append('svg:defs').append('svg:marker')
            .attr('id', 'end-arrow')
            .attr('viewBox', '0 -5 10 10')
            .attr('refX', 7)
            .attr('markerWidth', 4)
            .attr('markerHeight', 4)
            .attr('orient', 'auto')
          .append('svg:path')
            .attr('d', 'M0,-5L10,0L0,5')
            .attr('fill', '#ccc');

        this.svg.append('svg:defs').append('svg:marker')
            .attr('id', 'start-arrow')
            .attr('viewBox', '0 -5 10 10')
            .attr('refX', 1)
            .attr('markerWidth', 4)
            .attr('markerHeight', 4)
            .attr('orient', 'auto')
            .append('svg:path')
            .attr('d', 'M10,-5L0,0L10,5')
            .attr('fill', '#ccc');


this.updateSimulation();

Upvotes: 1

Views: 499

Answers (1)

Creis
Creis

Reputation: 96

Turns out I was selecting the parent elements class and not the children. I added a class to the nodes I created and this cleared up the problem.

Before:

this.node = this.nodeGroup.selectAll(".nodes")
    .data(this.dataNodes, function (node) { return node.id });

this.node.exit().remove();

const nodeEnter = this.node.enter()
    .append("g")
    .call(this.dragAndDrop);

After:

this.node = this.nodeGroup.selectAll(".onenode")
    .data(this.dataNodes, (node) => { return node.id });

this.node.exit().remove();

const nodeEnter = this.node.enter()
    .append("g")
    .attr("class", "onenode")
    .call(this.dragAndDrop);

Upvotes: 2

Related Questions