Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

New layout after opening a group not base on the last layout with cola.js

I recently learned about a excellent JS library cola.js . It can do a force layout and support groups. Learn more here : Cola.js

I create a simple demo to show a force layout with opening group feature. But I was confused with the openning behavior.

I think when open a group, the new layout should be a minor adjustments base on the last layout. But now it relayout all nodes. Why ?

I learned some ideals from this link : Offical Demo : Online Graph Exploration, which looks very complicated. The coordinates of new nodes dynamic adding to graph should be set as the coordinate of opening group. Unfortunately, it also can't resolve my issue.

The following is my demo :

var w = 480, h = 420, cola;
var data = {
	"nodes": [
		{"name": "Top","width": 60,"height": 60},
		{"name": "A","width": 60,"height": 60},
		{"name": "B","width": 60,"height": 60},
		{"name": "C","width": 60,"height": 60},
		{"name": "D","width": 60,"height": 60},
		{"name": "E","width": 60,"height": 60},
		{"name": "F","width": 60,"height": 60},
		{"name": "G","width": 60,"height": 60},
		{"name": "H","width": 60,"height": 60},
		{"name": "I","width": 60,"height": 60}
	],
	"links": [
		{"source": 0,"target": 6},
		{"source": 0,"target": 4},
		{"source": 0,"target": 3},
		{"source": 0,"target": 7},
		{"source": 0,"target": 8},
		{"source": 6,"target": 0},
		{"source": 6,"target": 7},
		{"source": 4,"target": 0},
		{"source": 4,"target": 3},
		{"source": 3,"target": 0},
		{"source": 3,"target": 4},
		{"source": 7,"target": 0},
		{"source": 7,"target": 6},
		{"source": 7,"target": 4},
		{"source": 8,"target": 0},
		{"source": 8,"target": 7}
	],
	"groups": [
		{"leaves": [0,1,2],"groups": [1],"name": "Product"},
		{"leaves": [7],"name": "Businness"},
		{"leaves": [3,4,6,8,9],"name": "Tech"}
	]
};

cola = cola.d3adaptor()
		.linkDistance(150)
		.avoidOverlaps(true)
	    .handleDisconnected(true)
		.size([w, h]);
	
	svg = d3.select("body").append("svg")
		.attr("width", w)
		.attr("height", h)
		.on("dblclick.zoom", null);
	
	svg.append('rect')
		.attr("width", w)
		.attr("height", h)
		.style("fill", "none")
		.style("pointer-events", "all");
	
	svg = svg.append('g');
	
	update(data);

	cola.on("tick", function () {
		svg.selectAll(".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; });
		
		svg.selectAll(".nodeimage").attr("x", function(d){ return d.x - 25 / 2 }).attr("y", function(d){ return d.y - 25 / 2 });

		svg.selectAll(".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(); });
		
		svg.selectAll(".label").attr("x", function (d) {
				var w = this.getBBox().width;
				return d.x - w/2;
			})
			.attr("y", function (d) {
				var h = this.getBBox().height;
				return d.y + 25;
			});
		
		svg.selectAll('.groupclosebutton')
			.attr("x", function (d) { return d.bounds.x + d.bounds.width() - 20; })
			.attr("y", function (d) { return d.bounds.y + 2; });
		
		
		svg.selectAll('.groupname')
			.attr("x", function (d) { return d.bounds.x + 5; })
			.attr("y", function (d) { return d.bounds.y + 15; });
	});

function update(data){
	//data.groups.forEach(function (g) { g.padding = 15; });
	cola.nodes(data.nodes).links(data.links).groups(data.groups).start();

	var color = ['#d3d4e5', '#f7e0c8', '#dee8f2', '#cbe5c4', '#ededeb'];
	var group = svg.selectAll(".group").data(data.groups, function(d) { return d.name;});
	group.enter().append("rect")//, ":last-child"
		.attr("rx", 8).attr("ry", 8)
		.attr("class", "group")
		.style("fill", function (d, i) { return color[i%5]; })
		.call(cola.drag);
	group.exit().remove();
	
	var groupName = svg.selectAll(".groupname").data(data.groups, function(d) { return d.name;});
	groupName.enter().append("text")
		.attr("class", "groupname")
		.attr("width", "40px")
		.attr("height", "13px")
		.text(function (d) { return d.name; });
	groupName.exit().remove();
	
	var link = svg.selectAll(".link").data(data.links, function(d) { return d.source.name+'-'+d.target.name;});
	link.enter().append("line").attr("class", "link").style("stroke", "rgb(168, 168, 168)");
	link.exit().remove();
	
	var nodes = svg.selectAll('.nodeimage').data(data.nodes, function(d) { return d.name;});
	nodes.enter().append('svg:image')
		.attr("class", "nodeimage")
		.call(cola.drag)
		.attr("xlink:href", function(d){
			var img = "http://icons.iconarchive.com/icons/hopstarter/sleek-xp-basic/24/Folder-icon.png";
			return img;
		})
		.attr('temp', function(d){
			var self = d3.select(this);
			self.attr("width", 25);
			self.attr("height", 25);
		})
		.on("dblclick", function(node, index, selection){
			d3.event.preventDefault();
			openGroup(node);
		});
	nodes.exit().transition().attr("width", 0).attr("width", 0).remove();
	
	var label = svg.selectAll(".label").data(data.nodes, function(d) { return d.name;});
	label.enter().append("text")
		.attr("class", "label")
		.attr("width", "40")
		.attr("height", 15)
		.text(function (d) { return d.name; })
		.call(cola.drag);
	label.exit().remove();
}

function openGroup(node){
	var i,j, flag,maxnodes = 3, groupDeletedIndex = -1;
	
	// Delete the node
	for(i = 0; i < this.data.nodes.length; i++){
		if(this.data.nodes[i].name == node.name){
			this.data.nodes.splice(i, 1);
			break;
		}
	}
	// Delete old links linked to the node
	for(i = this.data.links.length - 1; i >= 0; i--){
		if(this.data.links[i].source.name == node.name
				|| this.data.links[i].target.name == node.name){
			this.data.links.splice(i, 1);
		}
	}
	// Delete the relationship of the node
	flag = false;
	for(i = 0; i < this.data.groups.length; i++){
		for(j = 0; j < this.data.groups[i].leaves.length; j++){
			if(this.data.groups[i].leaves[j].name == node.name){
				this.data.groups[i].leaves.splice(j, 1);
				flag = true;
				groupDeletedIndex = i;
				break;
			}
		}
		if(flag)break;
	}
	
	// Create new nodes belong to openning group
	for(var i = 0; i < maxnodes; i++){
		var obj = { 
			name : node.name+'child'+i , 
			width : 100, 
			height : 100, 
			x:node.x, 
			y:node.y,
			px:node.px,
			py:node.py
		};
		this.data.nodes.push(obj);
		if(i%3!=0){
			this.data.links.push({// Create demo links
				source : this.data.nodes.length-1,
				target : Math.floor(Math.random(this.data.nodes.length-1)) 
			});
		}
	}
	// Create a group to contain the new nodes and push to groups
	this.data.groups.push({
		leaves : [], 
		name : node.name, 
		bounds : {x:node.x, y:node.y, X:node.x+100, Y:node.y+100},
		padding : 15
	});
	var begin = this.data.nodes.length - maxnodes;
	for(var i = 0; i < maxnodes; i++){
		this.data.groups[this.data.groups.length-1].leaves.push(begin+i);
	}
	
	if(groupDeletedIndex > -1){
		if(!this.data.groups[groupDeletedIndex].groups){
			this.data.groups[groupDeletedIndex].groups = [];
		}
		this.data.groups[groupDeletedIndex].groups.push(this.data.groups.length-1);
	}
	update(this.data);
}
<script src="https://marvl.infotech.monash.edu/webcola/cola.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<body/>

My codepen demo also works well : Cola open group demo

Is it possible that the relayout base on the last opening state ? Not a newly full relayout?

like image 399
Does Avatar asked Nov 07 '22 23:11

Does


1 Answers

It's been a month since I asked this question, but unfortunately few people pay attention to it.

Recently, I have been learning how to do a reasonable layout supported group feature with JavaScript library. I find some libraries, like D3, WebCola, cytoscape.js, yfile

Here is a link to compare D3 vs cytoscape. Base on it, I choose cytoscape finally, because it provide a more powerful API and reasonable algorithm to layout complex relationship. So I'm so sorry to cola.js.

cytoscape has many extensions to do a better layout work. Click the following links to see more cytoscape.js-cose-bilkent, cytoscape.js-expand-collapse, cytoscape.js-cola

like image 73
Does Avatar answered Nov 15 '22 08:11

Does