Projekt

Obecné

Profil

Stáhnout (18.7 KB) Statistiky
| Větev: | Tag: | Revize:
1 1e2b2c27 Tomáš Šimandl
2
/**
3
 * Object which represent graph.
4
 */
5
var GraphManager = {
6
	graph: null,
7
	unconList: [],
8
	isEfpGraph : false,
9
	isCompatibilityGraph: true,
10
	vertices_position: null,
11
	forceField: [],
12
	otherVertex: [],
13
	canvasSize: 0,
14
15
	// parametry layoutu
16
	inumber: 30, // pocet iteraci; default 300
17
	inumberClick: 20, // pocet iteraci u tlacitka
18
	restrength: 400, // odpudiva sila (prima umera); default 450
19
	atstrength: 510, // pritazliva sila (neprima umera, nesmi byt 0); default 110
20
	deffect: 1000, // tlumeni sily (nesmi byt 0); default 200
21
	borderRatio: 1, // hranice layoutu (cislo kterym se deli velikost canvasu)
22
	// tahle funkce se mi nelibi, je treba to vyresit jinak, nici to layout (nechat na 1)
23
24
	layouting: false,
25
	myVar: null,
26
27
	/**
28
	 * Initialization
29
	 */
30
	init: function(){
31
		this.buildGraph.bind(this);
32
		this.forceDirectedLayoutOnClick = this.forceDirectedLayoutOnClick.bind(this);
33
	},
34
35
	/**
36
	 * Apply layout and set interval
37
	 */
38
	forceDirectedLayoutOnClick: function(){
39
		if (this.layouting === true) {
40
			$('#applyLayoutImg').attr('src', 'images/layout_off.png');
41
			this.layouting = false;
42
			clearInterval(this.myVar);
43
		} else {
44
			$('#applyLayoutImg').attr('src', 'images/layout_on.png');
45
			this.layouting = true;
46
			this.myVar = window.setInterval(this.awesomeFirePenguin.bind(this), 10);
47
		}
48
	},
49
50
	/**
51
	 * Force directed layout for visible components
52
	 */
53
	awesomeFirePenguin: function(){
54
		var canvas = this.canvasSize,
55
56
			repulsiveStrength = this.restrength, // more is more
57
			attractiveStrength = this.atstrength, // more is less
58
			dampeningEffect = this.deffect,
59
			border = canvas/this.borderRatio,
60
61
			visibleVertices = [],
62
			vertices = this.graph.vertices,
63
			i = 0,
64
			j = 0,
65
			counter = 0,
66
			otherVisibleVertices = [];
67
68
		// gets the visible components
69
		for (i = 0; i < vertices.length; i++){
70
			if (vertices[i].$selector[0].isVisible()){
71
				visibleVertices[counter] = vertices[i];
72
				counter++;
73
			}
74
		}
75
76
		for (i = 0; i < this.graph.vertices.length; i++){
77
			this.forceField[i][0] = 0;
78
			this.forceField[i][1] = 0;
79
		}
80
81
		// calculate repulsive force
82
		for (i = 0; i < visibleVertices.length; i++){
83
			var currVertex = visibleVertices[i];
84
85
			// other vertices
86
			for (var j = 0; j < visibleVertices.length; j++){
87
				otherVisibleVertices[j] = visibleVertices[j];
88
			}
89
			otherVisibleVertices.splice(i, 1);
90
91
			// iterate over other vertices
92
			for (j = 0; j < otherVisibleVertices.length; j++){
93
				// currVertex position
94
				var currX = currVertex.x,
95
					currY = currVertex.y,
96
97
				// otherVertex position
98
					otherX = otherVisibleVertices[j].x,
99
					otherY = otherVisibleVertices[j].y,
100
101
				// calculate force
102
					x = currX - otherX,
103
					y = currY - otherY,
104
105
					sum = Math.pow(x,2) + Math.pow(y,2),
106
					distance = Math.sqrt(sum);
107
108
				if (distance !== 0) {
109
					this.forceField[currVertex.id-1][0] += Math.floor((x * (repulsiveStrength / distance)));
110
					this.forceField[currVertex.id-1][1] += Math.floor((y * (repulsiveStrength / distance)));
111
				}
112
			}
113
		}
114
115
		// calculate attractive forces
116
		for (i = 0; i < visibleVertices.length; i++){
117
			var currVertex = visibleVertices[i];
118
119
			for (j = 0; j < currVertex.edges.length; j++){
120
				var otherVertex;
121
122
				if (currVertex.edges[j].to == currVertex){
123
					otherVertex = currVertex.edges[j].from;
124
				} else {
125
					otherVertex = currVertex.edges[j].to;
126
				}
127
128
				if (otherVertex.$selector[0].isHidden()) continue;
129
130
				// currVertex position
131
				var currX = currVertex.x,
132
					currY = currVertex.y,
133
134
				// otherVertex position
135
					otherX = otherVertex.x,
136
					otherY = otherVertex.y,
137
138
				// calculate force
139
					x = currX - otherX,
140
					y = currY - otherY,
141
142
					sum = Math.pow(x, 2) + Math.pow(y, 2),
143
					distance = Math.sqrt(sum);
144
145
				//$('#rightPanel').append(distance+"<BR>");
146
				this.forceField[visibleVertices[i].id-1][0] += Math.round(-( (x * (distance / attractiveStrength))));
147
				this.forceField[visibleVertices[i].id-1][1] += Math.round(-( (y * (distance / attractiveStrength))));
148
				/*
149
				 this.forceField[otherVertex.id-1][0] -= Math.round(-( (x * (distance / attractiveStrength))));
150
				 this.forceField[otherVertex.id-1][1] -= Math.round(-( (y * (distance / attractiveStrength))));
151
				 */
152
			}
153
		}
154
155
		// applying the force
156
		for (i = 0; i < this.graph.vertices.length; i++){
157
			var currVertex = this.graph.vertices[i],
158
159
				halfCan = canvas / 2,
160
161
				deltaX = currVertex.x - halfCan,
162
				deltaY = currVertex.y - halfCan;
163
164
			// tohle drzi layout uprostred, chtelo by to vymyslet nejak lip, docela ho to kurvi
165
			/*
166
			 if (deltaX > 0) {
167
			 currVertex.x = Math.min(currVertex.x, (canvas/2)+border);
168
			 } else {
169
			 currVertex.x = Math.max(currVertex.x, (canvas/2)-border);
170
			 }
171
			 if (deltaY > 0) {
172
			 currVertex.y = Math.min(currVertex.y, (canvas/2)+border);
173
			 } else {
174
			 currVertex.y = Math.max(currVertex.y, (canvas/2)-border);
175
			 }
176
			 */
177
178
			// kolecko
179
			var dist = Math.sqrt(Math.pow(deltaX,2)+Math.pow(deltaY,+2)),
180
				maxDist = Math.sqrt(Math.pow(border,2)+Math.pow(border,+2));
181
182
			if (dist > maxDist){
183
				var ratio = maxDist / dist,
184
185
					newX = deltaX * ratio,
186
					newY = deltaY * ratio;
187
188
				currVertex.x += newX - deltaX;
189
				currVertex.y += newY - deltaY;
190
			}
191
192
			// force dampening
193
			var forceX = Math.floor(this.forceField[i][0]/dampeningEffect),
194
				forceY = Math.floor(this.forceField[i][1]/dampeningEffect);
195
196
			// adding a random effect
197
			/*
198
			 forceX += -3+Math.floor((Math.random()*6)+1);
199
			 forceY += -3+Math.floor((Math.random()*6)+1);
200
			 */
201
202
203
			// moving a component
204
			if (Math.abs(forceX) > 1 || Math.abs(forceY) > 1){
205
				currVertex.x += forceX;
206
				currVertex.y += forceY;
207
			}
208
209
			// translating the graph
210
			currVertex.$selector.attr('transform', 'translate(' + currVertex.x + ',' + currVertex.y + ')');
211
212
			// translating edges
213
			var newX, newY, lollipop;
214
215
			if (currVertex.edges !== null) {
216
				for (var k = 0; k< currVertex.edges.length; k++) {
217
					var edge = currVertex.edges[k],
218
						sizeOfRectFrom = edge.from.size,
219
						sizeOfRectTo = edge.to.size;
220
221
					if(edge.from.id == currVertex.id){
222
						newX = currVertex.edges[k].to.x + sizeOfRectTo/2;
223
						newY = currVertex.edges[k].to.y + 13;
224
						lollipop = getLollipopPosition(currVertex.x + sizeOfRectFrom/2, currVertex.y +13 , newX, newY);
225
226
						edge.$selector.children("line").attr('x1', currVertex.x + sizeOfRectFrom/2);
227
						edge.$selector.children("line").attr('y1', currVertex.y + 13);
228
						edge.$selector.children("g").attr('transform', 'rotate(' + lollipop.angle + ',' + lollipop.x + ',' + lollipop.y  + ') translate('+ lollipop.x + ',' + lollipop.y  + ')');
229
230
						ViewportManager.preventTickRotation(edge, lollipop);
231
						ViewportManager.preventCrossRotation(edge, lollipop);
232
					} else {
233
						newX = currVertex.edges[k].from.x + sizeOfRectFrom/2;
234
						newY = currVertex.edges[k].from.y + 13;
235
						lollipop = getLollipopPosition(newX, newY, currVertex.x + sizeOfRectTo/2, currVertex.y + 13);
236
237
						edge.$selector.children("line").attr('x2', currVertex.x + sizeOfRectTo/2);
238
						edge.$selector.children("line").attr('y2', currVertex.y + 13);
239
						edge.$selector.children("g").attr('transform', 'rotate(' + lollipop.angle + ',' + lollipop.x + ',' + lollipop.y  + ') translate('+ lollipop.x + ',' + lollipop.y  + ')');
240
241
						ViewportManager.preventTickRotation(edge, lollipop);
242
						ViewportManager.preventCrossRotation(edge, lollipop);
243
					}
244
				}
245
			}
246
		}
247
	},
248
249
	/**
250
	 * Force Directed Layout
251
	 *
252
	 * @param canvas canvas
253
	 */
254
	forceDirectedLayout: function(canvas) {
255
		var repulsiveStrength = this.restrength, // more is more
256
			attractiveStrength = this.atstrength, // more is less
257
			dampeningEffect = this.deffect,
258
			border = canvas/this.borderRatio;
259
260
		//$('#rightPanel').append("s<BR>");
261
262
		var length = this.graph.vertices.length;
263
		// initialize repulsive forces
264
265
		for (var i = 0; i < length; i++){
266
			this.forceField[i][0] = 0;
267
			this.forceField[i][1] = 0;
268
		}
269
270
		// calculate repulsive force
271
		for (var i = 0; i < length; i++){
272
273
			var currVertex = this.graph.vertices[i];
274
			//var otherVertex = new Array();
275
276
			// other vertices
277
			for (var j = 0; j < length; j++){
278
				this.otherVertex[j] = this.graph.vertices[j];
279
			}
280
			this.otherVertex.splice(i, 1);
281
282
			// iterate over other vertices
283
			for (var j = 0; j < this.otherVertex.length; j++){
284
				// currVertex position
285
				var currX = currVertex.x,
286
					currY = currVertex.y,
287
288
				// otherVertex position
289
					otherX = this.otherVertex[j].x,
290
					otherY = this.otherVertex[j].y,
291
292
				// calculate force
293
					x = currX - otherX,
294
					y = currY - otherY,
295
296
					sum = Math.pow(x,2) + Math.pow(y,2),
297
					distance = Math.sqrt(sum);
298
299
				if (distance !== 0) {
300
					this.forceField[i][0] += Math.floor((x * (repulsiveStrength / distance)));
301
					this.forceField[i][1] += Math.floor((y * (repulsiveStrength / distance)));
302
				}
303
			}
304
305
			//$('#rightPanel').append(forceField[i][0]+" | "+forceField[i][1]+" - rf<BR>");
306
		}
307
308
		// calculate attractive forces
309
		for (var i = 0; i < length; i++){
310
			var currVertex = this.graph.vertices[i];
311
			//$('#rightPanel').append(currVertex.id+"<BR>");
312
			//$('#rightPanel').append(currVertex.edges.length+"<BR>");
313
			for (var j = 0; j < currVertex.edges.length; j++){
314
				var otherVertex;
315
316
				if (currVertex.edges[j].to == currVertex){
317
					otherVertex = currVertex.edges[j].from;
318
				} else {
319
					otherVertex = currVertex.edges[j].to;
320
				}
321
322
				// currVertex position
323
				var currX = currVertex.x,
324
					currY = currVertex.y,
325
326
				// otherVertex position
327
					otherX = otherVertex.x,
328
					otherY = otherVertex.y,
329
330
				// calculate force
331
					x = currX - otherX,
332
					y = currY - otherY,
333
334
					sum = Math.pow(x, 2) + Math.pow(y, 2),
335
					distance = Math.sqrt(sum);
336
337
				//$('#rightPanel').append(distance+"<BR>");
338
				this.forceField[i][0] += Math.round(-( (x * (distance / attractiveStrength))));
339
				this.forceField[i][1] += Math.round(-( (y * (distance / attractiveStrength))));
340
				/*
341
				 this.forceField[otherVertex.id-1][0] -= Math.round(-( (x * (distance / attractiveStrength))));
342
				 this.forceField[otherVertex.id-1][1] -= Math.round(-( (y * (distance / attractiveStrength))));
343
				 */
344
			}
345
			//$('#rightPanel').append(forceField[i][0]+" | "+forceField[i][1]+" - ff<BR>");
346
		}
347
348
		// applying the force
349
		for (var i = 0; i < length; i++){
350
			var currVertex = this.graph.vertices[i],
351
352
				deltaX = currVertex.x - (canvas/2),
353
				deltaY = currVertex.y - (canvas/2);
354
355
			// tohle drzi layout uprostred, chtelo by to vymyslet nejak lip, docela ho to kurvi
356
			/*
357
			 if (deltaX > 0) {
358
			 currVertex.x = Math.min(currVertex.x, (canvas/2)+border);
359
			 } else {
360
			 currVertex.x = Math.max(currVertex.x, (canvas/2)-border);
361
			 }
362
			 if (deltaY > 0) {
363
			 currVertex.y = Math.min(currVertex.y, (canvas/2)+border);
364
			 } else {
365
			 currVertex.y = Math.max(currVertex.y, (canvas/2)-border);
366
			 }
367
			 */
368
369
			//kolecko
370
			var dist = Math.sqrt(Math.pow(deltaX,2)+Math.pow(deltaY,+2)),
371
				maxDist = Math.sqrt(Math.pow(border,2)+Math.pow(border,+2));
372
373
			if (dist > maxDist){
374
				var ratio = maxDist / dist,
375
376
					newX = deltaX * ratio,
377
					newY = deltaY * ratio;
378
379
				currVertex.x += newX - deltaX;
380
				currVertex.y += newY - deltaY;
381
			}
382
383
			// force dampening
384
			var forceX = Math.floor(this.forceField[i][0]/dampeningEffect),
385
				forceY = Math.floor(this.forceField[i][1]/dampeningEffect);
386
387
			// adding a random effect
388
			/*
389
			 forceX += -3+Math.floor((Math.random()*6)+1);
390
			 forceY += -3+Math.floor((Math.random()*6)+1);
391
			 */
392
393
			//$('#rightPanel').append(forceX+" | "+forceY+" - asaf<BR>");
394
395
			// moving a component
396
			currVertex.x += forceX;
397
			currVertex.y += forceY;
398
		}
399
	},
400
401
	/**
402
	 * Create graph from data which were send by server.
403
	 */
404
	buildGraph: function(){
405
		var vertexMap = {},
406
			verticesBuff = '',
407
			edgesBuff = '',
408
			position_loaded = false;
409
410
		if (this.graph === null) {
411
			alert('Error loading graph.');
412
			window.location.href = app.HOME_URL;
413
			return;
414
		}
415
416
		if (this.graph !== null && this.graph.cause !== undefined) {
417
			console.log("Error generating graph.");
418
			console.log(this.graph);
419
			app.loader.disable();
420
			alert("Error generating graph. Check the console for details.");
421
			return;
422
		}
423
424
		// start generating graph
425
		var odm = Math.sqrt(this.graph.vertices.length),
426
			canvas = ((this.graph.vertices.length * 75) / Math.round(odm)) + 1000;
427
428
		this.canvasSize = canvas;
429
		this.graph.vertices.sort(function(a, b) {
430
			return a.id > b.id;
431
		});
432
433
		// initialize vertices
434
		for (var i = 0; i < this.graph.vertices.length; i++) {
435
			var vertex = this.graph.vertices[i];
436
			vertexMap[vertex.symbolicName] = vertex;
437
			vertex.edges = [];
438
			vertex.gridMark = new GridMarks();
439
440
			//pokud je nastavena pozice x y u prvku - nastavit tuto pozici
441
			if (this.vertices_position !== null && this.vertices_position.vertices_position !== null) {
442
				$.each(this.vertices_position.vertices_position, function(i, item) {
443
					if ("vertex" + vertex.id == item.id){
444
						var coords = getCoordinates(item.transform);
445
446
						vertex.x = parseFloat(coords.x);
447
						vertex.y = parseFloat(coords.y);
448
449
						position_loaded = true;
450
						return false;	// break
451
					}
452
				});
453
			}
454
455
			// nahodna pozice
456
			if (position_loaded === false) {
457
				vertex.x = Math.floor(Math.random() * canvas);
458
				vertex.y = Math.floor(Math.random() * canvas);
459
			}
460
		}
461
462
		// initialize edges
463
		for (var j = 0; j < this.graph.edges.length; j++) {
464
			var edge = this.graph.edges[j];
465
			edge.from = vertexMap[edge.from];
466
467
			if (typeof vertexMap[edge.to] !== 'undefined') {
468
				edge.to = vertexMap[edge.to];
469
			} else {
470
				edge.to = vertexMap["vertex_NOT_FOUND"];
471
			}
472
473
			edge.from.edges.push(edge);
474
			edge.to.edges.push(edge);
475
		}
476
477
		// calling force layout
478
		if (position_loaded === false) {
479
			//var avgX = 0, avgY = 0;
480
481
			for (var x = 0; x < this.graph.vertices.length; x++) {
482
				this.forceField[x] = [];
483
			}
484
485
			for (var x = 0; x < this.inumber; x++){
486
				this.forceDirectedLayout(canvas);
487
			}
488
		}
489
490
		// fill the vertices buffer
491
		for (var i = 0; i < this.graph.vertices.length; i++) {
492
			var vertex = this.graph.vertices[i];
493
			vertex.size = getSizeOfRectangle(vertex.name);
494
495
			verticesBuff += SvgFactory.createVertex(vertex);
496
		}
497
498
		// fill the edges buffer
499
		for (var j = 0; j < this.graph.edges.length; j++){
500
			var edge = this.graph.edges[j];
501
502
			edgesBuff += SvgFactory.createEdge(edge, this.isEfpGraph);
503
		}
504
505
		$("#graph #edges").html(edgesBuff);
506
		$("#graph #vertices").html(verticesBuff);
507
508
		this.setSizeOfSvg();
509
		this.centerGraphInViewport(1);
510
		this.setTooltips();
511
		this.fillUnconnectedList();
512
		this.setVertexCounters();
513
514
		/* Creating text field with total number of components loaded */
515
		$('#allComps').text(this.graph.vertices.length+ ' components displayed');
516
517
		/* Highlight if there are any incompability */
518
		if (!this.isJarsCompatible()) {
519
			$('#incomCmpList').empty();
520
521
			var missingComponents = this.getMissingComponents();
522
			missingComponents.forEach(function(component) {
523
				$('#incomCmpList').append('<li>' + component + '</li>');
524
			});
525
526
			$('#incompatible').show();
527
			$('#allIncomps').show();
528
		}
529
530
		// vytvoreni seznamu nepropojenych komponent
531
		for (var i = 0; i < this.unconList.length; i++){
532
			var ind = this.unconList[i],
533
				name = this.graph.vertices[ind-1].name;
534
535
			$('#unconCmpList').append('<li class="node vertex" id="liuc' + ind + '" data-id="' + ind + '"><p data-vertexId="' + ind + '" data-title="' + name + '">' + name + '</p><img class="" id="liuc_del_' + ind  + '" alt="delete" src="images/button_cancel.png"/></li>');
536
		}
537
538
		app.loader.disable();
539
	},
540
541
	/**
542
	 * Set size of svg according bounding box of svg.
543
	 */
544
	setSizeOfSvg: function(){
545
		var svg = $("#svg1"),
546
			bbox = svg.get(0).getBBox(),
547
			viewportWidth = stringToInt(cutTwoLastCharacters($("#viewport").css("width"))),
548
			viewportHeight = stringToInt(cutTwoLastCharacters($("#viewport").css("height")));
549
550
		if (bbox.width < viewportWidth){
551
			svg.css("width", viewportWidth*1.5);
552
		} else {
553
			svg.css("width", bbox.width*1.5);
554
		}
555
556
		if (bbox.height < viewportHeight){
557
			svg.css("height", viewportHeight*1.5);
558
		} else {
559
			svg.css("height", bbox.height*1.5);
560
		}
561
	},
562
563
	/**
564
	 * Centers graph in viewport. 
565
	 * @param currentZoom
566
	 */
567
	centerGraphInViewport: function(currentZoom){
568
		var svg = $("#svg1"),
569
			graph = $("#graph"),
570
			bboxSvg = svg.get(0).getBBox(),
571
			svgWidth = stringToInt(cutTwoLastCharacters(svg.css("width"))),
572
			svgHeight = stringToInt(cutTwoLastCharacters(svg.css("height"))),
573
574
			centerSvgX = svgWidth/2.0,
575
			centerSvgY = svgHeight/2.0,
576
577
			newPositionGraphX = centerSvgX - bboxSvg.width/2.0 - bboxSvg.x,
578
			newPositionGraphY = centerSvgY - bboxSvg.height/2.0 - bboxSvg.y,
579
			viewport = $("#viewport");
580
581
		viewport.scrollLeft(-(svgWidth-(viewport.width()-17))/2.0);
582
		viewport.scrollTop((svgHeight-(viewport.height()-17))/2.0);
583
		graph.attr("transform", "translate(" + newPositionGraphX/currentZoom + "," + newPositionGraphY/currentZoom + ")");
584
585
		return "translate(" + newPositionGraphX/currentZoom + "," + newPositionGraphY/currentZoom + ")";
586
	},
587
588
	/**
589
	 * Sets qTip tooltips to edge lollipops and vertex interface symbols
590
	 */
591
	setTooltips: function() {
592
		configurationEdgeTooltip(".lollipop");
593
594
		// only for non-EFP graphs
595
		if (!GraphManager.isEfpGraph) {
596
			configurationVertexTooltip(".interface");
597
		}
598
	},
599
600
	/**
601
	 * Fills list of unconnected components in graph.
602
	 */
603
	fillUnconnectedList: function() {
604
		var unCount = 0;
605
606
		for (var i = 0; i < this.graph.vertices.length; i++){
607
			var vertex = this.graph.vertices[i];
608
609
			if (vertex.edges.length === 0) {
610
				this.unconList[unCount] = vertex.id;
611
				$('#vertex'+ vertex.id).hide();
612
613
				vertex.leftAlone = true;
614
615
				unCount++;
616
			}
617
		}		
618
	},
619
620
	/**
621
	 * Sets vertex provided and required components counters.
622
	 */
623
	setVertexCounters: function() {
624
		for (var i = 0; i < this.graph.vertices.length; i++){
625
			var countFrom = 0,
626
				countTo = 0,
627
				vertex = this.graph.vertices[i];
628
629
			if (this.isEfpGraph) { // legacy method
630
				// count TO and FROM numbers for lollipops
631
				for (var j = 0; j < vertex.edges.length;j++){
632
					if (vertex.id == vertex.edges[j].from.id){
633
						countFrom++;
634
					}
635
				}
636
				countTo = vertex.edges.length - countFrom;
637
638
				$('#rectTopText'+ vertex.id).text(countTo);
639
				$('#rectBotText'+ vertex.id).text(countFrom);
640
			} else { // count lollis by packages
641
				$('#rectTopText'+ vertex.id).text(vertex.importedPackages.length);
642
				$('#rectBotText'+ vertex.id).text(vertex.exportedPackages.length);
643
			}
644
		}
645
	},
646
	
647
	/**
648
	 * Finds incompatible jars by vertex name.
649
	 * @returns true if all jars are compatible, otherwise false 
650
	 */
651
	isJarsCompatible: function() {
652
		return this.graph.vertices.every(function(vertex) {
653
			return vertex.name !== "NOT_FOUND";
654
		});
655
	},
656
657
	/**
658
	 * Retrieves all missing components.
659
	 *
660
	 * @returns array List of missing components.
661
	 */
662
	getMissingComponents: function() {
663
		var missing = [];
664
665
		this.graph.edges.forEach(function(edge) {
666
			if (edge.from.name === "NOT_FOUND") {
667
				var compatibilityInfo = JSON.parse(edge.compInfoJSON);
668
669
				missing.push(compatibilityInfo[0].causedBy);
670
			}
671
		});
672
673
		return missing;
674
	},
675
};