Browse Source

tweaks. NagivationMixin has now been ported along with keyboard bindings using keycharm.

flowchartTest
Alex de Mulder 9 years ago
parent
commit
208da974db
12 changed files with 816 additions and 3332 deletions
  1. +559
    -2402
      dist/vis.js
  2. +7
    -24
      lib/network/Network.js
  3. +0
    -158
      lib/network/mixins/MixinLoader.js
  4. +0
    -174
      lib/network/mixins/NavigationMixin.js
  5. +0
    -553
      lib/network/mixins/SectorsMixin.js
  6. +5
    -1
      lib/network/modules/Canvas.js
  7. +1
    -1
      lib/network/modules/CanvasRenderer.js
  8. +33
    -7
      lib/network/modules/InteractionHandler.js
  9. +2
    -2
      lib/network/modules/PhysicsEngine.js
  10. +5
    -5
      lib/network/modules/View.js
  11. +203
    -0
      lib/network/modules/components/NavigationHandler.js
  12. +1
    -5
      lib/timeline/component/Group.js

+ 559
- 2402
dist/vis.js
File diff suppressed because it is too large
View File


+ 7
- 24
lib/network/Network.js View File

@ -133,9 +133,6 @@ function Network (container, data, options) {
inheritColor: "from", // to, from, false, true (== from) inheritColor: "from", // to, from, false, true (== from)
useGradients: false // release in 4.0 useGradients: false // release in 4.0
}, },
navigation: {
enabled: false
},
dataManipulation: { dataManipulation: {
enabled: false, enabled: false,
initiallyVisible: false initiallyVisible: false
@ -152,6 +149,7 @@ function Network (container, data, options) {
dragView: true, dragView: true,
zoomView: true, zoomView: true,
hoverEnabled: false, hoverEnabled: false,
showNavigationIcons: false,
tooltip: { tooltip: {
delay: 300, delay: 300,
fontColor: 'black', fontColor: 'black',
@ -538,39 +536,24 @@ Network.prototype.setOptions = function (options) {
} }
if ('clickToUse' in options) { if ('clickToUse' in options) {
if (options.clickToUse) {
if (!this.activator) {
if (options.clickToUse === true) {
if (this.activator === undefined) {
this.activator = new Activator(this.frame); this.activator = new Activator(this.frame);
this.activator.on('change', this._createKeyBinds.bind(this)); this.activator.on('change', this._createKeyBinds.bind(this));
} }
} }
else { else {
if (this.activator) {
if (this.activator !== undefined) {
this.activator.destroy(); this.activator.destroy();
delete this.activator; delete this.activator;
} }
this.body.emitter.emit("activate");
} }
} }
if (options.labels) {
throw new Error('Option "labels" is deprecated. Use options "locale" and "locales" instead.');
else {
this.body.emitter.emit("activate");
} }
// (Re)loading the mixins that can be enabled or disabled in the options.
// load the force calculation functions, grouped under the physics system.
// load the navigation system.
//this._loadNavigationControls();
//// load the data manipulation system
//this._loadManipulationSystem();
//// configure the smooth curves
//this._configureSmoothCurves();
// bind hammer
//this.canvas._bindHammer();
// bind keys. If disabled, this will not do anything;
//this._createKeyBinds();
this._markAllEdgesAsDirty(); this._markAllEdgesAsDirty();
this.canvas.setSize(); this.canvas.setSize();
if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) { if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {

+ 0
- 158
lib/network/mixins/MixinLoader.js View File

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

+ 0
- 174
lib/network/mixins/NavigationMixin.js View File

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

+ 0
- 553
lib/network/mixins/SectorsMixin.js View File

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

+ 5
- 1
lib/network/modules/Canvas.js View File

@ -89,7 +89,11 @@ class Canvas {
} }
this.drag = {}; this.drag = {};
this.pinch = {}; this.pinch = {};
// init hammer
this.hammer = new Hammer(this.frame.canvas); this.hammer = new Hammer(this.frame.canvas);
this.hammer.get('pinch').set({enable: true});
this.hammer.on('tap', this.body.eventListeners.onTap ); this.hammer.on('tap', this.body.eventListeners.onTap );
this.hammer.on('doubletap', this.body.eventListeners.onDoubleTap ); this.hammer.on('doubletap', this.body.eventListeners.onDoubleTap );
this.hammer.on('press', this.body.eventListeners.onHold ); this.hammer.on('press', this.body.eventListeners.onHold );
@ -97,7 +101,7 @@ class Canvas {
this.hammer.on('panstart', this.body.eventListeners.onDragStart ); this.hammer.on('panstart', this.body.eventListeners.onDragStart );
this.hammer.on('panmove', this.body.eventListeners.onDrag ); this.hammer.on('panmove', this.body.eventListeners.onDrag );
this.hammer.on('panend', this.body.eventListeners.onDragEnd ); this.hammer.on('panend', this.body.eventListeners.onDragEnd );
this.hammer.on('pinch', function() {console.log("pinching!");});//this.body.eventListeners.onPinch );
this.hammer.on('pinch', this.body.eventListeners.onPinch );
// TODO: neatly cleanup these handlers when re-creating the Canvas, IF these are done with hammer, event.stopPropagation will not work? // TODO: neatly cleanup these handlers when re-creating the Canvas, IF these are done with hammer, event.stopPropagation will not work?
this.frame.canvas.addEventListener('mousewheel', this.body.eventListeners.onMouseWheel); this.frame.canvas.addEventListener('mousewheel', this.body.eventListeners.onMouseWheel);

+ 1
- 1
lib/network/modules/CanvasRenderer.js View File

@ -111,7 +111,7 @@ class CanvasRenderer {
} }
_redraw(hidden = false) { _redraw(hidden = false) {
this.body.emitter.emit("_beforeRender");
this.body.emitter.emit("initRedraw");
this.redrawRequested = false; this.redrawRequested = false;
var ctx = this.canvas.frame.canvas.getContext('2d'); var ctx = this.canvas.frame.canvas.getContext('2d');

+ 33
- 7
lib/network/modules/InteractionHandler.js View File

@ -6,11 +6,14 @@
var util = require('../../util'); var util = require('../../util');
import { NavigationHandler } from "./components/NavigationHandler"
class InteractionHandler { class InteractionHandler {
constructor(body, canvas, selectionHandler) { constructor(body, canvas, selectionHandler) {
this.body = body; this.body = body;
this.canvas = canvas; this.canvas = canvas;
this.selectionHandler = selectionHandler; this.selectionHandler = selectionHandler;
this.navigationHandler = new NavigationHandler(body,canvas);
// bind the events from hammer to functions in this object // bind the events from hammer to functions in this object
this.body.eventListeners.onTap = this.onTap.bind(this); this.body.eventListeners.onTap = this.onTap.bind(this);
@ -37,16 +40,38 @@ class InteractionHandler {
dragNodes:true, dragNodes:true,
dragView: true, dragView: true,
zoomView: true, zoomView: true,
selectEnabled: true,
hoverEnabled: false
hoverEnabled: false,
showNavigationIcons: true,
tooltip: {
delay: 300,
fontColor: 'black',
fontSize: 14, // px
fontFace: 'verdana',
color: {
border: '#666',
background: '#FFFFC6'
}
},
keyboard: {
enabled: true,
speed: {x: 10, y: 10, zoom: 0.02},
bindToWindow: true
}
} }
util.extend(this.options,this.defaultOptions); util.extend(this.options,this.defaultOptions);
} }
setOptions(options) { setOptions(options) {
if (options !== undefined) { if (options !== undefined) {
util.deepExtend(this.options, options);
// extend all but the values in fields
var fields = ['keyboard'];
util.selectiveNotDeepExtend(fields,this.options, options);
// merge the keyboard options in.
util.mergeOptions(this.options, options,'keyboard');
} }
this.navigationHandler.setOptions(this.options);
} }
@ -131,7 +156,9 @@ class InteractionHandler {
* *
* @private * @private
*/ */
onRelease() {}
onRelease(event) {
this.body.emitter.emit("release",event)
}
/** /**
@ -274,16 +301,15 @@ class InteractionHandler {
* @private * @private
*/ */
onPinch(event) { onPinch(event) {
console.log("on pinch")
var pointer = this.getPointer(event.center); var pointer = this.getPointer(event.center);
this.drag.pinched = true; this.drag.pinched = true;
if (this.pinch[scale] === undefined) {
if (this.pinch['scale'] === undefined) {
this.pinch.scale = 1; this.pinch.scale = 1;
} }
// TODO: enabled moving while pinching? // TODO: enabled moving while pinching?
var scale = this.pinch.scale * event.gesture.scale;
var scale = this.pinch.scale * event.scale;
this.zoom(scale, pointer) this.zoom(scale, pointer)
} }

+ 2
- 2
lib/network/modules/PhysicsEngine.js View File

@ -119,7 +119,7 @@ class PhysicsEngine {
stopSimulation() { stopSimulation() {
this.stabilized = true; this.stabilized = true;
if (this.viewFunction !== undefined) { if (this.viewFunction !== undefined) {
this.body.emitter.off("_beforeRender", this.viewFunction);
this.body.emitter.off("initRedraw", this.viewFunction);
this.viewFunction = undefined; this.viewFunction = undefined;
this.body.emitter.emit("_stopRendering"); this.body.emitter.emit("_stopRendering");
} }
@ -128,7 +128,7 @@ class PhysicsEngine {
runSimulation() { runSimulation() {
if (this.viewFunction === undefined) { if (this.viewFunction === undefined) {
this.viewFunction = this.simulationStep.bind(this); this.viewFunction = this.simulationStep.bind(this);
this.body.emitter.on("_beforeRender", this.viewFunction);
this.body.emitter.on("initRedraw", this.viewFunction);
this.body.emitter.emit("_startRendering"); this.body.emitter.emit("_startRendering");
} }
} }

+ 5
- 5
lib/network/modules/View.js View File

@ -252,7 +252,7 @@ class View {
if (options.animation.duration == 0) { if (options.animation.duration == 0) {
if (this.lockedOnNodeId != undefined) { if (this.lockedOnNodeId != undefined) {
this.viewFunction = this._lockedRedraw.bind(this); this.viewFunction = this._lockedRedraw.bind(this);
this.body.emitter.on("_beforeRender", this.viewFunction);
this.body.emitter.on("initRedraw", this.viewFunction);
} }
else { else {
this.body.view.scale = this.targetScale; this.body.view.scale = this.targetScale;
@ -266,7 +266,7 @@ class View {
this.viewFunction = this._transitionRedraw.bind(this); this.viewFunction = this._transitionRedraw.bind(this);
this.body.emitter.on("_beforeRender", this.viewFunction);
this.body.emitter.on("initRedraw", this.viewFunction);
this.body.emitter.emit("_startRendering"); this.body.emitter.emit("_startRendering");
} }
} }
@ -293,7 +293,7 @@ class View {
releaseNode() { releaseNode() {
if (this.lockedOnNodeId !== undefined && this.viewFunction !== undefined) { if (this.lockedOnNodeId !== undefined && this.viewFunction !== undefined) {
this.body.emitter.off("_beforeRender", this.viewFunction);
this.body.emitter.off("initRedraw", this.viewFunction);
this.lockedOnNodeId = undefined; this.lockedOnNodeId = undefined;
this.lockedOnNodeOffset = undefined; this.lockedOnNodeOffset = undefined;
} }
@ -318,11 +318,11 @@ class View {
// cleanup // cleanup
if (this.easingTime >= 1.0) { if (this.easingTime >= 1.0) {
this.body.emitter.off("_beforeRender", this.viewFunction);
this.body.emitter.off("initRedraw", this.viewFunction);
this.easingTime = 0; this.easingTime = 0;
if (this.lockedOnNodeId != undefined) { if (this.lockedOnNodeId != undefined) {
this.viewFunction = this._lockedRedraw.bind(this); this.viewFunction = this._lockedRedraw.bind(this);
this.body.emitter.on("_beforeRender", this.viewFunction);
this.body.emitter.on("initRedraw", this.viewFunction);
} }
this.body.emitter.emit("animationFinished"); this.body.emitter.emit("animationFinished");
} }

+ 203
- 0
lib/network/modules/components/NavigationHandler.js View File

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

+ 1
- 5
lib/timeline/component/Group.js View File

@ -214,7 +214,7 @@ Group.prototype._calculateHeight = function (margin) {
//this.visibleSubgroups = 0; //this.visibleSubgroups = 0;
this.resetSubgroups(); this.resetSubgroups();
var me = this; var me = this;
if (visibleItems.length) {
if (visibleItems.length > 0) {
var min = visibleItems[0].top; var min = visibleItems[0].top;
var max = visibleItems[0].top + visibleItems[0].height; var max = visibleItems[0].top + visibleItems[0].height;
util.forEach(visibleItems, function (item) { util.forEach(visibleItems, function (item) {
@ -223,10 +223,6 @@ Group.prototype._calculateHeight = function (margin) {
if (item.data.subgroup !== undefined) { if (item.data.subgroup !== undefined) {
me.subgroups[item.data.subgroup].height = Math.max(me.subgroups[item.data.subgroup].height,item.height); me.subgroups[item.data.subgroup].height = Math.max(me.subgroups[item.data.subgroup].height,item.height);
me.subgroups[item.data.subgroup].visible = true; me.subgroups[item.data.subgroup].visible = true;
//if (visibleSubgroups.indexOf(item.data.subgroup) == -1){
// visibleSubgroups.push(item.data.subgroup);
// me.visibleSubgroups += 1;
//}
} }
}); });
if (min > margin.axis) { if (min > margin.axis) {

Loading…
Cancel
Save