blob: ca88b3d73fb745c9e96c13171dc8a73c27e1c982 [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
Michael Lando5b593492018-07-29 16:13:45 +0300126 init: function (cy) {
Michael Landoed64b5e2017-06-09 03:19:04 +0300127 this.DOUBLE_CLICK_INTERVAL = 300;
Michael Lando5b593492018-07-29 16:13:45 +0300128 this.HANDLE_SIZE = 18;
Michael Landoed64b5e2017-06-09 03:19:04 +0300129 this.ARROW_END_ID = "ARROW_END_ID";
130
131 this._handles = {};
132 this._dragging = false;
133 this._hover = null;
Michael Lando5b593492018-07-29 16:13:45 +0300134 this._tagMode = false;
Michael Landoed64b5e2017-06-09 03:19:04 +0300135
136 this._cy = cy;
137 this._$container = $(cy.container());
138
Michael Lando5b593492018-07-29 16:13:45 +0300139 this._$canvas = $('<canvas></canvas>');
140 this._$canvas.css("top", 0);
141
142 this._ctx = this._$canvas[0].getContext('2d');
143 this._$container.children("div").append(this._$canvas);
144
145 this._resizeCanvas();
146
147 this.initContainerEvents();
148
149 },
150 initContainerEvents: function () {
151 this._cy.on("resize", this._resizeCanvas.bind(this));
152 /*$(window).bind('resize', this._resizeCanvas.bind(this));
153 $(window).bind('resize', this._resizeCanvas.bind(this));*/
154
155 this._$container.bind('resize', function () {
156 this._resizeCanvas();
157 }.bind(this));
158
159 this._cy.bind('zoom pan', this._redraw.bind(this));
160
161 this._cy.on('showhandle', function (cy, target, customHandle) {
162 this.permanentHandle = true;
163 this._showHandles(target, customHandle);
164 }.bind(this));
165
166 this._cy.on('hidehandles', this._hideHandles.bind(this));
Michael Landoed64b5e2017-06-09 03:19:04 +0300167
168 this._$container.on('mouseout', function (e) {
169 if (this.permanentHandle) {
170 return;
171 }
172
173 this._clear();
174 }.bind(this));
175
Michael Landoed64b5e2017-06-09 03:19:04 +0300176
Michael Lando5b593492018-07-29 16:13:45 +0300177 },
178 initNodeEvents: function (){
179
180 this._$canvas.on("mousedown", this._mouseDown.bind(this));
181 this._$canvas.on("mousemove", this._mouseMove.bind(this));
182 this._$canvas.on('mouseup', this._mouseUp.bind(this));
183
184 this._cy.on('tapdragover', 'node', this._mouseOver.bind(this));
185 this._cy.on('tapdragout', 'node', this._mouseOut.bind(this));
186
187
188
189 //this._cy.on("select", "node", this._redraw.bind(this))
Michael Landoed64b5e2017-06-09 03:19:04 +0300190
191 this._cy.on("mousedown", "node", function () {
Michael Lando5b593492018-07-29 16:13:45 +0300192 if(!this._tagMode) {
193 this._nodeClicked = true;
194 }
Michael Landoed64b5e2017-06-09 03:19:04 +0300195 }.bind(this));
196
197 this._cy.on("mouseup", "node", function () {
198 this._nodeClicked = false;
199 }.bind(this));
200
201 this._cy.on("remove", "node", function () {
202 this._hover = false;
203 this._clear();
204 }.bind(this));
205
Michael Lando5b593492018-07-29 16:13:45 +0300206 // this._$container.on('mouseover', function (e) {
207 // if (this._hover) {
208 // this._mouseOver({cyTarget: this._hover});
209 // }
210 // }.bind(this));
211
212 this._cy.on('tagstart', function(){
213 this._tagMode = true;
Michael Landoed64b5e2017-06-09 03:19:04 +0300214 }.bind(this));
215
Michael Lando5b593492018-07-29 16:13:45 +0300216 this._cy.on('tagend', function(){
217 this._tagMode = false;
218 }.bind(this))
Michael Landoed64b5e2017-06-09 03:19:04 +0300219
220 },
221 registerHandle: function (handle) {
Michael Lando5b593492018-07-29 16:13:45 +0300222
223 if (handle.imageUrl) {
224
225 var base_image = new Image();
226 base_image.src = handle.imageUrl;
227 base_image.onload = function() {
228 handle.image = base_image;
229 };
Michael Landoed64b5e2017-06-09 03:19:04 +0300230 }
Michael Lando5b593492018-07-29 16:13:45 +0300231
232 this._handles[handle.type] = this._handles[handle.type] || [];
233 this._handles[handle.type] = handle;
234
Michael Landoed64b5e2017-06-09 03:19:04 +0300235
236 },
Michael Lando5b593492018-07-29 16:13:45 +0300237 _showHandles: function (target, handleType) {
Michael Landoed64b5e2017-06-09 03:19:04 +0300238
Michael Lando5b593492018-07-29 16:13:45 +0300239 if(!handleType){
240 handleType = 'add-edge'; //ie, CanvasHandleTypes.ADD_EDGE, which is the default
Michael Landoed64b5e2017-06-09 03:19:04 +0300241 }
Michael Lando5b593492018-07-29 16:13:45 +0300242 this._drawHandle(this._handles[handleType], target);
Michael Landoed64b5e2017-06-09 03:19:04 +0300243
244 },
245 _clear: function () {
246
247 var w = this._$container.width();
248 var h = this._$container.height();
249 this._ctx.clearRect(0, 0, w, h);
250 },
251 _drawHandle: function (handle, target) {
252
Michael Lando5b593492018-07-29 16:13:45 +0300253 target.data().handleType = handle.type;
254 var position = this._getHandlePosition(target);
Michael Lando9db40522017-07-22 17:10:02 +0300255 var handleSize = this.HANDLE_SIZE * this._cy.zoom();
Michael Lando5b593492018-07-29 16:13:45 +0300256 this._ctx.clearRect(position.x, position.y, handleSize, handleSize);
Michael Lando39a4e0c2017-07-18 20:46:42 +0300257
Michael Lando5b593492018-07-29 16:13:45 +0300258 if (handle.image) {
259 this._ctx.drawImage(handle.image, position.x, position.y, handleSize, handleSize);
Michael Landoed64b5e2017-06-09 03:19:04 +0300260 }
Michael Landoed64b5e2017-06-09 03:19:04 +0300261 },
262 _drawArrow: function (fromNode, toPosition, handle) {
263 var toNode;
264 if (this._hover) {
265 toNode = this._hover;
266 } else {
Michael Lando9db40522017-07-22 17:10:02 +0300267 if (!this._arrowEnd) {
268 this._arrowEnd = this._cy.add({
269 group: "nodes",
270 data: {
271 "id": this.ARROW_END_ID,
272 "position": { x: 150, y: 150 }
273 }
274 });
275
276 this._arrowEnd.css({
277 "opacity": 0,
278 'width': 0.0001,
279 'height': 0.0001
280 });
281 }
282
Michael Landoed64b5e2017-06-09 03:19:04 +0300283 this._arrowEnd.renderedPosition(toPosition);
284 toNode = this._arrowEnd;
285 }
286
287
288 if (this._edge) {
289 this._edge.remove();
290 }
291
292 this._edge = this._cy.add({
293 group: "edges",
294 data: {
295 id: "edge",
296 source: fromNode.id(),
297 target: toNode.id(),
298 type: 'temporary-link'
299 },
300 css: $.extend(
301 this._getEdgeCSSByHandle(handle),
302 {opacity: 0.5}
303 )
304 });
305
306 },
307 _clearArrow: function () {
308 if (this._edge) {
309 this._edge.remove();
310 this._edge = null;
311 }
Michael Lando9db40522017-07-22 17:10:02 +0300312
313 if (this._arrowEnd) {
314 this._arrowEnd.remove();
315 this._arrowEnd = null;
316 }
Michael Landoed64b5e2017-06-09 03:19:04 +0300317 },
318 _resizeCanvas: function () {
319 this._$canvas
320 .attr('height', this._$container.height())
321 .attr('width', this._$container.width())
322 .css({
323 'position': 'absolute',
324 'z-index': '999'
325 });
326 },
327 _mouseDown: function (e) {
Michael Lando5b593492018-07-29 16:13:45 +0300328 if(this._tagMode){
329 return;
330 }
331 //this._hit = this._hitTestHandles(e);
Michael Landoed64b5e2017-06-09 03:19:04 +0300332
333 if (this._hit) {
334 this._lastClick = Date.now();
335 this._dragging = this._hover;
336 this._hover = null;
337 e.stopImmediatePropagation();
338 }
Michael Lando5b593492018-07-29 16:13:45 +0300339
Michael Landoed64b5e2017-06-09 03:19:04 +0300340 },
341 _hideHandles: function () {
342 this.permanentHandle = false;
343 this._clear();
344
Michael Landoed64b5e2017-06-09 03:19:04 +0300345 },
Michael Lando5b593492018-07-29 16:13:45 +0300346 _mouseUp: function (e) {
Michael Landoed64b5e2017-06-09 03:19:04 +0300347 if (this._hover) {
Michael Lando5b593492018-07-29 16:13:45 +0300348 if(this._tagMode){
349 if(this._hitTestHandles(e))
350 this._cy.trigger('handletagclick', {
351 nodeId: this._hover.data().id
352 });
353 //this._hover = null;
354 } else if (this._hit && this._dragging) {
Michael Landoed64b5e2017-06-09 03:19:04 +0300355 //check if custom listener was passed, if so trigger it and do not add edge
356 var listeners = this._cy._private.listeners;
357 for (var i = 0; i < listeners.length; i++) {
358 if (listeners[i].type === 'addedgemouseup') {
359 this._cy.trigger('addedgemouseup', {
360 source: this._dragging,
361 target: this._hover,
362 edge: this._edge
363 });
364 var that = this;
365 setTimeout(function () {
366 that._dragging = false;
367 that._clearArrow();
368 that._hit = null;
369 }, 0);
370
371
372 return;
373 }
374 }
375
376 var edgeToRemove = this._checkSingleEdge(this._hit.handle, this._dragging);
377 if (edgeToRemove) {
378 this._cy.remove("#" + edgeToRemove.id());
379 }
380 var edge = this._cy.add({
381 data: {
382 source: this._dragging.id(),
383 target: this._hover.id(),
Michael Lando5b593492018-07-29 16:13:45 +0300384 type: "default"
Michael Landoed64b5e2017-06-09 03:19:04 +0300385 }
386 });
387 this._initEdgeEvents(edge);
388 }
389 }
390 this._cy.trigger('handlemouseout', {
391 node: this._hover
392 });
393 $("body").css("cursor", "inherit");
394 this._dragging = false;
395 this._clearArrow();
396 },
397 _mouseMove: function (e) {
398 if (this._hover) {
399 if (!this._dragging) {
Michael Lando5b593492018-07-29 16:13:45 +0300400 this._hit = this._hitTestHandles(e);
401 if (this._hit) {
Michael Landoed64b5e2017-06-09 03:19:04 +0300402 this._cy.trigger('handlemouseover', {
403 node: this._hover
404 });
405 $("body").css("cursor", "pointer");
Michael Landoed64b5e2017-06-09 03:19:04 +0300406 } else {
407 this._cy.trigger('handlemouseout', {
408 node: this._hover
409 });
Michael Lando5b593492018-07-29 16:13:45 +0300410 if(!this._tagMode){
411 this._showHandles(this._hover);
412 }
Michael Landoed64b5e2017-06-09 03:19:04 +0300413 $("body").css("cursor", "inherit");
414 }
415 }
Michael Landoed64b5e2017-06-09 03:19:04 +0300416 }
417
418 if (this._dragging && this._hit.handle) {
419 this._drawArrow(this._dragging, this._getRelativePosition(e), this._hit.handle);
420 }
421
422 if (this._nodeClicked) {
423 this._clear();
424 }
425 },
426 _mouseOver: function (e) {
427
428 if (this._dragging) {
Michael Lando5b593492018-07-29 16:13:45 +0300429 if ( (e.cyTarget.id() != this._dragging.id()) && e.cyTarget.data().allowConnection) {
Michael Landoed64b5e2017-06-09 03:19:04 +0300430 this._hover = e.cyTarget;
431 }
432 } else {
433 this._hover = e.cyTarget;
Michael Lando5b593492018-07-29 16:13:45 +0300434 if (!this._tagMode) {
435 this._showHandles(this._hover);
436 }
Michael Landoed64b5e2017-06-09 03:19:04 +0300437 }
438 },
439 _mouseOut: function (e) {
440 if(!this._dragging) {
Michael Lando5b593492018-07-29 16:13:45 +0300441 if (!this.permanentHandle) {
442 this._clear();
Michael Landoed64b5e2017-06-09 03:19:04 +0300443 }
Michael Lando5b593492018-07-29 16:13:45 +0300444 this._cy.trigger('handlemouseout', {
445 node: this._hover
446 });
Michael Landoed64b5e2017-06-09 03:19:04 +0300447 }
448 this._hover = null;
449 },
450 _removeEdge: function (edge) {
451 edge.off("mousedown");
452 this._cy.remove("#" + edge.id());
453 },
454 _initEdgeEvents: function (edge) {
455 var self = this;
456 edge.on("mousedown", function () {
457 if (self.__lastClick && Date.now() - self.__lastClick < self.DOUBLE_CLICK_INTERVAL) {
458 self._removeEdge(this);
459 }
460 self.__lastClick = Date.now();
461 })
462 },
463 _hitTestHandles: function (e) {
464 var mousePoisition = this._getRelativePosition(e);
465
Michael Lando5b593492018-07-29 16:13:45 +0300466 //if (this._hover) {
467 var position = this._getHandlePosition(this._hover);
468 var renderedHandleSize = this.HANDLE_SIZE * this._cy.zoom(); //actual number of pixels that handle uses.
469 if (VectorMath.distance(position, mousePoisition) < renderedHandleSize) {
470 var handleType = this._hover.data().handleType;
471 return {
472 handle: this._handles[handleType]
473 };
Michael Landoed64b5e2017-06-09 03:19:04 +0300474 }
Michael Lando5b593492018-07-29 16:13:45 +0300475 //}
Michael Landoed64b5e2017-06-09 03:19:04 +0300476 },
Michael Lando5b593492018-07-29 16:13:45 +0300477 _getHandlePosition: function (target) { //returns the upper left point at which to begin drawing the handle
Michael Landoed64b5e2017-06-09 03:19:04 +0300478 var position = target.renderedPosition();
Michael Lando39a4e0c2017-07-18 20:46:42 +0300479 var width = target.renderedWidth();
480 var height = target.renderedHeight();
Michael Lando9db40522017-07-22 17:10:02 +0300481 var renderedHandleSize = this.HANDLE_SIZE * this._cy.zoom(); //actual number of pixels that handle will use.
Michael Lando5b593492018-07-29 16:13:45 +0300482 var xpos = position.x + width / 2 - renderedHandleSize;
483 var ypos = position.y - height / 2;
Michael Landoed64b5e2017-06-09 03:19:04 +0300484
Michael Lando5b593492018-07-29 16:13:45 +0300485 return {x: xpos, y: ypos};
Michael Landoed64b5e2017-06-09 03:19:04 +0300486 },
487 _getEdgeCSSByHandle: function (handle) {
488 var color = handle.lineColor ? handle.lineColor : handle.color;
489 return {
490 "line-color": color,
491 "target-arrow-color": color,
492 "line-style": handle.lineStyle? handle.lineStyle: 'solid',
493 "width": handle.width? handle.width : 3
494 };
495 },
Michael Landoed64b5e2017-06-09 03:19:04 +0300496 _getRelativePosition: function (e) {
497 var containerPosition = this._$container.offset();
498 return {
499 x: e.pageX - containerPosition.left,
500 y: e.pageY - containerPosition.top
501 }
502 },
503 _checkSingleEdge: function (handle, node) {
504
505 if (handle.noMultigraph) {
506 var edges = this._cy.edges("[source='" + this._hover.id() + "'][target='" + node.id() + "'],[source='" + node.id() + "'][target='" + this._hover.id() + "']");
507
508 for (var i = 0; i < edges.length; i++) {
509 return edges[i];
510 }
511 } else {
512
513 if (handle.single == false) {
514 return;
515 }
516 var edges = this._cy.edges("[source='" + node.id() + "']");
517
518 for (var i = 0; i < edges.length; i++) {
519 if (edges[i].data()["type"] == handle.type) {
520 return edges[i];
521 }
522 }
523 }
524 },
525 _redraw: function () {
526 this._clear();
Michael Lando5b593492018-07-29 16:13:45 +0300527 if(this._tagMode) {
528 this._cy.trigger('canvasredraw');
Michael Landoed64b5e2017-06-09 03:19:04 +0300529 }
530 }
531 });
532
533})(this);
534