D3js force layout destroy and reset

15,303

Solution 1

  1. No. You have to do this manually.
  2. You could have a look at the DOM, but it looks like you're deleting everything.
  3. I'm guessing that this happens because you're not actually deleting the nodes/links from the force layout. At some point, you've given the variables nodes and links to the force layout. Changing what those names point to (i.e. []) doesn't change the reference in the force layout. That is, the data objects are still there and referenced. There are two ways to remove them. You can either modify the nodes and links in place (e.g. with .slice()), or reset them explicitly in the force layout.

    nodes = []; links = []; force.nodes(nodes); force.links(links);

  4. Hard to say without a specific example, but the answer is most likely no. Javascript is garbage collected, so doing it manually shouldn't have an impact.

Solution 2

I did it with:

nodeCircles = {};
node.remove();
link.remove();
svg.clear();
nodes = [];
links = [];

Just put this into a method and then recreate your force and svg. That works great.

Share:
15,303

Related videos on Youtube

Jason
Author by

Jason

Updated on June 04, 2022

Comments

  • Jason
    Jason about 2 years

    Based on two D3 examples: Force layout (http://bl.ocks.org/mbostock/1095795) and clustered force layout (http://bl.ocks.org/mbostock/1748247), I managed to built a force layout with a few independent point of gravity to control nodes position on top of the links between nodes.

    // Set up map
    function map_init(){
    
        force = d3.layout.force()
            .nodes(nodes)
            .links(links)
            .size([width, height])
            .on("tick", tick);
    
        svg = d3.select("#map").append("svg")
            .attr("width", width)
            .attr("height", height);
    
        link = $map.selectAll(".link");
        node = $map.selectAll(".node");
    
        d3.json("graph.json", function(error, graph) {
    
            // set up nodes
            for( i = 0; i < graph.nodes.length; i++ ){          
                nodes.push( graph.nodes[i] );
            }
    
            // position nodes to three different gravity centres based on theme
            for( i = 0; i < nodes.length; i++ ){
                if ( nodes[i].theme == "theme1" ){ 
                    nodes[i].cx = 100;
                    nodes[i].cy = 100; 
                } else if ( nodes[i].theme == "theme2" ){ 
                    nodes[i].cx = 300;
                    nodes[i].cy = 300; 
                } else if ( nodes[i].theme == "theme3" ){ 
                    nodes[i].cx = 500;
                    nodes[i].cy = 500; 
                }   
            }
    
            // link nodes of the same theme
            theme1_nodes = nodes.filter(function(d){ return (d.theme == "theme1"); });
            theme2_nodes = nodes.filter(function(d){ return (d.theme == "theme2"); });
            theme3_nodes = nodes.filter(function(d){ return (d.theme == "theme3"); });
            for (i = 0; i < theme1_nodes.length-1; i++){
                links.push({ source: theme1_nodes[i], target: theme1_nodes[i+1] });
            }
            for (i = 0; i < theme2_nodes.length-1; i++){
                links.push({ source: theme2_nodes[i], target: theme2_nodes[i+1] });
            }
            for (i = 0; i < theme3_nodes.length-1; i++){
                links.push({ source: theme3_nodes[i], target: theme3_nodes[i+1] });
            }
    
            start();
    
        }); 
    
    }
    
    // Start
    function start() {
    
      link = link.data(force.links(), function(d) { return d.source.id + "-" + d.target.id; });
      link.enter()
        .insert("svg:line")
            .attr("class", "link");
      link.exit()
            .remove();
    
      node = node.data(force.nodes(), function(d) { return d.id; });
      var nodeEnter = node.enter()
              .append("svg:g")
                .attr("class", "node");
            .on("click", map_nodeClick);
      node.exit().remove();
    
      // Enter node information
      nodeEnter.each(function(d) {
            theTitle = d3.select(this).append("svg:text")
            .attr("font-family", "Helvetica")
                .attr("class", "title")
            .text( d.title );
        });
    
        // More content to go into each node
        // .
        // .
        // .
    
      force.start();
    
    }
    
    // Tick
    function tick(e) {
    
        node
          .each(gravity(.2 * e.alpha))
          .attr("cx", function(d) { return d.x; })
          .attr("cy", function(d) { return d.y; })
          .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
    
        link.attr("x1", function(d) { return d.source.x; })
          .attr("y1", function(d) { return d.source.y; })
          .attr("x2", function(d) { return d.target.x; })
          .attr("y2", function(d) { return d.target.y; });
    
    }
    
    // Gravity
    function gravity(alpha) {
    
      return function(d) {
        d.y += (d.cy - d.y) * alpha;
        d.x += (d.cx - d.x) * alpha;
      };
    
    }
    
    // Set up when page first loads
    map_init();
    

    In order to reset/restart the force layout anytime without reloading the page, I bound the following function to a reset button:

    // Remove force layout and data
    function map_remove(){
    
        node.remove();
        link.remove();
        svg.remove();
        nodes = [];
        links = [];
    
    }
    
    // Reset button
    $('a#reset').click(function(e){
    
        e.preventDefault();
    
        map_remove();
        map_init();
    
    });
    

    This webpage is displayed on a device accessible by group of people. Only loaded once in the morning and stayed running on iPad Safari for 12 hours. Link between nodes ideally changes dynamically based on users input (to be implemented). Apart from the force layout there are other info on the webpage. An option to relaunch/reset the force layout without reloading the page is required.

    1. Is there a built-in method to destroy the D3 force layout and its data?
    2. Currently this works ok as no extra DOM elements were created and no errors found from inspector. However I am not sure how to check if all the D3 objects has been cleared/emptied so no duplicated data was stored/accumulated?
    3. Currently each reset for some reasons pull the nodes closer and closer to the centre of the map. Have I missed out something in the map_remove() function?
    4. Would a complete restart of the D3 force layout improve the performance of the browser at any point? i.e. clearing up the memory for painting the SVG?
  • Carr
    Carr over 7 years
    I'm sorry that I was little bit confused about "...in place (e.g. with .slice())...". is that should be splice()?
  • Lars Kotthoff
    Lars Kotthoff over 7 years
    You can use both, depending on what exactly you want to do.