blob: 0e6ca8b3c27f29ce41c1be7e6aee3540ab44ccfe [file] [log] [blame]
(function (scope) {
var Class = function (param1, param2) {
var extend, mixins, definition;
if (param2) { //two parameters passed, first is extends, second definition object
extend = Array.isArray(param1) ? param1[0] : param1;
mixins = Array.isArray(param1) ? param1.slice(1) : null;
definition = param2;
} else { //only one parameter passed => no extend, only definition
extend = null;
definition = param1;
}
var Definition = definition.hasOwnProperty("constructor") ? definition.constructor : function () {
};
Definition.prototype = Object.create(extend ? extend.prototype : null);
var propertiesObject = definition.propertiesObject ? definition.propertiesObject : {};
if (mixins) {
var i, i2;
for (i in mixins) {
for (i2 in mixins[i].prototype) {
Definition.prototype[i2] = mixins[i].prototype[i2];
}
for (var i2 in mixins[i].prototype.propertiesObject) {
propertiesObject[i2] = mixins[i].prototype.propertiesObject[i2];
}
}
}
Definition.prototype.propertiesObject = propertiesObject;
Object.defineProperties(Definition.prototype, propertiesObject);
for (var key in definition) {
if (definition.hasOwnProperty(key)) {
Definition.prototype[key] = definition[key];
}
}
Definition.prototype.constructor = Definition;
return Definition;
};
var Interface = function (properties) {
this.properties = properties;
};
var InterfaceException = function (message) {
this.name = "InterfaceException";
this.message = message || "";
};
InterfaceException.prototype = new Error();
Interface.prototype.implements = function (target) {
for (var i in this.properties) {
if (target[this.properties[i]] == undefined) {
throw new InterfaceException("Missing property " + this.properties[i]);
}
}
return true;
};
Interface.prototype.doesImplement = function (target) {
for (var i in this.properties) {
if (target[this.properties[i]] === undefined) {
return false;
}
}
return true;
};
var VectorMath = {
distance: function (vector1, vector2) {
return Math.sqrt(Math.pow(vector1.x - vector2.x, 2) + Math.pow(vector1.y - vector2.y, 2));
}
};
var EventDispatcher = Class({
constructor: function () {
this.events = {};
},
on: function (name, listener, context) {
this.events[name] = this.events[name] ? this.events[name] : [];
this.events[name].push({
listener: listener,
context: context
})
},
once: function (name, listener, context) {
this.off(name, listener, context);
this.on(name, listener, context);
},
off: function (name, listener, context) {
//no event with this name registered? => finish
if (!this.events[name]) {
return;
}
if (listener) { //searching only for certains listeners
for (var i in this.events[name]) {
if (this.events[name][i].listener === listener) {
if (!context || this.events[name][i].context === context) {
this.events[name].splice(i, 1);
}
}
}
} else {
delete this.events[name];
}
},
trigger: function (name) {
var listeners = this.events[name];
for (var i in listeners) {
listeners[i].listener.apply(listeners[i].context, Array.prototype.slice.call(arguments, 1));
}
}
});
exports.CytoscapeEdgeEditation = Class({
init: function (cy, handleSize) {
this.DOUBLE_CLICK_INTERVAL = 300;
this.HANDLE_SIZE = handleSize ? handleSize : 5;
this.ARROW_END_ID = "ARROW_END_ID";
this._handles = {};
this._dragging = false;
this._hover = null;
this._cy = cy;
this._$container = $(cy.container());
this._cy.on('mouseover tap', 'node', this._mouseOver.bind(this));
this._cy.on('mouseout', 'node', this._mouseOut.bind(this));
this._$container.on('mouseout', function (e) {
if (this.permanentHandle) {
return;
}
this._clear();
}.bind(this));
this._$container.on('mouseover', function (e) {
if (this._hover) {
this._mouseOver({cyTarget: this._hover});
}
}.bind(this));
this._cy.on("select", "node", this._redraw.bind(this))
this._cy.on("mousedown", "node", function () {
this._nodeClicked = true;
}.bind(this));
this._cy.on("mouseup", "node", function () {
this._nodeClicked = false;
}.bind(this));
this._cy.on("remove", "node", function () {
this._hover = false;
this._clear();
}.bind(this));
this._cy.on('showhandle', function (cy, target) {
this.permanentHandle = true;
this._showHandles(target);
}.bind(this));
this._cy.on('hidehandles', this._hideHandles.bind(this));
this._cy.bind('zoom pan', this._redraw.bind(this));
this._$canvas = $('<canvas></canvas>');
this._$canvas.css("top", 0);
this._$canvas.on("mousedown", this._mouseDown.bind(this));
this._$canvas.on("mousemove", this._mouseMove.bind(this));
this._ctx = this._$canvas[0].getContext('2d');
this._$container.children("div").append(this._$canvas);
$(window).bind('mouseup', this._mouseUp.bind(this));
/*$(window).bind('resize', this._resizeCanvas.bind(this));
$(window).bind('resize', this._resizeCanvas.bind(this));*/
this._cy.on("resize", this._resizeCanvas.bind(this));
this._$container.bind('resize', function () {
this._resizeCanvas();
}.bind(this));
this._resizeCanvas();
},
registerHandle: function (handle) {
if (handle.nodeTypeNames) {
for (var i in handle.nodeTypeNames) {
var nodeTypeName = handle.nodeTypeNames[i];
this._handles[nodeTypeName] = this._handles[nodeTypeName] || [];
this._handles[nodeTypeName].push(handle);
}
} else {
this._handles["*"] = this._handles["*"] || [];
this._handles["*"].push(handle);
}
},
_showHandles: function (target) {
var nodeTypeName = target.data().type;
if (nodeTypeName) {
var handles = this._handles[nodeTypeName] ? this._handles[nodeTypeName] : this._handles["*"];
for (var i in handles) {
if (handles[i].type != null) {
this._drawHandle(handles[i], target);
}
}
}
},
_clear: function () {
var w = this._$container.width();
var h = this._$container.height();
this._ctx.clearRect(0, 0, w, h);
},
_drawHandle: function (handle, target) {
var position = this._getHandlePosition(handle, target);
var handleSize = this.HANDLE_SIZE * this._cy.zoom();
this._ctx.beginPath();
if (handle.imageUrl) {
var base_image = new Image();
base_image.src = handle.imageUrl;
this._ctx.drawImage(base_image, position.x, position.y, handleSize, handleSize);
} else {
this._ctx.arc(position.x, position.y, this.HANDLE_SIZE, 0, 2 * Math.PI, false);
this._ctx.fillStyle = handle.color;
this._ctx.strokeStyle = "white";
this._ctx.lineWidth = 0;
this._ctx.fill();
this._ctx.stroke();
}
},
_drawArrow: function (fromNode, toPosition, handle) {
var toNode;
if (this._hover) {
toNode = this._hover;
} else {
if (!this._arrowEnd) {
this._arrowEnd = this._cy.add({
group: "nodes",
data: {
"id": this.ARROW_END_ID,
"position": { x: 150, y: 150 }
}
});
this._arrowEnd.css({
"opacity": 0,
'width': 0.0001,
'height': 0.0001
});
}
this._arrowEnd.renderedPosition(toPosition);
toNode = this._arrowEnd;
}
if (this._edge) {
this._edge.remove();
}
this._edge = this._cy.add({
group: "edges",
data: {
id: "edge",
source: fromNode.id(),
target: toNode.id(),
type: 'temporary-link'
},
css: $.extend(
this._getEdgeCSSByHandle(handle),
{opacity: 0.5}
)
});
},
_clearArrow: function () {
if (this._edge) {
this._edge.remove();
this._edge = null;
}
if (this._arrowEnd) {
this._arrowEnd.remove();
this._arrowEnd = null;
}
},
_resizeCanvas: function () {
this._$canvas
.attr('height', this._$container.height())
.attr('width', this._$container.width())
.css({
'position': 'absolute',
'z-index': '999'
});
},
_mouseDown: function (e) {
this._hit = this._hitTestHandles(e);
if (this._hit) {
this._lastClick = Date.now();
this._dragging = this._hover;
this._hover = null;
e.stopImmediatePropagation();
}
},
_hideHandles: function () {
this.permanentHandle = false;
this._clear();
if(this._hover){
this._showHandles(this._hover);
}
},
_mouseUp: function () {
if (this._hover) {
if (this._hit) {
//check if custom listener was passed, if so trigger it and do not add edge
var listeners = this._cy._private.listeners;
for (var i = 0; i < listeners.length; i++) {
if (listeners[i].type === 'addedgemouseup') {
this._cy.trigger('addedgemouseup', {
source: this._dragging,
target: this._hover,
edge: this._edge
});
var that = this;
setTimeout(function () {
that._dragging = false;
that._clearArrow();
that._hit = null;
}, 0);
return;
}
}
var edgeToRemove = this._checkSingleEdge(this._hit.handle, this._dragging);
if (edgeToRemove) {
this._cy.remove("#" + edgeToRemove.id());
}
var edge = this._cy.add({
data: {
source: this._dragging.id(),
target: this._hover.id(),
type: this._hit.handle.type
}
});
this._initEdgeEvents(edge);
}
}
this._cy.trigger('handlemouseout', {
node: this._hover
});
$("body").css("cursor", "inherit");
this._dragging = false;
this._clearArrow();
},
_mouseMove: function (e) {
if (this._hover) {
if (!this._dragging) {
var hit = this._hitTestHandles(e);
if (hit) {
this._cy.trigger('handlemouseover', {
node: this._hover
});
$("body").css("cursor", "pointer");
} else {
this._cy.trigger('handlemouseout', {
node: this._hover
});
$("body").css("cursor", "inherit");
}
}
} else {
$("body").css("cursor", "inherit");
}
if (this._dragging && this._hit.handle) {
this._drawArrow(this._dragging, this._getRelativePosition(e), this._hit.handle);
}
if (this._nodeClicked) {
this._clear();
}
},
_mouseOver: function (e) {
if (this._dragging) {
if ( (e.cyTarget.id() != this._dragging.id()) && e.cyTarget.data().allowConnection || this._hit.handle.allowLoop) {
this._hover = e.cyTarget;
}
} else {
this._hover = e.cyTarget;
this._showHandles(this._hover);
}
},
_mouseOut: function (e) {
if(!this._dragging) {
if (this.permanentHandle) {
return;
}
this._clear();
}
this._hover = null;
},
_removeEdge: function (edge) {
edge.off("mousedown");
this._cy.remove("#" + edge.id());
},
_initEdgeEvents: function (edge) {
var self = this;
edge.on("mousedown", function () {
if (self.__lastClick && Date.now() - self.__lastClick < self.DOUBLE_CLICK_INTERVAL) {
self._removeEdge(this);
}
self.__lastClick = Date.now();
})
},
_hitTestHandles: function (e) {
var mousePoisition = this._getRelativePosition(e);
if (this._hover) {
var nodeTypeName = this._hover.data().type;
if (nodeTypeName) {
var handles = this._handles[nodeTypeName] ? this._handles[nodeTypeName] : this._handles["*"];
for (var i in handles) {
var handle = handles[i];
var position = this._getHandlePosition(handle, this._hover);
var renderedHandleSize = this.HANDLE_SIZE * this._cy.zoom(); //actual number of pixels that handle uses.
if (VectorMath.distance(position, mousePoisition) < renderedHandleSize) {
return {
handle: handle,
position: position
};
}
}
}
}
},
_getHandlePosition: function (handle, target) { //returns the upper left point at which to begin drawing the handle
var position = target.renderedPosition();
var width = target.renderedWidth();
var height = target.renderedHeight();
var renderedHandleSize = this.HANDLE_SIZE * this._cy.zoom(); //actual number of pixels that handle will use.
var xpos = null;
var ypos = null;
switch (handle.positionX) {
case "left":
xpos = position.x - width / 2;
break;
case "right": //position.x is the exact center of the node. Need to add half the width to get to the right edge. Then, subtract renderedHandleSize to get handle position
xpos = position.x + width / 2 - renderedHandleSize;
break;
case "center":
xpos = position.x;
break;
}
switch (handle.positionY) {
case "top":
ypos = position.y - height / 2;
break;
case "center":
ypos = position.y;
break;
case "bottom":
ypos = position.y + height / 2;
break;
}
//Determine if handle will be too big and require offset to prevent it from covering too much of the node icon (in which case, move it over by 1/2 the renderedHandleSize, so half the handle overlaps).
//Need to use target.width(), which is the size of the node, unrelated to rendered size/zoom
var offsetX = (target.width() < 30) ? renderedHandleSize / 2 : 0;
var offsetY = (target.height() < 30) ? renderedHandleSize /2 : 0;
return {x: xpos + offsetX, y: ypos - offsetY};
},
_getEdgeCSSByHandle: function (handle) {
var color = handle.lineColor ? handle.lineColor : handle.color;
return {
"line-color": color,
"target-arrow-color": color,
"line-style": handle.lineStyle? handle.lineStyle: 'solid',
"width": handle.width? handle.width : 3
};
},
_getHandleByType: function (type) {
for (var i in this._handles) {
var byNodeType = this._handles[i];
for (var i2 in byNodeType) {
var handle = byNodeType[i2];
if (handle.type == type) {
return handle;
}
}
}
},
_getRelativePosition: function (e) {
var containerPosition = this._$container.offset();
return {
x: e.pageX - containerPosition.left,
y: e.pageY - containerPosition.top
}
},
_checkSingleEdge: function (handle, node) {
if (handle.noMultigraph) {
var edges = this._cy.edges("[source='" + this._hover.id() + "'][target='" + node.id() + "'],[source='" + node.id() + "'][target='" + this._hover.id() + "']");
for (var i = 0; i < edges.length; i++) {
return edges[i];
}
} else {
if (handle.single == false) {
return;
}
var edges = this._cy.edges("[source='" + node.id() + "']");
for (var i = 0; i < edges.length; i++) {
if (edges[i].data()["type"] == handle.type) {
return edges[i];
}
}
}
},
_redraw: function () {
this._clear();
if (this._hover) {
this._showHandles(this._hover);
}
}
});
})(this);