Projekt

Obecné

Profil

Stáhnout (25.3 KB) Statistiky
| Větev: | Tag: | Revize:
1
/**
2
 * Class representing a vertex in graph.
3
 * @constructor
4
 * @param {object} props Properties of the vertex.
5
 */
6
function Vertex(props) {
7
	/** @prop {integer} id Unique identifier of the vertex. */
8
	this.id = props.id;
9
	/** @prop {string} name Name of the vertex. */
10
	this.name = props.name;
11
	/** @prop {array} symbol Symbol of the group. */
12
	this.symbol = app.markSymbol.getMarkSymbol();
13

    
14
	var rootElement;
15
	var symbolListComponent;
16

    
17
	var position = new Coordinates(0, 0);
18
	var size = {
19
		width: Math.max(30 + props.name.length * 8.3, 200),	// 8.3 is approximate width (in pixels) of one character using Consolas at 15px font size
20
		height: 30,
21
	};
22
	var group = null;
23
	var floater = null;
24

    
25
	var pan = false;
26
	var excluded = false;
27
	var found = 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
	var iconsDisplayed = false;
35

    
36
	var inEdgeList = [];
37
	var outEdgeList = [];
38
	var symbolList = [];
39
	
40
	/**
41
	 * Adds a new edge ending in the vertex. Its ending point is moved to the current position of the vertex.
42
	 * @param {Edge} edge Edge going to the vertex.
43
	 */
44
	this.addInEdge = function(edge) {
45
		if (!(edge instanceof Edge)) {
46
			throw new TypeError(edge.toString() + 'is not instance of Edge');
47
		}
48
		
49
		edge.setTo(this);
50
		
51
		inEdgeList.push(edge);
52
	};
53
	
54
	/**
55
	 * Adds a new edge starting in the vertex. Its starting point is moved to the current position of the vertex.
56
	 * @param {Edge} edge Edge going from the vertex.
57
	 */
58
	this.addOutEdge = function(edge) {
59
		if (!(edge instanceof Edge)) {
60
			throw new TypeError(edge.toString() + 'is not instance of Edge');
61
		}
62
		
63
		edge.setFrom(this);
64
		
65
		outEdgeList.push(edge);
66
	};
67

    
68
	/**
69
	 * @returns {array<Edge>} Array of edges going to the vertex.
70
	 */
71
	this.getInEdgeList = function() {
72
		return inEdgeList;
73
	};
74

    
75
	/**
76
	 * @returns {array<Edge>} Array of edges going from the vertex.
77
	 */
78
	this.getOutEdgeList = function() {
79
		return outEdgeList;
80
	};
81

    
82
	/**
83
	 * 
84
	 * @param {array} symbol Node symbol to be displayed next to the vertex.
85
	 */
86
	this.addSymbol = function(symbol) {
87
		symbolList.push(symbol);
88

    
89
		if (excluded) return;
90

    
91
		symbolListComponent.appendChild(symbol);
92
	};
93

    
94
	this.removeSymbol = function(symbol) {
95
		symbolList.splice(symbolList.indexOf(symbol), 1);
96

    
97
		if (excluded) return;
98

    
99
		symbolListComponent.removeChild(symbol);
100
	};
101

    
102
	this.countEdges = function() {
103
		return inEdgeList.length + outEdgeList.length;
104
	};
105
	
106
	/**
107
	 * @returns {Coordinates} Current position of the vertex.
108
	 */
109
	this.getPosition = function() {
110
		return position;
111
	};
112

    
113
	/**
114
	 * Updates the current position of the vertex in graph.
115
	 */
116
	this.setPosition = function(coords) {
117
		if (!(coords instanceof Coordinates)) {
118
			throw new TypeError(coords.toString() + 'is not instance of Coordinates');
119
		}
120

    
121
		position = coords;
122
	};
123

    
124
	/**
125
	 * @returns {Coordinates} Centre of the group.
126
	 */
127
	this.getCenter = function() {
128
		return new Coordinates(
129
			position.x + size.width / 2,
130
			position.y + size.height / 2,
131
		);
132
	};
133

    
134
	/**
135
	 * Moves the vertex to a new position in graph. Edges related to the vertex are moved as well.
136
	 * @param {Coordinates} coords Coordinates to be moved to.
137
	 */
138
	this.move = function(coords) {
139
		if (!(coords instanceof Coordinates)) {
140
			throw new TypeError(coords.toString() + 'is not instance of Coordinates');
141
		}
142

    
143
		rootElement.setAttribute('x', coords.x);
144
		rootElement.setAttribute('y', coords.y);
145

    
146
		inEdgeList.forEach(function(edge) {
147
			edge.moveEnd(new Coordinates(
148
				coords.x + size.width / 2,
149
				coords.y + size.height / 2
150
			));
151
		});
152
	
153
		outEdgeList.forEach(function(edge) {
154
			edge.moveStart(new Coordinates(
155
				coords.x + size.width / 2,
156
				coords.y + size.height / 2
157
			));
158
		});
159
	};
160

    
161
	/**
162
	 * @returns {Group} Group this vertex is part of. If the vertex stands alone, null is returned.
163
	 */
164
	this.getGroup = function() {
165
		return group;
166
	};
167

    
168
	/**
169
	 * Sets a new group that the vertex is added to. If the vertex is currently excluded, its floating point is destroyed.
170
	 * @param {Group} newValue Group this vertex is a part of.
171
	 */
172
	this.setGroup = function(newValue) {
173
		if (!(newValue instanceof Group) && newValue !== null) {
174
			throw new TypeError(newValue.toString() + 'is neither instance of Group nor null');
175
		}
176

    
177
		group = newValue;
178

    
179
		if (newValue && this.isExcluded()) {
180
			// remove floater
181
			app.sidebarComponent.removeFloater(floater);
182
			delete floater;
183
		}
184
	};
185

    
186
	/**
187
	 * Sets the vertex as found. Highlighting is skipped when the vertex is excluded.
188
	 * @param {boolean} newValue True to mark the vertex as found, otherwise false.
189
	 */
190
	this.setFound = function(newValue) {
191
		found = newValue;
192

    
193
		if (excluded) return;
194
		
195
		if (newValue) {
196
			rootElement.classList.add('vertex--found');
197
		} else {
198
			rootElement.classList.remove('vertex--found');
199
		}
200
	};
201

    
202
	/**
203
	 * Toggles transparency of the vertex. Style change is skipped when the vertex is excluded.
204
	 * @param {boolean} newValue True to dim the vertex, false to display it normally.
205
	 */
206
	this.setDimmed = function(newValue) {
207
		dimmed = newValue;
208

    
209
		if (excluded) return;
210

    
211
		if (newValue) {
212
			rootElement.classList.add('node--dimmed');
213
		} else {
214
			rootElement.classList.remove('node--dimmed');
215
		}
216
	};
217

    
218
	/**
219
	 * Toggles highlighting of the vertex.
220
	 * @param {boolean} newValue True to highlight the vertex, otherwise false.
221
	 */
222
	this.setHighlighted = function(newValue) {
223
		highlighted = newValue;
224

    
225
		if (newValue) {
226
			rootElement.classList.add('node--highlighted');
227
		} else {
228
			rootElement.classList.remove('node--highlighted');
229
		}
230
	};
231

    
232
	/**
233
	 * Toggles highlighting of the vertex to mark it as requirement of some other node.
234
	 * @param {boolean} newValue True to highlight the vertex as required, false to unhighlight.
235
	 */
236
	this.setHighlightedRequired = function(newValue) {
237
		highlightedRequired = newValue;
238

    
239
		if (newValue) {
240
			rootElement.classList.add('node--highlighted-required');
241
		} else {
242
			rootElement.classList.remove('node--highlighted-required');
243
		}
244

    
245
		if (group !== null) {
246
			group.setHighlightedRequired(newValue);
247
		}
248
	};
249
	
250
	/**
251
	 * Toggles highlighting of the vertex to mark it as dependent of some other node.
252
	 * @param {boolean} newValue True to highlight the vertex as provided, false to unhighlight.
253
	 */
254
	this.setHighlightedProvided = function(newValue) {
255
		highlightedProvided = newValue;
256

    
257
		if (newValue) {
258
			rootElement.classList.add('node--highlighted-provided');
259
		} else {
260
			rootElement.classList.remove('node--highlighted-provided');
261
		}
262

    
263
		if (group !== null) {
264
			group.setHighlightedProvided(newValue);
265
		}
266
	};
267
	
268
	/**
269
	 * Toggles highlighting of the vertex when only its requirements should be highlighted. Anytime this value is changed, generic
270
	 * {@link Vertex#setHighlighted} method should be called too.
271
	 * @param {boolean} newValue True to highlight the vertex when only its requirements should be highlighted, false to unhighlight.
272
	 */
273
	this.setHighlightedRequiredNeighbours = function(newValue) {
274
		highlightedRequiredNeighbours = newValue;
275

    
276
		if (newValue) {
277
			rootElement.classList.add('node--highlighted-required-neighbours');
278
		} else {
279
			rootElement.classList.remove('node--highlighted-required-neighbours');
280
		}
281
	};
282
	
283
	/**
284
	 * Toggles highlighting of the vertex when only its dependents should be highlighted. Anytime this value is changed, generic 
285
	 * {@link Vertex#setHighlighted} method should be called too.
286
	 * @param {boolean} newValue True to highlight the vertex when only its dependents should be highlighted, false to unhighlight.
287
	 */
288
	this.setHighlightedProvidedNeighbours = function(newValue) {
289
		highlightedProvidedNeighbours = newValue;
290

    
291
		if (newValue) {
292
			rootElement.classList.add('node--highlighted-provided-neighbours');
293
		} else {
294
			rootElement.classList.remove('node--highlighted-provided-neighbours');
295
		}
296
	};
297

    
298
	/**
299
	 * @returns {boolean} True is the vertex is currently excluded from the viewport, otherwise false.
300
	 */
301
	this.isExcluded = function() {
302
		return excluded;
303
	};
304

    
305
	/**
306
	 * Toggles excluded state of the vertex. If the vertex is set excluded, a new floating point is created to connect it with 
307
	 * related nodes in the viewport. Otherwise, the floating point is deleted.
308
	 * Any node is called excluded when it is not visible in the viewport but instead in the sidebar.
309
	 * @param {boolean} newValue True to set the vertex as excluded, otherwise false.
310
	 */
311
	this.setExcluded = function(newValue) {
312
		excluded = newValue;
313

    
314
		if (group !== null) return;
315

    
316
		if (newValue) {
317
			// set floater
318
			floater = new FloatingPoint;
319
			floater.setNode(this);
320
			app.sidebarComponent.addFloater(floater);
321

    
322
		} else {
323
			// remove floater
324
			app.sidebarComponent.removeFloater(floater);
325
			delete floater;
326
		}
327
	};
328

    
329
	/**
330
	 * Excludes the vertex from the viewport. Removes vertex DOM element and hides its edges.
331
	 */
332
	this.exclude = function() {
333
		this.setExcluded(true);
334
		this.remove(true);
335

    
336
		app.viewportComponent.removeVertex(this);
337
	};
338

    
339
	/**
340
	 * Includes the vertex in the viewport. Afterwards, edges related to the vertex are moved to the current position of the vertex.
341
	 */
342
	this.include = function() {
343
		this.removeFromSidebarList();
344

    
345
		this.setExcluded(false);
346
		this.remove(false);
347

    
348
		app.viewportComponent.addVertex(this);
349

    
350
		// set edges' ends
351
		var inEdgeList = this.getInEdgeList();
352
		inEdgeList.forEach(function(edge) {
353
			edge.setTo(this);
354
			edge.moveEnd(this.getCenter());
355
		}, this);
356

    
357
		var outEdgeList = this.getOutEdgeList();
358
		outEdgeList.forEach(function(edge) {
359
			edge.setFrom(this);
360
			edge.moveStart(this.getCenter());
361
		}, this);
362
	};
363

    
364
	/**
365
	 * Hook function used to remove the vertex from the sidebar list it is located in before it is moved to the viewport.
366
	 */
367
	this.removeFromSidebarList = app.utils.noop;
368

    
369
	/**
370
	 * @returns {boolean} True if the vertex is not connected to any other nodes.
371
	 */
372
	this.isUnconnected = function() {
373
		return inEdgeList.length === 0 && outEdgeList.length === 0;
374
	};
375

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

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

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

    
399
		// toggle edges
400
		inEdgeList.filter(function(edge) {
401
			return !edge.getFrom().isExcluded();
402
		}).forEach(function(edge) {
403
			edge.setHidden(hideEdges);
404
		});
405

    
406
		outEdgeList.filter(function(edge) {
407
			return !edge.getTo().isExcluded();
408
		}).forEach(function(edge) {
409
			edge.setHidden(hideEdges);
410
		});
411
	};
412

    
413
	/**
414
	 * @returns {Element} SVG DOM element.
415
	 */
416
	function renderIncluded() {
417
		rootElement = app.utils.createSvgElement('svg', {
418
			'class': 'node vertex',
419
			'x': position.x,
420
			'y': position.y,
421
			'data-id': this.id,
422
			'data-name': this.name,
423
		});
424
		rootElement.addEventListener('click', click.bind(this));
425
		rootElement.addEventListener('contextmenu', contextMenu.bind(this));
426
		rootElement.addEventListener('mousedown', mouseDown.bind(this));
427
		
428
		rootElement.appendChild(app.utils.createSvgElement('rect', {
429
			'height': size.height,
430
			'width': size.width,
431
			'x': 1,
432
			'y': 1,
433
		}));
434
		
435
		// interface
436
		var interface = app.utils.createSvgElement('g', {
437
			'class': 'interface',
438
			'transform': 'translate(8, 8)',
439
		});
440
		interface.addEventListener('click', interfaceClick.bind(this));
441
		
442
		interface.appendChild(app.utils.createSvgElement('rect', {
443
			'width': 10,
444
			'height': 15,
445
			'x': 0,
446
			'y': 0,
447
		}));
448
		interface.appendChild(app.utils.createSvgElement('rect', {
449
			'width': 6,
450
			'height': 3,
451
			'x': -3,
452
			'y': 3,
453
		}));
454
		interface.appendChild(app.utils.createSvgElement('rect', {
455
			'width': 6,
456
			'height': 3,
457
			'x': -3,
458
			'y': 9,
459
		}));
460
		rootElement.appendChild(interface);
461
		
462
		// name
463
		var nameText = app.utils.createSvgElement('text', {
464
			'fill': 'black',
465
			'x': 25,
466
			'y': 20,
467
		});
468
		nameText.appendChild(document.createTextNode(props.name));
469
		rootElement.appendChild(nameText);
470

    
471
		// symbol list
472
		symbolListComponent = new VertexSymbolList;
473
		rootElement.appendChild(symbolListComponent.render());
474

    
475
		symbolList.forEach(function(symbol) {
476
			symbolListComponent.appendChild(symbol);
477
		}, this);
478

    
479
		return rootElement;
480
	}
481

    
482
	/**
483
	 * @returns {Element} HTML DOM element.
484
	 */
485
	function renderExcluded() {
486
		rootElement = app.utils.createHtmlElement('li', {
487
			'class': 'node vertex',
488
			'data-id': props.id,
489
		});
490

    
491
		var svg = app.utils.createSvgElement('svg', {
492
			'xmlns': 'http://www.w3.org/2000/svg',
493
			'height': 60,
494
			'width': 46,
495
		});
496
		rootElement.appendChild(svg);
497

    
498
		var group = app.utils.createSvgElement('g', {
499
			'transform': 'translate(60,10)',
500
		});
501
		svg.appendChild(group);
502

    
503
		// required
504
		var required = app.utils.createSvgElement('g', {
505
			'class': 'required-counter',
506
		});
507
		required.addEventListener('click', requiredClick.bind(this));
508
		group.appendChild(required);
509

    
510
		required.appendChild(app.utils.createSvgElement('line', {
511
			'x1': -50,
512
			'y1': 5,
513
			'x2': -42,
514
			'y2': 5,
515
			'stroke': 'black',
516
			'class': 'outer-floater',
517
		}));
518
		required.appendChild(app.utils.createSvgElement('line', {
519
			'x1': -20,
520
			'y1': 5,
521
			'x2': -14,
522
			'y2': 5,
523
			'stroke': 'black',
524
		}));
525
		required.appendChild(app.utils.createSvgElement('rect', {
526
			'x': -58,
527
			'y': 1,
528
			'width': 8,
529
			'height': 8,
530
			'class': 'outer-port',
531
		}));
532
		required.appendChild(app.utils.createSvgElement('path', {
533
			'class': 'lollipop',
534
			'd': 'M-31,-5 C-16,-5 -16,15 -31,16',
535
		}));
536

    
537
		var requiredCounterText = app.utils.createSvgElement('text', {
538
			'x': -36,
539
			'y': 10,
540
		});
541
		requiredCounterText.appendChild(document.createTextNode(props.importedPackages.length));
542
		required.appendChild(requiredCounterText);
543

    
544
		// provided
545
		var provided = app.utils.createSvgElement('g', {
546
			'class': 'provided-counter',
547
		});
548
		provided.addEventListener('click', providedClick.bind(this));
549
		group.appendChild(provided);
550

    
551
		provided.appendChild(app.utils.createSvgElement('line', {
552
			'x1': -50,
553
			'y1': 35,
554
			'x2': -44,
555
			'y2': 35,
556
			'stroke': 'black',
557
			'class': 'outer-floater',
558
		}));
559
		provided.appendChild(app.utils.createSvgElement('line', {
560
			'x1': -20,
561
			'y1': 35,
562
			'x2': -14,
563
			'y2': 35,
564
			'stroke': 'black',
565
		}));
566
		provided.appendChild(app.utils.createSvgElement('rect', {
567
			'x': -58,
568
			'y': 31,
569
			'width': 8,
570
			'height': 8,
571
			'class': 'outer-port',
572
		}));
573
		provided.appendChild(app.utils.createSvgElement('circle', {
574
			'class': 'lollipop',
575
			'cx': -32,
576
			'cy': 35,
577
			'r': 11,
578
		}));
579

    
580
		var providedCounterText = app.utils.createSvgElement('text', {
581
			'x': -36,
582
			'y': 40,
583
		});
584
		providedCounterText.appendChild(document.createTextNode(props.exportedPackages.length));
585
		provided.appendChild(providedCounterText);
586

    
587
		// name
588
		var nameText = app.utils.createHtmlElement('div', {
589
			'class': 'vertex-name',
590
			'title': this.name,
591
		});
592
		nameText.appendChild(document.createTextNode(this.name));
593
		nameText.addEventListener('click', click.bind(this));
594
		rootElement.appendChild(nameText);
595

    
596
		// buttons
597
		var buttonGroup = app.utils.createHtmlElement('div', {
598
			'class': 'button-group',
599
		});
600
		rootElement.appendChild(buttonGroup);
601

    
602
		// show symbol button
603
		var showSymbolButton = app.utils.createHtmlElement('button', {
604
			'class': 'show-symbol-button button',
605
			'style': 'background-color: ' + this.symbol[1] + ';',
606
			'title': 'Show symbol next to all neighbouring components',
607
		});
608
		showSymbolButton.appendChild(document.createTextNode(this.symbol[0]));
609
		showSymbolButton.addEventListener('click', showIconClick.bind(this));
610
		buttonGroup.appendChild(showSymbolButton);
611

    
612
		// to change button
613
		var toChangeButton = app.utils.createHtmlElement('button', {
614
			'class': 'change-button button',
615
			'title': 'Set component for change',
616
		});
617
		toChangeButton.appendChild(app.utils.createHtmlElement('img', {
618
			'src': 'images/tochange/tochange-trans.gif',
619
			'alt': 'Icon of "set component for change" action',
620
		}));
621
		toChangeButton.addEventListener('click', addToChange.bind(this));
622
		buttonGroup.appendChild(toChangeButton);
623

    
624
		// include button
625
		var includeButton = app.utils.createHtmlElement('button', {
626
			'class': 'include-button button',
627
			'title': 'Display node in viewport',
628
		});
629
		includeButton.appendChild(app.utils.createHtmlElement('img', {
630
			'src': 'images/button_cancel.png',
631
			'alt': 'Icon of "Icon of "display node in viewport" action" action',
632
		}));
633
		includeButton.addEventListener('click', includeClick.bind(this));
634
		buttonGroup.appendChild(includeButton);
635

    
636
		// set floater element
637
		floater.setElement(rootElement);
638

    
639
		// set edges' ends
640
		var inEdgeList = this.getInEdgeList();
641
		inEdgeList.forEach(function(edge) {
642
			floater.addInEdge(edge);
643
		});
644

    
645
		var outEdgeList = this.getOutEdgeList();
646
		outEdgeList.forEach(function(edge) {
647
			floater.addOutEdge(edge);
648
		});
649

    
650
		return rootElement;
651
	}
652
	
653
	/**
654
	 * Vertex click interaction. Based on whether the vertex is excluded and currently selected mouse mode (move, exclude),
655
	 * the vertex is either highlighted or moved within the graph.
656
	 */
657
	function click() {
658
		if (excluded) {
659
			this.setHighlighted(!highlighted);
660
			this.setHighlightedRequiredNeighbours(highlighted);
661
			this.setHighlightedProvidedNeighbours(highlighted);
662

    
663
			highlightNeighbours.call(this);
664
			return;
665
		}
666

    
667
		if (pan) {
668
			pan = false;
669
			return;
670
		}
671

    
672
		switch (document.actionForm.actionMove.value) {
673
			case 'move':
674
				this.setHighlighted(!highlighted);
675
				this.setHighlightedRequiredNeighbours(highlighted);
676
				this.setHighlightedProvidedNeighbours(highlighted);
677

    
678
				highlightNeighbours.call(this);
679
				break;
680

    
681
			case 'exclude':
682
				this.exclude.call(this);
683

    
684
				app.sidebarComponent.excludedNodeListComponent.add(this);
685
				break;
686
		}
687
	}
688

    
689
	/**
690
	 * Highlights the vertex as a requirement.
691
	 */
692
	function requiredClick() {
693
		this.setHighlighted(!highlighted);
694
		this.setHighlightedRequiredNeighbours(highlighted);
695
		this.setHighlightedProvidedNeighbours(false);
696

    
697
		highlightRequiredNeighbours.call(this);
698
	}
699

    
700
	/**
701
	 * Highlights the vertex as a dependent.
702
	 */
703
	function providedClick() {
704
		this.setHighlighted(!highlighted);
705
		this.setHighlightedRequiredNeighbours(false);
706
		this.setHighlightedProvidedNeighbours(highlighted);
707

    
708
		highlightProvidedNeighbours.call(this);
709
	}
710
	
711
	/**
712
	 * Reveals vertex popover.
713
	 * @param {Event} e Click event.
714
	 */
715
	function interfaceClick(e) {
716
		e.stopPropagation();
717

    
718
		app.viewportComponent.vertexPopoverComponent.setContent(props.symbolicName, props.exportedPackages, props.importedPackages);
719
		app.viewportComponent.vertexPopoverComponent.setPosition(new Coordinates(e.clientX, e.clientY));
720
		app.viewportComponent.vertexPopoverComponent.open();
721
	}
722

    
723
	/**
724
	 * Displays symbol of the vertex next to all nodes that it is connected with.
725
	 * @param {Event} e Click event.
726
	 */
727
	function showIconClick(e) {
728
		iconsDisplayed = !iconsDisplayed;
729

    
730
		var neighbourList = [];
731

    
732
		inEdgeList.filter(function(edge) {
733
			return !edge.getFrom().isExcluded();
734
		}).forEach(function(edge) {
735
			neighbourList.push(edge.getFrom());
736
		});
737

    
738
		outEdgeList.filter(function(edge) {
739
			return !edge.getTo().isExcluded();
740
		}).forEach(function(edge) {
741
			neighbourList.push(edge.getTo());
742
		});
743

    
744
		neighbourList.forEach(function(node) {
745
			if (iconsDisplayed) {
746
				node.addSymbol(this.symbol);
747
			} else {
748
				node.removeSymbol(this.symbol);
749
			}
750
		}, this);
751
	}
752

    
753
	/**
754
	 * Adds the vertex to the list of components to be changed.
755
	 * @param {Event} e Click event.
756
	 */
757
	function addToChange(e) {
758
		app.sidebarComponent.addToChange(this);
759
	}
760

    
761
	/**
762
	 * Includes the group back to the viewport.
763
	 */
764
	function includeClick() {
765
		this.include.call(this);
766
	}
767

    
768
	/**
769
	 * Vertex right click interaction. Displays context menu filled with items representing groups that the vertex can be added to.
770
	 * @param {Event} e Context menu event.
771
	 */
772
	function contextMenu(e) {
773
		e.preventDefault();
774

    
775
		var excludedNodeList = app.sidebarComponent.excludedNodeListComponent.getNodeList();
776
		var includedGroupList = app.viewportComponent.getGroupList();
777

    
778
		var nodeList = [].concat(excludedNodeList, includedGroupList);
779
		if (nodeList.length === 0) return;
780

    
781
		app.viewportComponent.contextMenuComponent.setVertex(this);
782

    
783
		// fill list with items
784
		nodeList.forEach(function(node) {
785
			app.viewportComponent.contextMenuComponent.addNode(node);
786
		});
787

    
788
		app.viewportComponent.contextMenuComponent.setPosition(new Coordinates(e.clientX, e.clientY));
789
		app.viewportComponent.contextMenuComponent.open();
790
	}
791
	
792
	/**
793
	 * Handles drag and drop interaction with the vertex. At the moment mouse button is pressed, it is not yet known whether 
794
	 * it is just clicked or dragged.
795
	 * @param {Event} e Mouse down event.
796
	 */
797
	function mouseDown(e) {
798
		e.stopPropagation();
799
		app.closeFloatingComponents();
800
		
801
		var self = this;
802
		var start = new Coordinates(e.clientX, e.clientY);
803

    
804
		rootElement.classList.add('node--dragged');
805
		
806
		document.body.addEventListener('mousemove', mouseMove);
807
		document.body.addEventListener('mouseup', mouseUp);
808
		document.body.addEventListener('mouseleave', mouseUp);
809

    
810
		/**
811
		 * At the moment mouse is moved, the vertex is clearly being dragged. The vertex is moved to the current position of mouse.
812
		 * @param {Event} e Mouse move event.
813
		 */
814
		function mouseMove(e) {
815
			pan = true;
816

    
817
			self.move(new Coordinates(
818
				position.x - (start.x - e.clientX) / app.zoom.scale,
819
				position.y - (start.y - e.clientY) / app.zoom.scale,
820
			));
821
		}
822

    
823
		/**
824
		 * At the moment mouse button is released, dragging is done and its final position is set to the vertex.
825
		 * @param {Event} e Mouse up event.
826
		 */
827
		function mouseUp(e) {
828
			self.setPosition(new Coordinates(
829
				+rootElement.getAttribute('x'),
830
				+rootElement.getAttribute('y'),
831
			));
832

    
833
			rootElement.classList.remove('node--dragged');
834
			
835
			document.body.removeEventListener('mousemove', mouseMove);
836
			document.body.removeEventListener('mouseup', mouseUp);
837
			document.body.removeEventListener('mouseleave', mouseUp);
838
		}
839
	}
840
	
841
	/**
842
	 * Highlights all neighbours of the vertex. They are either highlighted as required or provided, or dimmed.
843
	 */
844
	function highlightNeighbours() {
845
		this.setDimmed(false);
846
		this.setHighlightedRequired(false);
847
		this.setHighlightedProvided(false);
848

    
849
		if (highlighted) {
850
			// dim and unhighlight all nodes but this
851
			app.nodeList.forEach(function(node) {
852
				if (node === this) return;
853

    
854
				node.setDimmed(true);
855

    
856
				node.setHighlighted(false);
857
				node.setHighlightedRequired(false);
858
				node.setHighlightedProvided(false);
859
				node.setHighlightedRequiredNeighbours(false);
860
				node.setHighlightedProvidedNeighbours(false);
861
			}, this);
862

    
863
			// dim and unhighlight all edges
864
			app.edgeList.forEach(function(edge) {
865
				edge.setHidden(edge.getFrom().isExcluded() || edge.getTo().isExcluded());
866
				edge.setDimmed(true);
867

    
868
				edge.setHighlighted(false);
869
				edge.setHighlightedRequired(false);
870
				edge.setHighlightedProvided(false);
871
			});
872

    
873
			// highlight required neighbours
874
			inEdgeList.forEach(function(edge) {
875
				edge.setHidden(false);
876
				edge.setDimmed(false);
877
				edge.setHighlightedRequired(true);
878

    
879
				edge.getFrom().setDimmed(false);
880
				edge.getFrom().setHighlightedRequired(true);
881
			});
882

    
883
			// highlight provided neighbours
884
			outEdgeList.forEach(function(edge) {
885
				edge.setHidden(false);
886
				edge.setDimmed(false);
887
				edge.setHighlightedProvided(true);
888

    
889
				edge.getTo().setDimmed(false);
890
				edge.getTo().setHighlightedProvided(true);
891
			});
892

    
893
		} else {
894
			app.nodeList.forEach(function(node) {
895
				node.setDimmed(false);
896

    
897
				node.setHighlighted(false);
898
				node.setHighlightedRequired(false);
899
				node.setHighlightedProvided(false);
900
				node.setHighlightedRequiredNeighbours(false);
901
				node.setHighlightedProvidedNeighbours(false);
902
			}, this);
903

    
904
			app.edgeList.forEach(function(edge) {
905
				edge.setHidden(edge.getFrom().isExcluded() || edge.getTo().isExcluded());
906
				edge.setDimmed(false);
907

    
908
				edge.setHighlighted(false);
909
				edge.setHighlightedRequired(false);
910
				edge.setHighlightedProvided(false);
911
			});
912
		}
913
	}
914

    
915
	/**
916
	 * Highlights only neighbours the vertex that are required.
917
	 */
918
	function highlightRequiredNeighbours() {
919
		if (highlighted) {
920
			inEdgeList.forEach(function(edge) {
921
				edge.setHidden(false);
922
				edge.setHighlightedRequired(true);
923
				edge.getFrom().setHighlightedRequired(true);
924
			});
925

    
926
		} else {
927
			inEdgeList.forEach(function(edge) {
928
				edge.setHidden(true);
929
				edge.setHighlightedRequired(false);
930
				edge.getFrom().setHighlightedRequired(false);
931
			});
932
		}
933
	}
934

    
935
	/**
936
	 * Highlights only neighbours the vertex that are provided.
937
	 */
938
	function highlightProvidedNeighbours() {
939
		if (highlighted) {
940
			outEdgeList.filter(function(edge) {
941
				return !edge.getTo().isExcluded();
942
			}).forEach(function(edge) {
943
				edge.setHidden(false);
944
				edge.setHighlightedProvided(true);
945
				edge.getTo().setHighlightedProvided(true);
946
			});
947

    
948
		} else {
949
			outEdgeList.filter(function(edge) {
950
				return !edge.getTo().isExcluded();
951
			}).forEach(function(edge) {
952
				edge.setHidden(true);
953
				edge.setHighlightedProvided(false);
954
				edge.getTo().setHighlightedProvided(false);
955
			});
956
		}
957
	}
958
}
(15-15/20)