Projekt

Obecné

Profil

Stáhnout (25.4 KB) Statistiky
| Větev: | Tag: | Revize:
1
/**
2
 * Class representing a group of vertices in graph. A group is composed from one or more vertices.
3
 * @constructor
4
 * @param {object} props Properties of the group.
5
 */
6
function Group(props) {
7
	/** @prop {integer} id Unique identifier of the group. */
8
	this.id = app.groupList.length + 1;
9
	/** @prop {string} name Name of the group. */
10
	this.name = 'Group ' + this.id;
11
	/** @prop {array} symbol Symbol of the group. */
12
	this.symbol = app.markSymbol.getMarkSymbol();
13

    
14
	var rootElement;
15
	var vertexListComponent;
16

    
17
	var position = new Coordinates(0, 0);
18
	var size = {
19
		width: 70,
20
		height: 70,
21
	};
22
	var requiredCounter = 0;
23
	var providedCounter = 0;
24
	var floater = null;
25

    
26
	var pan = false;
27
	var excluded = false;
28
	var dimmed = false;
29
	var highlighted = false;
30
	var highlightedRequired = false;
31
	var highlightedProvided = false;
32
	var highlightedRequiredNeighbours = false;
33
	var highlightedProvidedNeighbours = false;
34

    
35
	var vertexList = [];
36

    
37
	/**
38
	 * Adds a new vertex to the group. The vertex is set as excluded and its DOM element is removed from document. Its edges are moved so that they end at the group.
39
	 * @param {Vertex} vertex Vertex to be added to the group.
40
	 */
41
	this.addVertex = function(vertex) {
42
		if (!(vertex instanceof Vertex)) {
43
			throw new TypeError(vertex.toString() + 'is not instance of Vertex');
44
		}
45

    
46
		if (vertexList.length === 0) {
47
			position = vertex.getPosition();
48
		}
49

    
50
		vertex.setGroup(this);
51
		vertex.setExcluded(this.isExcluded());
52
		vertex.remove(this.isExcluded());	// edges of the vertex should be removed from DOM only if group is excluded
53

    
54
		vertex.getInEdgeList().forEach(function(edge) {
55
			if (this.isExcluded()) {
56
				floater.addInEdge(edge);
57
			} else {
58
				edge.moveEnd(this.getCenter());
59
			}
60
		}, this);
61

    
62
		vertex.getOutEdgeList().forEach(function(edge) {
63
			if (this.isExcluded()) {
64
				floater.addOutEdge(edge);
65
			} else {
66
				edge.moveStart(this.getCenter());
67
			}
68
		}, this);
69

    
70
		app.viewportComponent.removeVertex(vertex);
71

    
72
		if (rootElement) {
73
			vertexListComponent.appendChild(vertex);
74
		}
75

    
76
		requiredCounter += vertex.getInEdgeList().length;
77
		providedCounter += vertex.getOutEdgeList().length;
78

    
79
		vertexList.push(vertex);
80
	};
81

    
82
	/**
83
	 * Removes a vertex from the group. The vertex is returned back to the viewport and its edges are moved to it.
84
	 * @param {Vertex} vertex Vertex to be removed from the group.
85
	 */
86
	this.removeVertex = function(vertex) {
87
		if (!(vertex instanceof Vertex)) {
88
			throw new TypeError(vertex.toString() + 'is not instance of Vertex');
89
		}
90

    
91
		vertex.setGroup(null);
92

    
93
		vertex.getInEdgeList().forEach(function(edge) {
94
			edge.moveEnd(this.getCenter());
95
		}, vertex);
96

    
97
		vertex.getOutEdgeList().forEach(function(edge) {
98
			edge.moveStart(this.getCenter());
99
		}, vertex);
100

    
101
		app.viewportComponent.addVertex(vertex);
102

    
103
		if (rootElement) {
104
			vertexListComponent.removeChild(vertex);
105
		}
106

    
107
		requiredCounter -= vertex.getInEdgeList().length;
108
		providedCounter -= vertex.getOutEdgeList().length;
109

    
110
		vertexList.splice(vertexList.indexOf(vertex), 1);
111
	};
112
	
113
	/**
114
	 * @returns {array<Vertex>} List of vertices added to the group.
115
	 */
116
	this.getVertexList = function() {
117
		return vertexList;
118
	};
119

    
120
	/**
121
	 * @returns {array<Edge>} List of edges going to vertices added to the group.
122
	 */
123
	this.getInEdgeList = function() {
124
		var edgeList = [];
125

    
126
		vertexList.forEach(function(vertex) {
127
			vertex.getInEdgeList().filter(function(edge) {
128
				return edgeList.indexOf(edge) === -1;
129
			}).forEach(function(edge) {
130
				edgeList.push(edge);
131
			});
132
		});
133

    
134
		return edgeList;
135
	};
136

    
137
	/**
138
	 * @returns {array<Edge>} List of edges going from vertices added to the group.
139
	 */
140
	this.getOutEdgeList = function() {
141
		var edgeList = [];
142

    
143
		vertexList.forEach(function(vertex) {
144
			vertex.getOutEdgeList().filter(function(edge) {
145
				return edgeList.indexOf(edge) === -1;
146
			}).forEach(function(edge) {
147
				edgeList.push(edge);
148
			});
149
		});
150

    
151
		return edgeList;
152
	};
153

    
154
	/**
155
	 * @returns {array<Edge>} List of all edges related to vertices added to the group.
156
	 */
157
	this.getEdgeList = function() {
158
		return [].concat(this.getInEdgeList(), this.getOutEdgeList());
159
	};
160

    
161
	/**
162
	 * @returns {Coordinates} Current position of the group in graph.
163
	 */
164
	this.getPosition = function() {
165
		return position;
166
	};
167

    
168
	/**
169
	 * Updates the current position of the group in graph.
170
	 */
171
	this.setPosition = function(coords) {
172
		if (!(coords instanceof Coordinates)) {
173
			throw new TypeError(coords.toString() + 'is not instance of Coordinates');
174
		}
175

    
176
		position = coords;
177
	};
178

    
179
	/**
180
	 * @returns {Coordinates} Centre of the group.
181
	 */
182
	this.getCenter = function() {
183
		return new Coordinates(
184
			position.x + size.width / 2,
185
			position.y + size.height / 2,
186
		);
187
	};
188

    
189
	/**
190
	 * Moves the group to a new position in graph. Edges related to vertices in the group are moved as well.
191
	 * @param {Coordinates} coords Coordinates to be moved to.
192
	 */
193
	this.move = function(coords) {
194
		if (!(coords instanceof Coordinates)) {
195
			throw new TypeError(coords.toString() + 'is not instance of Coordinates');
196
		}
197

    
198
		rootElement.setAttribute('x', coords.x);
199
		rootElement.setAttribute('y', coords.y);
200

    
201
		vertexList.forEach(function(vertex) {
202
			vertex.getInEdgeList().forEach(function(edge) {
203
				edge.moveEnd(new Coordinates(
204
					coords.x + size.width / 2,
205
					coords.y + size.height / 2,
206
				));
207
			});
208

    
209
			vertex.getOutEdgeList().forEach(function(edge) {
210
				edge.moveStart(new Coordinates(
211
					coords.x + size.width / 2,
212
					coords.y + size.height / 2,
213
				));
214
			});
215
		}, this);
216
	};
217

    
218
	/**
219
	 * Toggles transparency of the group.
220
	 * @param {boolean} newValue True to set the group semitransparent, false to display it normally.
221
	 */
222
	this.setDimmed = function(newValue) {
223
		dimmed = newValue;
224

    
225
		if (excluded) return;
226

    
227
		if (newValue) {
228
			rootElement.classList.add('node--dimmed');
229
		} else {
230
			rootElement.classList.remove('node--dimmed');
231
		}
232
	};
233

    
234
	/**
235
	 * Toggles highlighting of the group.
236
	 * @param {boolean} newValue True to highlight the group, false to unhighlight.
237
	 */
238
	this.setHighlighted = function(newValue) {
239
		highlighted = newValue;
240

    
241
		if (newValue) {
242
			rootElement.classList.add('node--highlighted');
243
		} else {
244
			rootElement.classList.remove('node--highlighted');
245
		}
246
	};
247

    
248
	/**
249
	 * Toggles highlighting of the group to mark it as requirement of some other node.
250
	 * @param {boolean} newValue True to highlight the group as required, false to unhighlight.
251
	 */
252
	this.setHighlightedRequired = function(newValue) {
253
		highlightedRequired = newValue;
254

    
255
		if (newValue) {
256
			rootElement.classList.add('node--highlighted-required');
257
		} else {
258
			rootElement.classList.remove('node--highlighted-required');
259
		}
260
	};
261
	
262
	/**
263
	 * Toggles highlighting of the group to mark it as dependent of some other node.
264
	 * @param {boolean} newValue True to highlight the group as provided, false to unhighlight.
265
	 */
266
	this.setHighlightedProvided = function(newValue) {
267
		highlightedProvided = newValue;
268

    
269
		if (newValue) {
270
			rootElement.classList.add('node--highlighted-provided');
271
		} else {
272
			rootElement.classList.remove('node--highlighted-provided');
273
		}
274
	};
275
	
276
	/**
277
	 * Toggles highlighting of the group when only its requirements should be highlighted. Anytime this value is changed, generic 
278
	 * {@link Group#setHighlighted} method should be called too.
279
	 * @param {boolean} newValue True to highlight the group when only its requirements should be highlighted, false to unhighlight.
280
	 */
281
	this.setHighlightedRequiredNeighbours = function(newValue) {
282
		highlightedRequiredNeighbours = newValue;
283

    
284
		if (newValue) {
285
			rootElement.classList.add('node--highlighted-required-neighbours');
286
		} else {
287
			rootElement.classList.remove('node--highlighted-required-neighbours');
288
		}
289
	};
290
	
291
	/**
292
	 * Toggles highlighting of the group when only its dependents should be highlighted. Anytime this value is changed, generic 
293
	 * {@link Group#setHighlighted} method should be called too.
294
	 * @param {boolean} newValue True to highlight the group when only its dependents should be highlighted, false to unhighlight.
295
	 */
296
	this.setHighlightedProvidedNeighbours = function(newValue) {
297
		highlightedProvidedNeighbours = newValue;
298

    
299
		if (newValue) {
300
			rootElement.classList.add('node--highlighted-provided-neighbours');
301
		} else {
302
			rootElement.classList.remove('node--highlighted-provided-neighbours');
303
		}
304
	};
305

    
306
	/**
307
	 * @returns {boolean} True is the group is currently excluded from the viewport, otherwise false.
308
	 */
309
	this.isExcluded = function() {
310
		return excluded;
311
	};
312

    
313
	/**
314
	 * Toggles excluded state of the group. If the group is set excluded, a new floating point is created to connect it with 
315
	 * related nodes in the viewport. Otherwise, the floating point is deleted.
316
	 * Any node is called excluded when it is not visible in the viewport but instead in the sidebar.
317
	 * @param {boolean} newValue True to set the group as excluded, otherwise false.
318
	 */
319
	this.setExcluded = function(newValue) {
320
		excluded = newValue;
321

    
322
		vertexList.forEach(function(vertex) {
323
			vertex.setExcluded(newValue);
324
		});
325

    
326
		if (newValue) {
327
			// set floater
328
			floater = new FloatingPoint;
329
			floater.setNode(this);
330
			app.sidebarComponent.addFloater(floater);
331

    
332
		} else {
333
			// remove floater
334
			app.sidebarComponent.removeFloater(floater);
335
			delete floater;
336
		}
337
	};
338

    
339
	/**
340
	 * Excludes the group from the viewport. Removes group DOM element and hides its edges.
341
	 */
342
	this.exclude = function() {
343
		this.setExcluded(true);
344
		this.remove(true);
345

    
346
		app.viewportComponent.removeGroup(this);
347
	};
348

    
349
	/**
350
	 * Includes the group in the viewport. Afterwards, edges related to the group are moved to the current position of the group.
351
	 */
352
	this.include = function() {
353
		this.removeFromSidebarList();
354

    
355
		this.setExcluded(false);
356
		this.remove(false);
357

    
358
		app.viewportComponent.addGroup(this);
359

    
360
		// set edges' ends
361
		var inEdgeList = this.getInEdgeList();
362
		inEdgeList.forEach(function(edge) {
363
			edge.moveEnd(this.getCenter());
364
		}, this);
365

    
366
		var outEdgeList = this.getOutEdgeList();
367
		outEdgeList.forEach(function(edge) {
368
			edge.moveStart(this.getCenter());
369
		}, this);
370
	};
371

    
372
	/**
373
	 * Hook function used to remove the group from the sidebar list it is located in before it is moved to the viewport.
374
	 */
375
	this.removeFromSidebarList = app.utils.noop;
376

    
377
	/**
378
	 * Creates a new DOM element representing the group in memory. The element being created depends on whether the group
379
	 * is excluded at the moment. Binds user interactions to local handler functions.
380
	 * @returns {Element} depending on whether the group is excluded.
381
	 */
382
	this.render = function() {
383
		rootElement = excluded ? renderExcluded.call(this) : renderIncluded.call(this);
384

    
385
		this.setHighlighted(highlighted);
386
		this.setHighlightedRequiredNeighbours(highlightedRequiredNeighbours);
387
		this.setHighlightedProvidedNeighbours(highlightedProvidedNeighbours);
388

    
389
		return rootElement;
390
	};
391

    
392
	/**
393
	 * Removes the DOM element representing the group from document.
394
	 * @param {boolean} hideEdges True to hide edges of vertices in the group in the viewport. Edges are (almost) never really
395
	 * removed but rather hidden for cases when a node is included back in the viewport.
396
	 */
397
	this.remove = function(hideEdges) {
398
		rootElement.remove();
399

    
400
		// hide edges
401
		var inEdgeList = this.getInEdgeList();
402
		inEdgeList.filter(function(edge) {
403
			return !edge.getFrom().isExcluded();
404
		}).forEach(function(edge) {
405
			edge.setHidden(hideEdges);
406
		});
407

    
408
		var outEdgeList = this.getOutEdgeList();
409
		outEdgeList.filter(function(edge) {
410
			return !edge.getTo().isExcluded();
411
		}).forEach(function(edge) {
412
			edge.setHidden(hideEdges);
413
		});
414
	};
415

    
416
	/**
417
	 * @returns {Element} SVG DOM element.
418
	 */
419
	function renderIncluded() {
420
		rootElement = app.utils.createSvgElement('svg', {
421
			'class': 'node group',
422
			'x': position.x,
423
			'y': position.y,
424
			'data-id': this.id,
425
		});
426
		rootElement.addEventListener('click', click.bind(this));
427
		rootElement.addEventListener('mousedown', mouseDown.bind(this));
428

    
429
		rootElement.appendChild(app.utils.createSvgElement('rect', {
430
			'width': size.width,
431
			'height': size.height,
432
			'x': 1,
433
			'y': 1,
434
			'fill': this.symbol[1],
435
			'stroke': 'black',
436
			'stroke-width': 1,
437
		}));
438

    
439
		// name
440
		var nameText = app.utils.createSvgElement('text', {
441
			'class': 'group-name',
442
			'x': 0,
443
			'y': 15,
444
		});
445
		nameText.appendChild(document.createTextNode(this.name));
446
		nameText.addEventListener('click', nameClick.bind(this));
447
		rootElement.appendChild(nameText);
448

    
449
		// symbol
450
		var symbolText = app.utils.createSvgElement('text', {
451
			'class': 'group-symbol',
452
			'x': 10,
453
			'y': 60,
454
		});
455
		symbolText.appendChild(document.createTextNode(this.symbol[0]));
456
		rootElement.appendChild(symbolText);
457

    
458
		// expand button
459
		var expandButton = app.utils.createSvgElement('g', {
460
			'class': 'button expand-button',
461
		});
462
		expandButton.appendChild(app.utils.createSvgElement('rect', {
463
			'rx': 4,
464
			'ry': 4,
465
			'x': 4,
466
			'y': 4,
467
			'height': 14,
468
			'width': 14,
469
		}));
470
		expandButton.appendChild(app.utils.createSvgElement('line', {
471
			'x1': 11,
472
			'y1': 6,
473
			'x2': 11,
474
			'y2': 16,
475
		}));
476
		expandButton.appendChild(app.utils.createSvgElement('line', {
477
			'x1': 6,
478
			'y1': 11,
479
			'x2': 16,
480
			'y2': 11,
481
		}));
482
		expandButton.addEventListener('click', expandClick.bind(this));
483
		rootElement.appendChild(expandButton);
484

    
485
		// compress button
486
		var compressButton = app.utils.createSvgElement('g', {
487
			'class': 'button compress-button',
488
		});
489
		compressButton.appendChild(app.utils.createSvgElement('rect', {
490
			'rx': 4,
491
			'ry': 4,
492
			'x': 4,
493
			'y': 4,
494
			'height': 14,
495
			'width': 14,
496
		}));
497
		compressButton.appendChild(app.utils.createSvgElement('line', {
498
			'x1': 6,
499
			'y1': 11,
500
			'x2': 16,
501
			'y2': 11,
502
		}));
503
		compressButton.addEventListener('click', compressClick.bind(this));
504
		rootElement.appendChild(compressButton);
505

    
506
		// dissolve button
507
		var dissolveButton = app.utils.createSvgElement('g', {
508
			'class': 'button dissolve-button',
509
		});
510
		dissolveButton.appendChild(app.utils.createSvgElement('rect', {
511
			'rx': 4,
512
			'ry': 4,
513
			'x': 52,
514
			'y': 4,
515
			'height': 14,
516
			'width': 14,
517
		}));
518
		dissolveButton.appendChild(app.utils.createSvgElement('line', {
519
			'x1': 55,
520
			'y1': 7,
521
			'x2': 63,
522
			'y2': 15,
523
		}));
524
		dissolveButton.appendChild(app.utils.createSvgElement('line', {
525
			'x1': 63,
526
			'y1': 7,
527
			'x2': 55,
528
			'y2': 15,
529
		}));
530
		dissolveButton.addEventListener('click', dissolveClick.bind(this));
531
		rootElement.appendChild(dissolveButton);
532

    
533
		// vertex list
534
		vertexListComponent = new GroupVertexList(this);
535
		rootElement.appendChild(vertexListComponent.render());
536

    
537
		vertexList.forEach(function(vertex) {
538
			vertexListComponent.appendChild(vertex);
539
		}, this);
540

    
541
		return rootElement;
542
	}
543

    
544
	/**
545
	 * @returns {Element} HTML DOM element.
546
	 */
547
	function renderExcluded() {
548
		rootElement = app.utils.createHtmlElement('li', {
549
			'class': 'node group',
550
			'data-id': this.id,
551
		});
552

    
553
		var svg = app.utils.createSvgElement('svg', {
554
			'xmlns': 'http://www.w3.org/2000/svg',
555
			'height': 60,
556
			'width': 40,
557
		});
558
		rootElement.appendChild(svg);
559

    
560
		var group = app.utils.createSvgElement('g', {
561
			'transform': 'translate(60,10)',
562
		});
563
		svg.appendChild(group);
564

    
565
		// required
566
		var required = app.utils.createSvgElement('g', {
567
			'class': 'required-counter',
568
		});
569
		required.addEventListener('click', requiredClick.bind(this));
570
		group.appendChild(required);
571

    
572
		required.appendChild(app.utils.createSvgElement('line', {
573
			'x1': -60,
574
			'y1': 5,
575
			'x2': -50,
576
			'y2': 5,
577
			'stroke': 'black',
578
			'class': 'outer-floater',
579
		}));
580
		required.appendChild(app.utils.createSvgElement('line', {
581
			'x1': -30,
582
			'y1': 5,
583
			'x2': -20,
584
			'y2': 5,
585
			'stroke': 'black',
586
		}));
587
		required.appendChild(app.utils.createSvgElement('path', {
588
			'class': 'lollipop',
589
			'd': 'M-42,-5 C-27,-5 -27,15 -42,15',
590
		}));
591

    
592
		var requiredCounterText = app.utils.createSvgElement('text', {
593
			'x': -45,
594
			'y': 10,
595
		});
596
		requiredCounterText.appendChild(document.createTextNode(requiredCounter));
597
		required.appendChild(requiredCounterText);
598

    
599
		// provided
600
		var provided = app.utils.createSvgElement('g', {
601
			'class': 'provided-counter',
602
		});
603
		provided.addEventListener('click', providedClick.bind(this));
604
		group.appendChild(provided);
605

    
606
		provided.appendChild(app.utils.createSvgElement('line', {
607
			'x1': -60,
608
			'y1': 35,
609
			'x2': -50,
610
			'y2': 35,
611
			'stroke': 'black',
612
			'class': 'outer-floater',
613
		}));
614
		provided.appendChild(app.utils.createSvgElement('line', {
615
			'x1': -30,
616
			'y1': 35,
617
			'x2': -20,
618
			'y2': 35,
619
			'stroke': 'black',
620
		}));
621
		provided.appendChild(app.utils.createSvgElement('circle', {
622
			'class': 'lollipop',
623
			'cx': -42,
624
			'cy': 35,
625
			'r': 11,
626
		}));
627

    
628
		var providedCounterText = app.utils.createSvgElement('text', {
629
			'x': -45,
630
			'y': 40,
631
		});
632
		providedCounterText.appendChild(document.createTextNode(providedCounter));
633
		provided.appendChild(providedCounterText);
634

    
635
		// symbol
636
		var symbolText = app.utils.createHtmlElement('span', {
637
			'class': 'group-symbol',
638
			'style': 'background-color: ' + this.symbol[1] + ';',
639
			'x': 10,
640
			'y': 55,
641
		});
642
		symbolText.appendChild(document.createTextNode(this.symbol[0]));
643
		symbolText.addEventListener('click', click.bind(this));
644
		rootElement.appendChild(symbolText);
645

    
646
		// name
647
		var nameText = app.utils.createHtmlElement('span', {
648
			'class': 'group-name',
649
		});
650
		nameText.appendChild(document.createTextNode(this.name));
651
		nameText.addEventListener('click', nameClick.bind(this));
652
		rootElement.appendChild(nameText);
653

    
654
		// vertex list
655
		vertexListComponent = new GroupVertexList(this);
656
		rootElement.appendChild(vertexListComponent.render());
657

    
658
		vertexList.forEach(function(vertex) {
659
			vertexListComponent.appendChild(vertex);
660
		}, this);
661

    
662
		// buttons
663
		var buttonGroup = app.utils.createHtmlElement('div', {
664
			'class': 'button-group',
665
		});
666
		rootElement.appendChild(buttonGroup);
667

    
668
		// include button
669
		var includeButton = app.utils.createHtmlElement('button', {
670
			'class': 'include-button button',
671
			'title': 'Display group in viewport',
672
		});
673
		includeButton.appendChild(app.utils.createHtmlElement('img', {
674
			'src': 'images/button_cancel.png',
675
			'alt': 'Icon of "display group in viewport" action',
676
		}));
677
		includeButton.addEventListener('click', includeClick.bind(this));
678
		buttonGroup.appendChild(includeButton);
679

    
680
		// set floater
681
		floater.setElement(rootElement);
682

    
683
		// set edges' ends
684
		var inEdgeList = this.getInEdgeList();
685
		inEdgeList.forEach(function(edge) {
686
			floater.addInEdge(edge);
687
		});
688

    
689
		var outEdgeList = this.getOutEdgeList();
690
		outEdgeList.forEach(function(edge) {
691
			floater.addOutEdge(edge);
692
		});
693

    
694
		return rootElement;
695
	}
696

    
697
	/**
698
	 * Group click interaction. Based on whether the group is excluded and currently selected mouse mode (move, exclude),
699
	 * the group is either highlighted or moved within the graph.
700
	 */
701
	function click() {
702
		if (excluded) {
703
			this.setHighlighted(!highlighted);
704
			this.setHighlightedRequiredNeighbours(highlighted);
705
			this.setHighlightedProvidedNeighbours(highlighted);
706

    
707
			highlightNeighbours.call(this);
708
			return;
709
		}
710

    
711
		if (pan) {
712
			pan = false;
713
			return;
714
		}
715

    
716
		switch (document.actionForm.actionMove.value) {
717
			case 'move':
718
				this.setHighlighted(!highlighted);
719
				this.setHighlightedRequiredNeighbours(highlighted);
720
				this.setHighlightedProvidedNeighbours(highlighted);
721

    
722
				highlightNeighbours.call(this);
723
				break;
724

    
725
			case 'exclude':
726
				this.exclude.call(this);
727

    
728
				app.sidebarComponent.excludedNodeListComponent.add(this);
729
				break;
730
		}
731
	}
732

    
733
	/**
734
	 * Group name click interaction. Reveals a prompt for new group name.
735
	 * @param {Event} e Click event.
736
	 */
737
	function nameClick(e) {
738
		e.stopPropagation();
739

    
740
		var newValue = prompt('Enter new group name:', this.name);
741
		if (newValue !== null) {
742
			this.name = newValue;
743

    
744
			rootElement.querySelector('.group-name').textContent = this.name;
745
		}
746
	}
747

    
748
	/**
749
	 * Highlights the group as a requirement.
750
	 */
751
	function requiredClick() {
752
		this.setHighlighted(!highlighted);
753
		this.setHighlightedRequiredNeighbours(highlighted);
754
		this.setHighlightedProvidedNeighbours(false);
755

    
756
		highlightRequiredNeighbours.call(this);
757
	}
758

    
759
	/**
760
	 * Highlights the group as a dependent.
761
	 */
762
	function providedClick() {
763
		this.setHighlighted(!highlighted);
764
		this.setHighlightedRequiredNeighbours(false);
765
		this.setHighlightedProvidedNeighbours(highlighted);
766

    
767
		highlightProvidedNeighbours.call(this);
768
	}
769

    
770
	/**
771
	 * Includes the group back to the viewport.
772
	 */
773
	function includeClick() {
774
		this.include.call(this);
775
	}
776

    
777
	/**
778
	 * Sets the group as expanded so that its vertices are listed too.
779
	 * @param {Event} e Click event.
780
	 */
781
	function expandClick(e) {
782
		e.stopPropagation();
783

    
784
		rootElement.classList.add('group--expanded');
785
	}
786

    
787
	/**
788
	 * Sets the group as compress so that its vertices are not explicitly listed.
789
	 * @param {Event} e Click event.
790
	 */
791
	function compressClick(e) {
792
		e.stopPropagation();
793

    
794
		rootElement.classList.remove('group--expanded');
795
	}
796

    
797
	/**
798
	 * Dissolves the group so that its vertices are displayed separately again. The group itself is destroyed.
799
	 * @param {Event} e Click event.
800
	 */
801
	function dissolveClick(e) {
802
		e.stopPropagation();
803

    
804
		this.remove.call(this, false);
805

    
806
		var vertexListCopy = vertexList.slice(0);
807
		vertexListCopy.forEach(function(vertex) {
808
			this.removeVertex.call(this, vertex);
809
		}, this);
810

    
811
		app.viewportComponent.removeGroup(this);
812
	}
813
	
814
	/**
815
	 * Handles drag and drop interaction with the group. At the moment mouse button is pressed, it is not yet known whether 
816
	 * it is just clicked or dragged.
817
	 * @param {Event} e Mouse down event.
818
	 */
819
	function mouseDown(e) {
820
		e.stopPropagation();
821
		app.closeFloatingComponents();
822
		
823
		var self = this;
824
		var start = new Coordinates(e.clientX, e.clientY);
825
		
826
		rootElement.classList.add('node--dragged');
827
		
828
		document.body.addEventListener('mousemove', mouseMove);
829
		document.body.addEventListener('mouseup', mouseUp);
830
		document.body.addEventListener('mouseleave', mouseUp);
831
		
832
		/**
833
		 * At the moment mouse is moved, the group is clearly being dragged. The group is moved to the current position of mouse.
834
		 * @param {Event} e Mouse move event.
835
		 */
836
		function mouseMove(e) {
837
			pan = true;
838
			
839
			self.move(new Coordinates(
840
				position.x - (start.x - e.clientX) / app.zoom.scale,
841
				position.y - (start.y - e.clientY) / app.zoom.scale,
842
			));
843
		}
844

    
845
		/**
846
		 * At the moment mouse button is released, dragging is done and its final position is set to the group.
847
		 * @param {Event} e Mouse up event.
848
		 */
849
		function mouseUp(e) {
850
			self.setPosition(new Coordinates(
851
				+rootElement.getAttribute('x'),
852
				+rootElement.getAttribute('y'),
853
			));
854
			
855
			rootElement.classList.remove('node--dragged');
856
			
857
			document.body.removeEventListener('mousemove', mouseMove);
858
			document.body.removeEventListener('mouseup', mouseUp);
859
			document.body.removeEventListener('mouseleave', mouseUp);
860
		}
861
	}
862
	
863
	/**
864
	 * Highlights all neighbours of vertices in the group. They are either highlighted as required or provided, or dimmed.
865
	 */
866
	function highlightNeighbours() {
867
		this.setDimmed(false);
868
		this.setHighlightedRequired(false);
869
		this.setHighlightedProvided(false);
870

    
871
		if (highlighted) {
872
			// dim and unhighlight all nodes but this
873
			app.nodeList.forEach(function(node) {
874
				if (node === this) return;
875

    
876
				node.setDimmed(true);
877

    
878
				node.setHighlighted(false);
879
				node.setHighlightedRequired(false);
880
				node.setHighlightedProvided(false);
881
				node.setHighlightedRequiredNeighbours(false);
882
				node.setHighlightedProvidedNeighbours(false);
883
			}, this);
884

    
885
			// dim and unhighlight all edges
886
			app.edgeList.forEach(function(edge) {
887
				edge.setHidden(edge.getFrom().isExcluded() || edge.getTo().isExcluded());
888
				edge.setDimmed(true);
889

    
890
				edge.setHighlighted(false);
891
				edge.setHighlightedRequired(false);
892
				edge.setHighlightedProvided(false);
893
			});
894

    
895
			// highlight required neighbours
896
			this.getInEdgeList().forEach(function(edge) {
897
				edge.setHidden(false);
898
				edge.setDimmed(false);
899
				edge.setHighlightedRequired(true);
900

    
901
				edge.getFrom().setDimmed(false);
902
				edge.getFrom().setHighlightedRequired(true);
903
			});
904
			
905
			// highlight provided neighbours
906
			this.getOutEdgeList().forEach(function(edge) {
907
				edge.setHidden(false);
908
				edge.setDimmed(false);
909
				edge.setHighlightedProvided(true);
910

    
911
				edge.getTo().setDimmed(false);
912
				edge.getTo().setHighlightedProvided(true);
913
			});
914

    
915
		} else {
916
			app.nodeList.forEach(function(node) {
917
				node.setDimmed(false);
918

    
919
				node.setHighlighted(false);
920
				node.setHighlightedRequired(false);
921
				node.setHighlightedProvided(false);
922
				node.setHighlightedRequiredNeighbours(false);
923
				node.setHighlightedProvidedNeighbours(false);
924
			}, this);
925

    
926
			app.edgeList.forEach(function(edge) {
927
				edge.setHidden(edge.getFrom().isExcluded() || edge.getTo().isExcluded());
928
				edge.setDimmed(false);
929

    
930
				edge.setHighlighted(false);
931
				edge.setHighlightedRequired(false);
932
				edge.setHighlightedProvided(false);
933
			});
934
		}
935
	}
936

    
937
	/**
938
	 * Highlights only neighbours of vertices in the group that are required.
939
	 */
940
	function highlightRequiredNeighbours() {
941
		if (highlighted) {
942
			this.getInEdgeList().forEach(function(edge) {
943
				edge.setHidden(false);
944
				edge.setHighlightedRequired(true);
945
				edge.getFrom().setHighlightedRequired(true);
946
			});
947

    
948
		} else {
949
			this.getInEdgeList().forEach(function(edge) {
950
				edge.setHidden(true);
951
				edge.setHighlightedRequired(false);
952
				edge.getFrom().setHighlightedRequired(false);
953
			});
954
		}
955
	}
956

    
957
	/**
958
	 * Highlights only neighbours of vertices in the group that are provided.
959
	 */
960
	function highlightProvidedNeighbours() {
961
		if (highlighted) {
962
			this.getOutEdgeList().forEach(function(edge) {
963
				edge.setHidden(false);
964
				edge.setHighlightedProvided(true);
965
				edge.getTo().setHighlightedProvided(true);
966
			});
967

    
968
		} else {
969
			this.getOutEdgeList().forEach(function(edge) {
970
				edge.setHidden(true);
971
				edge.setHighlightedProvided(false);
972
				edge.getTo().setHighlightedProvided(false);
973
			});
974
		}
975
	}
976
}
(7-7/20)