@ -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}; |