blob: 9be07f2ea14062f0ba685f8e65e8e8234548764e [file] [log] [blame]
Michael Landoed64b5e2017-06-09 03:19:04 +03001(function (scope) {
2 var Class = function (param1, param2) {
3
4 var extend, mixins, definition;
5 if (param2) { //two parameters passed, first is extends, second definition object
6 extend = Array.isArray(param1) ? param1[0] : param1;
7 mixins = Array.isArray(param1) ? param1.slice(1) : null;
8 definition = param2;
9 } else { //only one parameter passed => no extend, only definition
10 extend = null;
11 definition = param1;
12 }
13
14
15 var Definition = definition.hasOwnProperty("constructor") ? definition.constructor : function () {
16 };
17
18 Definition.prototype = Object.create(extend ? extend.prototype : null);
19 var propertiesObject = definition.propertiesObject ? definition.propertiesObject : {};
20 if (mixins) {
21 var i, i2;
22 for (i in mixins) {
23 for (i2 in mixins[i].prototype) {
24 Definition.prototype[i2] = mixins[i].prototype[i2];
25 }
26 for (var i2 in mixins[i].prototype.propertiesObject) {
27 propertiesObject[i2] = mixins[i].prototype.propertiesObject[i2];
28 }
29 }
30 }
31
32 Definition.prototype.propertiesObject = propertiesObject;
33
34 Object.defineProperties(Definition.prototype, propertiesObject);
35
36 for (var key in definition) {
37 if (definition.hasOwnProperty(key)) {
38 Definition.prototype[key] = definition[key];
39 }
40 }
41
42 Definition.prototype.constructor = Definition;
43
44 return Definition;
45 };
46
47
48 var Interface = function (properties) {
49 this.properties = properties;
50 };
51
52 var InterfaceException = function (message) {
53 this.name = "InterfaceException";
54 this.message = message || "";
55 };
56
57 InterfaceException.prototype = new Error();
58
59 Interface.prototype.implements = function (target) {
60 for (var i in this.properties) {
61 if (target[this.properties[i]] == undefined) {
62 throw new InterfaceException("Missing property " + this.properties[i]);
63 }
64 }
65 return true;
66 };
67
68 Interface.prototype.doesImplement = function (target) {
69 for (var i in this.properties) {
70 if (target[this.properties[i]] === undefined) {
71 return false;
72 }
73 }
74 return true;
75 };
76
77 var VectorMath = {
78 distance: function (vector1, vector2) {
79 return Math.sqrt(Math.pow(vector1.x - vector2.x, 2) + Math.pow(vector1.y - vector2.y, 2));
80 }
81 };
82
83 var EventDispatcher = Class({
84 constructor: function () {
85 this.events = {};
86 },
87 on: function (name, listener, context) {
88 this.events[name] = this.events[name] ? this.events[name] : [];
89 this.events[name].push({
90 listener: listener,
91 context: context
92 })
93 },
94 once: function (name, listener, context) {
95 this.off(name, listener, context);
96 this.on(name, listener, context);
97 },
98 off: function (name, listener, context) {
99 //no event with this name registered? => finish
100 if (!this.events[name]) {
101 return;
102 }
103 if (listener) { //searching only for certains listeners
104 for (var i in this.events[name]) {
105 if (this.events[name][i].listener === listener) {
106 if (!context || this.events[name][i].context === context) {
107 this.events[name].splice(i, 1);
108 }
109 }
110 }
111 } else {
112 delete this.events[name];
113 }
114 },
115 trigger: function (name) {
116 var listeners = this.events[name];
117
118 for (var i in listeners) {
119 listeners[i].listener.apply(listeners[i].context, Array.prototype.slice.call(arguments, 1));
120 }
121 }
122 });
123
124 exports.CytoscapeEdgeEditation = Class({
125
126 init: function (cy, handleSize) {
127 this.DOUBLE_CLICK_INTERVAL = 300;
128 this.HANDLE_SIZE = handleSize ? handleSize : 5;
129 this.ARROW_END_ID = "ARROW_END_ID";
130
131 this._handles = {};
132 this._dragging = false;
133 this._hover = null;
134
135
136 this._cy = cy;
137 this._$container = $(cy.container());
138
139 this._cy.on('mouseover tap', 'node', this._mouseOver.bind(this));
140 this._cy.on('mouseout', 'node', this._mouseOut.bind(this));
141
142 this._$container.on('mouseout', function (e) {
143 if (this.permanentHandle) {
144 return;
145 }
146
147 this._clear();
148 }.bind(this));
149
150 this._$container.on('mouseover', function (e) {
151 if (this._hover) {
152 this._mouseOver({cyTarget: this._hover});
153 }
154 }.bind(this));
155
156 this._cy.on("select", "node", this._redraw.bind(this))
157
158 this._cy.on("mousedown", "node", function () {
159 this._nodeClicked = true;
160 }.bind(this));
161
162 this._cy.on("mouseup", "node", function () {
163 this._nodeClicked = false;
164 }.bind(this));
165
166 this._cy.on("remove", "node", function () {
167 this._hover = false;
168 this._clear();
169 }.bind(this));
170
171 this._cy.on('showhandle', function (cy, target) {
172 this.permanentHandle = true;
173 this._showHandles(target);
174 }.bind(this));
175
176 this._cy.on('hidehandles', this._hideHandles.bind(this));
177
178 this._cy.bind('zoom pan', this._redraw.bind(this));
179
180
181 this._$canvas = $('<canvas></canvas>');
182 this._$canvas.css("top", 0);
183 this._$canvas.on("mousedown", this._mouseDown.bind(this));
184 this._$canvas.on("mousemove", this._mouseMove.bind(this));
185
186 this._ctx = this._$canvas[0].getContext('2d');
187 this._$container.children("div").append(this._$canvas);
188
189 $(window).bind('mouseup', this._mouseUp.bind(this));
190
191 /*$(window).bind('resize', this._resizeCanvas.bind(this));
192 $(window).bind('resize', this._resizeCanvas.bind(this));*/
193
194 this._cy.on("resize", this._resizeCanvas.bind(this));
195
196 this._$container.bind('resize', function () {
197 this._resizeCanvas();
198 }.bind(this));
199
200 this._resizeCanvas();
201
202 this._arrowEnd = this._cy.add({
203 group: "nodes",
204 data: {
205 "id": this.ARROW_END_ID,
206 "position": {x: 150, y: 150}
207 }
208 });
209
210 this._arrowEnd.css({
211 "opacity": 0,
212 'width': 0.0001,
213 'height': 0.0001
214 });
215
216 },
217 registerHandle: function (handle) {
218 if (handle.nodeTypeNames) {
219 for (var i in handle.nodeTypeNames) {
220 var nodeTypeName = handle.nodeTypeNames[i];
221 this._handles[nodeTypeName] = this._handles[nodeTypeName] || [];
222 this._handles[nodeTypeName].push(handle);
223 }
224 } else {
225 this._handles["*"] = this._handles["*"] || [];
226 this._handles["*"].push(handle);
227 }
228
229 },
230 _showHandles: function (target) {
231 var nodeTypeName = target.data().type;
232 if (nodeTypeName) {
233
234 var handles = this._handles[nodeTypeName] ? this._handles[nodeTypeName] : this._handles["*"];
235
236 for (var i in handles) {
237 if (handles[i].type != null) {
238 this._drawHandle(handles[i], target);
239 }
240 }
241 }
242
243 },
244 _clear: function () {
245
246 var w = this._$container.width();
247 var h = this._$container.height();
248 this._ctx.clearRect(0, 0, w, h);
249 },
250 _drawHandle: function (handle, target) {
251
252 var position = this._getHandlePosition(handle, target);
Michael Lando39a4e0c2017-07-18 20:46:42 +0300253 var handleSize = target.renderedWidth() / 4;
254
Michael Landoed64b5e2017-06-09 03:19:04 +0300255 this._ctx.beginPath();
256
257 if (handle.imageUrl) {
258 var base_image = new Image();
259 base_image.src = handle.imageUrl;
Michael Lando39a4e0c2017-07-18 20:46:42 +0300260 this._ctx.drawImage(base_image, position.x, position.y, handleSize, handleSize);
Michael Landoed64b5e2017-06-09 03:19:04 +0300261 } else {
262 this._ctx.arc(position.x, position.y, this.HANDLE_SIZE, 0, 2 * Math.PI, false);
263 this._ctx.fillStyle = handle.color;
264 this._ctx.strokeStyle = "white";
265 this._ctx.lineWidth = 0;
266 this._ctx.fill();
267 this._ctx.stroke();
268 }
269
270 },
271 _drawArrow: function (fromNode, toPosition, handle) {
272 var toNode;
273 if (this._hover) {
274 toNode = this._hover;
275 } else {
276 this._arrowEnd.renderedPosition(toPosition);
277 toNode = this._arrowEnd;
278 }
279
280
281 if (this._edge) {
282 this._edge.remove();
283 }
284
285 this._edge = this._cy.add({
286 group: "edges",
287 data: {
288 id: "edge",
289 source: fromNode.id(),
290 target: toNode.id(),
291 type: 'temporary-link'
292 },
293 css: $.extend(
294 this._getEdgeCSSByHandle(handle),
295 {opacity: 0.5}
296 )
297 });
298
299 },
300 _clearArrow: function () {
301 if (this._edge) {
302 this._edge.remove();
303 this._edge = null;
304 }
305 },
306 _resizeCanvas: function () {
307 this._$canvas
308 .attr('height', this._$container.height())
309 .attr('width', this._$container.width())
310 .css({
311 'position': 'absolute',
312 'z-index': '999'
313 });
314 },
315 _mouseDown: function (e) {
316 this._hit = this._hitTestHandles(e);
317
318 if (this._hit) {
319 this._lastClick = Date.now();
320 this._dragging = this._hover;
321 this._hover = null;
322 e.stopImmediatePropagation();
323 }
324 },
325 _hideHandles: function () {
326 this.permanentHandle = false;
327 this._clear();
328
329 if(this._hover){
330 this._showHandles(this._hover);
331 }
332 },
333 _mouseUp: function () {
334 if (this._hover) {
335 if (this._hit) {
336 //check if custom listener was passed, if so trigger it and do not add edge
337 var listeners = this._cy._private.listeners;
338 for (var i = 0; i < listeners.length; i++) {
339 if (listeners[i].type === 'addedgemouseup') {
340 this._cy.trigger('addedgemouseup', {
341 source: this._dragging,
342 target: this._hover,
343 edge: this._edge
344 });
345 var that = this;
346 setTimeout(function () {
347 that._dragging = false;
348 that._clearArrow();
349 that._hit = null;
350 }, 0);
351
352
353 return;
354 }
355 }
356
357 var edgeToRemove = this._checkSingleEdge(this._hit.handle, this._dragging);
358 if (edgeToRemove) {
359 this._cy.remove("#" + edgeToRemove.id());
360 }
361 var edge = this._cy.add({
362 data: {
363 source: this._dragging.id(),
364 target: this._hover.id(),
365 type: this._hit.handle.type
366 }
367 });
368 this._initEdgeEvents(edge);
369 }
370 }
371 this._cy.trigger('handlemouseout', {
372 node: this._hover
373 });
374 $("body").css("cursor", "inherit");
375 this._dragging = false;
376 this._clearArrow();
377 },
378 _mouseMove: function (e) {
379 if (this._hover) {
380 if (!this._dragging) {
381 var hit = this._hitTestHandles(e);
382 if (hit) {
383 this._cy.trigger('handlemouseover', {
384 node: this._hover
385 });
386 $("body").css("cursor", "pointer");
387
388 } else {
389 this._cy.trigger('handlemouseout', {
390 node: this._hover
391 });
392 $("body").css("cursor", "inherit");
393 }
394 }
395 } else {
396 $("body").css("cursor", "inherit");
397 }
398
399 if (this._dragging && this._hit.handle) {
400 this._drawArrow(this._dragging, this._getRelativePosition(e), this._hit.handle);
401 }
402
403 if (this._nodeClicked) {
404 this._clear();
405 }
406 },
407 _mouseOver: function (e) {
408
409 if (this._dragging) {
410 if ( (e.cyTarget.id() != this._dragging.id()) && e.cyTarget.data().allowConnection || this._hit.handle.allowLoop) {
411 this._hover = e.cyTarget;
412 }
413 } else {
414 this._hover = e.cyTarget;
415 this._showHandles(this._hover);
416 }
417 },
418 _mouseOut: function (e) {
419 if(!this._dragging) {
420 if (this.permanentHandle) {
421 return;
422 }
423
424 this._clear();
425 }
426 this._hover = null;
427 },
428 _removeEdge: function (edge) {
429 edge.off("mousedown");
430 this._cy.remove("#" + edge.id());
431 },
432 _initEdgeEvents: function (edge) {
433 var self = this;
434 edge.on("mousedown", function () {
435 if (self.__lastClick && Date.now() - self.__lastClick < self.DOUBLE_CLICK_INTERVAL) {
436 self._removeEdge(this);
437 }
438 self.__lastClick = Date.now();
439 })
440 },
441 _hitTestHandles: function (e) {
442 var mousePoisition = this._getRelativePosition(e);
443
444 if (this._hover) {
445 var nodeTypeName = this._hover.data().type;
446 if (nodeTypeName) {
447 var handles = this._handles[nodeTypeName] ? this._handles[nodeTypeName] : this._handles["*"];
448
449 for (var i in handles) {
450 var handle = handles[i];
451
452 var position = this._getHandlePosition(handle, this._hover);
453 if (VectorMath.distance(position, mousePoisition) < this.HANDLE_SIZE) {
454 return {
455 handle: handle,
456 position: position
457 };
458 }
459 }
460 }
461 }
462 },
463 _getHandlePosition: function (handle, target) {
464 var position = target.renderedPosition();
Michael Lando39a4e0c2017-07-18 20:46:42 +0300465 var width = target.renderedWidth();
466 var height = target.renderedHeight();
Michael Landoed64b5e2017-06-09 03:19:04 +0300467 var xpos = null;
468 var ypos = null;
469
470 switch (handle.positionX) {
471 case "left":
Michael Lando39a4e0c2017-07-18 20:46:42 +0300472 xpos = position.x - width / 4;
Michael Landoed64b5e2017-06-09 03:19:04 +0300473 break;
474 case "right":
Michael Lando39a4e0c2017-07-18 20:46:42 +0300475 xpos = position.x + width / 4;
Michael Landoed64b5e2017-06-09 03:19:04 +0300476 break;
477 case "center":
478 xpos = position.x;
479 break;
480 }
481
482 switch (handle.positionY) {
483 case "top":
Michael Lando39a4e0c2017-07-18 20:46:42 +0300484 ypos = position.y - width / 2;
Michael Landoed64b5e2017-06-09 03:19:04 +0300485 break;
486 case "center":
487 ypos = position.y;
488 break;
489 case "bottom":
Michael Lando39a4e0c2017-07-18 20:46:42 +0300490 ypos = position.y + width / 2;
Michael Landoed64b5e2017-06-09 03:19:04 +0300491 break;
492 }
493
Michael Lando39a4e0c2017-07-18 20:46:42 +0300494 var offsetX = 0;
495 var offsetY = 0;
Michael Landoed64b5e2017-06-09 03:19:04 +0300496 return {x: xpos + offsetX, y: ypos + offsetY};
497 },
498 _getEdgeCSSByHandle: function (handle) {
499 var color = handle.lineColor ? handle.lineColor : handle.color;
500 return {
501 "line-color": color,
502 "target-arrow-color": color,
503 "line-style": handle.lineStyle? handle.lineStyle: 'solid',
504 "width": handle.width? handle.width : 3
505 };
506 },
507 _getHandleByType: function (type) {
508 for (var i in this._handles) {
509 var byNodeType = this._handles[i];
510 for (var i2 in byNodeType) {
511 var handle = byNodeType[i2];
512 if (handle.type == type) {
513 return handle;
514 }
515 }
516 }
517 },
518 _getRelativePosition: function (e) {
519 var containerPosition = this._$container.offset();
520 return {
521 x: e.pageX - containerPosition.left,
522 y: e.pageY - containerPosition.top
523 }
524 },
525 _checkSingleEdge: function (handle, node) {
526
527 if (handle.noMultigraph) {
528 var edges = this._cy.edges("[source='" + this._hover.id() + "'][target='" + node.id() + "'],[source='" + node.id() + "'][target='" + this._hover.id() + "']");
529
530 for (var i = 0; i < edges.length; i++) {
531 return edges[i];
532 }
533 } else {
534
535 if (handle.single == false) {
536 return;
537 }
538 var edges = this._cy.edges("[source='" + node.id() + "']");
539
540 for (var i = 0; i < edges.length; i++) {
541 if (edges[i].data()["type"] == handle.type) {
542 return edges[i];
543 }
544 }
545 }
546 },
547 _redraw: function () {
548 this._clear();
549 if (this._hover) {
550 this._showHandles(this._hover);
551 }
552 }
553 });
554
555})(this);
556