Browse Source

most of the manipulation is now in 4.0

flowchartTest
Alex de Mulder 9 years ago
parent
commit
39eb2e5079
16 changed files with 3039 additions and 1988 deletions
  1. +1863
    -881
      dist/vis.js
  2. +4
    -4
      examples/network/01_basic_usage.html
  3. +53
    -55
      lib/network/Network.js
  4. +0
    -715
      lib/network/mixins/ManipulationMixin.js
  5. +12
    -12
      lib/network/modules/Canvas.js
  6. +3
    -2
      lib/network/modules/CanvasRenderer.js
  7. +10
    -15
      lib/network/modules/InteractionHandler.js
  8. +18
    -3
      lib/network/modules/LayoutEngine.js
  9. +985
    -0
      lib/network/modules/ManipulationSystem.js
  10. +10
    -1
      lib/network/modules/PhysicsEngine.js
  11. +7
    -6
      lib/network/modules/SelectionHandler.js
  12. +0
    -236
      lib/network/modules/components/Edge.js
  13. +1
    -1
      lib/network/modules/components/edges/bezierEdgeStatic.js
  14. +61
    -51
      lib/network/modules/components/edges/util/EdgeBase.js
  15. +2
    -2
      lib/network/modules/components/nodes/util/ShapeBase.js
  16. +10
    -4
      lib/network/modules/components/physics/SpringSolver.js

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


+ 4
- 4
examples/network/01_basic_usage.html View File

@ -8,7 +8,7 @@
<style type="text/css">
#mynetwork {
width: 400px;
width: 600px;
height: 400px;
border: 1px solid lightgray;
}
@ -22,7 +22,7 @@
<script type="text/javascript">
// create an array with nodes
var nodes = [
{id: 1, label: 'Node 1',title:'blaat'},
{id: 1, label: 'Node 1'},
{id: 2, label: 'Node 2'},
{id: 3, label: 'Node 3'},
{id: 4, label: 'Node 4'},
@ -31,6 +31,7 @@
// create an array with edges
var edges = [
{from: 1, to: 1},
{from: 1, to: 3},
{from: 1, to: 2},
{from: 2, to: 4},
@ -43,9 +44,8 @@
nodes: nodes,
edges: edges
};
var options = {edges:{arrows:'to from'}}//{physics:{stabilization:false}};
var options = {edges:{arrows:'to'},manipulation:{initiallyVisible: true}}//{physics:{stabilization:false}};
var network = new vis.Network(container, data, options);
network.on("selected",function () {alert("1")})
</script>
</body>

+ 53
- 55
lib/network/Network.js View File

@ -10,9 +10,6 @@ var dotparser = require('./dotparser');
var gephiParser = require('./gephiParser');
var Images = require('./Images');
var Activator = require('../shared/Activator');
var locales = require('./locales');
import Groups from './modules/Groups';
import NodesHandler from './modules/NodesHandler';
@ -25,6 +22,7 @@ import View from './modules/View';
import InteractionHandler from './modules/InteractionHandler';
import SelectionHandler from "./modules/SelectionHandler";
import LayoutEngine from "./modules/LayoutEngine";
import ManipulationSystem from "./modules/ManipulationSystem";
/**
* @constructor Network
@ -45,12 +43,7 @@ function Network (container, data, options) {
// set constant values
this.options = {};
this.defaultOptions = {
//dataManipulation: {
// enabled: false,
// initiallyVisible: false
//},
locale: 'en',
locales: locales
clickToUse: false
};
util.extend(this.options, this.defaultOptions);
@ -66,7 +59,8 @@ function Network (container, data, options) {
},
functions:{
createNode: () => {},
createEdge: () => {}
createEdge: () => {},
getPointer: () => {}
},
emitter: {
on: this.on.bind(this),
@ -107,8 +101,9 @@ function Network (container, data, options) {
this.view = new View(this.body, this.canvas); // camera handler, does animations and zooms
this.renderer = new CanvasRenderer(this.body, this.canvas); // renderer, starts renderloop, has events that modules can hook into
this.physics = new PhysicsEngine(this.body); // physics engine, does all the simulations
this.layoutEngine = new LayoutEngine(this.body);
this.layoutEngine = new LayoutEngine(this.body); // layout engine for inital layout and hierarchical layout
this.clustering = new ClusterEngine(this.body); // clustering api
this.manipulation = new ManipulationSystem(this.body, this.canvas, this.selectionHandler); // data manipulation system
this.nodesHandler = new NodesHandler(this.body, images, this.groups, this.layoutEngine); // Handle adding, deleting and updating of nodes as well as global options
this.edgesHandler = new EdgesHandler(this.body, images, this.groups); // Handle adding, deleting and updating of edges as well as global options
@ -128,6 +123,53 @@ function Network (container, data, options) {
Emitter(Network.prototype);
/**
* Set options
* @param {Object} options
*/
Network.prototype.setOptions = function (options) {
if (options !== undefined) {
// the hierarchical system can adapt the edges and the physics to it's own options because not all combinations work with the hierarichical system.
options = this.layoutEngine.setOptions(options.layout, options);
this.groups.setOptions(options.groups);
this.nodesHandler.setOptions(options.nodes);
this.edgesHandler.setOptions(options.edges);
this.physics.setOptions(options.physics);
this.canvas.setOptions(options.canvas);
this.renderer.setOptions(options.rendering);
this.view.setOptions(options.view);
this.interactionHandler.setOptions(options.interaction);
this.selectionHandler.setOptions(options.selection);
this.clustering.setOptions(options.clustering);
this.manipulation.setOptions(options.manipulation);
if (options.clickToUse !== undefined) {
if (options.clickToUse === true) {
if (this.activator === undefined) {
this.activator = new Activator(this.frame);
this.activator.on('change', this._createKeyBinds.bind(this));
}
}
else {
if (this.activator !== undefined) {
this.activator.destroy();
delete this.activator;
}
this.body.emitter.emit("activate");
}
}
else {
this.body.emitter.emit("activate");
}
this.canvas.setSize();
}
};
/**
* Update the this.body.nodeIndices with the most recent node index list
* @private
@ -237,50 +279,6 @@ Network.prototype.setData = function(data) {
this.body.emitter.emit("initPhysics");
};
/**
* Set options
* @param {Object} options
*/
Network.prototype.setOptions = function (options) {
if (options !== undefined) {
// the hierarchical system can adapt the edges and the physics to it's own options because not all combinations work with the hierarichical system.
options = this.layoutEngine.setOptions(options.layout, options);
this.groups.setOptions(options.groups);
this.nodesHandler.setOptions(options.nodes);
this.edgesHandler.setOptions(options.edges);
this.physics.setOptions(options.physics);
this.canvas.setOptions(options.canvas);
this.renderer.setOptions(options.rendering);
this.view.setOptions(options.view);
this.interactionHandler.setOptions(options.interaction);
this.selectionHandler.setOptions(options.selection);
this.clustering.setOptions(options.clustering);
if (options.clickToUse !== undefined) {
if (options.clickToUse === true) {
if (this.activator === undefined) {
this.activator = new Activator(this.frame);
this.activator.on('change', this._createKeyBinds.bind(this));
}
}
else {
if (this.activator !== undefined) {
this.activator.destroy();
delete this.activator;
}
this.body.emitter.emit("activate");
}
}
else {
this.body.emitter.emit("activate");
}
this.canvas.setSize();
}
};
/**
* Cleans up all bindings of the network, removing it fully from the memory IF the variable is set to null after calling this function.

+ 0
- 715
lib/network/mixins/ManipulationMixin.js View File

@ -1,715 +0,0 @@
var util = require('../../util');
var Node = require('../Node');
var Edge = require('../Edge');
var Hammer = require('../../module/hammer');
var hammerUtil = require('../../hammerUtil');
/**
* clears the toolbar div element of children
*
* @private
*/
exports._clearManipulatorBar = function() {
this._recursiveDOMDelete(this.manipulationDiv);
this.manipulationDOM = {};
this._cleanManipulatorHammers();
this._manipulationReleaseOverload = function () {};
delete this.body.sectors['support']['nodes']['targetNode'];
delete this.body.sectors['support']['nodes']['targetViaNode'];
this.controlNodesActive = false;
this.freezeSimulationEnabled = false;
};
exports._cleanManipulatorHammers = function() {
// clean hammer bindings
if (this.manipulationHammers.length != 0) {
for (var i = 0; i < this.manipulationHammers.length; i++) {
this.manipulationHammers[i].destroy();
}
this.manipulationHammers = [];
}
};
/**
* Manipulation UI temporarily overloads certain functions to extend or replace them. To be able to restore
* these functions to their original functionality, we saved them in this.cachedFunctions.
* This function restores these functions to their original function.
*
* @private
*/
exports._restoreOverloadedFunctions = function() {
for (var functionName in this.cachedFunctions) {
if (this.cachedFunctions.hasOwnProperty(functionName)) {
this[functionName] = this.cachedFunctions[functionName];
delete this.cachedFunctions[functionName];
}
}
};
/**
* Enable or disable edit-mode.
*
* @private
*/
exports._toggleEditMode = function() {
this.editMode = !this.editMode;
var toolbar = this.manipulationDiv;
var closeDiv = this.closeDiv;
var editModeDiv = this.editModeDiv;
if (this.editMode == true) {
toolbar.style.display="block";
closeDiv.style.display="block";
editModeDiv.style.display="none";
this._bindHammerToDiv(closeDiv,'_toggleEditMode');
}
else {
toolbar.style.display="none";
closeDiv.style.display="none";
editModeDiv.style.display="block";
}
this._createManipulatorBar()
};
/**
* main function, creates the main toolbar. Removes functions bound to the select event. Binds all the buttons of the toolbar.
*
* @private
*/
exports._createManipulatorBar = function() {
// remove bound functions
if (this.boundFunction) {
this.off('select', this.boundFunction);
}
this._cleanManipulatorHammers();
var locale = this.constants.locales[this.constants.locale];
if (this.edgeBeingEdited !== undefined) {
this.edgeBeingEdited._disableControlNodes();
this.edgeBeingEdited = undefined;
this.selectedControlNode = null;
this.controlNodesActive = false;
this._redraw();
}
// restore overloaded functions
this._restoreOverloadedFunctions();
// resume calculation
this.freezeSimulation(false);
// reset global variables
this.blockConnectingEdgeSelection = false;
this.forceAppendSelection = false;
this.manipulationDOM = {};
if (this.editMode == true) {
while (this.manipulationDiv.hasChildNodes()) {
this.manipulationDiv.removeChild(this.manipulationDiv.firstChild);
}
this.manipulationDOM['addNodeSpan'] = document.createElement('div');
this.manipulationDOM['addNodeSpan'].className = 'network-manipulationUI add';
this.manipulationDOM['addNodeLabelSpan'] = document.createElement('div');
this.manipulationDOM['addNodeLabelSpan'].className = 'network-manipulationLabel';
this.manipulationDOM['addNodeLabelSpan'].innerHTML = locale['addNode'];
this.manipulationDOM['addNodeSpan'].appendChild(this.manipulationDOM['addNodeLabelSpan']);
this.manipulationDOM['seperatorLineDiv1'] = document.createElement('div');
this.manipulationDOM['seperatorLineDiv1'].className = 'network-seperatorLine';
this.manipulationDOM['addEdgeSpan'] = document.createElement('div');
this.manipulationDOM['addEdgeSpan'].className = 'network-manipulationUI connect';
this.manipulationDOM['addEdgeLabelSpan'] = document.createElement('div');
this.manipulationDOM['addEdgeLabelSpan'].className = 'network-manipulationLabel';
this.manipulationDOM['addEdgeLabelSpan'].innerHTML = locale['addEdge'];
this.manipulationDOM['addEdgeSpan'].appendChild(this.manipulationDOM['addEdgeLabelSpan']);
this.manipulationDiv.appendChild(this.manipulationDOM['addNodeSpan']);
this.manipulationDiv.appendChild(this.manipulationDOM['seperatorLineDiv1']);
this.manipulationDiv.appendChild(this.manipulationDOM['addEdgeSpan']);
if (this._getSelectedNodeCount() == 1 && this.triggerFunctions.edit) {
this.manipulationDOM['seperatorLineDiv2'] = document.createElement('div');
this.manipulationDOM['seperatorLineDiv2'].className = 'network-seperatorLine';
this.manipulationDOM['editNodeSpan'] = document.createElement('div');
this.manipulationDOM['editNodeSpan'].className = 'network-manipulationUI edit';
this.manipulationDOM['editNodeLabelSpan'] = document.createElement('div');
this.manipulationDOM['editNodeLabelSpan'].className = 'network-manipulationLabel';
this.manipulationDOM['editNodeLabelSpan'].innerHTML = locale['editNode'];
this.manipulationDOM['editNodeSpan'].appendChild(this.manipulationDOM['editNodeLabelSpan']);
this.manipulationDiv.appendChild(this.manipulationDOM['seperatorLineDiv2']);
this.manipulationDiv.appendChild(this.manipulationDOM['editNodeSpan']);
}
else if (this._getSelectedEdgeCount() == 1 && this._getSelectedNodeCount() == 0) {
this.manipulationDOM['seperatorLineDiv3'] = document.createElement('div');
this.manipulationDOM['seperatorLineDiv3'].className = 'network-seperatorLine';
this.manipulationDOM['editEdgeSpan'] = document.createElement('div');
this.manipulationDOM['editEdgeSpan'].className = 'network-manipulationUI edit';
this.manipulationDOM['editEdgeLabelSpan'] = document.createElement('div');
this.manipulationDOM['editEdgeLabelSpan'].className = 'network-manipulationLabel';
this.manipulationDOM['editEdgeLabelSpan'].innerHTML = locale['editEdge'];
this.manipulationDOM['editEdgeSpan'].appendChild(this.manipulationDOM['editEdgeLabelSpan']);
this.manipulationDiv.appendChild(this.manipulationDOM['seperatorLineDiv3']);
this.manipulationDiv.appendChild(this.manipulationDOM['editEdgeSpan']);
}
if (this._selectionIsEmpty() == false) {
this.manipulationDOM['seperatorLineDiv4'] = document.createElement('div');
this.manipulationDOM['seperatorLineDiv4'].className = 'network-seperatorLine';
this.manipulationDOM['deleteSpan'] = document.createElement('div');
this.manipulationDOM['deleteSpan'].className = 'network-manipulationUI delete';
this.manipulationDOM['deleteLabelSpan'] = document.createElement('div');
this.manipulationDOM['deleteLabelSpan'].className = 'network-manipulationLabel';
this.manipulationDOM['deleteLabelSpan'].innerHTML = locale['del'];
this.manipulationDOM['deleteSpan'].appendChild(this.manipulationDOM['deleteLabelSpan']);
this.manipulationDiv.appendChild(this.manipulationDOM['seperatorLineDiv4']);
this.manipulationDiv.appendChild(this.manipulationDOM['deleteSpan']);
}
// bind the icons
this._bindHammerToDiv(this.manipulationDOM['addNodeSpan'],'_createAddNodeToolbar');
this._bindHammerToDiv(this.manipulationDOM['addEdgeSpan'],'_createAddEdgeToolbar');
this._bindHammerToDiv(this.closeDiv,'_toggleEditMode');
if (this._getSelectedNodeCount() == 1 && this.triggerFunctions.edit) {
this._bindHammerToDiv(this.manipulationDOM['editNodeSpan'],'_editNode');
}
else if (this._getSelectedEdgeCount() == 1 && this._getSelectedNodeCount() == 0) {
this._bindHammerToDiv(this.manipulationDOM['editEdgeSpan'],'_createEditEdgeToolbar');
}
if (this._selectionIsEmpty() == false) {
this._bindHammerToDiv(this.manipulationDOM['deleteSpan'],'_deleteSelected');
}
var me = this;
this.boundFunction = me._createManipulatorBar;
this.on('select', this.boundFunction);
}
else {
while (this.editModeDiv.hasChildNodes()) {
this.editModeDiv.removeChild(this.editModeDiv.firstChild);
}
this.manipulationDOM['editModeSpan'] = document.createElement('div');
this.manipulationDOM['editModeSpan'].className = 'network-manipulationUI edit editmode';
this.manipulationDOM['editModeLabelSpan'] = document.createElement('div');
this.manipulationDOM['editModeLabelSpan'].className = 'network-manipulationLabel';
this.manipulationDOM['editModeLabelSpan'].innerHTML = locale['edit'];
this.manipulationDOM['editModeSpan'].appendChild(this.manipulationDOM['editModeLabelSpan']);
this.editModeDiv.appendChild(this.manipulationDOM['editModeSpan']);
this._bindHammerToDiv(this.manipulationDOM['editModeSpan'],'_toggleEditMode');
}
};
exports._bindHammerToDiv = function(domElement, funct) {
var hammer = new Hammer(domElement, {});
hammerUtil.onTouch(hammer, this[funct].bind(this));
this.manipulationHammers.push(hammer);
};
/**
* Create the toolbar for adding Nodes
*
* @private
*/
exports._createAddNodeToolbar = function() {
// clear the toolbar
this._clearManipulatorBar();
if (this.boundFunction) {
this.off('select', this.boundFunction);
}
var locale = this.constants.locales[this.constants.locale];
this.manipulationDOM = {};
this.manipulationDOM['backSpan'] = document.createElement('div');
this.manipulationDOM['backSpan'].className = 'network-manipulationUI back';
this.manipulationDOM['backLabelSpan'] = document.createElement('div');
this.manipulationDOM['backLabelSpan'].className = 'network-manipulationLabel';
this.manipulationDOM['backLabelSpan'].innerHTML = locale['back'];
this.manipulationDOM['backSpan'].appendChild(this.manipulationDOM['backLabelSpan']);
this.manipulationDOM['seperatorLineDiv1'] = document.createElement('div');
this.manipulationDOM['seperatorLineDiv1'].className = 'network-seperatorLine';
this.manipulationDOM['descriptionSpan'] = document.createElement('div');
this.manipulationDOM['descriptionSpan'].className = 'network-manipulationUI none';
this.manipulationDOM['descriptionLabelSpan'] = document.createElement('div');
this.manipulationDOM['descriptionLabelSpan'].className = 'network-manipulationLabel';
this.manipulationDOM['descriptionLabelSpan'].innerHTML = locale['addDescription'];
this.manipulationDOM['descriptionSpan'].appendChild(this.manipulationDOM['descriptionLabelSpan']);
this.manipulationDiv.appendChild(this.manipulationDOM['backSpan']);
this.manipulationDiv.appendChild(this.manipulationDOM['seperatorLineDiv1']);
this.manipulationDiv.appendChild(this.manipulationDOM['descriptionSpan']);
// bind the icon
this._bindHammerToDiv(this.manipulationDOM['backSpan'],'_createManipulatorBar');
// we use the boundFunction so we can reference it when we unbind it from the "select" event.
var me = this;
this.boundFunction = me._addNode;
this.on('select', this.boundFunction);
};
/**
* create the toolbar to connect nodes
*
* @private
*/
exports._createAddEdgeToolbar = function() {
// clear the toolbar
this._clearManipulatorBar();
this._unselectAll(true);
this.freezeSimulationEnabled = true;
if (this.boundFunction) {
this.off('select', this.boundFunction);
}
var locale = this.constants.locales[this.constants.locale];
this.forceAppendSelection = false;
this.blockConnectingEdgeSelection = true;
this.manipulationDOM = {};
this.manipulationDOM['backSpan'] = document.createElement('div');
this.manipulationDOM['backSpan'].className = 'network-manipulationUI back';
this.manipulationDOM['backLabelSpan'] = document.createElement('div');
this.manipulationDOM['backLabelSpan'].className = 'network-manipulationLabel';
this.manipulationDOM['backLabelSpan'].innerHTML = locale['back'];
this.manipulationDOM['backSpan'].appendChild(this.manipulationDOM['backLabelSpan']);
this.manipulationDOM['seperatorLineDiv1'] = document.createElement('div');
this.manipulationDOM['seperatorLineDiv1'].className = 'network-seperatorLine';
this.manipulationDOM['descriptionSpan'] = document.createElement('div');
this.manipulationDOM['descriptionSpan'].className = 'network-manipulationUI none';
this.manipulationDOM['descriptionLabelSpan'] = document.createElement('div');
this.manipulationDOM['descriptionLabelSpan'].className = 'network-manipulationLabel';
this.manipulationDOM['descriptionLabelSpan'].innerHTML = locale['edgeDescription'];
this.manipulationDOM['descriptionSpan'].appendChild(this.manipulationDOM['descriptionLabelSpan']);
this.manipulationDiv.appendChild(this.manipulationDOM['backSpan']);
this.manipulationDiv.appendChild(this.manipulationDOM['seperatorLineDiv1']);
this.manipulationDiv.appendChild(this.manipulationDOM['descriptionSpan']);
// bind the icon
this._bindHammerToDiv(this.manipulationDOM['backSpan'],'_createManipulatorBar');
// we use the boundFunction so we can reference it when we unbind it from the "select" event.
var me = this;
this.boundFunction = me._handleConnect;
this.on('select', this.boundFunction);
// temporarily overload functions
this.cachedFunctions["_handleTouch"] = this._handleTouch;
this.cachedFunctions["_manipulationReleaseOverload"] = this._manipulationReleaseOverload;
this.cachedFunctions["_handleDragStart"] = this._handleDragStart;
this.cachedFunctions["_handleDragEnd"] = this._handleDragEnd;
this.cachedFunctions["_handleOnHold"] = this._handleOnHold;
this._handleTouch = this._handleConnect;
this._manipulationReleaseOverload = function () {};
this._handleOnHold = function () {};
this._handleDragStart = function () {};
this._handleDragEnd = this._finishConnect;
// redraw to show the unselect
this._redraw();
};
/**
* create the toolbar to edit edges
*
* @private
*/
exports._createEditEdgeToolbar = function() {
// clear the toolbar
this._clearManipulatorBar();
this.controlNodesActive = true;
if (this.boundFunction) {
this.off('select', this.boundFunction);
}
this.edgeBeingEdited = this._getSelectedEdge();
this.edgeBeingEdited._enableControlNodes();
var locale = this.constants.locales[this.constants.locale];
this.manipulationDOM = {};
this.manipulationDOM['backSpan'] = document.createElement('div');
this.manipulationDOM['backSpan'].className = 'network-manipulationUI back';
this.manipulationDOM['backLabelSpan'] = document.createElement('div');
this.manipulationDOM['backLabelSpan'].className = 'network-manipulationLabel';
this.manipulationDOM['backLabelSpan'].innerHTML = locale['back'];
this.manipulationDOM['backSpan'].appendChild(this.manipulationDOM['backLabelSpan']);
this.manipulationDOM['seperatorLineDiv1'] = document.createElement('div');
this.manipulationDOM['seperatorLineDiv1'].className = 'network-seperatorLine';
this.manipulationDOM['descriptionSpan'] = document.createElement('div');
this.manipulationDOM['descriptionSpan'].className = 'network-manipulationUI none';
this.manipulationDOM['descriptionLabelSpan'] = document.createElement('div');
this.manipulationDOM['descriptionLabelSpan'].className = 'network-manipulationLabel';
this.manipulationDOM['descriptionLabelSpan'].innerHTML = locale['editEdgeDescription'];
this.manipulationDOM['descriptionSpan'].appendChild(this.manipulationDOM['descriptionLabelSpan']);
this.manipulationDiv.appendChild(this.manipulationDOM['backSpan']);
this.manipulationDiv.appendChild(this.manipulationDOM['seperatorLineDiv1']);
this.manipulationDiv.appendChild(this.manipulationDOM['descriptionSpan']);
// bind the icon
this._bindHammerToDiv(this.manipulationDOM['backSpan'],'_createManipulatorBar');
// temporarily overload functions
this.cachedFunctions["_handleTouch"] = this._handleTouch;
this.cachedFunctions["_manipulationReleaseOverload"] = this._manipulationReleaseOverload;
this.cachedFunctions["_handleTap"] = this._handleTap;
this.cachedFunctions["_handleDragStart"] = this._handleDragStart;
this.cachedFunctions["_handleOnDrag"] = this._handleOnDrag;
this._handleTouch = this._selectControlNode;
this._handleTap = function () {};
this._handleOnDrag = this._controlNodeDrag;
this._handleDragStart = function () {}
this._manipulationReleaseOverload = this._releaseControlNode;
// redraw to show the unselect
this._redraw();
};
/**
* the function bound to the selection event. It checks if you want to connect a cluster and changes the description
* to walk the user through the process.
*
* @private
*/
exports._selectControlNode = function(pointer) {
this.edgeBeingEdited.controlNodes.from.unselect();
this.edgeBeingEdited.controlNodes.to.unselect();
this.selectedControlNode = this.edgeBeingEdited._getSelectedControlNode(this._XconvertDOMtoCanvas(pointer.x),this._YconvertDOMtoCanvas(pointer.y));
if (this.selectedControlNode !== null) {
this.selectedControlNode.select();
this.freezeSimulation(true);
}
this._redraw();
};
/**
* the function bound to the selection event. It checks if you want to connect a cluster and changes the description
* to walk the user through the process.
*
* @private
*/
exports._controlNodeDrag = function(event) {
var pointer = this._getPointer(event.center);
if (this.selectedControlNode !== null && this.selectedControlNode !== undefined) {
this.selectedControlNode.x = this._XconvertDOMtoCanvas(pointer.x);
this.selectedControlNode.y = this._YconvertDOMtoCanvas(pointer.y);
}
this._redraw();
};
/**
*
* @param pointer
* @private
*/
exports._releaseControlNode = function(pointer) {
var newNode = this._getNodeAt(pointer);
if (newNode !== null) {
if (this.edgeBeingEdited.controlNodes.from.selected == true) {
this.edgeBeingEdited._restoreControlNodes();
this._editEdge(newNode.id, this.edgeBeingEdited.to.id);
this.edgeBeingEdited.controlNodes.from.unselect();
}
if (this.edgeBeingEdited.controlNodes.to.selected == true) {
this.edgeBeingEdited._restoreControlNodes();
this._editEdge(this.edgeBeingEdited.from.id, newNode.id);
this.edgeBeingEdited.controlNodes.to.unselect();
}
}
else {
this.edgeBeingEdited._restoreControlNodes();
}
this.freezeSimulation(false);
this._redraw();
};
/**
* the function bound to the selection event. It checks if you want to connect a cluster and changes the description
* to walk the user through the process.
*
* @private
*/
exports._handleConnect = function(pointer) {
if (this._getSelectedNodeCount() == 0) {
var node = this._getNodeAt(pointer);
if (node != null) {
if (this.isCluster(node.id) == true) {
alert(this.constants.locales[this.constants.locale]['createEdgeError'])
}
else {
this._selectObject(node,false);
var supportNodes = this.body.sectors['support']['nodes'];
// create a node the temporary line can look at
supportNodes['targetNode'] = this.body.functions.createNode({id:'targetNode'});
var targetNode = supportNodes['targetNode'];
targetNode.x = node.x;
targetNode.y = node.y;
// create a temporary edge
this.body.edges['connectionEdge'] = this.body.functions.createEdge({id:"connectionEdge",from:node.id,to:targetNode.id});
var connectionEdge = this.body.edges['connectionEdge'];
connectionEdge.from = node;
connectionEdge.connected = true;
connectionEdge.options.smoothCurves = {enabled: true,
dynamic: false,
type: "continuous",
roundness: 0.5
};
connectionEdge.selected = true;
connectionEdge.to = targetNode;
this.cachedFunctions["_handleOnDrag"] = this._handleOnDrag;
var me = this;
this._handleOnDrag = function(event) {
var pointer = me._getPointer(event.center);
var connectionEdge = me.body.edges['connectionEdge'];
connectionEdge.to.x = me._XconvertDOMtoCanvas(pointer.x);
connectionEdge.to.y = me._YconvertDOMtoCanvas(pointer.y);
me._redraw();
};
}
}
}
};
exports._finishConnect = function(event) {
if (this._getSelectedNodeCount() == 1) {
var pointer = this._getPointer(event.center);
// restore the drag function
this._handleOnDrag = this.cachedFunctions["_handleOnDrag"];
delete this.cachedFunctions["_handleOnDrag"];
// remember the edge id
var connectFromId = this.body.edges['connectionEdge'].fromId;
// remove the temporary nodes and edge
delete this.body.edges['connectionEdge'];
delete this.body.sectors['support']['nodes']['targetNode'];
delete this.body.sectors['support']['nodes']['targetViaNode'];
var node = this._getNodeAt(pointer);
if (node != null) {
if (this.isCluster(node.id) === true) {
alert(this.constants.locales[this.constants.locale]["createEdgeError"])
}
else {
this._createEdge(connectFromId,node.id);
this._createManipulatorBar();
}
}
this._unselectAll();
}
};
/**
* Adds a node on the specified location
*/
exports._addNode = function() {
if (this._selectionIsEmpty() && this.editMode == true) {
var positionObject = this._pointerToPositionObject(this.pointerPosition);
var defaultData = {id:util.randomUUID(),x:positionObject.left,y:positionObject.top,label:"new",allowedToMoveX:true,allowedToMoveY:true};
if (this.triggerFunctions.add) {
if (this.triggerFunctions.add.length == 2) {
var me = this;
this.triggerFunctions.add(defaultData, function(finalizedData) {
me.body.data.nodes.add(finalizedData);
me._createManipulatorBar();
me.moving = true;
me.start();
});
}
else {
throw new Error('The function for add does not support two arguments (data,callback)');
this._createManipulatorBar();
this.moving = true;
this.start();
}
}
else {
this.body.data.nodes.add(defaultData);
this._createManipulatorBar();
this.moving = true;
this.start();
}
}
};
/**
* connect two nodes with a new edge.
*
* @private
*/
exports._createEdge = function(sourceNodeId,targetNodeId) {
if (this.editMode == true) {
var defaultData = {from:sourceNodeId, to:targetNodeId};
if (this.triggerFunctions.connect) {
if (this.triggerFunctions.connect.length == 2) {
var me = this;
this.triggerFunctions.connect(defaultData, function(finalizedData) {
me.body.data.edges.add(finalizedData);
me.moving = true;
me.start();
});
}
else {
throw new Error('The function for connect does not support two arguments (data,callback)');
this.moving = true;
this.start();
}
}
else {
this.body.data.edges.add(defaultData);
this.moving = true;
this.start();
}
}
};
/**
* connect two nodes with a new edge.
*
* @private
*/
exports._editEdge = function(sourceNodeId,targetNodeId) {
if (this.editMode == true) {
var defaultData = {id: this.edgeBeingEdited.id, from:sourceNodeId, to:targetNodeId};
console.log(defaultData);
if (this.triggerFunctions.editEdge) {
if (this.triggerFunctions.editEdge.length == 2) {
var me = this;
this.triggerFunctions.editEdge(defaultData, function(finalizedData) {
me.body.data.edges.update(finalizedData);
me.moving = true;
me.start();
});
}
else {
throw new Error('The function for edit does not support two arguments (data, callback)');
this.moving = true;
this.start();
}
}
else {
this.body.data.edges.update(defaultData);
this.moving = true;
this.start();
}
}
};
/**
* Create the toolbar to edit the selected node. The label and the color can be changed. Other colors are derived from the chosen color.
*
* @private
*/
exports._editNode = function() {
if (this.triggerFunctions.edit && this.editMode == true) {
var node = this._getSelectedNode();
var data = {id:node.id,
label: node.label,
group: node.options.group,
shape: node.options.shape,
color: {
background:node.options.color.background,
border:node.options.color.border,
highlight: {
background:node.options.color.highlight.background,
border:node.options.color.highlight.border
}
}};
if (this.triggerFunctions.edit.length == 2) {
var me = this;
this.triggerFunctions.edit(data, function (finalizedData) {
me.body.data.nodes.update(finalizedData);
me._createManipulatorBar();
me.moving = true;
me.start();
});
}
else {
throw new Error('The function for edit does not support two arguments (data, callback)');
}
}
else {
throw new Error('No edit function has been bound to this button');
}
};
/**
* delete everything in the selection
*
* @private
*/
exports._deleteSelected = function() {
if (!this._selectionIsEmpty() && this.editMode == true) {
if (!this._clusterInSelection()) {
var selectedNodes = this.getSelectedNodes();
var selectedEdges = this.getSelectedEdges();
if (this.triggerFunctions.del) {
var me = this;
var data = {nodes: selectedNodes, edges: selectedEdges};
if (this.triggerFunctions.del.length == 2) {
this.triggerFunctions.del(data, function (finalizedData) {
me.body.data.edges.remove(finalizedData.edges);
me.body.data.nodes.remove(finalizedData.nodes);
me._unselectAll();
me.moving = true;
me.start();
});
}
else {
throw new Error('The function for delete does not support two arguments (data, callback)')
}
}
else {
this.body.data.edges.remove(selectedEdges);
this.body.data.nodes.remove(selectedNodes);
this._unselectAll();
this.moving = true;
this.start();
}
}
else {
alert(this.constants.locales[this.constants.locale]["deleteClusterError"]);
}
}
};

+ 12
- 12
lib/network/modules/Canvas.js View File

@ -95,23 +95,23 @@ class 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('doubletap', this.body.eventListeners.onDoubleTap );
this.hammer.on('press', this.body.eventListeners.onHold );
hammerUtil.onTouch(this.hammer, this.body.eventListeners.onTouch );
this.hammer.on('panstart', this.body.eventListeners.onDragStart );
this.hammer.on('panmove', this.body.eventListeners.onDrag );
this.hammer.on('panend', this.body.eventListeners.onDragEnd );
this.hammer.on('pinch', this.body.eventListeners.onPinch );
hammerUtil.onTouch(this.hammer, (event) => {this.body.eventListeners.onTouchn>pan class="p">(event)});
this.hammer.on('tap', (event) => {this.body.eventListeners.onTap(event)});
this.hammer.on('doubletap', (event) => {this.body.eventListeners.onDoubleTap(event)});
this.hammer.on('press', (event) => {this.body.eventListeners.onHold(event)});
this.hammer.on('panstart', (event) => {this.body.eventListeners.onDragStart(event)});
this.hammer.on('panmove', (event) => {this.body.eventListeners.onDrag(event)});
this.hammer.on('panend', (event) => {this.body.eventListeners.onDragEnd(event)});
this.hammer.on('pinch', (event) => {this.body.eventListeners.onPinch(event)});
// 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('DOMMouseScroll', this.body.eventListeners.onMouseWheel);
this.frame.canvas.addEventListener('mousewheel', (event) => {this.body.eventListeners.onMouseWheel(event)});
this.frame.canvas.addEventListener('DOMMouseScroll', (event) => {this.body.eventListeners.onMouseWheel(event)});
this.frame.canvas.addEventListener('mousemove', this.body.eventListeners.onMouseMove);
this.frame.canvas.addEventListener('mousemove', (event) => {this.body.eventListeners.onMouseMove(event)});
this.hammerFrame = new Hammer(this.frame);
hammerUtil.onRelease(this.hammerFrame, this.body.eventListeners.onRelease);
hammerUtil.onRelease(this.hammerFrame, (event) => {this.body.eventListeners.onRelease(event)});
}

+ 3
- 2
lib/network/modules/CanvasRenderer.js View File

@ -51,7 +51,6 @@ class CanvasRenderer {
}
}
startRendering() {
if (this.renderingActive === true) {
if (!this.renderTimer) {
@ -158,6 +157,8 @@ class CanvasRenderer {
//this.physics.nodesSolver._debug(ctx,"#F00F0F");
this.body.emitter.emit("afterDrawing", ctx);
// restore original scaling and translation
ctx.restore();
@ -165,7 +166,7 @@ class CanvasRenderer {
ctx.clearRect(0, 0, w, h);
}
this.body.emitter.emit("afterDrawing", ctx);
}

+ 10
- 15
lib/network/modules/InteractionHandler.js View File

@ -31,12 +31,13 @@ class InteractionHandler {
this.touchTime = 0;
this.drag = {};
this.pinch = {};
this.pointerPosition = {x:0,y:0};
this.hoverObj = {nodes:{},edges:{}};
this.popup = undefined;
this.popupObj = undefined;
this.popupTimer = undefined;
this.body.functions.getPointer = this.getPointer.bind(this);
this.options = {};
this.defaultOptions = {
@ -62,10 +63,6 @@ class InteractionHandler {
}
}
util.extend(this.options,this.defaultOptions);
this.body.emitter.on("_dataChanged", () => {
})
}
setOptions(options) {
@ -113,7 +110,6 @@ class InteractionHandler {
this.drag.pointer = this.getPointer(event.center);
this.drag.pinched = false;
this.pinch.scale = this.body.view.scale;
// to avoid double fireing of this event because we have two hammer instances. (on canvas and on frame)
this.touchTime = new Date().valueOf();
}
@ -193,11 +189,11 @@ class InteractionHandler {
this.drag.dragging = true;
this.drag.selection = [];
this.drag.translation = util.extend({},this.body.view.translation); // copy the object
this.drag.nodeId = null;
this.drag.nodeId = undefined;
this.body.emitter.emit("dragStart", {nodeIds: this.selectionHandler.getSelection().nodes});
if (node != null && this.options.dragNodes === true) {
if (node !== undefined && this.options.dragNodes === true) {
this.drag.nodeId = node.id;
// select the clicked node if not yet selected
if (node.isSelected() === false) {
@ -263,7 +259,6 @@ class InteractionHandler {
}
});
// start the simulation of the physics
this.body.emitter.emit("startSimulation");
}
@ -345,7 +340,7 @@ class InteractionHandler {
scale = 10;
}
let preScaleDragPointer = null;
let preScaleDragPointer = undefined;
if (this.drag !== undefined) {
if (this.drag.dragging === true) {
preScaleDragPointer = this.canvas.DOMtoCanvas(this.drag.pointer);
@ -361,7 +356,7 @@ class InteractionHandler {
this.body.view.scale = scale;
this.body.view.translation = {x:tx, y:ty};
if (preScaleDragPointer != null) {
if (preScaleDragPointer != undefined) {
let postScaleDragPointer = this.canvas.canvasToDOM(preScaleDragPointer);
this.drag.pointer.x = postScaleDragPointer.x;
this.drag.pointer.y = postScaleDragPointer.y;
@ -475,17 +470,17 @@ class InteractionHandler {
// adding hover highlights
let obj = this.selectionHandler.getNodeAt(pointer);
if (obj == null) {
if (obj == undefined) {
obj = this.selectionHandler.getEdgeAt(pointer);
}
if (obj != null) {
if (obj != undefined) {
this.selectionHandler.hoverObject(obj);
}
// removing all node hover highlights except for the selected one.
for (let nodeId in this.hoverObj.nodes) {
if (this.hoverObj.nodes.hasOwnProperty(nodeId)) {
if (obj instanceof Node && obj.id != nodeId || obj instanceof Edge || obj == null) {
if (obj instanceof Node && obj.id != nodeId || obj instanceof Edge || obj == undefined) {
this.selectionHandler.blurObject(this.hoverObj.nodes[nodeId]);
delete this.hoverObj.nodes[nodeId];
}
@ -620,7 +615,7 @@ class InteractionHandler {
}
}
else {
if (this.selectionHandler.getNodeAt(pointer) === null) {
if (this.selectionHandler.getNodeAt(pointer) === undefined) {
if (this.body.edges[this.popup.popupTargetId] !== undefined) {
stillOnObj = this.body.edges[this.popup.popupTargetId].isOverlappingWith(pointerObj);
}

+ 18
- 3
lib/network/modules/LayoutEngine.js View File

@ -8,8 +8,11 @@ class LayoutEngine {
constructor(body) {
this.body = body;
this.initialRandomSeed = Math.round(Math.random() * 1000000);
this.randomSeed = this.initialRandomSeed;
this.options = {};
this.defaultOptions = {
randomSeed: undefined,
hierarchical: {
enabled:false,
levelSeparation: 150,
@ -29,6 +32,10 @@ class LayoutEngine {
setOptions(options, allOptions) {
if (options !== undefined) {
util.mergeOptions(this.options, options, 'hierarchical');
if (options.randomSeed !== undefined) {
this.randomSeed = options.randomSeed;
}
if (this.options.hierarchical.enabled === true) {
// make sure the level seperation is the right way up
if (this.options.hierarchical.direction == "RL" || this.options.hierarchical.direction == "DU") {
@ -82,13 +89,19 @@ class LayoutEngine {
return allOptions;
}
seededRandom() {
var x = Math.sin(this.randomSeed++) * 10000;
return x - Math.floor(x);
}
positionInitially(nodesArray) {
if (this.options.hierarchical.enabled !== true) {
for (let i = 0; i < nodesArray.length; i++) {
let node = nodesArray[i];
if ((!node.isFixed()) && (node.x === undefined || node.y === undefined)) {
let radius = 10 * 0.1 * nodesArray.length + 10;
let angle = 2 * Math.PI * Math.random();
let angle = 2 * Math.PI * this.seededRandom();
if (node.options.fixed.x == false) {
node.x = radius * Math.cos(angle);
}
@ -100,6 +113,10 @@ class LayoutEngine {
}
}
getSeed() {
return this.initialRandomSeed;
}
/**
* This is the main function to layout the nodes in a hierarchical way.
* It checks if the node details are supplied correctly
@ -109,7 +126,6 @@ class LayoutEngine {
setupHierarchicalLayout() {
if (this.options.hierarchical.enabled == true && this.body.nodeIndices.length > 0) {
// get the size of the largest hubs and check if the user has defined a level for a node.
let hubsize = 0;
let node, nodeId;
let definedLevel = false;
let undefinedLevel = false;
@ -147,7 +163,6 @@ class LayoutEngine {
this._determineLevelsDirected();
}
}
console.log(this.hierarchicalLevels)
// check the distribution of the nodes per level.
let distribution = this._getDistribution();

+ 985
- 0
lib/network/modules/ManipulationSystem.js View File

@ -0,0 +1,985 @@
let util = require('../../util');
let Hammer = require('../../module/hammer');
let hammerUtil = require('../../hammerUtil');
let locales = require('../locales');
/**
* clears the toolbar div element of children
*
* @private
*/
class ManipulationSystem {
constructor(body, canvas, selectionHandler) {
this.body = body;
this.canvas = canvas;
this.selectionHandler = selectionHandler;
this.editMode = false;
this.manipulationDiv = undefined;
this.editModeDiv = undefined;
this.closeDiv = undefined;
this.boundFunction = undefined;
this.manipulationHammers = [];
this.cachedFunctions = {};
this.touchTime = 0;
this.temporaryIds = {nodes: [], edges:[]};
this.guiEnabled = false;
this.selectedControlNode = undefined;
this.options = {};
this.defaultOptions = {
enabled: false,
initiallyVisible: false,
locale: 'en',
locales: locales,
functionality:{
addNode: true,
addEdge: true,
editNode: true,
editEdge: true,
deleteNode: true,
deleteEdge: true
},
handlerFunctions: {
addNode: undefined,
addEdge: undefined,
editNode: undefined,
editEdge: undefined,
deleteNode: undefined,
deleteEdge: undefined
}
}
util.extend(this.options, this.defaultOptions);
}
setOptions(options) {
if (options !== undefined) {
if (typeof options == 'boolean') {
this.options.enabled = options;
}
else {
this.options.enabled = true;
for (let prop in options) {
if (options.hasOwnProperty(prop)) {
this.options[prop] = options[prop];
}
}
}
if (this.options.initiallyVisible === true) {
this.editMode = true;
}
this.init();
}
}
init() {
if (this.options.enabled === true) {
// Enable the GUI
this.guiEnabled = true;
// remove override
this.selectionHandler.forceSelectEdges = true;
this.createWrappers();
if (this.editMode === false) {
this.createEditButton();
}
else {
this.createManipulatorBar();
}
}
else {
this.removeManipulationDOM();
// disable the gui
this.guiEnabled = false;
}
}
createWrappers() {
// 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.canvas.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.canvas.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.canvas.frame.appendChild(this.closeDiv);
}
}
/**
* Create the edit button
*/
createEditButton() {
// restore everything to it's original state (if applicable)
this._clean();
// reset the manipulationDOM
this.manipulationDOM = {};
// empty the editModeDiv
util.recursiveDOMDelete(this.editModeDiv);
// create the contents for the editMode button
let locale = this.options.locales[this.options.locale];
let button = this.createButton('editMode', 'network-manipulationUI edit editmode', locale['edit']);
this.editModeDiv.appendChild(button);
// bind a hammer listener to the button, calling the function toggleEditMode.
this.bindHammerToDiv(button, 'toggleEditMode');
}
removeManipulationDOM() {
// removes all the bindings and overloads
this._clean();
// empty the manipulation divs
util.recursiveDOMDelete(this.manipulationDiv);
util.recursiveDOMDelete(this.editModeDiv);
util.recursiveDOMDelete(this.closeDiv);
// remove the manipulation divs
this.canvas.frame.removeChild(this.manipulationDiv);
this.canvas.frame.removeChild(this.editModeDiv);
this.canvas.frame.removeChild(this.closeDiv);
// set the references to undefined
this.manipulationDiv = undefined;
this.editModeDiv = undefined;
this.closeDiv = undefined;
// remove override
this.selectionHandler.forceSelectEdges = false;
}
//clearManipulatorBar() {
// util._recursiveDOMDelete(this.manipulationDiv);
// this.manipulationDOM = {};
// this._cleanManipulatorHammers();
// this._manipulationReleaseOverload();
//}
_cleanManipulatorHammers() {
// _clean hammer bindings
if (this.manipulationHammers.length != 0) {
for (let i = 0; i < this.manipulationHammers.length; i++) {
this.manipulationHammers[i].destroy();
}
this.manipulationHammers = [];
}
}
/**
* Manipulation UI temporarily overloads certain functions to extend or replace them. To be able to restore
* these functions to their original functionality, we saved them in this.cachedFunctions.
* This function restores these functions to their original function.
*
* @private
*/
_restoreOverloadedFunctions() {
for (let functionName in this.cachedFunctions) {
if (this.cachedFunctions.hasOwnProperty(functionName)) {
this.body.eventListeners[functionName] = this.cachedFunctions[functionName];
delete this.cachedFunctions[functionName];
}
}
this.cachedFunctions = {};
}
/**
* Enable or disable edit-mode.
*
* @private
*/
toggleEditMode() {
this.editMode = !this.editMode;
let toolbar = this.manipulationDiv;
let closeDiv = this.closeDiv;
let editModeDiv = this.editModeDiv;
if (this.editMode === true) {
toolbar.style.display = "block";
closeDiv.style.display = "block";
editModeDiv.style.display = "none";
this.bindHammerToDiv(closeDiv, 'toggleEditMode');
this.createManipulatorBar();
}
else {
toolbar.style.display = "none";
closeDiv.style.display = "none";
editModeDiv.style.display = "block";
this.createEditButton();
}
}
_clean() {
// _clean the divs
if (this.guiEnabled === true) {
util.recursiveDOMDelete(this.editModeDiv);
util.recursiveDOMDelete(this.manipulationDiv);
// removes all the bindings and overloads
this._cleanManipulatorHammers();
}
// remove temporary nodes and edges
this._cleanupTemporaryNodesAndEdges();
// restore overloaded UI functions
this._restoreOverloadedFunctions();
// remove the boundFunction
if (this.boundFunction !== undefined) {
this.body.emitter.off(this.boundFunction.event, this.boundFunction.fn);
}
this.boundFunction = undefined;
}
createSeperator(index = 1) {
this.manipulationDOM['seperatorLineDiv' + index] = document.createElement('div');
this.manipulationDOM['seperatorLineDiv' + index].className = 'network-seperatorLine';
this.manipulationDiv.appendChild(this.manipulationDOM['seperatorLineDiv' + index]);
}
createAddNodeButton(locale) {
let button = this.createButton('addNode', 'network-manipulationUI add', locale['addNode']);
this.manipulationDiv.appendChild(button);
this.bindHammerToDiv(button, 'addNodeMode');
}
createAddEdgeButton(locale) {
let button = this.createButton('addEdge', 'network-manipulationUI connect', locale['addEdge']);
this.manipulationDiv.appendChild(button);
this.bindHammerToDiv(button, 'addEdgeMode');
}
createEditNodeButton(locale) {
let button = this.createButton('editNode', 'network-manipulationUI edit', locale['editNode']);
this.manipulationDiv.appendChild(button);
this.bindHammerToDiv(button, '_editNode');
}
createEditEdgeButton(locale) {
let button = this.createButton('editEdge', 'network-manipulationUI edit', locale['editEdge']);
this.manipulationDiv.appendChild(button);
this.bindHammerToDiv(button, 'editEdgeMode');
}
createDeleteButton(locale) {
let button = this.createButton('delete', 'network-manipulationUI delete', locale['del']);
this.manipulationDiv.appendChild(button);
this.bindHammerToDiv(button, 'deleteSelected');
}
createBackButton(locale) {
let button = this.createButton('back', 'network-manipulationUI back', locale['back']);
this.manipulationDiv.appendChild(button);
this.bindHammerToDiv(button, 'createManipulatorBar');
}
createDescription(label) {
this.manipulationDiv.appendChild(
this.createButton('description', 'network-manipulationUI none', label)
);
}
createButton(id, className, label, labelClassName = 'network-manipulationLabel') {
this.manipulationDOM[id+"Div"] = document.createElement('div');
this.manipulationDOM[id+"Div"].className = className;
this.manipulationDOM[id+"Label"] = document.createElement('div');
this.manipulationDOM[id+"Label"].className = labelClassName;
this.manipulationDOM[id+"Label"].innerHTML = label;
this.manipulationDOM[id+"Div"].appendChild(this.manipulationDOM[id+'Label']);
return this.manipulationDOM[id+"Div"];
}
temporaryBind(fn, event) {
this.boundFunction = {fn:fn.bind(this), event};
this.body.emitter.on(event, this.boundFunction.fn);
}
/**
* main function, creates the main toolbar. Removes functions bound to the select event. Binds all the buttons of the toolbar.
*
* @private
*/
createManipulatorBar() {
this._clean();
// resume calculation
this.body.emitter.emit("restorePhysics");
// reset global letiables
this.manipulationDOM = {};
let selectedNodeCount = this.selectionHandler._getSelectedNodeCount();
let selectedEdgeCount = this.selectionHandler._getSelectedEdgeCount();
let selectedTotalCount = selectedNodeCount + selectedEdgeCount;
let locale = this.options.locales[this.options.locale];
let needSeperator = false;
if (this.options.functionality.addNode === true) {
this.createAddNodeButton(locale);
needSeperator = true;
}
if (this.options.functionality.addEdge === true) {
if (needSeperator === true) {this.createSeperator(1);} else {needSeperator = true;}
this.createAddEdgeButton(locale);
}
if (selectedNodeCount === 1 && typeof this.options.handlerFunctions.editNode === 'function' && this.options.functionality.editNode === true) {
if (needSeperator === true) {this.createSeperator(2);} else {needSeperator = true;}
this.createEditNodeButton(locale);
}
else if (selectedEdgeCount === 1 && selectedNodeCount === 0 && this.options.functionality.editEdge === true) {
if (needSeperator === true) {this.createSeperator(3);} else {needSeperator = true;}
this.createEditEdgeButton(locale);
}
// remove buttons
if (selectedTotalCount !== 0) {
if (selectedNodeCount === 1 && this.options.functionality.deleteNode === true) {
if (needSeperator === true) {this.createSeperator(4);}
this.createDeleteButton(locale);
}
else if (selectedNodeCount === 0 && this.options.functionality.deleteEdge === true) {
if (needSeperator === true) {this.createSeperator(4);}
this.createDeleteButton(locale);
}
}
// bind the close button
this.bindHammerToDiv(this.closeDiv, 'toggleEditMode');
// refresh this bar based on what has been selected
this.temporaryBind(this.createManipulatorBar,'select');
}
/**
* Bind an hammer instance to a DOM element. TODO: remove the double check.
* @param domElement
* @param funct
*/
bindHammerToDiv(domElement, funct) {
let hammer = new Hammer(domElement, {});
hammerUtil.onTouch(hammer, this[funct].bind(this));
this.manipulationHammers.push(hammer);
}
/**
* Create the toolbar for adding Nodes
*
* @private
*/
addNodeMode() {
// clear the toolbar
this._clean();
if (this.guiEnabled === true) {
let locale = this.options.locales[this.options.locale];
this.manipulationDOM = {};
this.createBackButton(locale);
this.createSeperator();
this.createDescription(locale['addDescription'])
// bind the close button
this.bindHammerToDiv(this.closeDiv, 'toggleEditMode');
}
this.temporaryBind(this._addNode,'click');
}
/**
* create the toolbar to connect nodes
*
* @private
*/
addEdgeMode() {
// _clean the system
this._clean();
if (this.guiEnabled === true) {
let locale = this.options.locales[this.options.locale];
this.manipulationDOM = {};
this.createBackButton(locale);
this.createSeperator();
this.createDescription(locale['edgeDescription']);
// bind the close button
this.bindHammerToDiv(this.closeDiv, 'toggleEditMode');
}
// temporarily overload functions
this.cachedFunctions["onTouch"] = this.body.eventListeners.onTouch;
this.cachedFunctions["onDragEnd"] = this.body.eventListeners.onDragEnd;
this.cachedFunctions["onHold"] = this.body.eventListeners.onHold;
this.body.eventListeners.onTouch = this._handleConnect.bind(this);
this.body.eventListeners.onDragEnd = this._finishConnect.bind(this);
this.body.eventListeners.onHold = function () {};
}
/**
* create the toolbar to edit edges
*
* @private
*/
editEdgeMode() {
// clear the system
this._clean();
if (this.guiEnabled === true) {
let locale = this.options.locales[this.options.locale];
this.manipulationDOM = {};
this.createBackButton(locale);
this.createSeperator();
this.createDescription(locale['editEdgeDescription']);
// bind the close button
this.bindHammerToDiv(this.closeDiv, 'toggleEditMode');
}
this.edgeBeingEditedId = this.selectionHandler.getSelectedEdges()[0];
let edge = this.body.edges[this.edgeBeingEditedId];
// create control nodes
let controlNodeFrom = this.body.functions.createNode(this.getTargetNodeProperties(edge.from.x, edge.from.y));
let controlNodeTo = this.body.functions.createNode(this.getTargetNodeProperties(edge.to.x, edge.to.y));
this.temporaryIds.nodes.push(controlNodeFrom.id);
this.temporaryIds.nodes.push(controlNodeTo.id);
this.body.nodes[controlNodeFrom.id] = controlNodeFrom;
this.body.nodeIndices.push(controlNodeFrom.id);
this.body.nodes[controlNodeTo.id] = controlNodeTo;
this.body.nodeIndices.push(controlNodeTo.id);
// temporarily overload functions
this.cachedFunctions['onTouch'] = this.body.eventListeners.onTouch;
this.cachedFunctions['onTap'] = this.body.eventListeners.onTap;
this.cachedFunctions['onHold'] = this.body.eventListeners.onHold;
this.cachedFunctions['onDragStart'] = this.body.eventListeners.onDragStart;
this.cachedFunctions['onDrag'] = this.body.eventListeners.onDrag;
this.cachedFunctions['onDragEnd'] = this.body.eventListeners.onDragEnd;
this.cachedFunctions['onMouseOver'] = this.body.eventListeners.onMouseOver;
this.body.eventListeners.onTouch = this._controlNodeTouch.bind(this);
this.body.eventListeners.onTap = function() {};
this.body.eventListeners.onHold = function() {};
this.body.eventListeners.onDragStart= this._controlNodeDragStart.bind(this);
this.body.eventListeners.onDrag = this._controlNodeDrag.bind(this);
this.body.eventListeners.onDragEnd = this._controlNodeDragEnd.bind(this);
this.body.eventListeners.onMouseOver= function() {}
// create function to position control nodes correctly on movement
let positionControlNodes = (ctx) => {
let positions = edge.edgeType.findBorderPositions(ctx);
if (controlNodeFrom.selected === false) {
controlNodeFrom.x = positions.from.x;
controlNodeFrom.y = positions.from.y;
}
if (controlNodeTo.selected === false) {
controlNodeTo.x = positions.to.x;
controlNodeTo.y = positions.to.y;
}
}
this.temporaryBind(positionControlNodes, "beforeDrawing");
this.body.emitter.emit("_redraw");
}
_controlNodeTouch(event) {
this.lastTouch = this.body.functions.getPointer(event.center);
this.lastTouch.translation = util.extend({},this.body.view.translation); // copy the object
}
_controlNodeDragStart(event) {
let pointer = this.lastTouch;
let pointerObj = this.selectionHandler._pointerToPositionObject(pointer);
let from = this.body.nodes[this.temporaryIds.nodes[0]];
let to = this.body.nodes[this.temporaryIds.nodes[1]];
let edge = this.body.edges[this.edgeBeingEditedId];
this.selectedControlNode = undefined;
let fromSelect = from.isOverlappingWith(pointerObj);
let toSelect = to.isOverlappingWith(pointerObj);
if (fromSelect === true) {
this.selectedControlNode = from;
edge.edgeType.from = from;
}
else if (toSelect === true) {
this.selectedControlNode = to;
edge.edgeType.to = to;
}
this.body.emitter.emit("_redraw");
}
_controlNodeDrag(event) {
this.body.emitter.emit("disablePhysics");
let pointer = this.body.functions.getPointer(event.center);
let pos = this.canvas.DOMtoCanvas(pointer);
if (this.selectedControlNode !== undefined) {
this.selectedControlNode.x = pos.x;
this.selectedControlNode.y = pos.y;
}
else {
// if the drag was not started properly because the click started outside the network div, start it now.
let diffX = pointer.x - this.lastTouch.x;
let diffY = pointer.y - this.lastTouch.y;
this.body.view.translation = {x:this.lastTouch.translation.x + diffX, y:this.lastTouch.translation.y + diffY};
}
this.body.emitter.emit("_redraw");
}
_controlNodeDragEnd(event) {
let pointer = this.body.functions.getPointer(event.center);
let pointerObj = this.selectionHandler._pointerToPositionObject(pointer);
let edge = this.body.edges[this.edgeBeingEditedId];
let overlappingNodeIds = this.selectionHandler._getAllNodesOverlappingWith(pointerObj);
let node = undefined;
for (let i = overlappingNodeIds.length-1; i >= 0; i--) {
if (overlappingNodeIds[i] !== this.selectedControlNode.id) {
node = this.body.nodes[overlappingNodeIds[i]];
break;
}
}
// perform the connection
if (node !== undefined && this.selectedControlNode !== undefined) {
if (node.isCluster === true) {
alert(this.options.locales[this.options.locale]["createEdgeError"])
}
else {
let from = this.body.nodes[this.temporaryIds.nodes[0]];
if (this.selectedControlNode.id == from.id) {
this._editEdge(node.id, edge.to.id);
}
else {
this._editEdge(edge.from.id, node.id);
}
}
}
else {
edge.updateEdgeType();
this.body.emitter.emit("restorePhysics");
}
this.body.emitter.emit("_redraw");
}
/**
* the function bound to the selection event. It checks if you want to connect a cluster and changes the description
* to walk the user through the process.
*
* @private
*/
_selectControlNode(event) {
}
/**
*
* @param pointer
* @private
*/
_releaseControlNode(pointer) {
if (new Date().valueOf() - this.touchTime > 100) {
console.log("release")
// perform the connection
let node = this.selectionHandler.getNodeAt(pointer);
if (node !== undefined) {
if (node.isCluster === true) {
alert(this.options.locales[this.options.locale]["createEdgeError"])
}
else {
let edge = this.body.edges[this.edgeBeingEditedId];
let targetNodeId = undefined;
if (edge.to.selected === true) {
targetNodeId = edge.toId;
}
else if (edge.from.selected === true) {
targetNodeId = edge.fromId;
}
//this.body.eventListeners.onDrag = this.cachedFunctions["onDrag"];
//this.body.eventListeners.onRelease = this.cachedFunctions["onRelease"];
//delete this.cachedFunctions["onRelease"];
//delete this.cachedFunctions["onDrag"];
////
//
//
//
//
//
//
//if (this.body.nodes[connectFromId] !== undefined && this.body.nodes[node.id] !== undefined) {
// this._createEdge(connectFromId, node.id);
//}
}
}
this.body.emitter.emit("_redraw");
//this.body.emitter.emit("_redraw");
//let newNode = this.getNodeAt(pointer);
//if (newNode !== undefined) {
// if (this.edgeBeingEditedId.controlNodes.from.selected == true) {
// this.edgeBeingEditedId._restoreControlNodes();
// this._editEdge(newNode.id, this.edgeBeingEditedId.to.id);
// this.edgeBeingEditedId.controlNodes.from.unselect();
// }
// if (this.edgeBeingEditedId.controlNodes.to.selected == true) {
// this.edgeBeingEditedId._restoreControlNodes();
// this._editEdge(this.edgeBeingEditedId.from.id, newNode.id);
// this.edgeBeingEditedId.controlNodes.to.unselect();
// }
//}
//else {
// this.edgeBeingEditedId._restoreControlNodes();
//}
this.touchTime = new Date().valueOf();
}
}
/**
* the function bound to the selection event. It checks if you want to connect a cluster and changes the description
* to walk the user through the process.
*
* @private
*/
_handleConnect(event) {
// check to avoid double fireing of this function.
if (new Date().valueOf() - this.touchTime > 100) {
let pointer = this.body.functions.getPointer(event.center);
let node = this.selectionHandler.getNodeAt(pointer);
if (node !== undefined) {
if (node.isCluster === true) {
alert(this.options.locales[this.options.locale]['createEdgeError'])
}
else {
// create a node the temporary line can look at
let targetNode = this.body.functions.createNode(this.getTargetNodeProperties(node.x,node.y));
let targetNodeId = targetNode.id;
this.body.nodes[targetNode.id] = targetNode;
this.body.nodeIndices.push(targetNode.id);
// create a temporary edge
let connectionEdge = this.body.functions.createEdge({
id: "connectionEdge" + util.randomUUID(),
from: node.id,
to: targetNode.id,
physics:false,
smooth: {
enabled: true,
dynamic: false,
type: "continuous",
roundness: 0.5
}
});
this.body.edges[connectionEdge.id] = connectionEdge;
this.body.edgeIndices.push(connectionEdge.id);
this.temporaryIds.nodes.push(targetNode.id);
this.temporaryIds.edges.push(connectionEdge.id);
this.cachedFunctions["onDrag"] = this.body.eventListeners.onDrag;
this.body.eventListeners.onDrag = (event) => {
let pointer = this.body.functions.getPointer(event.center);
let targetNode = this.body.nodes[targetNodeId];
targetNode.x = this.canvas._XconvertDOMtoCanvas(pointer.x);
targetNode.y = this.canvas._YconvertDOMtoCanvas(pointer.y);
this.body.emitter.emit("_redraw");
}
}
}
this.touchTime = new Date().valueOf();
// do the original touch events
this.cachedFunctions["onTouch"](event);
}
}
_finishConnect(event) {
let pointer = this.body.functions.getPointer(event.center);
let pointerObj = this.selectionHandler._pointerToPositionObject(pointer);
// remember the edge id
let connectFromId = undefined;
if (this.temporaryIds.edges[0] !== undefined) {
connectFromId = this.body.edges[this.temporaryIds.edges[0]].fromId;
}
//restore the drag function
if (this.cachedFunctions["onDrag"] !== undefined) {
this.body.eventListeners.onDrag = this.cachedFunctions["onDrag"];
delete this.cachedFunctions["onDrag"];
}
// get the overlapping node but NOT the temporary node;
let overlappingNodeIds = this.selectionHandler._getAllNodesOverlappingWith(pointerObj);
let node = undefined;
for (let i = overlappingNodeIds.length-1; i >= 0; i--) {
if (this.temporaryIds.nodes.indexOf(overlappingNodeIds[i]) !== -1) {
node = this.body.nodes[overlappingNodeIds[i]];
break;
}
}
// clean temporary nodes and edges.
this._cleanupTemporaryNodesAndEdges();
// perform the connection
if (node !== undefined) {
if (node.isCluster === true) {
alert(this.options.locales[this.options.locale]["createEdgeError"])
}
else {
if (this.body.nodes[connectFromId] !== undefined && this.body.nodes[node.id] !== undefined) {
this._createEdge(connectFromId, node.id);
}
}
}
this.body.emitter.emit("_redraw");
}
_cleanupTemporaryNodesAndEdges() {
// _clean temporary edges
for (let i = 0; i < this.temporaryIds.edges.length; i++) {
this.body.edges[this.temporaryIds.edges[i]].disconnect();
delete this.body.edges[this.temporaryIds.edges[i]];
let indexTempEdge = this.body.edgeIndices.indexOf(this.temporaryIds.edges[i]);
if (indexTempEdge !== -1) {this.body.edgeIndices.splice(indexTempEdge,1);}
}
// _clean temporary nodes
for (let i = 0; i < this.temporaryIds.nodes.length; i++) {
delete this.body.nodes[this.temporaryIds.nodes[i]];
let indexTempNode = this.body.nodeIndices.indexOf(this.temporaryIds.nodes[i]);
if (indexTempNode !== -1) {this.body.nodeIndices.splice(indexTempNode,1);}
}
this.temporaryIds = {nodes: [], edges: []};
}
/**
* Adds a node on the specified location
*/
_addNode(clickData) {
let defaultData = {
id: util.randomUUID(),
x: clickData.pointer.canvas.x,
y: clickData.pointer.canvas.y,
label: "new"
};
if (typeof this.options.handlerFunctions.addNode === 'function') {
if (this.options.handlerFunctions.addNode.length == 2) {
this.options.handlerFunctions.addNode(defaultData, (finalizedData) => {
this.body.data.nodes.add(finalizedData);
this.createManipulatorBar();
});
}
else {
throw new Error('The function for add does not support two arguments (data,callback)');
this.createManipulatorBar();
}
}
else {
this.body.data.nodes.add(defaultData);
this.createManipulatorBar();
}
}
/**
* connect two nodes with a new edge.
*
* @private
*/
_createEdge(sourceNodeId, targetNodeId) {
let defaultData = {from: sourceNodeId, to: targetNodeId};
if (this.options.handlerFunctions.addEdge) {
if (this.options.handlerFunctions.addEdge.length == 2) {
this.options.handlerFunctions.addEdge(defaultData, (finalizedData) => {
this.body.data.edges.add(finalizedData);
this.selectionHandler.unselectAll();
this.createManipulatorBar();
});
}
else {
throw new Error('The function for connect does not support two arguments (data,callback)');
}
}
else {
this.body.data.edges.add(defaultData);
this.selectionHandler.unselectAll();
this.createManipulatorBar();
}
}
/**
* connect two nodes with a new edge.
*
* @private
*/
_editEdge(sourceNodeId, targetNodeId) {
let defaultData = {id: this.edgeBeingEditedId, from: sourceNodeId, to: targetNodeId};
console.log(defaultData)
if (this.options.handlerFunctions.editEdge) {
if (this.options.handlerFunctions.editEdge.length == 2) {
this.options.handlerFunctions.editEdge(defaultData, (finalizedData) => {
this.body.data.edges.update(finalizedData);
this.selectionHandler.unselectAll();
this.createManipulatorBar();
});
}
else {
throw new Error('The function for edit does not support two arguments (data, callback)');
}
}
else {
this.body.data.edges.update(defaultData);
this.selectionHandler.unselectAll();
this.createManipulatorBar();
}
}
/**
* Create the toolbar to edit the selected node. The label and the color can be changed. Other colors are derived from the chosen color.
*
* @private
*/
_editNode() {
if (this.options.handlerFunctions.edit && this.editMode == true) {
let node = this._getSelectedNode();
let data = {
id: node.id,
label: node.label,
group: node.options.group,
shape: node.options.shape,
color: {
background: node.options.color.background,
border: node.options.color.border,
highlight: {
background: node.options.color.highlight.background,
border: node.options.color.highlight.border
}
}
};
if (this.options.handlerFunctions.edit.length == 2) {
let me = this;
this.options.handlerFunctions.edit(data, function (finalizedData) {
me.body.data.nodes.update(finalizedData);
me.createManipulatorBar();
me.moving = true;
me.start();
});
}
else {
throw new Error('The function for edit does not support two arguments (data, callback)');
}
}
else {
throw new Error('No edit function has been bound to this button');
}
}
/**
* delete everything in the selection
*
* @private
*/
deleteSelected() {
let selectedNodes = this.selectionHandler.getSelectedNodes();
let selectedEdges = this.selectionHandler.getSelectedEdges();
let deleteFunction = undefined;
if (selectedNodes.length > 0) {
for (let i = 0; i < selectedNodes.length; i++) {
if (this.body.nodes[selectedNodes[i]].isCluster === true) {
alert("You cannot delete a cluster.");
return;
}
}
if (typeof this.options.handlerFunctions.deleteNode === 'function') {
deleteFunction = this.options.handlerFunctions.deleteNode;
}
}
else if (selectedEdges.length > 0) {
if (typeof this.options.handlerFunctions.deleteEdge === 'function') {
deleteFunction = this.options.handlerFunctions.deleteEdge;
}
}
if (typeof deleteFunction === 'function') {
let data = {nodes: selectedNodes, edges: selectedEdges};
if (deleteFunction.length == 2) {
deleteFunction(data, (finalizedData) => {
this.body.data.edges.remove(finalizedData.edges);
this.body.data.nodes.remove(finalizedData.nodes);
this.body.emitter.emit("startSimulation");
});
}
else {
throw new Error('The function for delete does not support two arguments (data, callback)')
}
}
else {
this.body.data.edges.remove(selectedEdges);
this.body.data.nodes.remove(selectedNodes);
this.body.emitter.emit("startSimulation");
}
}
getTargetNodeProperties(x,y) {
return {
id: 'targetNode' + util.randomUUID(),
hidden: false,
physics: false,
shape:'dot',
size:6,
x:x,
y:y,
color: {background: '#ff0000', border: '#3c3c3c', highlight: {background: '#07f968'}},
borderWidth: 2,
borderWidthSelected: 2
}
}
}
export default ManipulationSystem;

+ 10
- 1
lib/network/modules/PhysicsEngine.js View File

@ -69,13 +69,21 @@ class PhysicsEngine {
this.body.emitter.on("initPhysics", () => {this.initPhysics();});
this.body.emitter.on("resetPhysics", () => {this.stopSimulation(); this.ready = false;});
this.body.emitter.on("disablePhysics", () => {this.physicsEnabled = false; this.stopSimulation();});
this.body.emitter.on("restorePhysics", () => {
this.setOptions(this.options);
if (this.ready === true) {
this.stabilized = false;
this.runSimulation();
}
});
this.body.emitter.on("startSimulation", () => {
if (this.ready === true) {
this.stabilized = false;
this.runSimulation();
}
})
this.body.emitter.on("stopSimulation", () => {console.log(4);this.stopSimulation();});
this.body.emitter.on("stopSimulation", () => {this.stopSimulation();});
}
setOptions(options) {
@ -84,6 +92,7 @@ class PhysicsEngine {
this.stopSimulation();
}
else {
this.physicsEnabled = true;
if (options !== undefined) {
util.selectiveNotDeepExtend(['stabilization'],this.options, options);
util.mergeOptions(this.options, options, 'stabilization')

+ 7
- 6
lib/network/modules/SelectionHandler.js View File

@ -10,6 +10,7 @@ class SelectionHandler {
this.body = body;
this.canvas = canvas;
this.selectionObj = {nodes: [], edges: []};
this.forceSelectEdges = false;
this.options = {};
this.defaultOptions = {
@ -83,7 +84,7 @@ class SelectionHandler {
selectObject(obj) {
if (obj !== undefined) {
if (obj instanceof Node) {
if (this.options.selectConnectedEdges === true) {
if (this.options.selectConnectedEdges === true || this.forceSelectEdges === true) {
this._selectConnectedEdges(obj);
}
}
@ -132,10 +133,10 @@ class SelectionHandler {
_pointerToPositionObject(pointer) {
var canvasPos = this.canvas.DOMtoCanvas(pointer);
return {
left: canvasPos.x,
top: canvasPos.y,
right: canvasPos.x,
bottom: canvasPos.y
left: canvasPos.x - 1,
top: canvasPos.y + 1,
right: canvasPos.x + 1,
bottom: canvasPos.y - 1
};
}
@ -515,7 +516,7 @@ class SelectionHandler {
}
}
}
return idArray
return idArray;
}
/**

+ 0
- 236
lib/network/modules/components/Edge.js View File

@ -131,7 +131,6 @@ class Edge {
util.mergeOptions(this.options.color, options.color, 'inherit');
}
// A node is connected when it has a from and to node that both exist in the network.body.nodes.
this.connect();
@ -409,241 +408,6 @@ class Edge {
unselect() {
this.selected = false;
}
//*************************************************************************************************//
//*************************************************************************************************//
//*************************************************************************************************//
//*************************************************************************************************//
//*********************** MOVE THESE FUNCTIONS TO THE MANIPULATION SYSTEM ************************//
//*************************************************************************************************//
//*************************************************************************************************//
//*************************************************************************************************//
//*************************************************************************************************//
/**
* This function draws the control nodes for the manipulator.
* In order to enable this, only set the this.controlNodesEnabled to true.
* @param ctx
*/
_drawControlNodes(ctx) {
if (this.controlNodesEnabled == true) {
if (this.controlNodes.from === undefined && this.controlNodes.to === undefined) {
var nodeIdFrom = "edgeIdFrom:".concat(this.id);
var nodeIdTo = "edgeIdTo:".concat(this.id);
var nodeFromOptions = {
id: nodeIdFrom,
shape: 'dot',
color: {background: '#ff0000', border: '#3c3c3c', highlight: {background: '#07f968'}},
radius: 7,
borderWidth: 2,
borderWidthSelected: 2,
hidden: false,
physics: false
};
var nodeToOptions = util.deepExtend({},nodeFromOptions);
nodeToOptions.id = nodeIdTo;
this.controlNodes.from = this.body.functions.createNode(nodeFromOptions);
this.controlNodes.to = this.body.functions.createNode(nodeToOptions);
}
this.controlNodes.positions = {};
if (this.controlNodes.from.selected == false) {
this.controlNodes.positions.from = this.getControlNodeFromPosition(ctx);
this.controlNodes.from.x = this.controlNodes.positions.from.x;
this.controlNodes.from.y = this.controlNodes.positions.from.y;
}
if (this.controlNodes.to.selected == false) {
this.controlNodes.positions.to = this.getControlNodeToPosition(ctx);
this.controlNodes.to.x = this.controlNodes.positions.to.x;
this.controlNodes.to.y = this.controlNodes.positions.to.y;
}
this.controlNodes.from.draw(ctx);
this.controlNodes.to.draw(ctx);
}
else {
this.controlNodes = {from: undefined, to: undefined, positions: {}};
}
}
/**
* Enable control nodes.
* @private
*/
_enableControlNodes() {
this.fromBackup = this.from;
this.toBackup = this.to;
this.controlNodesEnabled = true;
}
/**
* disable control nodes and remove from dynamicEdges from old node
* @private
*/
_disableControlNodes() {
this.fromId = this.from.id;
this.toId = this.to.id;
if (this.fromId != this.fromBackup.id) { // from was changed, remove edge from old 'from' node dynamic edges
this.fromBackup.detachEdge(this);
}
else if (this.toId != this.toBackup.id) { // to was changed, remove edge from old 'to' node dynamic edges
this.toBackup.detachEdge(this);
}
this.fromBackup = undefined;
this.toBackup = undefined;
this.controlNodesEnabled = false;
}
/**
* This checks if one of the control nodes is selected and if so, returns the control node object. Else it returns undefined.
* @param x
* @param y
* @returns {undefined}
* @private
*/
_getSelectedControlNode(x, y) {
var positions = this.controlNodes.positions;
var fromDistance = Math.sqrt(Math.pow(x - positions.from.x, 2) + Math.pow(y - positions.from.y, 2));
var toDistance = Math.sqrt(Math.pow(x - positions.to.x, 2) + Math.pow(y - positions.to.y, 2));
if (fromDistance < 15) {
this.connectedNode = this.from;
this.from = this.controlNodes.from;
return this.controlNodes.from;
}
else if (toDistance < 15) {
this.connectedNode = this.to;
this.to = this.controlNodes.to;
return this.controlNodes.to;
}
else {
return undefined;
}
}
/**
* this resets the control nodes to their original position.
* @private
*/
_restoreControlNodes() {
if (this.controlNodes.from.selected == true) {
this.from = this.connectedNode;
this.connectedNode = undefined;
this.controlNodes.from.unselect();
}
else if (this.controlNodes.to.selected == true) {
this.to = this.connectedNode;
this.connectedNode = undefined;
this.controlNodes.to.unselect();
}
}
/**
* this calculates the position of the control nodes on the edges of the parent nodes.
*
* @param ctx
* @returns {x: *, y: *}
*/
getControlNodeFromPosition(ctx) {
// draw arrow head
var controlnodeFromPos;
if (this.options.smooth.enabled == true) {
controlnodeFromPos = this._findBorderPositionBezier(true, ctx);
}
else {
var angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
var dx = (this.to.x - this.from.x);
var dy = (this.to.y - this.from.y);
var edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
var fromBorderDist = this.from.distanceToBorder(ctx, angle + Math.PI);
var fromBorderPoint = (edgeSegmentLength - fromBorderDist) / edgeSegmentLength;
controlnodeFromPos = {};
controlnodeFromPos.x = (fromBorderPoint) * this.from.x + (1 - fromBorderPoint) * this.to.x;
controlnodeFromPos.y = (fromBorderPoint) * this.from.y + (1 - fromBorderPoint) * this.to.y;
}
return controlnodeFromPos;
}
/**
* this calculates the position of the control nodes on the edges of the parent nodes.
*
* @param ctx
* @returns {{from: {x: number, y: number}, to: {x: *, y: *}}}
*/
getControlNodeToPosition(ctx) {
// draw arrow head
var controlnodeToPos;
if (this.options.smooth.enabled == true) {
controlnodeToPos = this._findBorderPositionBezier(false, ctx);
}
else {
var angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
var dx = (this.to.x - this.from.x);
var dy = (this.to.y - this.from.y);
var edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
var toBorderDist = this.to.distanceToBorder(ctx, angle);
var toBorderPoint = (edgeSegmentLength - toBorderDist) / edgeSegmentLength;
controlnodeToPos = {};
controlnodeToPos.x = (1 - toBorderPoint) * this.from.x + toBorderPoint * this.to.x;
controlnodeToPos.y = (1 - toBorderPoint) * this.from.y + toBorderPoint * this.to.y;
}
return controlnodeToPos;
}
}
export default Edge;

+ 1
- 1
lib/network/modules/components/edges/bezierEdgeStatic.js View File

@ -214,7 +214,7 @@ class BezierEdgeStatic extends BezierEdgeBase {
return {x: xVia, y: yVia};
}
_findBorderPosition(nearNode, ctx, options) {
_findBorderPosition(nearNode, ctx, options = {}) {
return this._findBorderPositionBezier(nearNode, ctx, options.via);
}

+ 61
- 51
lib/network/modules/components/edges/util/EdgeBase.js View File

@ -40,18 +40,7 @@ class EdgeBase {
}
}
else {
let x, y;
let radius = this.options.selfReferenceSize;
let node = this.from;
node.resize(ctx);
if (node.shape.width > node.shape.height) {
x = node.x + node.shape.width * 0.5;
y = node.y - radius;
}
else {
x = node.x + radius;
y = node.y - node.shape.height * 0.5;
}
let [x,y,radius] = this._getCircleData();
this._circle(ctx, x, y, radius);
}
@ -119,17 +108,61 @@ class EdgeBase {
}
}
findBorderPositions(ctx) {
let from = {};
let to = {};
if (this.from != this.to) {
from = this._findBorderPosition(this.from, ctx);
to = this._findBorderPosition(this.to, ctx);
}
else {
let [x,y,radius] = this._getCircleData();
from = this._findBorderPositionCircle(this.from, ctx, {x, y, low:0.25, high:0.6, direction:-1});
to = this._findBorderPositionCircle(this.from, ctx, {x, y, low:0.6, high:0.8, direction:1});
}
return {from, to};
}
_getCircleData() {
let x, y;
let node = this.from;
let radius = this.options.selfReferenceSize;
// get circle coordinates
if (node.shape.width > node.shape.height) {
x = node.x + node.shape.width * 0.5;
y = node.y - radius;
}
else {
x = node.x + radius;
y = node.y - node.shape.height * 0.5;
}
return [x,y,radius];
}
/**
* Get a point on a circle
* @param {Number} x
* @param {Number} y
* @param {Number} radius
* @param {Number} percentage. Value between 0 (line start) and 1 (line end)
* @return {Object} point
* @private
*/
_pointOnCircle(x, y, radius, percentage) {
var angle = percentage * 2 * Math.PI;
return {
x: x + radius * Math.cos(angle),
y: y - radius * Math.sin(angle)
}
}
/**
* This function uses binary search to look for the point where the circle crosses the border of the node.
* @param x
* @param y
* @param radius
* @param node
* @param low
* @param high
* @param direction
* @param ctx
* @param options
* @returns {*}
* @private
*/
@ -145,9 +178,10 @@ class EdgeBase {
let radius = this.options.selfReferenceSize;
let pos, angle, distanceToBorder, distanceToPoint, difference;
let threshold = 0.05;
let middle = (low + high) * 0.5
while (low <= high && iteration < maxIterations) {
let middle = (low + high) * 0.5;
middle = (low + high) * 0.5;
pos = this._pointOnCircle(x, y, radius, middle);
angle = Math.atan2((node.y - pos.y), (node.x - pos.x));
@ -174,6 +208,7 @@ class EdgeBase {
}
}
iteration++;
}
pos.t = middle;
@ -290,19 +325,9 @@ class EdgeBase {
returnValue = this._getDistanceToEdge(x1, y1, x2, y2, x3, y3, via)
}
else {
var x, y, dx, dy;
var radius = this.options.selfReferenceSize;
var node = this.from;
if (node.width > node.height) {
x = node.x + 0.5 * node.width;
y = node.y - radius;
}
else {
x = node.x + radius;
y = node.y - 0.5 * node.height;
}
dx = x - x3;
dy = y - y3;
let [x,y,radius] = this._getCircleData();
let dx = x - x3;
let dy = y - y3;
returnValue = Math.abs(Math.sqrt(dx * dx + dy * dy) - radius);
}
@ -410,33 +435,18 @@ class EdgeBase {
else {
// draw circle
let angle, point;
let x, y;
let radius = this.options.selfReferenceSize;
if (!node1.width) {
node1.resize(ctx);
}
// get circle coordinates
if (node1.width > node1.height) {
x = node1.x + node1.width * 0.5;
y = node1.y - radius;
}
else {
x = node1.x + radius;
y = node1.y - node1.height * 0.5;
}
let [x,y,radius] = this._getCircleData();
if (position == 'from') {
point = this.findBorderPosition(x, y, radius, node1, 0.25, 0.6, -1, ctx);
point = this.findBorderPosition(this.from, ctx, {x, y, low:0.25, high:0.6, direction:-1});
angle = point.t * -2 * Math.PI + 1.5 * Math.PI + 0.1 * Math.PI;
}
else if (position == 'to') {
point = this.findBorderPosition(x, y, radius, node1, 0.6, 0.8, 1, ctx);
point = this.findBorderPosition(this.from, ctx, {x, y, low:0.6, high:1.0, direction:1});
angle = point.t * -2 * Math.PI + 1.5 * Math.PI - 1.1 * Math.PI;
}
else {
point = this.findBorderPosition(x, y, radius, 0.175);
point = this._pointOnCircle(x, y, radius, 0.175);
angle = 3.9269908169872414; // == 0.175 * -2 * Math.PI + 1.5 * Math.PI + 0.1 * Math.PI;
}

+ 2
- 2
lib/network/modules/components/nodes/util/ShapeBase.js View File

@ -30,7 +30,7 @@ class ShapeBase extends NodeBase {
ctx.lineWidth /= this.body.view.scale;
ctx.lineWidth = Math.min(this.width, ctx.lineWidth);
ctx.fillStyle = selected ? this.options.color.highlight.background : hover ? this.options.color.hover.background : this.options.color.background;
ctx[shape](x, y, this.options.size + sizeMultiplier * ctx.lineWidth);
ctx[shape](x, y, this.options.size);
ctx.fill();
ctx.stroke();
@ -40,7 +40,7 @@ class ShapeBase extends NodeBase {
this.boundingBox.bottom = y + this.options.size;
if (this.options.label!== undefined) {
let yLabel = y + 0.5 * this.height + 3 + sizeMultiplier * ctx.lineWidth; // the + 3 is to offset it a bit below the node.
let yLabel = y + 0.5 * this.height + 3; // the + 3 is to offset it a bit below the node.
this.labelModule.draw(ctx, x, yLabel, selected, 'hanging');
this.boundingBox.left = Math.min(this.boundingBox.left, this.labelModule.size.left);
this.boundingBox.right = Math.max(this.boundingBox.right, this.labelModule.size.left + this.labelModule.size.width);

+ 10
- 4
lib/network/modules/components/physics/SpringSolver.js View File

@ -73,10 +73,16 @@ class SpringSolver {
fx = dx * springForce;
fy = dy * springForce;
this.physicsBody.forces[node1.id].x += fx;
this.physicsBody.forces[node1.id].y += fy;
this.physicsBody.forces[node2.id].x -= fx;
this.physicsBody.forces[node2.id].y -= fy;
// handle the case where one node is not part of the physcis
if (this.physicsBody.forces[node1.id] !== undefined) {
this.physicsBody.forces[node1.id].x += fx;
this.physicsBody.forces[node1.id].y += fy;
}
if (this.physicsBody.forces[node2.id] !== undefined) {
this.physicsBody.forces[node2.id].x -= fx;
this.physicsBody.forces[node2.id].y -= fy;
}
}
}

Loading…
Cancel
Save