0

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();

  ...
xxx
  • 1,153
  • 1
  • 11
  • 23
galatia
  • 473
  • 1
  • 5
  • 18

2 Answers2

0

I think your problem is that the links you are passing in refer to the index of the nodes in the array they are in. when you merge these nodes into 1 array the indexes now no longer match. you would have to map your old indices from the new data, to where those nodes are in the node array now.

also i would recommend you use a Map data structure to remove duplicates. ie you go through your nodes put them all in a Map by ids. then go through the map and extract your now duplicate free list

for example (excuse any silly bugs i may have made)

// map indexes of nodes to their IDs 
const indexIDMap = graph.links.map(d=>d.id);

// remove duplicates
const nodeMap = new Map();
// add old nodes to map
myNodes.data().forEach(d=> nodeMap.set(d.id, d));
//note this would over write any data contained in the new node that isn't on the old node
myNodes.links.forEach(d=> nodeMap.set(d.id, d)); 
// Maps maintain insertion order which is important
const allNodes = [...myNodes.values()]

// links
const newIndices = indexIdMap.map(id => allNodes.findIndex(d => d.id === id))
const newLinks = graph.links.map(l => ({
    source: newIndices[l.source],
    target: newIndices[l.target]
}))

const allLinks = myLinks.data().concat(newLinks) 
theya
  • 33
  • 2
  • 8
  • The indexes are updated. I have not posted all the code but i end up with the link with the correct indexes but with different x and y. So, i end up with a link that supposed to show a node i.e. node "c" and it is just fixed in the old x and y position of node c. – galatia Aug 09 '18 at 12:13
  • are you merging these nodes in after you have started the cola simulation or before? have you tried to redraw the edges? – theya Aug 10 '18 at 00:26
  • i am redrawing nodes and links , at least i am trying. I have added all my code here: https://jsfiddle.net/galateia/pgdfe0vm/4/ so it is more clear, however is not working because cola.js is not available – galatia Aug 10 '18 at 08:51
0

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>
galatia
  • 473
  • 1
  • 5
  • 18