简体   繁体   中英

Update D3 Force chart - how to remove Circles & Text?

I'm using Angular 6 & D3v4 to create a force simulation chart, and it loads just fine the first time, and i added a click event to the circles & text which updates the data, however the transitions are working strangely - some nodes are removed, but the remaining nodes just have a new circle & text added... I tried the general update pattern from Mike Bostock, but that doesn't seem to work for me in Angular (or maybe it's this newer version of D3?)... here's what i'm doing:

ngOnInit() {
    // get data from service
    this.service.getGraphData(this.id).subscribe(results => {
        this.graphData = results;
        this.forceSimulation = d3.forceSimulation(this.graphData)
    // push nodes apart to space them out
      .force("charge", d3.forceManyBody().strength(-100))
      // add some collision detection so they don't overlap
      .force("collide", d3.forceCollide().radius(55))
      // and draw them around the centre of the space
      .force("center", d3.forceCenter(this.width / 2, this.height / 2));

    this.link = this.svg.selectAll(".link").data(this.graphData.links)
      .enter()
      .append("path")
      .attr("class", "link")
      .attr('stroke', "#FFF");

    this.node = this.svg.selectAll(".node")
      .data(this.graphData.nodes)
      .enter()
      .append('g');

    this.updateGraph();
  });
}

updateGraph() {
    let graphComponent = this;

    //start building/rebuilding the graph
    let t = d3.transition().duration(750);

    this.link = this.link.data(this.graphData.links);
    this.link.exit().remove();

    this.forceSimulation
    // pull nodes together based on the links between them
      .force("link", d3.forceLink().id(function (d) {
          return d.id;
      }).strength(0.025));

    // add the nodes to the graphic
    this.node = this.node.data(this.graphData.nodes);

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

    this.node.transition(t).style('fill', 'blue').attr('35');

    this.node.append("circle")
      .attr("class", "node")
      .attr("r", function (d) {
        // center node = larger & blue
        return d.id == graphComponent.programNode.id ? 65 : 40;
      })
      .attr('stroke', function (d) {
        // apply red border to nodes
        return 'red';
      })
      .attr('stroke-width', function (d) {
        return d.id == graphComponent.programNode.id ? 0 : 3;
      })
      .attr("fill", function (d) {
        return d.id == graphComponent.programNode.id ? 'blue' : '#FFF';
      })
      .on('click', function (d) {
        if (d.id !== graphComponent.programNode.id) {
          graphComponent.currentLevel++;
          graphComponent.componentClicked(d.programId);
        }
      });

    // add a label to each node
    this.node.append("text")
      .attr('x', 0)
      .attr('y', function (d) {
        return d.name.length > 10 ? -20 : 0;
      })
      .attr('text-anchor', 'middle')
      .attr("dy", "0").merge(this.node)
      .text(function (d) {
        return d.name.replace(/\w\S*/g, function (txt) {
          return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
        });
      })
      .style("stroke", function (d) {
        return d.id == graphComponent.programNode.id ? 'white' : 'black'
      })
      .style("stroke-width", .70)
      .style("fill", function (d) {
        return d.id == graphComponent.programNode.id ? '#FFF' : '#000';
      })
      .call(graphComponent.wrap, 35) // fit text inside of node circle
      .on('click', function (d) {
        if (d.programId !== graphComponent.programNode.id) {
          graphComponent.currentLevel++;
          graphComponent.componentClicked(d.programId);
        }
      });


    // Update and restart the simulation.
    this.forceSimulation.nodes(this.graphData.nodes)
      .force("collide", d3.forceCollide().strength(1).radius(55).iterations(1))
      .on("tick", ticked);

    this.forceSimulation
      .force("link")
      .links(this.graphData.links);

    // on each tick, update node and link positions
    // draws links and moves nodes' x & y positions inside the svg
    function ticked() {
      graphComponent.link.attr('d', graphComponent.positionLink);
      graphComponent.node.attr("transform", graphComponent.positionNode);
    }
}

So... after the update, i end up with

    <g transform="translate(..., ...)>
        <circle class="node" ...></circle>
        <text ...>First call's text</text>
        <circle class="node" ...></circle>
        <text ...>Second call's text</text>
    </g>

I just want to remove the first circle & text... any suggestions?

Thanks in advance!

Found out it was the timing of everything... so simplifying the node/link to this in init: this.link = this.g.append("g").selectAll(".link"); this.node = this.g.append("g").selectAll(".node"); this.link = this.g.append("g").selectAll(".link"); this.node = this.g.append("g").selectAll(".node");

and in createGraph and updateGraph i'm now calling a method makeNodes() - the secret sauce was this.svg.selectAll('g .node').remove(); this.svg.selectAll('g text').remove(); this.svg.selectAll('g .node').remove(); this.svg.selectAll('g text').remove();

then i could add my nodes back cleanly...

Hope this helps anyone else trying this pattern!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM