@ -1,158 +0,0 @@ | |||
var SelectionMixin = require('./SelectionMixin'); | |||
var ManipulationMixin = require('./ManipulationMixin'); | |||
var NavigationMixin = require('./NavigationMixin'); | |||
var HierarchicalLayoutMixin = require('./HierarchicalLayoutMixin'); | |||
/** | |||
* Load a mixin into the network object | |||
* | |||
* @param {Object} sourceVariable | this object has to contain functions. | |||
* @private | |||
*/ | |||
exports._loadMixin = function (sourceVariable) { | |||
for (var mixinFunction in sourceVariable) { | |||
if (sourceVariable.hasOwnProperty(mixinFunction)) { | |||
this[mixinFunction] = sourceVariable[mixinFunction]; | |||
} | |||
} | |||
}; | |||
/** | |||
* removes a mixin from the network object. | |||
* | |||
* @param {Object} sourceVariable | this object has to contain functions. | |||
* @private | |||
*/ | |||
exports._clearMixin = function (sourceVariable) { | |||
for (var mixinFunction in sourceVariable) { | |||
if (sourceVariable.hasOwnProperty(mixinFunction)) { | |||
this[mixinFunction] = undefined; | |||
} | |||
} | |||
}; | |||
/** | |||
* Mixin the physics system and initialize the parameters required. | |||
* | |||
* @private | |||
*/ | |||
exports._loadPhysicsSystem = function () { | |||
this._loadMixin(PhysicsMixin); | |||
this._loadSelectedForceSolver(); | |||
if (this.constants.configurePhysics == true) { | |||
this._loadPhysicsConfiguration(); | |||
} | |||
else { | |||
this._cleanupPhysicsConfiguration(); | |||
} | |||
}; | |||
/** | |||
* Mixin the selection system and initialize the parameters required | |||
* | |||
* @private | |||
*/ | |||
exports._loadSelectionSystem = function () { | |||
this.selectionObj = {nodes: {}, edges: {}}; | |||
this._loadMixin(SelectionMixin); | |||
}; | |||
/** | |||
* Mixin the navigationUI (User Interface) system and initialize the parameters required | |||
* | |||
* @private | |||
*/ | |||
exports._loadManipulationSystem = function () { | |||
// reset global variables -- these are used by the selection of nodes and edges. | |||
this.blockConnectingEdgeSelection = false; | |||
this.forceAppendSelection = false; | |||
if (this.constants.dataManipulation.enabled == true) { | |||
// load the manipulator HTML elements. All styling done in css. | |||
if (this.manipulationDiv === undefined) { | |||
this.manipulationDiv = document.createElement('div'); | |||
this.manipulationDiv.className = 'network-manipulationDiv'; | |||
if (this.editMode == true) { | |||
this.manipulationDiv.style.display = "block"; | |||
} | |||
else { | |||
this.manipulationDiv.style.display = "none"; | |||
} | |||
this.frame.appendChild(this.manipulationDiv); | |||
} | |||
if (this.editModeDiv === undefined) { | |||
this.editModeDiv = document.createElement('div'); | |||
this.editModeDiv.className = 'network-manipulation-editMode'; | |||
if (this.editMode == true) { | |||
this.editModeDiv.style.display = "none"; | |||
} | |||
else { | |||
this.editModeDiv.style.display = "block"; | |||
} | |||
this.frame.appendChild(this.editModeDiv); | |||
} | |||
if (this.closeDiv === undefined) { | |||
this.closeDiv = document.createElement('div'); | |||
this.closeDiv.className = 'network-manipulation-closeDiv'; | |||
this.closeDiv.style.display = this.manipulationDiv.style.display; | |||
this.frame.appendChild(this.closeDiv); | |||
} | |||
// load the manipulation functions | |||
this._loadMixin(ManipulationMixin); | |||
// create the manipulator toolbar | |||
this._createManipulatorBar(); | |||
} | |||
else { | |||
if (this.manipulationDiv !== undefined) { | |||
// removes all the bindings and overloads | |||
this._createManipulatorBar(); | |||
// remove the manipulation divs | |||
this.frame.removeChild(this.manipulationDiv); | |||
this.frame.removeChild(this.editModeDiv); | |||
this.frame.removeChild(this.closeDiv); | |||
this.manipulationDiv = undefined; | |||
this.editModeDiv = undefined; | |||
this.closeDiv = undefined; | |||
// remove the mixin functions | |||
this._clearMixin(ManipulationMixin); | |||
} | |||
} | |||
}; | |||
/** | |||
* Mixin the navigation (User Interface) system and initialize the parameters required | |||
* | |||
* @private | |||
*/ | |||
exports._loadNavigationControls = function () { | |||
this._loadMixin(NavigationMixin); | |||
// the clean function removes the button divs, this is done to remove the bindings. | |||
this._cleanNavigation(); | |||
if (this.constants.navigation.enabled == true) { | |||
this._loadNavigationElements(); | |||
} | |||
}; | |||
/** | |||
* Mixin the hierarchical layout system. | |||
* | |||
* @private | |||
*/ | |||
exports._loadHierarchySystem = function () { | |||
this._loadMixin(HierarchicalLayoutMixin); | |||
}; |
@ -1,174 +0,0 @@ | |||
var util = require('../../util'); | |||
var Hammer = require('../../module/hammer'); | |||
var hammerUtil = require('../../hammerUtil'); | |||
exports._cleanNavigation = function() { | |||
// clean hammer bindings | |||
if (this.navigationHammers.length != 0) { | |||
for (var i = 0; i < this.navigationHammers.length; i++) { | |||
this.navigationHammers[i].destroy(); | |||
} | |||
this.navigationHammers = []; | |||
} | |||
this._navigationReleaseOverload = function () {}; | |||
// clean up previous navigation items | |||
if (this.navigationDOM && this.navigationDOM['wrapper'] && this.navigationDOM['wrapper'].parentNode) { | |||
this.navigationDOM['wrapper'].parentNode.removeChild(this.navigationDOM['wrapper']); | |||
} | |||
}; | |||
/** | |||
* Creation of the navigation controls nodes. They are drawn over the rest of the nodes and are not affected by scale and translation | |||
* they have a triggerFunction which is called on click. If the position of the navigation controls is dependent | |||
* on this.frame.canvas.clientWidth or this.frame.canvas.clientHeight, we flag horizontalAlignLeft and verticalAlignTop false. | |||
* This means that the location will be corrected by the _relocateNavigation function on a size change of the canvas. | |||
* | |||
* @private | |||
*/ | |||
exports._loadNavigationElements = function() { | |||
this._cleanNavigation(); | |||
this.navigationDOM = {}; | |||
var navigationDivs = ['up','down','left','right','zoomIn','zoomOut','zoomExtends']; | |||
var navigationDivActions = ['_moveUp','_moveDown','_moveLeft','_moveRight','_zoomIn','_zoomOut','_zoomExtent']; | |||
this.navigationDOM['wrapper'] = document.createElement('div'); | |||
this.frame.appendChild(this.navigationDOM['wrapper']); | |||
for (var i = 0; i < navigationDivs.length; i++) { | |||
this.navigationDOM[navigationDivs[i]] = document.createElement('div'); | |||
this.navigationDOM[navigationDivs[i]].className = 'network-navigation ' + navigationDivs[i]; | |||
this.navigationDOM['wrapper'].appendChild(this.navigationDOM[navigationDivs[i]]); | |||
var hammer = new Hammer(this.navigationDOM[navigationDivs[i]], {prevent_default: true}); | |||
hammerUtil.onTouch(hammer, this[navigationDivActions[i]].bind(this)); | |||
this.navigationHammers.push(hammer); | |||
} | |||
this._navigationReleaseOverload = this._stopMovement; | |||
}; | |||
/** | |||
* this stops all movement induced by the navigation buttons | |||
* | |||
* @private | |||
*/ | |||
exports._zoomExtent = function(event) { | |||
this.zoomExtent({duration:700}); | |||
event.stopPropagation(); | |||
}; | |||
/** | |||
* this stops all movement induced by the navigation buttons | |||
* | |||
* @private | |||
*/ | |||
exports._stopMovement = function() { | |||
this._xStopMoving(); | |||
this._yStopMoving(); | |||
this._stopZoom(); | |||
}; | |||
/** | |||
* move the screen up | |||
* By using the increments, instead of adding a fixed number to the translation, we keep fluent and | |||
* instant movement. The onKeypress event triggers immediately, then pauses, then triggers frequently | |||
* To avoid this behaviour, we do the translation in the start loop. | |||
* | |||
* @private | |||
*/ | |||
exports._moveUp = function(event) { | |||
this.yIncrement = this.constants.keyboard.speed.y; | |||
this.start(); // if there is no node movement, the calculation wont be done | |||
event.preventDefault(); | |||
}; | |||
/** | |||
* move the screen down | |||
* @private | |||
*/ | |||
exports._moveDown = function(event) { | |||
this.yIncrement = -this.constants.keyboard.speed.y; | |||
this.start(); // if there is no node movement, the calculation wont be done | |||
event.preventDefault(); | |||
}; | |||
/** | |||
* move the screen left | |||
* @private | |||
*/ | |||
exports._moveLeft = function(event) { | |||
this.xIncrement = this.constants.keyboard.speed.x; | |||
this.start(); // if there is no node movement, the calculation wont be done | |||
event.preventDefault(); | |||
}; | |||
/** | |||
* move the screen right | |||
* @private | |||
*/ | |||
exports._moveRight = function(event) { | |||
this.xIncrement = -this.constants.keyboard.speed.y; | |||
this.start(); // if there is no node movement, the calculation wont be done | |||
event.preventDefault(); | |||
}; | |||
/** | |||
* Zoom in, using the same method as the movement. | |||
* @private | |||
*/ | |||
exports._zoomIn = function(event) { | |||
this.zoomIncrement = this.constants.keyboard.speed.zoom; | |||
this.start(); // if there is no node movement, the calculation wont be done | |||
event.preventDefault(); | |||
}; | |||
/** | |||
* Zoom out | |||
* @private | |||
*/ | |||
exports._zoomOut = function(event) { | |||
this.zoomIncrement = -this.constants.keyboard.speed.zoom; | |||
this.start(); // if there is no node movement, the calculation wont be done | |||
event.preventDefault(); | |||
}; | |||
/** | |||
* Stop zooming and unhighlight the zoom controls | |||
* @private | |||
*/ | |||
exports._stopZoom = function(event) { | |||
this.zoomIncrement = 0; | |||
event && event.preventDefault(); | |||
}; | |||
/** | |||
* Stop moving in the Y direction and unHighlight the up and down | |||
* @private | |||
*/ | |||
exports._yStopMoving = function(event) { | |||
this.yIncrement = 0; | |||
event && event.preventDefault(); | |||
}; | |||
/** | |||
* Stop moving in the X direction and unHighlight left and right. | |||
* @private | |||
*/ | |||
exports._xStopMoving = function(event) { | |||
this.xIncrement = 0; | |||
event && event.preventDefault(); | |||
}; |
@ -1,553 +0,0 @@ | |||
var util = require('../../util'); | |||
var Node = require('../Node'); | |||
/** | |||
* Creation of the SectorMixin var. | |||
* | |||
* This contains all the functions the Network object can use to employ the sector system. | |||
* The sector system is always used by Network, though the benefits only apply to the use of clustering. | |||
* If clustering is not used, there is no overhead except for a duplicate object with references to nodes and edges. | |||
*/ | |||
/** | |||
* This function is only called by the setData function of the Network object. | |||
* This loads the global references into the active sector. This initializes the sector. | |||
* | |||
* @private | |||
*/ | |||
exports._putDataInSector = function() { | |||
this.body.sectors["active"][this._sector()].nodes = this.body.nodes; | |||
this.body.sectors["active"][this._sector()].edges = this.body.edges; | |||
this.body.sectors["active"][this._sector()].nodeIndices = this.body.nodeIndices; | |||
}; | |||
/** | |||
* /** | |||
* This function sets the global references to nodes, edges and nodeIndices back to | |||
* those of the supplied (active) sector. If a type is defined, do the specific type | |||
* | |||
* @param {String} sectorId | |||
* @param {String} [sectorType] | "active" or "frozen" | |||
* @private | |||
*/ | |||
exports._switchToSector = function(sectorId, sectorType) { | |||
if (sectorType === undefined || sectorType == "active") { | |||
this._switchToActiveSector(sectorId); | |||
} | |||
else { | |||
this._switchToFrozenSector(sectorId); | |||
} | |||
}; | |||
/** | |||
* This function sets the global references to nodes, edges and nodeIndices back to | |||
* those of the supplied active sector. | |||
* | |||
* @param sectorId | |||
* @private | |||
*/ | |||
exports._switchToActiveSector = function(sectorId) { | |||
this.body.nodeIndices = this.body.sectors["active"][sectorId]["nodeIndices"]; | |||
this.body.nodes = this.body.sectors["active"][sectorId]["nodes"]; | |||
this.body.edges = this.body.sectors["active"][sectorId]["edges"]; | |||
}; | |||
/** | |||
* This function sets the global references to nodes, edges and nodeIndices back to | |||
* those of the supplied active sector. | |||
* | |||
* @private | |||
*/ | |||
exports._switchToSupportSector = function() { | |||
this.body.nodeIndices = this.body.sectors["support"]["nodeIndices"]; | |||
this.body.nodes = this.body.sectors["support"]["nodes"]; | |||
this.body.edges = this.body.sectors["support"]["edges"]; | |||
}; | |||
/** | |||
* This function sets the global references to nodes, edges and nodeIndices back to | |||
* those of the supplied frozen sector. | |||
* | |||
* @param sectorId | |||
* @private | |||
*/ | |||
exports._switchToFrozenSector = function(sectorId) { | |||
this.body.nodeIndices = this.body.sectors["frozen"][sectorId]["nodeIndices"]; | |||
this.body.nodes = this.body.sectors["frozen"][sectorId]["nodes"]; | |||
this.body.edges = this.body.sectors["frozen"][sectorId]["edges"]; | |||
}; | |||
/** | |||
* This function sets the global references to nodes, edges and nodeIndices back to | |||
* those of the currently active sector. | |||
* | |||
* @private | |||
*/ | |||
exports._loadLatestSector = function() { | |||
this._switchToSector(this._sector()); | |||
}; | |||
/** | |||
* This function returns the currently active sector Id | |||
* | |||
* @returns {String} | |||
* @private | |||
*/ | |||
exports._sector = function() { | |||
return this.activeSector[this.activeSector.length-1]; | |||
}; | |||
/** | |||
* This function returns the previously active sector Id | |||
* | |||
* @returns {String} | |||
* @private | |||
*/ | |||
exports._previousSector = function() { | |||
if (this.activeSector.length > 1) { | |||
return this.activeSector[this.activeSector.length-2]; | |||
} | |||
else { | |||
throw new TypeError('there are not enough sectors in the this.activeSector array.'); | |||
} | |||
}; | |||
/** | |||
* We add the active sector at the end of the this.activeSector array | |||
* This ensures it is the currently active sector returned by _sector() and it reaches the top | |||
* of the activeSector stack. When we reverse our steps we move from the end to the beginning of this stack. | |||
* | |||
* @param newId | |||
* @private | |||
*/ | |||
exports._setActiveSector = function(newId) { | |||
this.activeSector.push(newId); | |||
}; | |||
/** | |||
* We remove the currently active sector id from the active sector stack. This happens when | |||
* we reactivate the previously active sector | |||
* | |||
* @private | |||
*/ | |||
exports._forgetLastSector = function() { | |||
this.activeSector.pop(); | |||
}; | |||
/** | |||
* This function creates a new active sector with the supplied newId. This newId | |||
* is the expanding node id. | |||
* | |||
* @param {String} newId | Id of the new active sector | |||
* @private | |||
*/ | |||
exports._createNewSector = function(newId) { | |||
// create the new sector | |||
this.body.sectors["active"][newId] = {"nodes":{}, | |||
"edges":{}, | |||
"nodeIndices":[], | |||
"formationScale": this.scale, | |||
"drawingNode": undefined}; | |||
// create the new sector render node. This gives visual feedback that you are in a new sector. | |||
this.body.sectors["active"][newId]['drawingNode'] = new Node( | |||
{id:newId, | |||
color: { | |||
background: "#eaefef", | |||
border: "495c5e" | |||
} | |||
},{},{},this.constants); | |||
this.body.sectors["active"][newId]['drawingNode'].clusterSize = 2; | |||
}; | |||
/** | |||
* This function removes the currently active sector. This is called when we create a new | |||
* active sector. | |||
* | |||
* @param {String} sectorId | Id of the active sector that will be removed | |||
* @private | |||
*/ | |||
exports._deleteActiveSector = function(sectorId) { | |||
delete this.body.sectors["active"][sectorId]; | |||
}; | |||
/** | |||
* This function removes the currently active sector. This is called when we reactivate | |||
* the previously active sector. | |||
* | |||
* @param {String} sectorId | Id of the active sector that will be removed | |||
* @private | |||
*/ | |||
exports._deleteFrozenSector = function(sectorId) { | |||
delete this.body.sectors["frozen"][sectorId]; | |||
}; | |||
/** | |||
* Freezing an active sector means moving it from the "active" object to the "frozen" object. | |||
* We copy the references, then delete the active entree. | |||
* | |||
* @param sectorId | |||
* @private | |||
*/ | |||
exports._freezeSector = function(sectorId) { | |||
// we move the set references from the active to the frozen stack. | |||
this.body.sectors["frozen"][sectorId] = this.body.sectors["active"][sectorId]; | |||
// we have moved the sector data into the frozen set, we now remove it from the active set | |||
this._deleteActiveSector(sectorId); | |||
}; | |||
/** | |||
* This is the reverse operation of _freezeSector. Activating means moving the sector from the "frozen" | |||
* object to the "active" object. | |||
* | |||
* @param sectorId | |||
* @private | |||
*/ | |||
exports._activateSector = function(sectorId) { | |||
// we move the set references from the frozen to the active stack. | |||
this.body.sectors["active"][sectorId] = this.body.sectors["frozen"][sectorId]; | |||
// we have moved the sector data into the active set, we now remove it from the frozen stack | |||
this._deleteFrozenSector(sectorId); | |||
}; | |||
/** | |||
* This function merges the data from the currently active sector with a frozen sector. This is used | |||
* in the process of reverting back to the previously active sector. | |||
* The data that is placed in the frozen (the previously active) sector is the node that has been removed from it | |||
* upon the creation of a new active sector. | |||
* | |||
* @param sectorId | |||
* @private | |||
*/ | |||
exports._mergeThisWithFrozen = function(sectorId) { | |||
// copy all nodes | |||
for (var nodeId in this.body.nodes) { | |||
if (this.body.nodes.hasOwnProperty(nodeId)) { | |||
this.body.sectors["frozen"][sectorId]["nodes"][nodeId] = this.body.nodes[nodeId]; | |||
} | |||
} | |||
// copy all edges (if not fully clustered, else there are no edges) | |||
for (var edgeId in this.body.edges) { | |||
if (this.body.edges.hasOwnProperty(edgeId)) { | |||
this.body.sectors["frozen"][sectorId]["edges"][edgeId] = this.body.edges[edgeId]; | |||
} | |||
} | |||
// merge the nodeIndices | |||
for (var i = 0; i < this.body.nodeIndices.length; i++) { | |||
this.body.sectors["frozen"][sectorId]["nodeIndices"].push(this.body.nodeIndices[i]); | |||
} | |||
}; | |||
/** | |||
* This clusters the sector to one cluster. It was a single cluster before this process started so | |||
* we revert to that state. The clusterToFit function with a maximum size of 1 node does this. | |||
* | |||
* @private | |||
*/ | |||
exports._collapseThisToSingleCluster = function() { | |||
this.clusterToFit(1,false); | |||
}; | |||
/** | |||
* We create a new active sector from the node that we want to open. | |||
* | |||
* @param node | |||
* @private | |||
*/ | |||
exports._addSector = function(node) { | |||
// this is the currently active sector | |||
var sector = this._sector(); | |||
// // this should allow me to select nodes from a frozen set. | |||
// if (this.body.sectors['active'][sector]["nodes"].hasOwnProperty(node.id)) { | |||
// console.log("the node is part of the active sector"); | |||
// } | |||
// else { | |||
// console.log("I dont know what the fuck happened!!"); | |||
// } | |||
// when we switch to a new sector, we remove the node that will be expanded from the current nodes list. | |||
delete this.body.nodes[node.id]; | |||
var unqiueIdentifier = util.randomUUID(); | |||
// we fully freeze the currently active sector | |||
this._freezeSector(sector); | |||
// we create a new active sector. This sector has the Id of the node to ensure uniqueness | |||
this._createNewSector(unqiueIdentifier); | |||
// we add the active sector to the sectors array to be able to revert these steps later on | |||
this._setActiveSector(unqiueIdentifier); | |||
// we redirect the global references to the new sector's references. this._sector() now returns unqiueIdentifier | |||
this._switchToSector(this._sector()); | |||
// finally we add the node we removed from our previous active sector to the new active sector | |||
this.body.nodes[node.id] = node; | |||
}; | |||
/** | |||
* We close the sector that is currently open and revert back to the one before. | |||
* If the active sector is the "default" sector, nothing happens. | |||
* | |||
* @private | |||
*/ | |||
exports._collapseSector = function() { | |||
// the currently active sector | |||
var sector = this._sector(); | |||
// we cannot collapse the default sector | |||
if (sector != "default") { | |||
if ((this.body.nodeIndices.length == 1) || | |||
(this.body.sectors["active"][sector]["drawingNode"].width*this.scale < this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientWidth) || | |||
(this.body.sectors["active"][sector]["drawingNode"].height*this.scale < this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientHeight)) { | |||
var previousSector = this._previousSector(); | |||
// we collapse the sector back to a single cluster | |||
this._collapseThisToSingleCluster(); | |||
// we move the remaining nodes, edges and nodeIndices to the previous sector. | |||
// This previous sector is the one we will reactivate | |||
this._mergeThisWithFrozen(previousSector); | |||
// the previously active (frozen) sector now has all the data from the currently active sector. | |||
// we can now delete the active sector. | |||
this._deleteActiveSector(sector); | |||
// we activate the previously active (and currently frozen) sector. | |||
this._activateSector(previousSector); | |||
// we load the references from the newly active sector into the global references | |||
this._switchToSector(previousSector); | |||
// we forget the previously active sector because we reverted to the one before | |||
this._forgetLastSector(); | |||
// finally, we update the node index list. | |||
this._updateNodeIndexList(); | |||
// we refresh the list with calulation nodes and calculation node indices. | |||
this._updateCalculationNodes(); | |||
} | |||
} | |||
}; | |||
/** | |||
* This runs a function in all active sectors. This is used in _redraw() and the _initializeForceCalculation(). | |||
* | |||
* @param {String} runFunction | This is the NAME of a function we want to call in all active sectors | |||
* | we dont pass the function itself because then the "this" is the window object | |||
* | instead of the Network object | |||
* @param {*} [argument] | Optional: arguments to pass to the runFunction | |||
* @private | |||
*/ | |||
exports._doInAllActiveSectors = function(runFunction,argument) { | |||
var returnValues = []; | |||
if (argument === undefined) { | |||
for (var sector in this.body.sectors["active"]) { | |||
if (this.body.sectors["active"].hasOwnProperty(sector)) { | |||
// switch the global references to those of this sector | |||
this._switchToActiveSector(sector); | |||
returnValues.push( this[runFunction]() ); | |||
} | |||
} | |||
} | |||
else { | |||
for (var sector in this.body.sectors["active"]) { | |||
if (this.body.sectors["active"].hasOwnProperty(sector)) { | |||
// switch the global references to those of this sector | |||
this._switchToActiveSector(sector); | |||
var args = Array.prototype.splice.call(arguments, 1); | |||
if (args.length > 1) { | |||
returnValues.push( this[runFunction](args[0],args[1]) ); | |||
} | |||
else { | |||
returnValues.push( this[runFunction](argument) ); | |||
} | |||
} | |||
} | |||
} | |||
// we revert the global references back to our active sector | |||
this._loadLatestSector(); | |||
return returnValues; | |||
}; | |||
/** | |||
* This runs a function in all active sectors. This is used in _redraw() and the _initializeForceCalculation(). | |||
* | |||
* @param {String} runFunction | This is the NAME of a function we want to call in all active sectors | |||
* | we dont pass the function itself because then the "this" is the window object | |||
* | instead of the Network object | |||
* @param {*} [argument] | Optional: arguments to pass to the runFunction | |||
* @private | |||
*/ | |||
exports._doInSupportSector = function(runFunction,argument) { | |||
var returnValues = false; | |||
if (argument === undefined) { | |||
this._switchToSupportSector(); | |||
returnValues = this[runFunction](); | |||
} | |||
else { | |||
this._switchToSupportSector(); | |||
var args = Array.prototype.splice.call(arguments, 1); | |||
if (args.length > 1) { | |||
returnValues = this[runFunction](args[0],args[1]); | |||
} | |||
else { | |||
returnValues = this[runFunction](argument); | |||
} | |||
} | |||
// we revert the global references back to our active sector | |||
this._loadLatestSector(); | |||
return returnValues; | |||
}; | |||
/** | |||
* This runs a function in all frozen sectors. This is used in the _redraw(). | |||
* | |||
* @param {String} runFunction | This is the NAME of a function we want to call in all active sectors | |||
* | we don't pass the function itself because then the "this" is the window object | |||
* | instead of the Network object | |||
* @param {*} [argument] | Optional: arguments to pass to the runFunction | |||
* @private | |||
*/ | |||
exports._doInAllFrozenSectors = function(runFunction,argument) { | |||
if (argument === undefined) { | |||
for (var sector in this.body.sectors["frozen"]) { | |||
if (this.body.sectors["frozen"].hasOwnProperty(sector)) { | |||
// switch the global references to those of this sector | |||
this._switchToFrozenSector(sector); | |||
this[runFunction](); | |||
} | |||
} | |||
} | |||
else { | |||
for (var sector in this.body.sectors["frozen"]) { | |||
if (this.body.sectors["frozen"].hasOwnProperty(sector)) { | |||
// switch the global references to those of this sector | |||
this._switchToFrozenSector(sector); | |||
var args = Array.prototype.splice.call(arguments, 1); | |||
if (args.length > 1) { | |||
this[runFunction](args[0],args[1]); | |||
} | |||
else { | |||
this[runFunction](argument); | |||
} | |||
} | |||
} | |||
} | |||
this._loadLatestSector(); | |||
}; | |||
/** | |||
* This runs a function in all sectors. This is used in the _redraw(). | |||
* | |||
* @param {String} runFunction | This is the NAME of a function we want to call in all active sectors | |||
* | we don't pass the function itself because then the "this" is the window object | |||
* | instead of the Network object | |||
* @param {*} [argument] | Optional: arguments to pass to the runFunction | |||
* @private | |||
*/ | |||
exports._doInAllSectors = function(runFunction,argument) { | |||
var args = Array.prototype.splice.call(arguments, 1); | |||
if (argument === undefined) { | |||
this._doInAllActiveSectors(runFunction); | |||
this._doInAllFrozenSectors(runFunction); | |||
} | |||
else { | |||
if (args.length > 1) { | |||
this._doInAllActiveSectors(runFunction,args[0],args[1]); | |||
this._doInAllFrozenSectors(runFunction,args[0],args[1]); | |||
} | |||
else { | |||
this._doInAllActiveSectors(runFunction,argument); | |||
this._doInAllFrozenSectors(runFunction,argument); | |||
} | |||
} | |||
}; | |||
/** | |||
* This clears the nodeIndices list. We cannot use this.body.nodeIndices = [] because we would break the link with the | |||
* active sector. Thus we clear the nodeIndices in the active sector, then reconnect the this.body.nodeIndices to it. | |||
* | |||
* @private | |||
*/ | |||
exports._clearNodeIndexList = function() { | |||
var sector = this._sector(); | |||
this.body.sectors["active"][sector]["nodeIndices"] = []; | |||
this.body.nodeIndices = this.body.sectors["active"][sector]["nodeIndices"]; | |||
}; | |||
/** | |||
* Draw the encompassing sector node | |||
* | |||
* @param ctx | |||
* @param sectorType | |||
* @private | |||
*/ | |||
exports._drawSectorNodes = function(ctx,sectorType) { | |||
var minY = 1e9, maxY = -1e9, minX = 1e9, maxX = -1e9, node; | |||
for (var sector in this.body.sectors[sectorType]) { | |||
if (this.body.sectors[sectorType].hasOwnProperty(sector)) { | |||
if (this.body.sectors[sectorType][sector]["drawingNode"] !== undefined) { | |||
this._switchToSector(sector,sectorType); | |||
minY = 1e9; maxY = -1e9; minX = 1e9; maxX = -1e9; | |||
for (var nodeId in this.body.nodes) { | |||
if (this.body.nodes.hasOwnProperty(nodeId)) { | |||
node = this.body.nodes[nodeId]; | |||
node.resize(ctx); | |||
if (minX > node.x - 0.5 * node.width) {minX = node.x - 0.5 * node.width;} | |||
if (maxX < node.x + 0.5 * node.width) {maxX = node.x + 0.5 * node.width;} | |||
if (minY > node.y - 0.5 * node.height) {minY = node.y - 0.5 * node.height;} | |||
if (maxY < node.y + 0.5 * node.height) {maxY = node.y + 0.5 * node.height;} | |||
} | |||
} | |||
node = this.body.sectors[sectorType][sector]["drawingNode"]; | |||
node.x = 0.5 * (maxX + minX); | |||
node.y = 0.5 * (maxY + minY); | |||
node.width = 2 * (node.x - minX); | |||
node.height = 2 * (node.y - minY); | |||
node.options.radius = Math.sqrt(Math.pow(0.5*node.width,2) + Math.pow(0.5*node.height,2)); | |||
node.setScale(this.scale); | |||
node._drawCircle(ctx); | |||
} | |||
} | |||
} | |||
}; | |||
exports._drawAllSectorNodes = function(ctx) { | |||
this._drawSectorNodes(ctx,"frozen"); | |||
this._drawSectorNodes(ctx,"active"); | |||
this._loadLatestSector(); | |||
}; |
@ -0,0 +1,203 @@ | |||
var util = require('../../../util'); | |||
var Hammer = require('../../../module/hammer'); | |||
var hammerUtil = require('../../../hammerUtil'); | |||
var keycharm = require('keycharm'); | |||
class NavigationHandler { | |||
constructor(body, canvas) { | |||
this.body = body; | |||
this.canvas = canvas; | |||
this.iconsCreated = false; | |||
this.navigationHammers = []; | |||
this.boundFunctions = {}; | |||
this.touchTime = 0; | |||
this.activated = false; | |||
this.body.emitter.on("release", this._stopMovement.bind(this)); | |||
this.body.emitter.on("activate", () => {this.activated = true; this.configureKeyboardBindings();}); | |||
this.body.emitter.on("deactivate", () => {this.activated = false; this.configureKeyboardBindings();}); | |||
this.options = {} | |||
} | |||
setOptions(options) { | |||
if (options !== undefined) { | |||
this.options = options; | |||
this.create(); | |||
} | |||
} | |||
create() { | |||
if (this.options.showNavigationIcons === true) { | |||
if (this.iconsCreated === false) { | |||
this.loadNavigationElements(); | |||
} | |||
} | |||
else if (this.iconsCreated === true) { | |||
this.cleanNavigation(); | |||
} | |||
this.configureKeyboardBindings(); | |||
} | |||
cleanNavigation() { | |||
// clean hammer bindings | |||
if (this.navigationHammers.length != 0) { | |||
for (var i = 0; i < this.navigationHammers.length; i++) { | |||
this.navigationHammers[i].destroy(); | |||
} | |||
this.navigationHammers = []; | |||
} | |||
this._navigationReleaseOverload = function() {}; | |||
// clean up previous navigation items | |||
if (this.navigationDOM && this.navigationDOM['wrapper'] && this.navigationDOM['wrapper'].parentNode) { | |||
this.navigationDOM['wrapper'].parentNode.removeChild(this.navigationDOM['wrapper']); | |||
} | |||
this.iconsCreated = false; | |||
} | |||
/** | |||
* Creation of the navigation controls nodes. They are drawn over the rest of the nodes and are not affected by scale and translation | |||
* they have a triggerFunction which is called on click. If the position of the navigation controls is dependent | |||
* on this.frame.canvas.clientWidth or this.frame.canvas.clientHeight, we flag horizontalAlignLeft and verticalAlignTop false. | |||
* This means that the location will be corrected by the _relocateNavigation function on a size change of the canvas. | |||
* | |||
* @private | |||
*/ | |||
loadNavigationElements() { | |||
this.cleanNavigation(); | |||
this.navigationDOM = {}; | |||
var navigationDivs = ['up','down','left','right','zoomIn','zoomOut','zoomExtends']; | |||
var navigationDivActions = ['_moveUp','_moveDown','_moveLeft','_moveRight','_zoomIn','_zoomOut','_zoomExtent']; | |||
this.navigationDOM['wrapper'] = document.createElement('div'); | |||
this.canvas.frame.appendChild(this.navigationDOM['wrapper']); | |||
for (var i = 0; i < navigationDivs.length; i++) { | |||
this.navigationDOM[navigationDivs[i]] = document.createElement('div'); | |||
this.navigationDOM[navigationDivs[i]].className = 'network-navigation ' + navigationDivs[i]; | |||
this.navigationDOM['wrapper'].appendChild(this.navigationDOM[navigationDivs[i]]); | |||
var hammer = new Hammer(this.navigationDOM[navigationDivs[i]]); | |||
if (navigationDivActions[i] == "_zoomExtent") { | |||
hammerUtil.onTouch(hammer, this._zoomExtent.bind(this)); | |||
} | |||
else { | |||
hammerUtil.onTouch(hammer, this.bindToRedraw.bind(this,navigationDivActions[i])); | |||
} | |||
this.navigationHammers.push(hammer); | |||
} | |||
this.iconsCreated = true; | |||
} | |||
bindToRedraw(action) { | |||
if (this.boundFunctions[action] === undefined) { | |||
this.boundFunctions[action] = this[action].bind(this); | |||
this.body.emitter.on("initRedraw", this.boundFunctions[action]); | |||
this.body.emitter.emit("_startRendering"); | |||
} | |||
} | |||
unbindFromRedraw(action) { | |||
if (this.boundFunctions[action] !== undefined) { | |||
this.body.emitter.off("initRedraw", this.boundFunctions[action]); | |||
this.body.emitter.emit("_stopRendering"); | |||
delete this.boundFunctions[action]; | |||
} | |||
} | |||
/** | |||
* this stops all movement induced by the navigation buttons | |||
* | |||
* @private | |||
*/ | |||
_zoomExtent() { | |||
if (new Date().valueOf() - this.touchTime > 700) { // TODO: fix ugly hack to avoid hammer's double fireing of event (because we use release?) | |||
this.body.emitter.emit("zoomExtent", {duration: 700}); | |||
this.touchTime = new Date().valueOf(); | |||
} | |||
} | |||
/** | |||
* this stops all movement induced by the navigation buttons | |||
* | |||
* @private | |||
*/ | |||
_stopMovement() { | |||
for (let boundAction in this.boundFunctions) { | |||
if (this.boundFunctions.hasOwnProperty(boundAction)) { | |||
this.body.emitter.off("initRedraw", this.boundFunctions[boundAction]); | |||
this.body.emitter.emit("_stopRendering"); | |||
} | |||
} | |||
this.boundFunctions = {}; | |||
} | |||
_moveUp() {this.body.view.translation.y -= this.options.keyboard.speed.y;} | |||
_moveDown() {this.body.view.translation.y += this.options.keyboard.speed.y;} | |||
_moveLeft() {this.body.view.translation.x -= this.options.keyboard.speed.x;} | |||
_moveRight(){this.body.view.translation.x += this.options.keyboard.speed.x;} | |||
_zoomIn() {this.body.view.scale += this.options.keyboard.speed.zoom;} | |||
_zoomOut() {this.body.view.scale -= this.options.keyboard.speed.zoom;} | |||
/** | |||
* bind all keys using keycharm. | |||
*/ | |||
configureKeyboardBindings() { | |||
if (this.keycharm !== undefined) { | |||
this.keycharm.destroy(); | |||
} | |||
if (this.options.keyboard.enabled === true) { | |||
if (this.options.keyboard.bindToWindow === true) { | |||
this.keycharm = keycharm({container: window, preventDefault: false}); | |||
} | |||
else { | |||
this.keycharm = keycharm({container: this.canvas.frame, preventDefault: false}); | |||
} | |||
this.keycharm.reset(); | |||
if (this.activated === true) { | |||
this.keycharm.bind("up", this.bindToRedraw.bind(this, "_moveUp"), "keydown"); | |||
this.keycharm.bind("down", this.bindToRedraw.bind(this, "_moveDown"), "keydown"); | |||
this.keycharm.bind("left", this.bindToRedraw.bind(this, "_moveLeft"), "keydown"); | |||
this.keycharm.bind("right", this.bindToRedraw.bind(this, "_moveRight"), "keydown"); | |||
this.keycharm.bind("=", this.bindToRedraw.bind(this, "_zoomIn"), "keydown"); | |||
this.keycharm.bind("num+", this.bindToRedraw.bind(this, "_zoomIn"), "keydown"); | |||
this.keycharm.bind("num-", this.bindToRedraw.bind(this, "_zoomOut"), "keydown"); | |||
this.keycharm.bind("-", this.bindToRedraw.bind(this, "_zoomOut"), "keydown"); | |||
this.keycharm.bind("[", this.bindToRedraw.bind(this, "_zoomOut"), "keydown"); | |||
this.keycharm.bind("]", this.bindToRedraw.bind(this, "_zoomIn"), "keydown"); | |||
this.keycharm.bind("pageup", this.bindToRedraw.bind(this, "_zoomIn"), "keydown"); | |||
this.keycharm.bind("pagedown", this.bindToRedraw.bind(this, "_zoomOut"), "keydown"); | |||
this.keycharm.bind("up", this.unbindFromRedraw.bind(this, "_moveUp"), "keyup"); | |||
this.keycharm.bind("down", this.unbindFromRedraw.bind(this, "_moveDown"), "keyup"); | |||
this.keycharm.bind("left", this.unbindFromRedraw.bind(this, "_moveLeft"), "keyup"); | |||
this.keycharm.bind("right", this.unbindFromRedraw.bind(this, "_moveRight"), "keyup"); | |||
this.keycharm.bind("=", this.unbindFromRedraw.bind(this, "_zoomIn"), "keyup"); | |||
this.keycharm.bind("num+", this.unbindFromRedraw.bind(this, "_zoomIn"), "keyup"); | |||
this.keycharm.bind("num-", this.unbindFromRedraw.bind(this, "_zoomOut"), "keyup"); | |||
this.keycharm.bind("-", this.unbindFromRedraw.bind(this, "_zoomOut"), "keyup"); | |||
this.keycharm.bind("[", this.unbindFromRedraw.bind(this, "_zoomOut"), "keyup"); | |||
this.keycharm.bind("]", this.unbindFromRedraw.bind(this, "_zoomIn"), "keyup"); | |||
this.keycharm.bind("pageup", this.unbindFromRedraw.bind(this, "_zoomIn"), "keyup"); | |||
this.keycharm.bind("pagedown", this.unbindFromRedraw.bind(this, "_zoomOut"), "keyup"); | |||
} | |||
} | |||
} | |||
} | |||
export {NavigationHandler}; |