blob: 2da6f786eae90c18e66ce6ca294720cb47be0cbe [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);
253
254 this._ctx.beginPath();
255
256 if (handle.imageUrl) {
257 var base_image = new Image();
258 base_image.src = handle.imageUrl;
259 this._ctx.drawImage(base_image, position.x, position.y, this.HANDLE_SIZE, this.HANDLE_SIZE);
260 } else {
261 this._ctx.arc(position.x, position.y, this.HANDLE_SIZE, 0, 2 * Math.PI, false);
262 this._ctx.fillStyle = handle.color;
263 this._ctx.strokeStyle = "white";
264 this._ctx.lineWidth = 0;
265 this._ctx.fill();
266 this._ctx.stroke();
267 }
268
269 },
270 _drawArrow: function (fromNode, toPosition, handle) {
271 var toNode;
272 if (this._hover) {
273 toNode = this._hover;
274 } else {
275 this._arrowEnd.renderedPosition(toPosition);
276 toNode = this._arrowEnd;
277 }
278
279
280 if (this._edge) {
281 this._edge.remove();
282 }
283
284 this._edge = this._cy.add({
285 group: "edges",
286 data: {
287 id: "edge",
288 source: fromNode.id(),
289 target: toNode.id(),
290 type: 'temporary-link'
291 },
292 css: $.extend(
293 this._getEdgeCSSByHandle(handle),
294 {opacity: 0.5}
295 )
296 });
297
298 },
299 _clearArrow: function () {
300 if (this._edge) {
301 this._edge.remove();
302 this._edge = null;
303 }
304 },
305 _resizeCanvas: function () {
306 this._$canvas
307 .attr('height', this._$container.height())
308 .attr('width', this._$container.width())
309 .css({
310 'position': 'absolute',
311 'z-index': '999'
312 });
313 },
314 _mouseDown: function (e) {
315 this._hit = this._hitTestHandles(e);
316
317 if (this._hit) {
318 this._lastClick = Date.now();
319 this._dragging = this._hover;
320 this._hover = null;
321 e.stopImmediatePropagation();
322 }
323 },
324 _hideHandles: function () {
325 this.permanentHandle = false;
326 this._clear();
327
328 if(this._hover){
329 this._showHandles(this._hover);
330 }
331 },
332 _mouseUp: function () {
333 if (this._hover) {
334 if (this._hit) {
335 //check if custom listener was passed, if so trigger it and do not add edge
336 var listeners = this._cy._private.listeners;
337 for (var i = 0; i < listeners.length; i++) {
338 if (listeners[i].type === 'addedgemouseup') {
339 this._cy.trigger('addedgemouseup', {
340 source: this._dragging,
341 target: this._hover,
342 edge: this._edge
343 });
344 var that = this;
345 setTimeout(function () {
346 that._dragging = false;
347 that._clearArrow();
348 that._hit = null;
349 }, 0);
350
351
352 return;
353 }
354 }
355
356 var edgeToRemove = this._checkSingleEdge(this._hit.handle, this._dragging);
357 if (edgeToRemove) {
358 this._cy.remove("#" + edgeToRemove.id());
359 }
360 var edge = this._cy.add({
361 data: {
362 source: this._dragging.id(),
363 target: this._hover.id(),
364 type: this._hit.handle.type
365 }
366 });
367 this._initEdgeEvents(edge);
368 }
369 }
370 this._cy.trigger('handlemouseout', {
371 node: this._hover
372 });
373 $("body").css("cursor", "inherit");
374 this._dragging = false;
375 this._clearArrow();
376 },
377 _mouseMove: function (e) {
378 if (this._hover) {
379 if (!this._dragging) {
380 var hit = this._hitTestHandles(e);
381 if (hit) {
382 this._cy.trigger('handlemouseover', {
383 node: this._hover
384 });
385 $("body").css("cursor", "pointer");
386
387 } else {
388 this._cy.trigger('handlemouseout', {
389 node: this._hover
390 });
391 $("body").css("cursor", "inherit");
392 }
393 }
394 } else {
395 $("body").css("cursor", "inherit");
396 }
397
398 if (this._dragging && this._hit.handle) {
399 this._drawArrow(this._dragging, this._getRelativePosition(e), this._hit.handle);
400 }
401
402 if (this._nodeClicked) {
403 this._clear();
404 }
405 },
406 _mouseOver: function (e) {
407
408 if (this._dragging) {
409 if ( (e.cyTarget.id() != this._dragging.id()) && e.cyTarget.data().allowConnection || this._hit.handle.allowLoop) {
410 this._hover = e.cyTarget;
411 }
412 } else {
413 this._hover = e.cyTarget;
414 this._showHandles(this._hover);
415 }
416 },
417 _mouseOut: function (e) {
418 if(!this._dragging) {
419 if (this.permanentHandle) {
420 return;
421 }
422
423 this._clear();
424 }
425 this._hover = null;
426 },
427 _removeEdge: function (edge) {
428 edge.off("mousedown");
429 this._cy.remove("#" + edge.id());
430 },
431 _initEdgeEvents: function (edge) {
432 var self = this;
433 edge.on("mousedown", function () {
434 if (self.__lastClick && Date.now() - self.__lastClick < self.DOUBLE_CLICK_INTERVAL) {
435 self._removeEdge(this);
436 }
437 self.__lastClick = Date.now();
438 })
439 },
440 _hitTestHandles: function (e) {
441 var mousePoisition = this._getRelativePosition(e);
442
443 if (this._hover) {
444 var nodeTypeName = this._hover.data().type;
445 if (nodeTypeName) {
446 var handles = this._handles[nodeTypeName] ? this._handles[nodeTypeName] : this._handles["*"];
447
448 for (var i in handles) {
449 var handle = handles[i];
450
451 var position = this._getHandlePosition(handle, this._hover);
452 if (VectorMath.distance(position, mousePoisition) < this.HANDLE_SIZE) {
453 return {
454 handle: handle,
455 position: position
456 };
457 }
458 }
459 }
460 }
461 },
462 _getHandlePosition: function (handle, target) {
463 var position = target.renderedPosition();
464 var width = target.renderedOuterWidth();
465 var height = target.renderedOuterHeight();
466 var xpos = null;
467 var ypos = null;
468
469 switch (handle.positionX) {
470 case "left":
471 xpos = position.x - width / 2 + this.HANDLE_SIZE;
472 break;
473 case "right":
474 xpos = position.x + width / 2 - this.HANDLE_SIZE;
475 break;
476 case "center":
477 xpos = position.x;
478 break;
479 }
480
481 switch (handle.positionY) {
482 case "top":
483 ypos = position.y - height / 2 + this.HANDLE_SIZE;
484 break;
485 case "center":
486 ypos = position.y;
487 break;
488 case "bottom":
489 ypos = position.y + height / 2 - this.HANDLE_SIZE;
490 break;
491 }
492
493 var offsetX = handle.offsetX ? handle.offsetX : 0;
494 var offsetY = handle.offsetY ? handle.offsetY : 0;
495 return {x: xpos + offsetX, y: ypos + offsetY};
496 },
497 _getEdgeCSSByHandle: function (handle) {
498 var color = handle.lineColor ? handle.lineColor : handle.color;
499 return {
500 "line-color": color,
501 "target-arrow-color": color,
502 "line-style": handle.lineStyle? handle.lineStyle: 'solid',
503 "width": handle.width? handle.width : 3
504 };
505 },
506 _getHandleByType: function (type) {
507 for (var i in this._handles) {
508 var byNodeType = this._handles[i];
509 for (var i2 in byNodeType) {
510 var handle = byNodeType[i2];
511 if (handle.type == type) {
512 return handle;
513 }
514 }
515 }
516 },
517 _getRelativePosition: function (e) {
518 var containerPosition = this._$container.offset();
519 return {
520 x: e.pageX - containerPosition.left,
521 y: e.pageY - containerPosition.top
522 }
523 },
524 _checkSingleEdge: function (handle, node) {
525
526 if (handle.noMultigraph) {
527 var edges = this._cy.edges("[source='" + this._hover.id() + "'][target='" + node.id() + "'],[source='" + node.id() + "'][target='" + this._hover.id() + "']");
528
529 for (var i = 0; i < edges.length; i++) {
530 return edges[i];
531 }
532 } else {
533
534 if (handle.single == false) {
535 return;
536 }
537 var edges = this._cy.edges("[source='" + node.id() + "']");
538
539 for (var i = 0; i < edges.length; i++) {
540 if (edges[i].data()["type"] == handle.type) {
541 return edges[i];
542 }
543 }
544 }
545 },
546 _redraw: function () {
547 this._clear();
548 if (this._hover) {
549 this._showHandles(this._hover);
550 }
551 }
552 });
553
554})(this);
555