2 var Class = function (param1, param2) {
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;
9 } else { //only one parameter passed => no extend, only definition
15 var Definition = definition.hasOwnProperty("constructor") ? definition.constructor : function () {
18 Definition.prototype = Object.create(extend ? extend.prototype : null);
19 var propertiesObject = definition.propertiesObject ? definition.propertiesObject : {};
23 for (i2 in mixins[i].prototype) {
24 Definition.prototype[i2] = mixins[i].prototype[i2];
26 for (var i2 in mixins[i].prototype.propertiesObject) {
27 propertiesObject[i2] = mixins[i].prototype.propertiesObject[i2];
32 Definition.prototype.propertiesObject = propertiesObject;
34 Object.defineProperties(Definition.prototype, propertiesObject);
36 for (var key in definition) {
37 if (definition.hasOwnProperty(key)) {
38 Definition.prototype[key] = definition[key];
42 Definition.prototype.constructor = Definition;
48 var Interface = function (properties) {
49 this.properties = properties;
52 var InterfaceException = function (message) {
53 this.name = "InterfaceException";
54 this.message = message || "";
57 InterfaceException.prototype = new Error();
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]);
68 Interface.prototype.doesImplement = function (target) {
69 for (var i in this.properties) {
70 if (target[this.properties[i]] === undefined) {
78 distance: function (vector1, vector2) {
79 return Math.sqrt(Math.pow(vector1.x - vector2.x, 2) + Math.pow(vector1.y - vector2.y, 2));
83 var EventDispatcher = Class({
84 constructor: function () {
87 on: function (name, listener, context) {
88 this.events[name] = this.events[name] ? this.events[name] : [];
89 this.events[name].push({
94 once: function (name, listener, context) {
95 this.off(name, listener, context);
96 this.on(name, listener, context);
98 off: function (name, listener, context) {
99 //no event with this name registered? => finish
100 if (!this.events[name]) {
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);
112 delete this.events[name];
115 trigger: function (name) {
116 var listeners = this.events[name];
118 for (var i in listeners) {
119 listeners[i].listener.apply(listeners[i].context, Array.prototype.slice.call(arguments, 1));
124 exports.CytoscapeEdgeEditation = Class({
126 init: function (cy) {
127 this.DOUBLE_CLICK_INTERVAL = 300;
128 this.HANDLE_SIZE = 18;
129 this.ARROW_END_ID = "ARROW_END_ID";
132 this._dragging = false;
134 this._tagMode = false;
137 this._$container = $(cy.container());
139 this._$canvas = $('<canvas></canvas>');
140 this._$canvas.css("top", 0);
142 this._ctx = this._$canvas[0].getContext('2d');
143 this._$container.children("div").append(this._$canvas);
145 this._resizeCanvas();
147 this.initContainerEvents();
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));*/
155 this._$container.bind('resize', function () {
156 this._resizeCanvas();
159 this._cy.bind('zoom pan', this._redraw.bind(this));
161 this._cy.on('showhandle', function (cy, target, customHandle) {
162 this.permanentHandle = true;
163 this._showHandles(target, customHandle);
166 this._cy.on('hidehandles', this._hideHandles.bind(this));
168 this._$container.on('mouseout', function (e) {
169 if (this.permanentHandle) {
178 initNodeEvents: function (){
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));
184 this._cy.on('tapdragover', 'node', this._mouseOver.bind(this));
185 this._cy.on('tapdragout', 'node', this._mouseOut.bind(this));
189 //this._cy.on("select", "node", this._redraw.bind(this))
191 this._cy.on("mousedown", "node", function () {
193 this._nodeClicked = true;
197 this._cy.on("mouseup", "node", function () {
198 this._nodeClicked = false;
201 this._cy.on("remove", "node", function () {
206 // this._$container.on('mouseover', function (e) {
207 // if (this._hover) {
208 // this._mouseOver({cyTarget: this._hover});
212 this._cy.on('tagstart', function(){
213 this._tagMode = true;
216 this._cy.on('tagend', function(){
217 this._tagMode = false;
221 registerHandle: function (handle) {
223 if (handle.imageUrl) {
225 var base_image = new Image();
226 base_image.src = handle.imageUrl;
227 base_image.onload = function() {
228 handle.image = base_image;
232 this._handles[handle.type] = this._handles[handle.type] || [];
233 this._handles[handle.type] = handle;
237 _showHandles: function (target, handleType) {
240 handleType = 'add-edge'; //ie, CanvasHandleTypes.ADD_EDGE, which is the default
242 this._drawHandle(this._handles[handleType], target);
245 _clear: function () {
247 var w = this._$container.width();
248 var h = this._$container.height();
249 this._ctx.clearRect(0, 0, w, h);
251 _drawHandle: function (handle, target) {
253 target.data().handleType = handle.type;
254 var position = this._getHandlePosition(target);
255 var handleSize = this.HANDLE_SIZE * this._cy.zoom();
256 this._ctx.clearRect(position.x, position.y, handleSize, handleSize);
259 this._ctx.drawImage(handle.image, position.x, position.y, handleSize, handleSize);
262 _drawArrow: function (fromNode, toPosition, handle) {
265 toNode = this._hover;
267 if (!this._arrowEnd) {
268 this._arrowEnd = this._cy.add({
271 "id": this.ARROW_END_ID,
272 "position": { x: 150, y: 150 }
283 this._arrowEnd.renderedPosition(toPosition);
284 toNode = this._arrowEnd;
292 this._edge = this._cy.add({
296 source: fromNode.id(),
298 type: 'temporary-link'
301 this._getEdgeCSSByHandle(handle),
307 _clearArrow: function () {
313 if (this._arrowEnd) {
314 this._arrowEnd.remove();
315 this._arrowEnd = null;
318 _resizeCanvas: function () {
320 .attr('height', this._$container.height())
321 .attr('width', this._$container.width())
323 'position': 'absolute',
327 _mouseDown: function (e) {
331 //this._hit = this._hitTestHandles(e);
334 this._lastClick = Date.now();
335 this._dragging = this._hover;
337 e.stopImmediatePropagation();
341 _hideHandles: function () {
342 this.permanentHandle = false;
346 _mouseUp: function (e) {
349 if(this._hitTestHandles(e))
350 this._cy.trigger('handletagclick', {
351 nodeId: this._hover.data().id
353 //this._hover = null;
354 } else if (this._hit && this._dragging) {
355 //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,
365 setTimeout(function () {
366 that._dragging = false;
376 var edgeToRemove = this._checkSingleEdge(this._hit.handle, this._dragging);
378 this._cy.remove("#" + edgeToRemove.id());
380 var edge = this._cy.add({
382 source: this._dragging.id(),
383 target: this._hover.id(),
387 this._initEdgeEvents(edge);
390 this._cy.trigger('handlemouseout', {
393 $("body").css("cursor", "inherit");
394 this._dragging = false;
397 _mouseMove: function (e) {
399 if (!this._dragging) {
400 this._hit = this._hitTestHandles(e);
402 this._cy.trigger('handlemouseover', {
405 $("body").css("cursor", "pointer");
407 this._cy.trigger('handlemouseout', {
411 this._showHandles(this._hover);
413 $("body").css("cursor", "inherit");
418 if (this._dragging && this._hit.handle) {
419 this._drawArrow(this._dragging, this._getRelativePosition(e), this._hit.handle);
422 if (this._nodeClicked) {
426 _mouseOver: function (e) {
428 if (this._dragging) {
429 if ( (e.cyTarget.id() != this._dragging.id()) && e.cyTarget.data().allowConnection) {
430 this._hover = e.cyTarget;
433 this._hover = e.cyTarget;
434 if (!this._tagMode) {
435 this._showHandles(this._hover);
439 _mouseOut: function (e) {
440 if(!this._dragging) {
441 if (!this.permanentHandle) {
444 this._cy.trigger('handlemouseout', {
450 _removeEdge: function (edge) {
451 edge.off("mousedown");
452 this._cy.remove("#" + edge.id());
454 _initEdgeEvents: function (edge) {
456 edge.on("mousedown", function () {
457 if (self.__lastClick && Date.now() - self.__lastClick < self.DOUBLE_CLICK_INTERVAL) {
458 self._removeEdge(this);
460 self.__lastClick = Date.now();
463 _hitTestHandles: function (e) {
464 var mousePoisition = this._getRelativePosition(e);
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;
472 handle: this._handles[handleType]
477 _getHandlePosition: function (target) { //returns the upper left point at which to begin drawing the handle
478 var position = target.renderedPosition();
479 var width = target.renderedWidth();
480 var height = target.renderedHeight();
481 var renderedHandleSize = this.HANDLE_SIZE * this._cy.zoom(); //actual number of pixels that handle will use.
482 var xpos = position.x + width / 2 - renderedHandleSize;
483 var ypos = position.y - height / 2;
485 return {x: xpos, y: ypos};
487 _getEdgeCSSByHandle: function (handle) {
488 var color = handle.lineColor ? handle.lineColor : handle.color;
491 "target-arrow-color": color,
492 "line-style": handle.lineStyle? handle.lineStyle: 'solid',
493 "width": handle.width? handle.width : 3
496 _getRelativePosition: function (e) {
497 var containerPosition = this._$container.offset();
499 x: e.pageX - containerPosition.left,
500 y: e.pageY - containerPosition.top
503 _checkSingleEdge: function (handle, node) {
505 if (handle.noMultigraph) {
506 var edges = this._cy.edges("[source='" + this._hover.id() + "'][target='" + node.id() + "'],[source='" + node.id() + "'][target='" + this._hover.id() + "']");
508 for (var i = 0; i < edges.length; i++) {
513 if (handle.single == false) {
516 var edges = this._cy.edges("[source='" + node.id() + "']");
518 for (var i = 0; i < edges.length; i++) {
519 if (edges[i].data()["type"] == handle.type) {
525 _redraw: function () {
528 this._cy.trigger('canvasredraw');