Projekt

Obecné

Profil

Stáhnout (25.4 KB) Statistiky
| Větev: | Tag: | Revize:
1 1e2b2c27 Tomáš Šimandl
/**
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
}