将新的json数据合并到d3.js和cola.js中的现有图形

时间:2018-08-07 14:14:48

标签: javascript json d3.js webcola

我有一个功能buildTree,它将json数据作为输入,并使用带有cola.js的d3.js对其进行可视化。这个想法是我单击一个按钮,然后向d3添加一个json。然后通过单击另一个按钮,我添加另一个js并保留旧的js,所以我最终得到了两棵树。在我的示例中,两个json文件中都存在一个节点,因此我想将这两个树连接起来,并且该节点出现一次。

我设法获得了现有的树,添加了新的树,删除了两次存在的节点并更新了链接,但是一个链接从未连接到共存节点。

json文件具有以下格式:

{
"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":[

]

}

第二个:

{
 "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":[

]

}

因此c是两个json文件中都存在的节点,并且应该仅在树中出现一次,但同时具有两个链接。

buildTree类似于:

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

        ...

2 个答案:

答案 0 :(得分:0)

我认为您的问题是传递的链接引用了它们所在数组中节点的索引。当您将这些节点合并为1个数组时,索引现在不再匹配。 您将必须将新数据中的旧索引映射到这些节点现在在节点数组中的位置。

我也建议您使用Map数据结构删除重复项。 即,您遍历节点将它们全部通过id放置在Map中。然后浏览地图,提取您现在重复的空闲列表

例如(请问我可能犯的任何愚蠢的错误)

// 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) 

答案 1 :(得分:0)

最后,我通过正确更新链接解决了该问题,可以在此处找到javascript代码:

<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>