Update/Filter Nodes and Links in D3 Force Network

203 Views Asked by At

I'm having difficulties updating my D3 force network. The goal is to have the possibility to redraw the graph with filtered data. For simplicity I provided the code example below with two Dummydatasets (complete and filtered) which can be chosen with buttons. But instead of redrawing the graph it just keeps appending new svg-Elements besides the already existing. I guess it has something to do with calling the entire showData function again and again. Though I tried different solutions it keeps appending the svgs besides or the g-elements on top of each other. Here's the code and the data:

index.html

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>

    <script src="https://d3js.org/d3.v7.min.js"></script>
    <script src="network.js"></script>
    <script src="getData.js"></script>
</head>
<body>
    <button onclick="loadData('dummyData1.json')">Data1</button>
    <button onclick="loadData('dummyData2.json')">Data2</button>
    <div id="network"></div>
</body>
</html> 

getData.js

let store = {}
function loadData(url) {
    console.log(url)
    return Promise.all([
        d3.json(url)
    ]).then(datasets => {
        store.graph = datasets[0];
        showData(store.graph)
        return store;
    })
   
}

network.js

function showData (data){

        var toggle = 0;
        width=500;
        height=500;
        
        var linkedByIndex = {};
        data.links.forEach(function(d) {
            linkedByIndex[d.source + ',' + d.target] = 1;
            linkedByIndex[d.target + ',' + d.source] = 1;
        });

        
        let simulation = d3.forceSimulation()
                .force("link", d3.forceLink().id((d) => d.id).distance(10))
                .force("charge", d3.forceManyBody().strength(d => -20))
                .force("center", d3.forceCenter(width / 2, height / 2))
            
        var color = d3.scaleOrdinal()
        .domain(data.nodes)
        .range(['#e41a1c','#377eb8','#4daf4a','#984ea3','#ff7f00','#ffff33','#a65628'])
        
        svg = d3.select("#network").append("svg")
        .attr("width", width)
        .attr("height", height)

        var g = svg.append("g").attr("class", "everything")

        var link = g.append("g")
            .selectAll(".link")
                            
        var node = g.append("g")
            .selectAll(".node")
                
        var drag_handler = d3.drag()
            .on("start", drag_start)
            .on("drag", drag_drag)
            .on("end", drag_end);   
            
        drag_handler(node);
       
        var zoom_handler = d3.zoom()
            .on("zoom", zoom_actions);
        
        zoom_handler(svg); 
        
        update();
        
        /** Functions **/
        function update() {
        
        node = node.data(data.nodes)
        
        node.exit().remove()
        
        var newNode = node.enter().append("circle")
                .attr('class', 'node')
                .attr("r", 5)
                .attr('fill', function(d) {return(color(d.country))})
                .on('click', function(d, i) {
                    if (toggle == 0) {
                        // Ternary operator restyles links and nodes if they are adjacent.
                        d3.selectAll('.link').style('stroke-opacity', function (l) {
                            return l.target == i || l.source == i ? 1 : 0.1;
                        });
                        d3.selectAll('.node').style('opacity', function (n) {
                            return neighboring(n, i) ? 1 : 0.1;
                        });
                        d3.select(this).style('opacity', 1);
                        toggle = 1;
                    }
                    else {
                        // Restore nodes and links to normal opacity.
                        d3.selectAll('.link').style('stroke-opacity', '0.3');
                        d3.selectAll('.node').style('opacity', '1');
                        toggle = 0;
                    }
                })
        
        node = node.merge(newNode)
        
        link = link.data(data.links)
        
        link.exit().remove();
        
        var newLink = link.enter().append("line")
                .attr('class', 'link')
                .attr("stroke", "black")
                .attr("stroke-opacity", 0.3)
        
        link = link.merge(newLink)
        
        simulation
        .nodes(data.nodes)
        .on("tick", tickActions);
        
        simulation.force("link")
        .links(data.links);
        
        simulation.alpha(1).alphaTarget(0).restart();
        
            
        }
      
        function drag_start(event, d) {
         if (!event.active) simulation.alphaTarget(0.3).restart();
            d.fx = d.x;
            d.fy = d.y;
        }
        
        function drag_drag(event, d) {
          d.fx = event.x;
          d.fy = event.y;
        }
        
        function drag_end(event, d) {
          if (!event.active) simulation.alphaTarget(0);
          d.fx = null;
          d.fy = null;
        }
        
        function neighboring(a, b) {
            return linkedByIndex[a.id + ',' + b.id];
        }
        
        //Zoom functions 
        function zoom_actions(event, d){
            g.attr("transform", event.transform)
        }
        
        function tickActions() {
            
            node
                .attr("cx", function(d) { return d.x; })
                .attr("cy", function(d) { return 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; });
        } 
            }

dummyData1.json

{"nodes": [
    {"id":1, "country":"Germany"},
    {"id":2, "country":"UK"},
    {"id":3, "country":"USA"},
    {"id":4, "country":"China"},
    {"id":5, "country":"Greece"},
    {"id":6, "country":"Brazil"}
],
"links": [
    {"source": 2, "target": 4},
    {"source": 1, "target": 4},
    {"source": 3, "target": 5},
    {"source": 6, "target": 1},
    {"source": 5, "target": 4},
    {"source": 1, "target": 2},
    {"source": 2, "target": 3}
]}

dummyData2.json

{"nodes": [
    {"id":1, "country":"Germany"},
    {"id":2, "country":"UK"},
    {"id":3, "country":"USA"},
    {"id":4, "country":"China"}
],
"links": [
    {"source": 2, "target": 4},
    {"source": 1, "target": 4},
    {"source": 1, "target": 2},
    {"source": 2, "target": 3}
]}

Thanks in advance:-)

0

There are 0 best solutions below