Projekt

Obecné

Profil

Stáhnout (9.06 KB) Statistiky
| Větev: | Tag: | Revize:
1 1e2b2c27 Tomáš Šimandl
/**
2
 * Class representing an edge of a graph in viewport.
3
 * @constructor
4
 * @param {object} props Properties of the edge.
5
 */
6
function Edge(props) {
7
	/** @prop {integer} id Unique identifier of the edge. */
8
	this.id = props.id;
9
10
	var rootElement;
11
12
	var compatible = props.isCompatible;
13
	var compatibilityInfo = props.compInfoJSON ? JSON.parse(props.compInfoJSON) : [];
14
15
	var hidden = false;
16
	var dimmed = false;
17
	var highlighted = false;
18
	var highlightedRequired = false;
19
	var highlightedProvided = false;
20
21
	var fromNode = null;
22
	var toNode = null;
23
24
	var start = new Coordinates(0, 0);
25
	var end = new Coordinates(0, 0);
26
	
27
	/**
28
	 * Sets origin vertex of the edge without moving the starting point.
29
	 * @param {Vertex} node Vertex that this edge is going from.
30
	 */
31
	this.setFrom = function(node) {
32
		if (!(node instanceof Vertex)) {
33
			throw new TypeError(node.toString() + 'is not instance of Vertex');
34
		}
35
36
		fromNode = node;
37
38
		setStart(node.getCenter());
39
	};
40
	
41
	/**
42
	 * Returns origin vertex of the edge.
43
	 */
44
	this.getFrom = function() {
45
		return fromNode;
46
	};
47
	
48
	/**
49
	 * Sets target vertex of the edge without moving the ending point.
50
	 * @param {Vertex} node Vertex that this edge is going to.
51
	 */
52
	this.setTo = function(node) {
53
		if (!(node instanceof Vertex)) {
54
			throw new TypeError(node.toString() + 'is not instance of Vertex');
55
		}
56
57
		toNode = node;
58
59
		setEnd(node.getCenter());
60
	};
61
	
62
	/**
63
	 * Returns target vertex of the edge.
64
	 */
65
	this.getTo = function() {
66
		return toNode;
67
	};
68
	
69
	/**
70
	 * Moves starting point of the edge to new coordinates and rotates the lollipop.
71
	 * @param {Coordinates} coords New starting coordinates of the edge.
72
	 */
73
	this.moveStart = function(coords) {
74
		setStart(coords);
75
		
76
		var lines = rootElement.querySelectorAll('.line');
77
		lines.forEach(function(line) {
78
			line.setAttribute('x1', start.x);
79
			line.setAttribute('y1', start.y);
80
		});
81
		
82
		// lollipop position and rotation
83
		var position = getLollipopPosition.call(this);
84
		var rotation = getLollipopRotation.call(this);
85
86
		var lollipop = rootElement.querySelector('.lollipop');
87
		lollipop.setAttribute('transform', `rotate(${rotation}, ${position.x},${position.y}) translate(${position.x},${position.y})`);
88
	};
89
	
90
	/**
91
	 * Moved ending point of the edge to new coordinates and rotates the lollipop.
92
	 * @param {Coordinates} coords New ending coordinates of the edge.
93
	 */
94
	this.moveEnd = function(coords) {
95
		setEnd(coords);
96
		
97
		var lines = rootElement.querySelectorAll('.line');
98
		lines.forEach(function(line) {
99
			line.setAttribute('x2', end.x);
100
			line.setAttribute('y2', end.y);
101
		});
102
		
103
		// lollipop position and rotation
104
		var position = getLollipopPosition.call(this);
105
		var rotation = getLollipopRotation.call(this);
106
107
		var lollipop = rootElement.querySelector('.lollipop');
108
		lollipop.setAttribute('transform', `rotate(${rotation}, ${position.x},${position.y}) translate(${position.x},${position.y})`);
109
	};
110
111
	/**
112
	 * @returns {boolean} True if the edge is compatible, otherwise false.
113
	 */
114
	this.isCompatible = function() {
115
		return compatible;
116
	};
117
118
	/**
119
	 * @returns {object} Information on compatibility of the two vertices this edge is connecting.
120
	 */
121
	this.getCompatibilityInfo = function() {
122
		return compatibilityInfo;
123
	};
124
125
	/**
126
	 * Toggles visibility of the edge.
127
	 * @param {boolean} newValue True to hide the edge, false to display it.
128
	 */
129
	this.setHidden = function(newValue) {
130
		hidden = newValue;
131
132
		if (newValue) {
133
			rootElement.classList.add('hidden');
134
		} else {
135
			rootElement.classList.remove('hidden');
136
		}
137
	};
138
139
	/**
140
	 * Toggles transparency of the edge.
141
	 * @param {boolean} newValue True to set the edge semitransparent, false to display it normally.
142
	 */
143
	this.setDimmed = function(newValue) {
144
		dimmed = newValue;
145
146
		if (newValue) {
147
			rootElement.classList.add('edge--dimmed');
148
		} else {
149
			rootElement.classList.remove('edge--dimmed');
150
		}
151
	};
152
153
	/**
154
	 * Toggles highlighting of the edge.
155
	 * @param {boolean} newValue True to highlight the edge, false to unhighlight.
156
	 */
157
	this.setHighlighted = function(newValue) {
158
		highlighted = newValue;
159
160
		if (newValue) {
161
			rootElement.classList.add('edge--highlighted');
162
		} else {
163
			rootElement.classList.remove('edge--highlighted');
164
		}
165
	};
166
	
167
	/**
168
	 * Toggles highlighting of the edge between vertex and its requirement.
169
	 * @param {boolean} newValue True to highlight the edge as required, false to unhighlight.
170
	 */
171
	this.setHighlightedRequired = function(newValue) {
172
		highlightedRequired = newValue;
173
174
		if (newValue) {
175
			rootElement.classList.add('edge--highlighted-required');
176
			rootElement.classList.remove('edge--highlighted-provided');
177
178
		} else {
179
			rootElement.classList.remove('edge--highlighted-required');
180
		}
181
	};
182
	
183
	/**
184
	 * Toggles highlighting of the edge between vertex and its dependent.
185
	 * @param {boolean} newValue True to highlight the edge as provided, false to unhighlight.
186
	 */
187
	this.setHighlightedProvided = function(newValue) {
188
		highlightedProvided = newValue;
189
190
		if (newValue) {
191
			rootElement.classList.remove('edge--highlighted-required');
192
			rootElement.classList.add('edge--highlighted-provided');
193
194
		} else {
195
			rootElement.classList.remove('edge--highlighted-provided');
196
		}
197
	};
198
	
199
	/**
200
	 * Creates a new DOM element representing the edge in memory. Binds user interactions to local handler functions.
201
	 * @returns {Element} SVG DOM element.
202
	 */
203
	this.render = function() {
204
		rootElement = app.utils.createSvgElement('g', {
205
			'class': 'edge',
206
			'data-id': props.id,
207
			'data-from': props.from,
208
			'data-to': props.to,
209
		});
210
211
		rootElement.appendChild(app.utils.createSvgElement('line', {
212
			'class': 'line',
213
			'x1': start.x,
214
			'y1': start.y,
215
			'x2': end.x,
216
			'y2': end.y,
217
			'stroke': 'white',
218
			'stroke-width': 5,
219
		}));
220
		
221
		rootElement.appendChild(app.utils.createSvgElement('line', {
222
			'class': 'line',
223
			'x1': start.x,
224
			'y1': start.y,
225
			'x2': end.x,
226
			'y2': end.y,
227
		}));
228
229
		// lollipop position and rotation
230
		var position = getLollipopPosition.call(this);
231
		var rotation = getLollipopRotation.call(this);
232
		
233
		// lollipop
234
		var lollipop = app.utils.createSvgElement('g', {
235
			'class': 'lollipop lollipop--cross',
236
			'transform': `rotate(${rotation}, ${position.x},${position.y}) translate(${position.x},${position.y})`,
237
		});
238
		lollipop.appendChild(app.utils.createSvgElement('path', {
239
			'd': 'M0,-12 C16,-12 16,12 0,12',
240
		}));
241
		lollipop.appendChild(app.utils.createSvgElement('circle', {
242
			'cx': 0,
243
			'cy': 0,
244
			'r': 8,
245
		}));
246
		lollipop.addEventListener('click', click.bind(this));
247
		rootElement.appendChild(lollipop);
248
249
		if (compatible) {
250
			// tick
251
			lollipop.appendChild(app.utils.createSvgElement('line', {
252
				'x1': 6,
253
				'y1': -4,
254
				'x2': -4,
255
				'y2': 6,
256
				'transform': 'rotate(90)',
257
			}));
258
			lollipop.appendChild(app.utils.createSvgElement('line', {
259
				'x1': -5,
260
				'y1': -3,
261
				'x2': -4,
262
				'y2': 5,
263
				'transform': 'rotate(90)',
264
			}));
265
266
		} else {
267
			// cross
268
			lollipop.appendChild(app.utils.createSvgElement('line', {
269
				'x1': -5,
270
				'y1': -5,
271
				'x2': 5,
272
				'y2': 5,
273
			}));
274
			lollipop.appendChild(app.utils.createSvgElement('line', {
275
				'x1': -5,
276
				'y1': 5,
277
				'x2': 5,
278
				'y2': -5,
279
			}));
280
		}
281
282
		return rootElement;
283
	};
284
	
285
	/**
286
	 * Removes the DOM element representing the edge from document.
287
	 */
288
	this.remove = function() {
289
		rootElement.remove();
290
	};
291
292
	/**
293
	 * Edge click interaction. Highlights the edge and vertices related to it. Reveals edge popover.
294
	 * @param {Event} e Click event.
295
	 */
296
	function click(e) {
297
		if (compatibilityInfo.length > 0) {
298
			app.viewportComponent.edgePopoverComponent.setContent(compatibilityInfo);
299
			app.viewportComponent.edgePopoverComponent.setPosition(new Coordinates(e.clientX, e.clientY));
300
			app.viewportComponent.edgePopoverComponent.open();
301
		}
302
303
		// unhighlight other edges
304
		app.edgeList.filter(function(edge) {
305
			return edge !== this;
306
		}, this).forEach(function(edge) {
307
			edge.setHighlighted(false);
308
			edge.getFrom().setHighlighted(false);
309
			edge.getTo().setHighlighted(false);
310
		});
311
312
		// highlight this edge
313
		this.setHighlighted(!highlighted);
314
		this.getFrom().setHighlighted(highlighted);
315
		this.getTo().setHighlighted(highlighted);
316
	}
317
	
318
	/**
319
	 * Sets new coordinates of the starting point of the edge.
320
	 * @param {Coordinates} coords New starting coordinates of the edge.
321
	 */
322
	function setStart(coords) {
323
		if (!(coords instanceof Coordinates)) {
324
			throw new TypeError(coords.toString() + 'is not instance of Coordinates');
325
		}
326
327
		start = coords;
328
	}
329
	
330
	/**
331
	 * Sets new coordinates of the ending point of the edge.
332
	 * @param {Coordinates} coords New ending coordinates of the edge.
333
	 */
334
	function setEnd(coords) {
335
		if (!(coords instanceof Coordinates)) {
336
			throw new TypeError(coords.toString() + 'is not instance of Coordinates');
337
		}
338
339
		end = coords;
340
	}
341
	
342
	/**
343
	 * @returns {Coordinates} Current position of the lollipop.
344
	 */
345
	function getLollipopPosition() {
346
		// lollipop is placed at 1/3 of the distance from start to end
347
		return new Coordinates(
348
			(2 * start.x + end.x) / 3,
349
			(2 * start.y + end.y) / 3,
350
		);
351
	}
352
	
353
	/**
354
	 * @returns {float} Current rotation of the lollipop in degrees.
355
	 */
356
	function getLollipopRotation() {
357
		return -1 * Math.atan2(end.x - start.x, end.y - start.y) * 180 / Math.PI + 90;
358
	}
359
}