| (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); |
| |