|
@ -291,137 +291,116 @@ Graph.prototype._create = function () { |
|
|
this.frame.canvas.appendChild(noCanvas); |
|
|
this.frame.canvas.appendChild(noCanvas); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// create event listeners
|
|
|
|
|
|
var me = this; |
|
|
var me = this; |
|
|
var onmousedown = function (event) {me._onMouseDown(event);}; |
|
|
|
|
|
var onmousemove = function (event) {me._onMouseMoveTitle(event);}; |
|
|
|
|
|
var onmousewheel = function (event) {me._onMouseWheel(event);}; |
|
|
|
|
|
var ontouchstart = function (event) {me._onTouchStart(event);}; |
|
|
|
|
|
vis.util.addEventListener(this.frame.canvas, "mousedown", onmousedown); |
|
|
|
|
|
vis.util.addEventListener(this.frame.canvas, "mousemove", onmousemove); |
|
|
|
|
|
vis.util.addEventListener(this.frame.canvas, "mousewheel", onmousewheel); |
|
|
|
|
|
vis.util.addEventListener(this.frame.canvas, "touchstart", ontouchstart); |
|
|
|
|
|
|
|
|
this.drag = {}; |
|
|
|
|
|
this.pinch = {}; |
|
|
|
|
|
this.hammer = Hammer(this.frame.canvas, { |
|
|
|
|
|
prevent_default: true |
|
|
|
|
|
}); |
|
|
|
|
|
this.hammer.on('tap', me._onTap.bind(me) ); |
|
|
|
|
|
this.hammer.on('hold', me._onHold.bind(me) ); |
|
|
|
|
|
this.hammer.on('pinch', me._onPinch.bind(me) ); |
|
|
|
|
|
this.hammer.on('touch', me._onTouch.bind(me) ); |
|
|
|
|
|
this.hammer.on('dragstart', me._onDragStart.bind(me) ); |
|
|
|
|
|
this.hammer.on('drag', me._onDrag.bind(me) ); |
|
|
|
|
|
this.hammer.on('dragend', me._onDragEnd.bind(me) ); |
|
|
|
|
|
this.hammer.on('mousewheel',me._onMouseWheel.bind(me) ); |
|
|
|
|
|
this.hammer.on('mousemove', me._onMouseMoveTitle.bind(me) ); |
|
|
|
|
|
|
|
|
// add the frame to the container element
|
|
|
// add the frame to the container element
|
|
|
this.containerElement.appendChild(this.frame); |
|
|
this.containerElement.appendChild(this.frame); |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* handle on mouse down event |
|
|
|
|
|
|
|
|
* |
|
|
|
|
|
* @param {{x: Number, y: Number}} pointer |
|
|
|
|
|
* @return {Number | null} node |
|
|
* @private |
|
|
* @private |
|
|
*/ |
|
|
*/ |
|
|
Graph.prototype._onMouseDown = function (event) { |
|
|
|
|
|
event = event || window.event; |
|
|
|
|
|
|
|
|
Graph.prototype._getNodeAt = function (pointer) { |
|
|
|
|
|
var x = this._xToCanvas(pointer.x); |
|
|
|
|
|
var y = this._yToCanvas(pointer.y); |
|
|
|
|
|
|
|
|
if (!this.selectable) { |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
var obj = { |
|
|
|
|
|
left: x, |
|
|
|
|
|
top: y, |
|
|
|
|
|
right: x, |
|
|
|
|
|
bottom: y |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
// check if mouse is still down (may be up when focus is lost for example
|
|
|
|
|
|
// in an iframe)
|
|
|
|
|
|
if (this.leftButtonDown) { |
|
|
|
|
|
this._onMouseUp(event); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// if there are overlapping nodes, select the last one, this is the
|
|
|
|
|
|
// one which is drawn on top of the others
|
|
|
|
|
|
var overlappingNodes = this._getNodesOverlappingWith(obj); |
|
|
|
|
|
return (overlappingNodes.length > 0) ? |
|
|
|
|
|
overlappingNodes[overlappingNodes.length - 1] : null; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
// only react on left mouse button down
|
|
|
|
|
|
this.leftButtonDown = event.which ? (event.which == 1) : (event.button == 1); |
|
|
|
|
|
if (!this.leftButtonDown && !this.touchDown) { |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Get the pointer location from a touch location |
|
|
|
|
|
* @param {{pageX: Number, pageY: Number}} touch |
|
|
|
|
|
* @return {{x: Number, y: Number}} pointer |
|
|
|
|
|
* @private |
|
|
|
|
|
*/ |
|
|
|
|
|
Graph.prototype._getPointer = function (touch) { |
|
|
|
|
|
return { |
|
|
|
|
|
x: touch.pageX - vis.util.getAbsoluteLeft(this.frame.canvas), |
|
|
|
|
|
y: touch.pageY - vis.util.getAbsoluteTop(this.frame.canvas) |
|
|
|
|
|
}; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
// add event listeners to handle moving the contents
|
|
|
|
|
|
// we store the function onmousemove and onmouseup in the timeline, so we can
|
|
|
|
|
|
// remove the eventlisteners lateron in the function mouseUp()
|
|
|
|
|
|
var me = this; |
|
|
|
|
|
if (!this.onmousemove) { |
|
|
|
|
|
this.onmousemove = function (event) {me._onMouseMove(event);}; |
|
|
|
|
|
vis.util.addEventListener(document, "mousemove", me.onmousemove); |
|
|
|
|
|
} |
|
|
|
|
|
if (!this.onmouseup) { |
|
|
|
|
|
this.onmouseup = function (event) {me._onMouseUp(event);}; |
|
|
|
|
|
vis.util.addEventListener(document, "mouseup", me.onmouseup); |
|
|
|
|
|
} |
|
|
|
|
|
vis.util.preventDefault(event); |
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* On start of a touch gesture, store the pointer |
|
|
|
|
|
* @param event |
|
|
|
|
|
* @private |
|
|
|
|
|
*/ |
|
|
|
|
|
Graph.prototype._onTouch = function (event) { |
|
|
|
|
|
this.drag.pointer = this._getPointer(event.gesture.touches[0]); |
|
|
|
|
|
this.drag.pinched = false; |
|
|
|
|
|
this.pinch.scale = this._getScale(); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
// store the start x and y position of the mouse
|
|
|
|
|
|
this.startMouseX = util.getPageX(event); |
|
|
|
|
|
this.startMouseY = util.getPageY(event); |
|
|
|
|
|
this.startFrameLeft = vis.util.getAbsoluteLeft(this.frame.canvas); |
|
|
|
|
|
this.startFrameTop = vis.util.getAbsoluteTop(this.frame.canvas); |
|
|
|
|
|
this.startTranslation = this._getTranslation(); |
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* handle drag start event |
|
|
|
|
|
* @private |
|
|
|
|
|
*/ |
|
|
|
|
|
Graph.prototype._onDragStart = function (event) { |
|
|
|
|
|
var drag = this.drag; |
|
|
|
|
|
|
|
|
this.ctrlKeyDown = event.ctrlKey; |
|
|
|
|
|
this.shiftKeyDown = event.shiftKey; |
|
|
|
|
|
|
|
|
drag.translation = this._getTranslation(); |
|
|
|
|
|
|
|
|
var obj = { |
|
|
|
|
|
left: this._xToCanvas(this.startMouseX - this.startFrameLeft), |
|
|
|
|
|
top: this._yToCanvas(this.startMouseY - this.startFrameTop), |
|
|
|
|
|
right: this._xToCanvas(this.startMouseX - this.startFrameLeft), |
|
|
|
|
|
bottom: this._yToCanvas(this.startMouseY - this.startFrameTop) |
|
|
|
|
|
}; |
|
|
|
|
|
var overlappingNodes = this._getNodesOverlappingWith(obj); |
|
|
|
|
|
// if there are overlapping nodes, select the last one, this is the
|
|
|
|
|
|
// one which is drawn on top of the others
|
|
|
|
|
|
this.startClickedObj = (overlappingNodes.length > 0) ? |
|
|
|
|
|
overlappingNodes[overlappingNodes.length - 1] : undefined; |
|
|
|
|
|
|
|
|
|
|
|
if (this.startClickedObj) { |
|
|
|
|
|
// move clicked node with the mouse
|
|
|
|
|
|
|
|
|
|
|
|
// make the clicked node temporarily fixed, and store their original state
|
|
|
|
|
|
var node = this.nodes[this.startClickedObj]; |
|
|
|
|
|
this.startClickedObj.xFixed = node.xFixed; |
|
|
|
|
|
this.startClickedObj.yFixed = node.yFixed; |
|
|
|
|
|
node.xFixed = true; |
|
|
|
|
|
node.yFixed = true; |
|
|
|
|
|
|
|
|
|
|
|
if (!this.ctrlKeyDown || !node.isSelected()) { |
|
|
|
|
|
// select this node
|
|
|
|
|
|
this._selectNodes([this.startClickedObj], this.ctrlKeyDown); |
|
|
|
|
|
} |
|
|
|
|
|
else { |
|
|
|
|
|
// unselect this node
|
|
|
|
|
|
this._unselectNodes([this.startClickedObj]); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// note: drag.pointer is set in _onTouch to get the initial touch location
|
|
|
|
|
|
drag.nodeId = this._getNodeAt(drag.pointer); |
|
|
|
|
|
drag.node = this.nodes[drag.nodeId]; |
|
|
|
|
|
if (drag.node) { |
|
|
|
|
|
this._selectNodes([drag.nodeId]); |
|
|
|
|
|
|
|
|
if (!this.moving) { |
|
|
|
|
|
this._redraw(); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
else if (this.shiftKeyDown) { |
|
|
|
|
|
// start selection of multiple nodes
|
|
|
|
|
|
} |
|
|
|
|
|
else { |
|
|
|
|
|
// start moving the graph
|
|
|
|
|
|
this.moved = false; |
|
|
|
|
|
|
|
|
// store original xFixed and yFixed, make the node temporarily Fixed
|
|
|
|
|
|
drag.xFixed = drag.node.xFixed; |
|
|
|
|
|
drag.yFixed = drag.node.yFixed; |
|
|
|
|
|
drag.node.xFixed = true; |
|
|
|
|
|
drag.node.yFixed = true; |
|
|
} |
|
|
} |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* handle on mouse move event |
|
|
|
|
|
* @param {Event} event |
|
|
|
|
|
|
|
|
* handle drag event |
|
|
* @private |
|
|
* @private |
|
|
*/ |
|
|
*/ |
|
|
Graph.prototype._onMouseMove = function (event) { |
|
|
|
|
|
event = event || window.event; |
|
|
|
|
|
|
|
|
|
|
|
if (!this.selectable) { |
|
|
|
|
|
|
|
|
Graph.prototype._onDrag = function (event) { |
|
|
|
|
|
if (this.drag.pinched) { |
|
|
return; |
|
|
return; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
var mouseX = util.getPageX(event); |
|
|
|
|
|
var mouseY = util.getPageY(event); |
|
|
|
|
|
this.mouseX = mouseX; |
|
|
|
|
|
this.mouseY = mouseY; |
|
|
|
|
|
|
|
|
|
|
|
if (this.startClickedObj) { |
|
|
|
|
|
var node = this.nodes[this.startClickedObj]; |
|
|
|
|
|
|
|
|
var pointer = this._getPointer(event.gesture.touches[0]); |
|
|
|
|
|
|
|
|
if (!this.startClickedObj.xFixed) |
|
|
|
|
|
node.x = this._xToCanvas(mouseX - this.startFrameLeft); |
|
|
|
|
|
|
|
|
var drag= this.drag, |
|
|
|
|
|
node = drag.node; |
|
|
|
|
|
if (node) { |
|
|
|
|
|
if (!drag.xFixed) |
|
|
|
|
|
node.x = this._xToCanvas(pointer.x); |
|
|
|
|
|
|
|
|
if (!this.startClickedObj.yFixed) |
|
|
|
|
|
node.y = this._yToCanvas(mouseY - this.startFrameTop); |
|
|
|
|
|
|
|
|
if (!drag.yFixed) |
|
|
|
|
|
node.y = this._yToCanvas(pointer.y); |
|
|
|
|
|
|
|
|
// start animation if not yet running
|
|
|
// start animation if not yet running
|
|
|
if (!this.moving) { |
|
|
if (!this.moving) { |
|
@ -429,119 +408,140 @@ Graph.prototype._onMouseMove = function (event) { |
|
|
this.start(); |
|
|
this.start(); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
else if (this.shiftKeyDown) { |
|
|
|
|
|
// draw a rect from start mouse location to current mouse location
|
|
|
|
|
|
if (this.frame.selRect == undefined) { |
|
|
|
|
|
this.frame.selRect = document.createElement("DIV"); |
|
|
|
|
|
this.frame.appendChild(this.frame.selRect); |
|
|
|
|
|
|
|
|
|
|
|
this.frame.selRect.style.position = "absolute"; |
|
|
|
|
|
this.frame.selRect.style.border = "1px dashed red"; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var left = Math.min(this.startMouseX, mouseX) - this.startFrameLeft; |
|
|
|
|
|
var top = Math.min(this.startMouseY, mouseY) - this.startFrameTop; |
|
|
|
|
|
var right = Math.max(this.startMouseX, mouseX) - this.startFrameLeft; |
|
|
|
|
|
var bottom = Math.max(this.startMouseY, mouseY) - this.startFrameTop; |
|
|
|
|
|
|
|
|
|
|
|
this.frame.selRect.style.left = left + "px"; |
|
|
|
|
|
this.frame.selRect.style.top = top + "px"; |
|
|
|
|
|
this.frame.selRect.style.width = (right - left) + "px"; |
|
|
|
|
|
this.frame.selRect.style.height = (bottom - top) + "px"; |
|
|
|
|
|
} |
|
|
|
|
|
else { |
|
|
else { |
|
|
// move the graph
|
|
|
// move the graph
|
|
|
var diffX = mouseX - this.startMouseX; |
|
|
|
|
|
var diffY = mouseY - this.startMouseY; |
|
|
|
|
|
|
|
|
var diffX = pointer.x - this.drag.pointer.x; |
|
|
|
|
|
var diffY = pointer.y - this.drag.pointer.y; |
|
|
|
|
|
|
|
|
this._setTranslation( |
|
|
this._setTranslation( |
|
|
this.startTranslation.x + diffX, |
|
|
|
|
|
this.startTranslation.y + diffY); |
|
|
|
|
|
|
|
|
this.drag.translation.x + diffX, |
|
|
|
|
|
this.drag.translation.y + diffY); |
|
|
this._redraw(); |
|
|
this._redraw(); |
|
|
|
|
|
|
|
|
this.moved = true; |
|
|
this.moved = true; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
vis.util.preventDefault(event); |
|
|
|
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* handle on mouse up event |
|
|
|
|
|
* @param {Event} event |
|
|
|
|
|
|
|
|
* handle drag start event |
|
|
* @private |
|
|
* @private |
|
|
*/ |
|
|
*/ |
|
|
Graph.prototype._onMouseUp = function (event) { |
|
|
|
|
|
event = event || window.event; |
|
|
|
|
|
|
|
|
Graph.prototype._onDragEnd = function () { |
|
|
|
|
|
var drag = this.drag, |
|
|
|
|
|
node = drag.node; |
|
|
|
|
|
|
|
|
if (!this.selectable) { |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// remove event listeners here, important for Safari
|
|
|
|
|
|
if (this.onmousemove) { |
|
|
|
|
|
vis.util.removeEventListener(document, "mousemove", this.onmousemove); |
|
|
|
|
|
this.onmousemove = undefined; |
|
|
|
|
|
|
|
|
if (node) { |
|
|
|
|
|
// restore orginal xFixed and yFixed
|
|
|
|
|
|
node.xFixed = drag.xFixed; |
|
|
|
|
|
node.yFixed = drag.yFixed; |
|
|
} |
|
|
} |
|
|
if (this.onmouseup) { |
|
|
|
|
|
vis.util.removeEventListener(document, "mouseup", this.onmouseup); |
|
|
|
|
|
this.onmouseup = undefined; |
|
|
|
|
|
} |
|
|
|
|
|
vis.util.preventDefault(event); |
|
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
// check selected nodes
|
|
|
|
|
|
var endMouseX = util.getPageX(event) || this.mouseX || 0; |
|
|
|
|
|
var endMouseY = util.getPageY(event) || this.mouseY || 0; |
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* handle tap/click event: select/unselect a node |
|
|
|
|
|
* @private |
|
|
|
|
|
*/ |
|
|
|
|
|
Graph.prototype._onTap = function (event) { |
|
|
|
|
|
var pointer = this._getPointer(event.gesture.touches[0]); |
|
|
|
|
|
|
|
|
var ctrlKey = event ? event.ctrlKey : window.event.ctrlKey; |
|
|
|
|
|
|
|
|
var nodeId = this._getNodeAt(pointer); |
|
|
|
|
|
var node = this.nodes[nodeId]; |
|
|
|
|
|
if (node) { |
|
|
|
|
|
// select this node
|
|
|
|
|
|
this._selectNodes([nodeId]); |
|
|
|
|
|
|
|
|
if (this.startClickedObj) { |
|
|
|
|
|
// restore the original fixed state
|
|
|
|
|
|
var node = this.nodes[this.startClickedObj]; |
|
|
|
|
|
node.xFixed = this.startClickedObj.xFixed; |
|
|
|
|
|
node.yFixed = this.startClickedObj.yFixed; |
|
|
|
|
|
} |
|
|
|
|
|
else if (this.shiftKeyDown) { |
|
|
|
|
|
// select nodes inside selection area
|
|
|
|
|
|
var obj = { |
|
|
|
|
|
"left": this._xToCanvas(Math.min(this.startMouseX, endMouseX) - this.startFrameLeft), |
|
|
|
|
|
"top": this._yToCanvas(Math.min(this.startMouseY, endMouseY) - this.startFrameTop), |
|
|
|
|
|
"right": this._xToCanvas(Math.max(this.startMouseX, endMouseX) - this.startFrameLeft), |
|
|
|
|
|
"bottom": this._yToCanvas(Math.max(this.startMouseY, endMouseY) - this.startFrameTop) |
|
|
|
|
|
}; |
|
|
|
|
|
var overlappingNodes = this._getNodesOverlappingWith(obj); |
|
|
|
|
|
this._selectNodes(overlappingNodes, ctrlKey); |
|
|
|
|
|
this.redraw(); |
|
|
|
|
|
|
|
|
|
|
|
// remove the selection rectangle
|
|
|
|
|
|
if (this.frame.selRect) { |
|
|
|
|
|
this.frame.removeChild(this.frame.selRect); |
|
|
|
|
|
this.frame.selRect = undefined; |
|
|
|
|
|
|
|
|
if (!this.moving) { |
|
|
|
|
|
this._redraw(); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
else { |
|
|
else { |
|
|
if (!this.ctrlKeyDown && !this.moved) { |
|
|
|
|
|
// remove selection
|
|
|
|
|
|
this._unselectNodes(); |
|
|
|
|
|
|
|
|
// remove selection
|
|
|
|
|
|
this._unselectNodes(); |
|
|
|
|
|
this._redraw(); |
|
|
|
|
|
} |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* handle long tap event: multi select nodes |
|
|
|
|
|
* @private |
|
|
|
|
|
*/ |
|
|
|
|
|
Graph.prototype._onHold = function (event) { |
|
|
|
|
|
var pointer = this._getPointer(event.gesture.touches[0]); |
|
|
|
|
|
var nodeId = this._getNodeAt(pointer); |
|
|
|
|
|
var node = this.nodes[nodeId]; |
|
|
|
|
|
if (node) { |
|
|
|
|
|
if (!node.isSelected()) { |
|
|
|
|
|
// select this node, keep previous selection
|
|
|
|
|
|
var append = true; |
|
|
|
|
|
this._selectNodes([nodeId], append); |
|
|
|
|
|
} |
|
|
|
|
|
else { |
|
|
|
|
|
this._unselectNodes([nodeId]); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (!this.moving) { |
|
|
this._redraw(); |
|
|
this._redraw(); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
else { |
|
|
|
|
|
// Do nothing
|
|
|
|
|
|
} |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Handle pinch event |
|
|
|
|
|
* @param event |
|
|
|
|
|
* @private |
|
|
|
|
|
*/ |
|
|
|
|
|
Graph.prototype._onPinch = function (event) { |
|
|
|
|
|
var pointer = this._getPointer(event.gesture.center); |
|
|
|
|
|
|
|
|
|
|
|
this.drag.pinched = true; |
|
|
|
|
|
if (!('scale' in this.pinch)) { |
|
|
|
|
|
this.pinch.scale = 1; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
this.leftButtonDown = false; |
|
|
|
|
|
this.ctrlKeyDown = false; |
|
|
|
|
|
|
|
|
// TODO: enable moving while pinching?
|
|
|
|
|
|
var scale = this.pinch.scale * event.gesture.scale; |
|
|
|
|
|
this._zoom(scale, pointer) |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Zoom the graph in or out |
|
|
|
|
|
* @param {Number} scale a number around 1, and between 0.01 and 10 |
|
|
|
|
|
* @param {{x: Number, y: Number}} pointer |
|
|
|
|
|
* @return {Number} appliedScale scale is limited within the boundaries |
|
|
|
|
|
* @private |
|
|
|
|
|
*/ |
|
|
|
|
|
Graph.prototype._zoom = function(scale, pointer) { |
|
|
|
|
|
var scaleOld = this._getScale(); |
|
|
|
|
|
if (scale < 0.01) { |
|
|
|
|
|
scale = 0.01; |
|
|
|
|
|
} |
|
|
|
|
|
if (scale > 10) { |
|
|
|
|
|
scale = 10; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var translation = this._getTranslation(); |
|
|
|
|
|
var scaleFrac = scale / scaleOld; |
|
|
|
|
|
var tx = (1 - scaleFrac) * pointer.x + translation.x * scaleFrac; |
|
|
|
|
|
var ty = (1 - scaleFrac) * pointer.y + translation.y * scaleFrac; |
|
|
|
|
|
|
|
|
|
|
|
this._setScale(scale); |
|
|
|
|
|
this._setTranslation(tx, ty); |
|
|
|
|
|
this._redraw(); |
|
|
|
|
|
|
|
|
|
|
|
return scale; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* Event handler for mouse wheel event, used to zoom the timeline |
|
|
* Event handler for mouse wheel event, used to zoom the timeline |
|
|
* Code from http://adomas.org/javascript-mouse-wheel/
|
|
|
|
|
|
* @param {Event} event |
|
|
|
|
|
|
|
|
* See http://adomas.org/javascript-mouse-wheel/
|
|
|
|
|
|
* https://github.com/EightMedia/hammer.js/issues/256
|
|
|
|
|
|
* @param {MouseEvent} event |
|
|
* @private |
|
|
* @private |
|
|
*/ |
|
|
*/ |
|
|
Graph.prototype._onMouseWheel = function(event) { |
|
|
Graph.prototype._onMouseWheel = function(event) { |
|
|
event = event || window.event; |
|
|
|
|
|
var mouseX = util.getPageX(event); |
|
|
|
|
|
var mouseY = util.getPageY(event); |
|
|
|
|
|
|
|
|
|
|
|
// retrieve delta
|
|
|
// retrieve delta
|
|
|
var delta = 0; |
|
|
var delta = 0; |
|
|
if (event.wheelDelta) { /* IE/Opera. */ |
|
|
if (event.wheelDelta) { /* IE/Opera. */ |
|
@ -556,41 +556,31 @@ Graph.prototype._onMouseWheel = function(event) { |
|
|
// Basically, delta is now positive if wheel was scrolled up,
|
|
|
// Basically, delta is now positive if wheel was scrolled up,
|
|
|
// and negative, if wheel was scrolled down.
|
|
|
// and negative, if wheel was scrolled down.
|
|
|
if (delta) { |
|
|
if (delta) { |
|
|
// determine zoom factor, and adjust the zoom factor such that zooming in
|
|
|
|
|
|
// and zooming out correspond wich each other
|
|
|
|
|
|
|
|
|
if (!('mouswheelScale' in this.pinch)) { |
|
|
|
|
|
this.pinch.mouswheelScale = 1; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// calculate the new scale
|
|
|
|
|
|
var scale = this.pinch.mouswheelScale; |
|
|
var zoom = delta / 10; |
|
|
var zoom = delta / 10; |
|
|
if (delta < 0) { |
|
|
if (delta < 0) { |
|
|
zoom = zoom / (1 - zoom); |
|
|
zoom = zoom / (1 - zoom); |
|
|
} |
|
|
} |
|
|
|
|
|
scale *= (1 + zoom); |
|
|
|
|
|
|
|
|
var scaleOld = this._getScale(); |
|
|
|
|
|
var scaleNew = scaleOld * (1 + zoom); |
|
|
|
|
|
if (scaleNew < 0.01) { |
|
|
|
|
|
scaleNew = 0.01; |
|
|
|
|
|
} |
|
|
|
|
|
if (scaleNew > 10) { |
|
|
|
|
|
scaleNew = 10; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var frameLeft = vis.util.getAbsoluteLeft(this.frame.canvas); |
|
|
|
|
|
var frameTop = vis.util.getAbsoluteTop(this.frame.canvas); |
|
|
|
|
|
var x = mouseX - frameLeft; |
|
|
|
|
|
var y = mouseY - frameTop; |
|
|
|
|
|
|
|
|
// calculate the pointer location
|
|
|
|
|
|
var gesture = Hammer.event.collectEventData(this, 'scroll', event); |
|
|
|
|
|
var pointer = this._getPointer(gesture.center); |
|
|
|
|
|
|
|
|
var translation = this._getTranslation(); |
|
|
|
|
|
var scaleFrac = scaleNew / scaleOld; |
|
|
|
|
|
var tx = (1 - scaleFrac) * x + translation.x * scaleFrac; |
|
|
|
|
|
var ty = (1 - scaleFrac) * y + translation.y * scaleFrac; |
|
|
|
|
|
|
|
|
// apply the new scale
|
|
|
|
|
|
scale = this._zoom(scale, pointer); |
|
|
|
|
|
|
|
|
this._setScale(scaleNew); |
|
|
|
|
|
this._setTranslation(tx, ty); |
|
|
|
|
|
this._redraw(); |
|
|
|
|
|
|
|
|
// store the new, applied scale
|
|
|
|
|
|
this.pinch.mouswheelScale = scale; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// Prevent default actions caused by mouse wheel.
|
|
|
// Prevent default actions caused by mouse wheel.
|
|
|
// That might be ugly, but we handle scrolls somehow
|
|
|
|
|
|
// anyway, so don't bother here...
|
|
|
|
|
|
vis.util.preventDefault(event); |
|
|
|
|
|
|
|
|
event.preventDefault(); |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -600,26 +590,19 @@ Graph.prototype._onMouseWheel = function(event) { |
|
|
* @private |
|
|
* @private |
|
|
*/ |
|
|
*/ |
|
|
Graph.prototype._onMouseMoveTitle = function (event) { |
|
|
Graph.prototype._onMouseMoveTitle = function (event) { |
|
|
event = event || window.event; |
|
|
|
|
|
|
|
|
|
|
|
var startMouseX = util.getPageX(event); |
|
|
|
|
|
var startMouseY = util.getPageY(event); |
|
|
|
|
|
this.startFrameLeft = this.startFrameLeft || vis.util.getAbsoluteLeft(this.frame.canvas); |
|
|
|
|
|
this.startFrameTop = this.startFrameTop || vis.util.getAbsoluteTop(this.frame.canvas); |
|
|
|
|
|
|
|
|
|
|
|
var x = startMouseX - this.startFrameLeft; |
|
|
|
|
|
var y = startMouseY - this.startFrameTop; |
|
|
|
|
|
|
|
|
var gesture = Hammer.event.collectEventData(this, 'mousemove', event); |
|
|
|
|
|
var pointer = this._getPointer(gesture.center); |
|
|
|
|
|
|
|
|
// check if the previously selected node is still selected
|
|
|
// check if the previously selected node is still selected
|
|
|
if (this.popupNode) { |
|
|
if (this.popupNode) { |
|
|
this._checkHidePopup(x, y); |
|
|
|
|
|
|
|
|
this._checkHidePopup(pointer); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// start a timeout that will check if the mouse is positioned above
|
|
|
// start a timeout that will check if the mouse is positioned above
|
|
|
// an element
|
|
|
// an element
|
|
|
var me = this; |
|
|
var me = this; |
|
|
var checkShow = function() { |
|
|
var checkShow = function() { |
|
|
me._checkShowPopup(x, y); |
|
|
|
|
|
|
|
|
me._checkShowPopup(pointer); |
|
|
}; |
|
|
}; |
|
|
if (this.popupTimer) { |
|
|
if (this.popupTimer) { |
|
|
clearInterval(this.popupTimer); // stop any running timer
|
|
|
clearInterval(this.popupTimer); // stop any running timer
|
|
@ -634,16 +617,15 @@ Graph.prototype._onMouseMoveTitle = function (event) { |
|
|
* (a node or edge). If so, and if this element has a title, |
|
|
* (a node or edge). If so, and if this element has a title, |
|
|
* show a popup window with its title. |
|
|
* show a popup window with its title. |
|
|
* |
|
|
* |
|
|
* @param {number} x |
|
|
|
|
|
* @param {number} y |
|
|
|
|
|
|
|
|
* @param {{x:Number, y:Number}} pointer |
|
|
* @private |
|
|
* @private |
|
|
*/ |
|
|
*/ |
|
|
Graph.prototype._checkShowPopup = function (x, y) { |
|
|
|
|
|
|
|
|
Graph.prototype._checkShowPopup = function (pointer) { |
|
|
var obj = { |
|
|
var obj = { |
|
|
"left" : this._xToCanvas(x), |
|
|
|
|
|
"top" : this._yToCanvas(y), |
|
|
|
|
|
"right" : this._xToCanvas(x), |
|
|
|
|
|
"bottom" : this._yToCanvas(y) |
|
|
|
|
|
|
|
|
left: this._xToCanvas(pointer.x), |
|
|
|
|
|
top: this._yToCanvas(pointer.y), |
|
|
|
|
|
right: this._xToCanvas(pointer.x), |
|
|
|
|
|
bottom: this._yToCanvas(pointer.y) |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
var id; |
|
|
var id; |
|
@ -689,7 +671,7 @@ Graph.prototype._checkShowPopup = function (x, y) { |
|
|
// adjust a small offset such that the mouse cursor is located in the
|
|
|
// adjust a small offset such that the mouse cursor is located in the
|
|
|
// bottom left location of the popup, and you can easily move over the
|
|
|
// bottom left location of the popup, and you can easily move over the
|
|
|
// popup area
|
|
|
// popup area
|
|
|
me.popup.setPosition(x - 3, y - 3); |
|
|
|
|
|
|
|
|
me.popup.setPosition(pointer.x - 3, pointer.y - 3); |
|
|
me.popup.setText(me.popupNode.getTitle()); |
|
|
me.popup.setText(me.popupNode.getTitle()); |
|
|
me.popup.show(); |
|
|
me.popup.show(); |
|
|
} |
|
|
} |
|
@ -704,19 +686,11 @@ Graph.prototype._checkShowPopup = function (x, y) { |
|
|
/** |
|
|
/** |
|
|
* Check if the popup must be hided, which is the case when the mouse is no |
|
|
* Check if the popup must be hided, which is the case when the mouse is no |
|
|
* longer hovering on the object |
|
|
* longer hovering on the object |
|
|
* @param {number} x |
|
|
|
|
|
* @param {number} y |
|
|
|
|
|
|
|
|
* @param {{x:Number, y:Number}} pointer |
|
|
* @private |
|
|
* @private |
|
|
*/ |
|
|
*/ |
|
|
Graph.prototype._checkHidePopup = function (x, y) { |
|
|
|
|
|
var obj = { |
|
|
|
|
|
"left" : x, |
|
|
|
|
|
"top" : y, |
|
|
|
|
|
"right" : x, |
|
|
|
|
|
"bottom" : y |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
if (!this.popupNode || !this.popupNode.isOverlappingWith(obj) ) { |
|
|
|
|
|
|
|
|
Graph.prototype._checkHidePopup = function (pointer) { |
|
|
|
|
|
if (!this.popupNode || !this._getNodeAt(pointer) ) { |
|
|
this.popupNode = undefined; |
|
|
this.popupNode = undefined; |
|
|
if (this.popup) { |
|
|
if (this.popup) { |
|
|
this.popup.hide(); |
|
|
this.popup.hide(); |
|
@ -724,66 +698,6 @@ Graph.prototype._checkHidePopup = function (x, y) { |
|
|
} |
|
|
} |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Event handler for touchstart event on mobile devices |
|
|
|
|
|
* @param {Event} event |
|
|
|
|
|
* @private |
|
|
|
|
|
*/ |
|
|
|
|
|
Graph.prototype._onTouchStart = function(event) { |
|
|
|
|
|
vis.util.preventDefault(event); |
|
|
|
|
|
|
|
|
|
|
|
if (this.touchDown) { |
|
|
|
|
|
// if already moving, return
|
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
this.touchDown = true; |
|
|
|
|
|
|
|
|
|
|
|
var me = this; |
|
|
|
|
|
if (!this.ontouchmove) { |
|
|
|
|
|
this.ontouchmove = function (event) {me._onTouchMove(event);}; |
|
|
|
|
|
vis.util.addEventListener(document, "touchmove", this.ontouchmove); |
|
|
|
|
|
} |
|
|
|
|
|
if (!this.ontouchend) { |
|
|
|
|
|
this.ontouchend = function (event) {me._onTouchEnd(event);}; |
|
|
|
|
|
vis.util.addEventListener(document, "touchend", this.ontouchend); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
this._onMouseDown(event); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Event handler for touchmove event on mobile devices |
|
|
|
|
|
* @param {Event} event |
|
|
|
|
|
* @private |
|
|
|
|
|
*/ |
|
|
|
|
|
Graph.prototype._onTouchMove = function(event) { |
|
|
|
|
|
vis.util.preventDefault(event); |
|
|
|
|
|
this._onMouseMove(event); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Event handler for touchend event on mobile devices |
|
|
|
|
|
* @param {Event} event |
|
|
|
|
|
* @private |
|
|
|
|
|
*/ |
|
|
|
|
|
Graph.prototype._onTouchEnd = function(event) { |
|
|
|
|
|
vis.util.preventDefault(event); |
|
|
|
|
|
|
|
|
|
|
|
this.touchDown = false; |
|
|
|
|
|
|
|
|
|
|
|
if (this.ontouchmove) { |
|
|
|
|
|
vis.util.removeEventListener(document, "touchmove", this.ontouchmove); |
|
|
|
|
|
this.ontouchmove = undefined; |
|
|
|
|
|
} |
|
|
|
|
|
if (this.ontouchend) { |
|
|
|
|
|
vis.util.removeEventListener(document, "touchend", this.ontouchend); |
|
|
|
|
|
this.ontouchend = undefined; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
this._onMouseUp(event); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* Unselect selected nodes. If no selection array is provided, all nodes |
|
|
* Unselect selected nodes. If no selection array is provided, all nodes |
|
|
* are unselected |
|
|
* are unselected |
|
@ -893,8 +807,7 @@ Graph.prototype._selectNodes = function(selection, append) { |
|
|
/** |
|
|
/** |
|
|
* retrieve all nodes overlapping with given object |
|
|
* retrieve all nodes overlapping with given object |
|
|
* @param {Object} obj An object with parameters left, top, right, bottom |
|
|
* @param {Object} obj An object with parameters left, top, right, bottom |
|
|
* @return {Object[]} An array with selection objects containing |
|
|
|
|
|
* the parameter row. |
|
|
|
|
|
|
|
|
* @return {Number[]} An array with id's of the overlapping nodes |
|
|
* @private |
|
|
* @private |
|
|
*/ |
|
|
*/ |
|
|
Graph.prototype._getNodesOverlappingWith = function (obj) { |
|
|
Graph.prototype._getNodesOverlappingWith = function (obj) { |
|
@ -929,7 +842,7 @@ Graph.prototype.getSelection = function() { |
|
|
Graph.prototype.setSelection = function(selection) { |
|
|
Graph.prototype.setSelection = function(selection) { |
|
|
var i, iMax, id; |
|
|
var i, iMax, id; |
|
|
|
|
|
|
|
|
if (selection.length == undefined) |
|
|
|
|
|
|
|
|
if (!selection || (selection.length == undefined)) |
|
|
throw "Selection must be an array with ids"; |
|
|
throw "Selection must be an array with ids"; |
|
|
|
|
|
|
|
|
// first unselect any selected node
|
|
|
// first unselect any selected node
|
|
|