Search code examples
javascriptjsond3.jswebcola

Merge new json data to existing graph in d3.js and cola.js


I have a function buildTree that takes as an input json data and visualize them using d3.js with cola.js. The idea is that i click a button and add a json to d3. Then by clicking another button i add another js and keep the old one so i end up with two trees. In my example i have a node that exists in both json files so i want to end up with these two trees connected and the node appears one time.

I managed to get the existing tree, add new one, delete the node that exists two times and update the links but one link never connects to the coexisting node.

The JSON files have the following format:

{
  "nodes": [
    {"id": "a", "name": "a", "type": "tool", "width": 60, "height": 40},
    {"id": "b", "name": "b", "type": "tool", "width": 60, "height": 40},
    {"id": "c", "name": "c", "type": "tool", "width": 60, "height": 40}

  ],
  "links": [
    {"source": 0, "target": 1},
    {"source": 0, "target": 2}
  ],
  "groups": []
}

And the second one:

{
  "nodes": [
    {"id": "h", "name": "h", "type": "tool", "width": 60, "height": 40},
    {"id": "i", "name": "i", "type": "tool", "width": 60, "height": 40},
    {"id": "c", "name": "c", "type": "tool", "width": 60, "height": 40}

  ],
  "links": [
    {"source": 0, "target": 1},
    {"source": 0, "target": 2}

  ],
  "groups": []
}

So c is a node that exists in both JSON files and should appear in the tree only once but with both links.

And the buildTree is something like:

function buildTree(jsonSource) {
  d3.json(jsonSource, function (error, graph) {

    //get existing data if any and merge them with new data
    myNodes = svg.selectAll(".node");
    myLinks = svg.selectAll(".link");

    //update the existing nodes with the new ones, remove duplications and store them in removedNodes
    allNodes = graph.nodes.concat(myNodes.data());
    var uniqueIds = [];
    var allNodesUnique = [];
    var removedNodes = [];
    for (var i = 0; i < allNodes.length; i++) {
      var id = allNodes[i].id;
      if (uniqueIds.indexOf(id) == -1) {
        uniqueIds.push(id);
        allNodesUnique.push(allNodes[i]);
      } else {
        removedNodes.push(allNodes[i]);
      }
    }
    allNodes = allNodesUnique;

    //update links                  
    allLinks = graph.links.concat(myLinks.data());

    d3.selectAll("svg > *").remove();


    cola
      .nodes(allNodes)
      .links(allLinks)
      .groups(graph.groups)
      .start();

  ...

Solution

  • finally i solved the issue by updating the links correctly, the javascript code can be found here:

    <script src="d3v4.js"></script>
    <script src="cola.min.js"></script>
    <script>
    	
    //function on button click		
    function myFunction() {
    	buildTree("Data1.json");
    	document.getElementById('graph').style.visibility="visible";
       
    }
    
    //function on button click	
    function myFunction2() { 
      buildTree("Data2.json");
      document.getElementById('graph').style.visibility="visible";
    
       
    }
    
    <!-- initialize cola -->
      var width = 960,
          height = 500; //these are the dimensions of the graph
    
    	// map colors for nodes to their type	
    	var color = d3.scaleOrdinal()
    				.domain(["workflow", "tool", "task", "exposed variable"])
    				.range(["#009933", "#E3a322", "#E05B2B", "#81D42F"]);
    
        var cola = cola.d3adaptor(d3)
            .linkDistance(100)
            .avoidOverlaps(true)
            .handleDisconnected(false)
            .size([width, height]);
    
        var svg = d3.select("#graph").append("svg")
            .attr("width", width)
            .attr("height", height);
    
    <!-- end of initialize cola -->	
    
    
    
    
    /**
    This function takes as an iput a json with nodes and links and creates a tree.
    If another tree already exists it merges the json data and redraws old trees and new ones
    
    **/
    function buildTree(jsonSource){
    		var myNodes =[];
    		var myLinks=[];
    		var allNodes=[];
    		var allLinks=[];
    		
    		
    
      d3.json(jsonSource, function (error, graph) {
    	//console.log(error);
    
    	<!-- Data Merging -->
    	//get existing data if any and merge them with new data
    	myNodes = svg.selectAll(".node").data();
    	myLinks = svg.selectAll(".link").data();	
    		
    		
    	//update the existing NODES with the new ones, remove duplications and store them in removedNodes
    		allNodes = graph.nodes.concat(myNodes);
    		var uniqueIds=[];
    		var allNodesUnique=[];
    		var removedNodes=[];
    		var oldIds=[];
    		
    		for (var i=0; i < allNodes.length; i++){
    				var currentId = allNodes[i].id;
    				if(uniqueIds.indexOf(currentId) == -1){
    					uniqueIds.push(currentId);
    					allNodesUnique.push(allNodes[i]);
    				}else{
    					oldIds.push(currentId);
    					removedNodes.push(allNodes[i]);
    				}
        	}
    		allNodes=allNodesUnique;
    
    		var remainedNodes=[];
    		for (var j=0; j < oldIds.length; j++){
    			for (var i=0; i < allNodes.length; i++){
    				if(oldIds[j]!='undefined' && oldIds[j]==allNodes[i].id){
    				remainedNode = allNodes[i];
    				remainedNodes.push(allNodes[i]);
    				}
    			}
    		}
    		
    					
    		//update LINKS (remove dublications)		
    		var myCount = (myNodes.length);
    		   if(myCount>-1){
    			for (var j=0; j < remainedNodes.length; j++){
    				for (var i=0; i < myLinks.length; i++){
    					  if(myLinks[i].source.id == remainedNodes[j].id){	  
    						myLinks[i].source = remainedNodes[j];		  
    					  }
    					
    					if(myLinks[i].target.id == remainedNodes[j].id){		  
    						myLinks[i].target = remainedNodes[j];
    					  
    					  }
    					
    							myLinks[i].source.index=myLinks[i].source.index+myCount;
    							myLinks[i].target.index=myLinks[i].target.index+myCount;
    					}
    				}			
    			}
    			
    	    allLinks = graph.links.concat(myLinks); 
    		//update removed info
    		
    
    		//search for the removed node 
    		tempLinks=[];
    		for(var j=0; j<removedNodes.length; j++){
    			for (var i=0; i < allLinks.length; i++){
    				if(allLinks[i].source.id==removedNodes[j].id){		
    					allLinks[i].source.index = allLinks[i].source.index - myCount 
    				}
    				if(allLinks[i].target.id==removedNodes[j].id){
    					allLinks[i].target.index = allLinks[i].target.index - myCount 				
    				}
    			}
    		
    		}
    
    	<!-- End of Data Merging -->
    
    
    d3.selectAll("svg > *").remove();
    
    
            cola
                .nodes(allNodes)
                .links(allLinks)
                .groups(graph.groups)
                .start();
    
            var group = svg.selectAll(".group")
                .data(graph.groups)
                .enter().append("rect")
                .attr("rx", 8).attr("ry", 8)
                .attr("class", "group")
                .style("fill", function (d, i) { return color(i);})
                .call(cola.drag);
    
            var link = svg.selectAll(".link")
                .data(allLinks)
                .enter().append("line")
                .attr("class", function(d){ return ["link", d.source.name, d.target.name].join(" "); });
    			
    	
    
    
            var pad = 3;
            var node = svg.selectAll(".node")
                .data(allNodes)
                .enter().append("rect")
                .attr("class", "node")
                .attr("width", function (d) { return d.width - 2 * pad; })
                .attr("height", function (d) { return d.height - 2 * pad; })
                .attr("rx", 5).attr("ry", 5)
    			.style("fill", function(d) {  return color(d.type);   }) //color based on type
                .call(cola.drag);
    
    	
            node.append("title")
                .text(function (d) { return d.name; });
    
    			
            cola.on("tick", function () {
    		
                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; });
    
                node.attr("x", function (d) { return d.x - d.width / 2 + pad; })
                    .attr("y", function (d) { return d.y - d.height / 2 + pad; });
                
                group.attr("x", function (d) { return d.bounds.x; })
                     .attr("y", function (d) { return d.bounds.y; })
                    .attr("width", function (d) { return d.bounds.width(); })
                    .attr("height", function (d) { return d.bounds.height(); });
    
    			
            });
    		
    		
    		
        });
    	
    }
    	
    </script>