Browse Source

started creation of the edges handler and the nodes handler. currently broken.

flowchartTest
Alex de Mulder 10 years ago
parent
commit
333ec38c3c
20 changed files with 2281 additions and 2311 deletions
  1. +1236
    -825
      dist/vis.js
  2. +1
    -1
      examples/network/02_random_nodes.html
  3. +2
    -2
      index.js
  4. +2
    -11
      lib/network/Images.js
  5. +99
    -692
      lib/network/Network.js
  6. +0
    -707
      lib/network/mixins/SelectionMixin.js
  7. +2
    -1
      lib/network/modules/Canvas.js
  8. +1
    -1
      lib/network/modules/CanvasRenderer.js
  9. +24
    -32
      lib/network/modules/Clustering.js
  10. +241
    -0
      lib/network/modules/EdgesHandler.js
  11. +3
    -3
      lib/network/modules/InteractionHandler.js
  12. +421
    -0
      lib/network/modules/LayoutEngine.js
  13. +200
    -0
      lib/network/modules/NodesHandler.js
  14. +13
    -4
      lib/network/modules/PhysicsEngine.js
  15. +9
    -4
      lib/network/modules/SelectionHandler.js
  16. +2
    -7
      lib/network/modules/View.js
  17. +5
    -4
      lib/network/modules/components/NavigationHandler.js
  18. +7
    -8
      lib/network/modules/components/edges/EdgeMain.js
  19. +6
    -9
      lib/network/modules/components/nodes/NodeMain.js
  20. +7
    -0
      lib/util.js

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


+ 1
- 1
examples/network/02_random_nodes.html View File

@ -84,7 +84,7 @@
nodes: nodes,
edges: edges
};
var options = {stabilize:false};
var options = {physics:{stabilization:false}};
network = new vis.Network(container, data, options);
// add event listeners

+ 2
- 2
index.js View File

@ -54,10 +54,10 @@ exports.timeline = {
// Network
exports.Network = require('./lib/network/Network');
exports.network = {
Edge: require('./lib/network/Edge'),
//Edge: require('./lib/network/Edge'),
Groups: require('./lib/network/Groups'),
Images: require('./lib/network/Images'),
Node: require('./lib/network/Node'),
//Node: require('./lib/network/Node'),
Popup: require('./lib/network/Popup'),
dotparser: require('./lib/network/dotparser'),
gephiParser: require('./lib/network/gephiParser')

+ 2
- 11
lib/network/Images.js View File

@ -2,20 +2,11 @@
* @class Images
* This class loads images and keeps them stored.
*/
function Images() {
function Images(callback) {
this.images = {};
this.imageBroken = {};
this.callback = undefined;
}
/**
* Set an onload callback function. This will be called each time an image
* is loaded
* @param {function} callback
*/
Images.prototype.setOnloadCallback = function(callback) {
this.callback = callback;
};
}
/**
*

+ 99
- 692
lib/network/Network.js View File

@ -1,31 +1,31 @@
var Emitter = require('emitter-component');
var Hammer = require('../module/hammer');
var keycharm = require('keycharm');
var util = require('../util');
var hammerUtil = require('../hammerUtil');
var DataSet = require('../DataSet');
var DataView = require('../DataView');
var dotparser = require('./dotparser');
var gephiParser = require('./gephiParser');
var Groups = require('./Groups');
var Images = require('./Images');
var Node = require('./Node');
var Edge = require('./Edge');
var Node = require('./modules/components/nodes/NodeMain');
var Edge = require('./modules/components/edges/EdgeMain');
var Popup = require('./Popup');
var MixinLoader = require('./mixins/MixinLoader');
var Activator = require('../shared/Activator');
var locales = require('./locales');
// Load custom shapes into CanvasRenderingContext2D
require('./shapes');
import { PhysicsEngine } from './modules/PhysicsEngine'
import { ClusterEngine } from './modules/Clustering'
import { CanvasRenderer } from './modules/CanvasRenderer'
import { Canvas } from './modules/Canvas'
import { View } from './modules/View'
import { InteractionHandler } from './modules/InteractionHandler'
import { SelectionHandler } from "./modules/SelectionHandler"
import NodesHandler from './modules/NodesHandler';
import EdgesHandler from './modules/EdgesHandler';
import PhysicsEngine from './modules/PhysicsEngine';
import ClusterEngine from './modules/Clustering';
import CanvasRenderer from './modules/CanvasRenderer';
import Canvas from './modules/Canvas';
import View from './modules/View';
import InteractionHandler from './modules/InteractionHandler';
import SelectionHandler from "./modules/SelectionHandler";
import LayoutEngine from "./modules/LayoutEngine";
/**
* @constructor Network
@ -43,96 +43,8 @@ function Network (container, data, options) {
throw new SyntaxError('Constructor must be called with the new operator');
}
this._initializeMixinLoaders();
// render and calculation settings
this.initializing = true;
this.triggerFunctions = {add:null,edit:null,editEdge:null,connect:null,del:null};
var customScalingFunction = function (min,max,total,value) {
if (max == min) {
return 0.5;
}
else {
var scale = 1 / (max - min);
return Math.max(0,(value - min)*scale);
}
};
// set constant values
this.defaultOptions = {
nodes: {
customScalingFunction: customScalingFunction,
mass: 1,
radiusMin: 10,
radiusMax: 30,
radius: 10,
shape: 'ellipse',
image: undefined,
widthMin: 16, // px
widthMax: 64, // px
fontColor: 'black',
fontSize: 14, // px
fontFace: 'verdana',
fontFill: undefined,
fontStrokeWidth: 0, // px
fontStrokeColor: '#ffffff',
fontDrawThreshold: 3,
scaleFontWithValue: false,
fontSizeMin: 14,
fontSizeMax: 30,
fontSizeMaxVisible: 30,
value: 1,
level: -1,
color: {
border: '#2B7CE9',
background: '#97C2FC',
highlight: {
border: '#2B7CE9',
background: '#D2E5FF'
},
hover: {
border: '#2B7CE9',
background: '#D2E5FF'
}
},
group: undefined,
borderWidth: 1,
borderWidthSelected: undefined
},
edges: {
customScalingFunction: customScalingFunction,
widthMin: 1, //
widthMax: 15,//
width: 1,
widthSelectionMultiplier: 2,
hoverWidth: 1.5,
value:1,
style: 'line',
color: {
color:'#848484',
highlight:'#848484',
hover: '#848484'
},
opacity:1.0,
fontColor: '#343434',
fontSize: 14, // px
fontFace: 'arial',
fontFill: 'white',
fontStrokeWidth: 0, // px
fontStrokeColor: 'white',
labelAlignment:'horizontal',
arrowScaleFactor: 1,
dash: {
length: 10,
gap: 5,
altLength: undefined
},
inheritColor: "from", // to, from, false, true (== from)
useGradients: false // release in 4.0
},
this.remainingOptions = {
dataManipulation: {
enabled: false,
initiallyVisible: false
@ -144,43 +56,10 @@ function Network (container, data, options) {
direction: "UD", // UD, DU, LR, RL
layout: "hubsize" // hubsize, directed
},
interaction: {
dragNodes:true,
dragView: true,
zoomView: true,
hoverEnabled: false,
showNavigationIcons: false,
tooltip: {
delay: 300,
fontColor: 'black',
fontSize: 14, // px
fontFace: 'verdana',
color: {
border: '#666',
background: '#FFFFC6'
}
},
keyboard: {
enabled: false,
speed: {x: 10, y: 10, zoom: 0.02},
bindToWindow: true
}
},
selection: {
enabled: true,
selectConnectedEdges: true
},
smoothCurves: {
enabled: true,
dynamic: true,
type: "continuous",
roundness: 0.5
},
locale: 'en',
locales: locales,
useDefaultGroups: true
};
this.constants = util.extend({}, this.defaultOptions);
// containers for nodes and edges
this.body = {
@ -223,121 +102,67 @@ function Network (container, data, options) {
}
};
// modules
this.canvas = new Canvas(this.body);
this.selectionHandler = new SelectionHandler(this.body, this.canvas);
this.interactionHandler = new InteractionHandler(this.body, this.canvas, this.selectionHandler);
this.view = new View(this.body, this.canvas);
this.renderer = new CanvasRenderer(this.body, this.canvas);
this.clustering = new ClusterEngine(this.body);
this.physics = new PhysicsEngine(this.body);
// todo think of good comment for this set
var groups = new Groups(); // object with groups
var images = new Images(() => this.body.emitter.emit("_requestRedraw")); // object with images
// create the DOM elements
this.canvas.create();
// data handling modules
this.canvas = new Canvas(this.body); // DOM handler
this.selectionHandler = new SelectionHandler(this.body, this.canvas); // Selection handler
this.interactionHandler = new InteractionHandler(this.body, this.canvas, this.selectionHandler); // Interaction handler handles all the hammer bindings (that are bound by canvas), key
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); // TODO: layout engine for initial positioning and hierarchical positioning
this.clustering = new ClusterEngine(this.body); // clustering api
this.hoverObj = {nodes:{},edges:{}};
this.controlNodesActive = false;
this.navigationHammers = [];
this.manipulationHammers = [];
// Node variables
var me = this;
this.groups = new Groups(); // object with groups
this.images = new Images(); // object with images
this.images.setOnloadCallback(function (status) {
me._requestRedraw();
});
this.nodesHandler = new NodesHandler(this.body, images, groups, this.layoutEngine); // Handle adding, deleting and updating of nodes as well as global options
this.edgesHandler = new EdgesHandler(this.body, images, groups); // Handle adding, deleting and updating of edges as well as global options
// keyboard navigation variables
this.xIncrement = 0;
this.yIncrement = 0;
this.zoomIncrement = 0;
// loading all the mixins:
// load the force calculation functions, grouped under the physics system.
//this._loadPhysicsSystem();
// create a frame and canvas
// load the cluster system. (mandatory, even when not using the cluster system, there are function calls to it)
// load the selection system. (mandatory, required by Network)
this._loadSelectionSystem();
// load the selection system. (mandatory, required by Network)
//this._loadHierarchySystem();
// apply options
this.setOptions(options);
// position and scale variables and objects
this.pointerPosition = {"x": 0,"y": 0}; // coordinates of the bottom right of the canvas. they will be set during _redraw
this.scale = 1; // defining the global scale variable in the constructor
// this event will trigger a rebuilding of the cache everything. Used when nodes or edges have been added or removed.
this.body.emitter.on("_dataChanged", (params) => {
var t0 = new Date().valueOf();
// update shortcut lists
this._updateNodeIndexList();
this.physics._updateCalculationNodes();
// update values
this._updateValueRange(this.body.nodes);
this._updateValueRange(this.body.edges);
// update edges
this._reconnectEdges();
this.edgesHandler.createBezierNodes(params);
this._markAllEdgesAsDirty();
// start simulation (can be called safely, even if already running)
this.body.emitter.emit("startSimulation");
console.log("_dataChanged took:", new Date().valueOf() - t0);
})
// create event listeners used to subscribe on the DataSets of the nodes and edges
this.nodesListeners = {
'add': function (event, params) {
me._addNodes(params.items);
me.start();
},
'update': function (event, params) {
me._updateNodes(params.items, params.data);
me.start();
},
'remove': function (event, params) {
me._removeNodes(params.items);
me.start();
}
};
this.edgesListeners = {
'add': function (event, params) {
me._addEdges(params.items);
me.start();
},
'update': function (event, params) {
me._updateEdges(params.items);
me.start();
},
'remove': function (event, params) {
me._removeEdges(params.items);
me.start();
}
};
// this is called when options of EXISTING nodes or edges have changed.
this.body.emitter.on("_dataUpdated", () => {
var t0 = new Date().valueOf();
// update values
this._updateValueRange(this.body.nodes);
this._updateValueRange(this.body.edges);
// update edges
this._reconnectEdges();
this.edgesHandler.createBezierNodes(params);
this._markAllEdgesAsDirty();
// start simulation (can be called safely, even if already running)
this.body.emitter.emit("startSimulation");
console.log("_dataUpdated took:", new Date().valueOf() - t0);
});
// create the DOM elements
this.canvas.create();
// properties for the animation
this.moving = true;
this.renderTimer = undefined; // Scheduling function. Is definded in this.start();
// apply options
this.setOptions(options);
// load data (the disable start variable will be the same as the enabled clustering)
this.setData(data, this.constants.hierarchicalLayout.enabled);
this.setData(data);
// hierarchical layout
if (this.constants.hierarchicalLayout.enabled == true) {
this._setupHierarchicalLayout();
}
else {
// zoom so all data will fit on the screen, if clustering is enabled, we do not want start to be called here.
if (this.constants.stabilize == false) {
this.zoomExtent({duration:0}, true, this.constants.clustering.enabled);
}
}
if (this.constants.stabilize == false) {
this.initializing = false;
}
var me = this;
// this event will trigger a rebuilding of the cache of colors, nodes etc.
this.on("_dataChanged", function () {
me._updateNodeIndexList();
me.physics._updateCalculationNodes();
me._markAllEdgesAsDirty();
if (me.initializing !== true) {
me.moving = true;
me.start();
}
})
this.on("_newEdgesCreated", this._createBezierNodes.bind(this));
//this.on("stabilizationIterationsDone", function () {me.initializing = false; me.start();}.bind(this));
}
// Extend Network with an Emitter mixin
@ -352,9 +177,6 @@ Network.prototype._createEdge = function(properties) {
return new Edge(properties, this.body, this.constants)
}
/**
* Update the this.body.nodeIndices with the most recent node index list
* @private
@ -376,10 +198,10 @@ Network.prototype._updateNodeIndexList = function() {
* {Options} [options] Object with options
* @param {Boolean} [disableStart] | optional: disable the calling of the start function.
*/
Network.prototype.setData = function(data, disableStart) {
if (disableStart === undefined) {
disableStart = false;
}
Network.prototype.setData = function(data) {
// reset the physics engine.
this.body.emitter.emit("resetPhysics");
this.body.emitter.emit("_resetData");
// unselect all to ensure no selections from old data are carried over.
this.selectionHandler.unselectAll();
@ -393,9 +215,9 @@ Network.prototype.setData = function(data, disableStart) {
}
// clean up in case there is anyone in an active mode of the manipulation. This is the same option as bound to the escape button.
if (this.constants.dataManipulation.enabled == true) {
this._createManipulatorBar();
}
//if (this.constants.dataManipulation.enabled == true) {
// this._createManipulatorBar();
//}
// set options
this.setOptions(data && data.options);
@ -417,23 +239,12 @@ Network.prototype.setData = function(data, disableStart) {
}
}
else {
this._setNodes(data && data.nodes);
this._setEdges(data && data.edges);
this.nodesHandler.setData(data && data.nodes);
this.edgesHandler.setData(data && data.edges);
}
if (disableStart == false) {
if (this.constants.hierarchicalLayout.enabled == true) {
this._resetLevels();
this._setupHierarchicalLayout();
}
else {
// find a stable position or start animating to a stable position
this.body.emitter.emit("stabilize");
}
}
else {
this.initializing = false;
}
// find a stable position or start animating to a stable position
this.body.emitter.emit("initPhysics");
};
/**
@ -442,41 +253,38 @@ Network.prototype.setData = function(data, disableStart) {
*/
Network.prototype.setOptions = function (options) {
if (options) {
var prop;
var fields = ['nodes','edges','smoothCurves','hierarchicalLayout','navigation',
'keyboard','dataManipulation','onAdd','onEdit','onEditEdge','onConnect','onDelete','clickToUse'
];
//var fields = ['nodes','edges','smoothCurves','hierarchicalLayout','navigation',
// 'keyboard','dataManipulation','onAdd','onEdit','onEditEdge','onConnect','onDelete','clickToUse'
//];
// extend all but the values in fields
util.selectiveNotDeepExtend(fields,this.constants, options);
util.selectiveNotDeepExtend(['color'],this.constants.nodes, options.nodes);
util.selectiveNotDeepExtend(['color','length'],this.constants.edges, options.edges);
//util.selectiveNotDeepExtend(fields,this.constants, options);
//util.selectiveNotDeepExtend(['color'],this.constants.nodes, options.nodes);
//util.selectiveNotDeepExtend(['color','length'],this.constants.edges, options.edges);
this.groups.useDefaultGroups = this.constants.useDefaultGroups;
//this.groups.useDefaultGroups = this.constants.useDefaultGroups;
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.layoutEngine.setOptions(options.layout);
//this.clustering.setOptions(options.clustering);
//util.mergeOptions(this.constants, options,'smoothCurves');
//util.mergeOptions(this.constants, options,'hierarchicalLayout');
//util.mergeOptions(this.constants, options,'clustering');
//util.mergeOptions(this.constants, options,'navigation');
//util.mergeOptions(this.constants, options,'keyboard');
//util.mergeOptions(this.constants, options,'dataManipulation');
if (options.onAdd) {this.triggerFunctions.add = options.onAdd;}
if (options.onEdit) {this.triggerFunctions.edit = options.onEdit;}
if (options.onEditEdge) {this.triggerFunctions.editEdge = options.onEditEdge;}
if (options.onConnect) {this.triggerFunctions.connect = options.onConnect;}
if (options.onDelete) {this.triggerFunctions.del = options.onDelete;}
util.mergeOptions(this.constants, options,'smoothCurves');
util.mergeOptions(this.constants, options,'hierarchicalLayout');
util.mergeOptions(this.constants, options,'clustering');
util.mergeOptions(this.constants, options,'navigation');
util.mergeOptions(this.constants, options,'keyboard');
util.mergeOptions(this.constants, options,'dataManipulation');
if (options.dataManipulation) {
this.editMode = this.constants.dataManipulation.initiallyVisible;
}
//if (options.dataManipulation) {
// this.editMode = this.constants.dataManipulation.initiallyVisible;
//}
// TODO: work out these options and document them
@ -554,76 +362,11 @@ Network.prototype.setOptions = function (options) {
this.body.emitter.emit("activate");
}
this._markAllEdgesAsDirty();
this.canvas.setSize();
if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
this._resetLevels();
this._setupHierarchicalLayout();
}
if (this.initializing !== true) {
this.moving = true;
this.start();
}
}
};
/**
* Binding the keys for keyboard navigation. These functions are defined in the NavigationMixin
* @private
*/
Network.prototype._createKeyBinds = function() {
return;
//var me = this;
//if (this.keycharm !== undefined) {
// this.keycharm.destroy();
//}
//
//if (this.constants.keyboard.bindToWindow == true) {
// this.keycharm = keycharm({container: window, preventDefault: false});
//}
//else {
// this.keycharm = keycharm({container: this.frame, preventDefault: false});
//}
//
//this.keycharm.reset();
//
//if (this.constants.keyboard.enabled && this.isActive()) {
// this.keycharm.bind("up", this._moveUp.bind(me) , "keydown");
// this.keycharm.bind("up", this._yStopMoving.bind(me), "keyup");
// this.keycharm.bind("down", this._moveDown.bind(me) , "keydown");
// this.keycharm.bind("down", this._yStopMoving.bind(me), "keyup");
// this.keycharm.bind("left", this._moveLeft.bind(me) , "keydown");
// this.keycharm.bind("left", this._xStopMoving.bind(me), "keyup");
// this.keycharm.bind("right",this._moveRight.bind(me), "keydown");
// this.keycharm.bind("right",this._xStopMoving.bind(me), "keyup");
// this.keycharm.bind("=", this._zoomIn.bind(me), "keydown");
// this.keycharm.bind("=", this._stopZoom.bind(me), "keyup");
// this.keycharm.bind("num+", this._zoomIn.bind(me), "keydown");
// this.keycharm.bind("num+", this._stopZoom.bind(me), "keyup");
// this.keycharm.bind("num-", this._zoomOut.bind(me), "keydown");
// this.keycharm.bind("num-", this._stopZoom.bind(me), "keyup");
// this.keycharm.bind("-", this._zoomOut.bind(me), "keydown");
// this.keycharm.bind("-", this._stopZoom.bind(me), "keyup");
// this.keycharm.bind("[", this._zoomIn.bind(me), "keydown");
// this.keycharm.bind("[", this._stopZoom.bind(me), "keyup");
// this.keycharm.bind("]", this._zoomOut.bind(me), "keydown");
// this.keycharm.bind("]", this._stopZoom.bind(me), "keyup");
// this.keycharm.bind("pageup",this._zoomIn.bind(me), "keydown");
// this.keycharm.bind("pageup",this._stopZoom.bind(me), "keyup");
// this.keycharm.bind("pagedown",this._zoomOut.bind(me),"keydown");
// this.keycharm.bind("pagedown",this._stopZoom.bind(me), "keyup");
//}
//
//if (this.constants.dataManipulation.enabled == true) {
// this.keycharm.bind("esc",this._createManipulatorBar.bind(me));
// this.keycharm.bind("delete",this._deleteSelected.bind(me));
//}
};
/**
* Cleans up all bindings of the network, removing it fully from the memory IF the variable is set to null after calling this function.
* var network = new vis.Network(..);
@ -631,31 +374,15 @@ Network.prototype._createKeyBinds = function() {
* network = null;
*/
Network.prototype.destroy = function() {
this.start = function () {};
this.redraw = function () {};
this.renderTimer = false;
// cleanup physicsConfiguration if it exists
this._cleanupPhysicsConfiguration();
// remove keybindings
this.keycharm.reset();
// clear hammer bindings
this.hammer.destroy();
this.body.emitter.emit("destroy");
// clear events
this.off();
this.body.emitter.off();
this._recursiveDOMDelete(this.containerElement);
// remove the container and everything inside it recursively
this.util.recursiveDOMDelete(this.body.container);
};
Network.prototype._recursiveDOMDelete = function(DOMobject) {
while (DOMobject.hasChildNodes() == true) {
this._recursiveDOMDelete(DOMobject.firstChild);
DOMobject.removeChild(DOMobject.firstChild);
}
};
/**
* Check if there is an element on the given position in the network
@ -784,113 +511,6 @@ Network.prototype._checkHidePopup = function (pointer) {
};
/**
* Set a data set with nodes for the network
* @param {Array | DataSet | DataView} nodes The data containing the nodes.
* @private
*/
Network.prototype._setNodes = function(nodes) {
var oldNodesData = this.body.data.nodes;
if (nodes instanceof DataSet || nodes instanceof DataView) {
this.body.data.nodes = nodes;
}
else if (Array.isArray(nodes)) {
this.body.data.nodes = new DataSet();
this.body.data.nodes.add(nodes);
}
else if (!nodes) {
this.body.data.nodes = new DataSet();
}
else {
throw new TypeError('Array or DataSet expected');
}
if (oldNodesData) {
// unsubscribe from old dataset
util.forEach(this.nodesListeners, function (callback, event) {
oldNodesData.off(event, callback);
});
}
// remove drawn nodes
this.body.nodes = {};
if (this.body.data.nodes) {
// subscribe to new dataset
var me = this;
util.forEach(this.nodesListeners, function (callback, event) {
me.body.data.nodes.on(event, callback);
});
// draw all new nodes
var ids = this.body.data.nodes.getIds();
this._addNodes(ids);
}
this._updateSelection();
};
/**
* Add nodes
* @param {Number[] | String[]} ids
* @private
*/
Network.prototype._addNodes = function(ids) {
var id;
for (var i = 0, len = ids.length; i < len; i++) {
id = ids[i];
var data = this.body.data.nodes.get(id);
var node = new Node(data, this.images, this.groups, this.constants);
this.body.nodes[id] = node; // note: this may replace an existing node
if ((node.xFixed == false || node.yFixed == false) && (node.x === null || node.y === null)) {
var radius = 10 * 0.1*ids.length + 10;
var angle = 2 * Math.PI * Math.random();
if (node.xFixed == false) {node.x = radius * Math.cos(angle);}
if (node.yFixed == false) {node.y = radius * Math.sin(angle);}
}
this.moving = true;
}
this._updateNodeIndexList();
if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
this._resetLevels();
this._setupHierarchicalLayout();
}
this.physics._updateCalculationNodes();
this._reconnectEdges();
this._updateValueRange(this.body.nodes);
};
/**
* Update existing nodes, or create them when not yet existing
* @param {Number[] | String[]} ids
* @private
*/
Network.prototype._updateNodes = function(ids,changedData) {
var nodes = this.body.nodes;
for (var i = 0, len = ids.length; i < len; i++) {
var id = ids[i];
var node = nodes[id];
var data = changedData[i];
if (node) {
// update node
node.setProperties(data, this.constants);
}
else {
// create node
node = new Node(properties, this.images, this.groups, this.constants);
nodes[id] = node;
}
}
this.moving = true;
if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
this._resetLevels();
this._setupHierarchicalLayout();
}
this._updateNodeIndexList();
this._updateValueRange(nodes);
this._markAllEdgesAsDirty();
};
Network.prototype._markAllEdgesAsDirty = function() {
@ -899,190 +519,8 @@ Network.prototype._markAllEdgesAsDirty = function() {
}
}
/**
* Remove existing nodes. If nodes do not exist, the method will just ignore it.
* @param {Number[] | String[]} ids
* @private
*/
Network.prototype._removeNodes = function(ids) {
var nodes = this.body.nodes;
// remove from selection
for (var i = 0, len = ids.length; i < len; i++) {
if (this.selectionObj.nodes[ids[i]] !== undefined) {
this.body.nodes[ids[i]].unselect();
this._removeFromSelection(this.body.nodes[ids[i]]);
}
}
for (var i = 0, len = ids.length; i < len; i++) {
var id = ids[i];
delete nodes[id];
}
this._updateNodeIndexList();
if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
this._resetLevels();
this._setupHierarchicalLayout();
}
this.physics._updateCalculationNodes();
this._reconnectEdges();
this._updateSelection();
this._updateValueRange(nodes);
};
/**
* Load edges by reading the data table
* @param {Array | DataSet | DataView} edges The data containing the edges.
* @private
* @private
*/
Network.prototype._setEdges = function(edges) {
var oldEdgesData = this.body.data.edges;
if (edges instanceof DataSet || edges instanceof DataView) {
this.body.data.edges = edges;
}
else if (Array.isArray(edges)) {
this.body.data.edges = new DataSet();
this.body.data.edges.add(edges);
}
else if (!edges) {
this.body.data.edges = new DataSet();
}
else {
throw new TypeError('Array or DataSet expected');
}
if (oldEdgesData) {
// unsubscribe from old dataset
util.forEach(this.edgesListeners, function (callback, event) {
oldEdgesData.off(event, callback);
});
}
// remove drawn edges
this.body.edges = {};
if (this.body.data.edges) {
// subscribe to new dataset
var me = this;
util.forEach(this.edgesListeners, function (callback, event) {
me.body.data.edges.on(event, callback);
});
// draw all new nodes
var ids = this.body.data.edges.getIds();
this._addEdges(ids);
}
this._reconnectEdges();
};
/**
* Add edges
* @param {Number[] | String[]} ids
* @private
*/
Network.prototype._addEdges = function (ids) {
var edges = this.body.edges,
edgesData = this.body.data.edges;
for (var i = 0, len = ids.length; i < len; i++) {
var id = ids[i];
var oldEdge = edges[id];
if (oldEdge) {
oldEdge.disconnect();
}
var data = edgesData.get(id, {"showInternalIds" : true});
edges[id] = new Edge(data, this.body, this.constants);
}
this.moving = true;
this._updateValueRange(edges);
this._createBezierNodes();
this.physics._updateCalculationNodes();
if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
this._resetLevels();
this._setupHierarchicalLayout();
}
};
/**
* Update existing edges, or create them when not yet existing
* @param {Number[] | String[]} ids
* @private
*/
Network.prototype._updateEdges = function (ids) {
var edges = this.body.edges;
var edgesData = this.body.data.edges;
for (var i = 0, len = ids.length; i < len; i++) {
var id = ids[i];
var data = edgesData.get(id);
var edge = edges[id];
if (edge) {
// update edge
edge.disconnect();
edge.setProperties(data);
edge.connect();
}
else {
// create edge
edge = new Edge(data, this.body, this.constants);
this.body.edges[id] = edge;
}
}
this._createBezierNodes();
if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
this._resetLevels();
this._setupHierarchicalLayout();
}
this.moving = true;
this._updateValueRange(edges);
};
/**
* Remove existing edges. Non existing ids will be ignored
* @param {Number[] | String[]} ids
* @private
*/
Network.prototype._removeEdges = function (ids) {
var edges = this.body.edges;
// remove from selection
for (var i = 0, len = ids.length; i < len; i++) {
if (this.selectionObj.edges[ids[i]] !== undefined) {
edges[ids[i]].unselect();
this._removeFromSelection(edges[ids[i]]);
}
}
for (var i = 0, len = ids.length; i < len; i++) {
var id = ids[i];
var edge = edges[id];
if (edge) {
if (edge.via != null) {
delete this.body.supportNodes[edge.via.id];
}
edge.disconnect();
delete edges[id];
}
}
this.moving = true;
this._updateValueRange(edges);
if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
this._resetLevels();
this._setupHierarchicalLayout();
}
this.physics._updateCalculationNodes();
};
/**
* Reconnect all edges
* @private
@ -1273,37 +711,6 @@ Network.prototype._configureSmoothCurves = function(disableStart = true) {
};
/**
* Bezier curves require an anchor point to calculate the smooth flow. These points are nodes. These nodes are invisible but
* are used for the force calculation.
*
* @private
*/
Network.prototype._createBezierNodes = function(specificEdges = this.body.edges) {
if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
for (var edgeId in specificEdges) {
if (specificEdges.hasOwnProperty(edgeId)) {
var edge = specificEdges[edgeId];
if (edge.via == null) {
var nodeId = "edgeId:".concat(edge.id);
var node = new Node(
{id:nodeId,
mass:1,
shape:'circle',
image:"",
internalMultiplier:1
},{},{},this.constants);
this.body.supportNodes[nodeId] = node;
edge.via = node;
edge.via.parentEdgeId = edge.id;
edge.positionBezierNode();
}
}
}
this._updateNodeIndexList();
}
};
/**
* load the functions that load the mixins into the prototype.
*

+ 0
- 707
lib/network/mixins/SelectionMixin.js View File

@ -1,707 +0,0 @@
var Node = require('../Node');
/**
*
* @param object
* @param overlappingNodes
* @private
*/
exports._getNodesOverlappingWith = function(object, overlappingNodes) {
var nodes = this.body.nodes;
for (var nodeId in nodes) {
if (nodes.hasOwnProperty(nodeId)) {
if (nodes[nodeId].isOverlappingWith(object)) {
overlappingNodes.push(nodeId);
}
}
}
};
/**
* retrieve all nodes overlapping with given object
* @param {Object} object An object with parameters left, top, right, bottom
* @return {Number[]} An array with id's of the overlapping nodes
* @private
*/
exports._getAllNodesOverlappingWith = function (object) {
var overlappingNodes = [];
this._getNodesOverlappingWith(object,overlappingNodes);
return overlappingNodes;
};
/**
* Return a position object in canvasspace from a single point in screenspace
*
* @param pointer
* @returns {{left: number, top: number, right: number, bottom: number}}
* @private
*/
exports._pointerToPositionObject = function(pointer) {
var x = this._XconvertDOMtoCanvas(pointer.x);
var y = this._YconvertDOMtoCanvas(pointer.y);
return {
left: x,
top: y,
right: x,
bottom: y
};
};
/**
* Get the top node at the a specific point (like a click)
*
* @param {{x: Number, y: Number}} pointer
* @return {Node | null} node
* @private
*/
exports._getNodeAt = function (pointer) {
// we first check if this is an navigation controls element
var positionObject = this._pointerToPositionObject(pointer);
var overlappingNodes = this._getAllNodesOverlappingWith(positionObject);
// if there are overlapping nodes, select the last one, this is the
// one which is drawn on top of the others
if (overlappingNodes.length > 0) {
return this.body.nodes[overlappingNodes[overlappingNodes.length - 1]];
}
else {
return null;
}
};
/**
* retrieve all edges overlapping with given object, selector is around center
* @param {Object} object An object with parameters left, top, right, bottom
* @return {Number[]} An array with id's of the overlapping nodes
* @private
*/
exports._getEdgesOverlappingWith = function (object, overlappingEdges) {
var edges = this.body.edges;
for (var edgeId in edges) {
if (edges.hasOwnProperty(edgeId)) {
if (edges[edgeId].isOverlappingWith(object)) {
overlappingEdges.push(edgeId);
}
}
}
};
/**
* retrieve all nodes overlapping with given object
* @param {Object} object An object with parameters left, top, right, bottom
* @return {Number[]} An array with id's of the overlapping nodes
* @private
*/
exports._getAllEdgesOverlappingWith = function (object) {
var overlappingEdges = [];
this._getEdgesOverlappingWith(object,overlappingEdges);
return overlappingEdges;
};
/**
* Place holder. To implement change the getNodeAt to a _getObjectAt. Have the _getObjectAt call
* getNodeAt and _getEdgesAt, then priortize the selection to user preferences.
*
* @param pointer
* @returns {null}
* @private
*/
exports._getEdgeAt = function(pointer) {
var positionObject = this._pointerToPositionObject(pointer);
var overlappingEdges = this._getAllEdgesOverlappingWith(positionObject);
if (overlappingEdges.length > 0) {
return this.body.edges[overlappingEdges[overlappingEdges.length - 1]];
}
else {
return null;
}
};
/**
* Add object to the selection array.
*
* @param obj
* @private
*/
exports._addToSelection = function(obj) {
if (obj instanceof Node) {
this.selectionObj.nodes[obj.id] = obj;
}
else {
this.selectionObj.edges[obj.id] = obj;
}
};
/**
* Add object to the selection array.
*
* @param obj
* @private
*/
exports._addToHover = function(obj) {
if (obj instanceof Node) {
this.hoverObj.nodes[obj.id] = obj;
}
else {
this.hoverObj.edges[obj.id] = obj;
}
};
/**
* Remove a single option from selection.
*
* @param {Object} obj
* @private
*/
exports._removeFromSelection = function(obj) {
if (obj instanceof Node) {
delete this.selectionObj.nodes[obj.id];
}
else {
delete this.selectionObj.edges[obj.id];
}
};
/**
* Unselect all. The selectionObj is useful for this.
*
* @param {Boolean} [doNotTrigger] | ignore trigger
* @private
*/
exports._unselectAll = function(doNotTrigger) {
if (doNotTrigger === undefined) {
doNotTrigger = false;
}
for(var nodeId in this.selectionObj.nodes) {
if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
this.selectionObj.nodes[nodeId].unselect();
}
}
for(var edgeId in this.selectionObj.edges) {
if(this.selectionObj.edges.hasOwnProperty(edgeId)) {
this.selectionObj.edges[edgeId].unselect();
}
}
this.selectionObj = {nodes:{},edges:{}};
if (doNotTrigger == false) {
this.emit('select', this.getSelection());
}
};
/**
* Unselect all clusters. The selectionObj is useful for this.
*
* @param {Boolean} [doNotTrigger] | ignore trigger
* @private
*/
exports._unselectClusters = function(doNotTrigger) {
if (doNotTrigger === undefined) {
doNotTrigger = false;
}
for (var nodeId in this.selectionObj.nodes) {
if (this.selectionObj.nodes.hasOwnProperty(nodeId)) {
if (this.selectionObj.nodes[nodeId].clusterSize > 1) {
this.selectionObj.nodes[nodeId].unselect();
this._removeFromSelection(this.selectionObj.nodes[nodeId]);
}
}
}
if (doNotTrigger == false) {
this.emit('select', this.getSelection());
}
};
/**
* return the number of selected nodes
*
* @returns {number}
* @private
*/
exports._getSelectedNodeCount = function() {
var count = 0;
for (var nodeId in this.selectionObj.nodes) {
if (this.selectionObj.nodes.hasOwnProperty(nodeId)) {
count += 1;
}
}
return count;
};
/**
* return the selected node
*
* @returns {number}
* @private
*/
exports._getSelectedNode = function() {
for (var nodeId in this.selectionObj.nodes) {
if (this.selectionObj.nodes.hasOwnProperty(nodeId)) {
return this.selectionObj.nodes[nodeId];
}
}
return null;
};
/**
* return the selected edge
*
* @returns {number}
* @private
*/
exports._getSelectedEdge = function() {
for (var edgeId in this.selectionObj.edges) {
if (this.selectionObj.edges.hasOwnProperty(edgeId)) {
return this.selectionObj.edges[edgeId];
}
}
return null;
};
/**
* return the number of selected edges
*
* @returns {number}
* @private
*/
exports._getSelectedEdgeCount = function() {
var count = 0;
for (var edgeId in this.selectionObj.edges) {
if (this.selectionObj.edges.hasOwnProperty(edgeId)) {
count += 1;
}
}
return count;
};
/**
* return the number of selected objects.
*
* @returns {number}
* @private
*/
exports._getSelectedObjectCount = function() {
var count = 0;
for(var nodeId in this.selectionObj.nodes) {
if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
count += 1;
}
}
for(var edgeId in this.selectionObj.edges) {
if(this.selectionObj.edges.hasOwnProperty(edgeId)) {
count += 1;
}
}
return count;
};
/**
* Check if anything is selected
*
* @returns {boolean}
* @private
*/
exports._selectionIsEmpty = function() {
for(var nodeId in this.selectionObj.nodes) {
if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
return false;
}
}
for(var edgeId in this.selectionObj.edges) {
if(this.selectionObj.edges.hasOwnProperty(edgeId)) {
return false;
}
}
return true;
};
/**
* check if one of the selected nodes is a cluster.
*
* @returns {boolean}
* @private
*/
exports._clusterInSelection = function() {
for(var nodeId in this.selectionObj.nodes) {
if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
if (this.selectionObj.nodes[nodeId].clusterSize > 1) {
return true;
}
}
}
return false;
};
/**
* select the edges connected to the node that is being selected
*
* @param {Node} node
* @private
*/
exports._selectConnectedEdges = function(node) {
for (var i = 0; i < node.edges.length; i++) {
var edge = node.edges[i];
edge.select();
this._addToSelection(edge);
}
};
/**
* select the edges connected to the node that is being selected
*
* @param {Node} node
* @private
*/
exports._hoverConnectedEdges = function(node) {
for (var i = 0; i < node.edges.length; i++) {
var edge = node.edges[i];
edge.hover = true;
this._addToHover(edge);
}
};
/**
* unselect the edges connected to the node that is being selected
*
* @param {Node} node
* @private
*/
exports._unselectConnectedEdges = function(node) {
for (var i = 0; i < node.edges.length; i++) {
var edge = node.edges[i];
edge.unselect();
this._removeFromSelection(edge);
}
};
/**
* This is called when someone clicks on a node. either select or deselect it.
* If there is an existing selection and we don't want to append to it, clear the existing selection
*
* @param {Node || Edge} object
* @param {Boolean} append
* @param {Boolean} [doNotTrigger] | ignore trigger
* @private
*/
exports._selectObject = function(object, append, doNotTrigger, highlightEdges, overrideSelectable) {
if (doNotTrigger === undefined) {
doNotTrigger = false;
}
if (highlightEdges === undefined) {
highlightEdges = true;
}
if (this._selectionIsEmpty() == false && append == false && this.forceAppendSelection == false) {
this._unselectAll(true);
}
// selectable allows the object to be selected. Override can be used if needed to bypass this.
if (object.selected == false && (this.constants.selectable == true || overrideSelectable)) {
object.select();
this._addToSelection(object);
if (object instanceof Node && this.blockConnectingEdgeSelection == false && highlightEdges == true) {
this._selectConnectedEdges(object);
}
}
// do not select the object if selectable is false, only add it to selection to allow drag to work
else if (object.selected == false) {
this._addToSelection(object);
doNotTrigger = true;
}
else {
object.unselect();
this._removeFromSelection(object);
}
if (doNotTrigger == false) {
this.emit('select', this.getSelection());
}
};
/**
* This is called when someone clicks on a node. either select or deselect it.
* If there is an existing selection and we don't want to append to it, clear the existing selection
*
* @param {Node || Edge} object
* @private
*/
exports._blurObject = function(object) {
if (object.hover == true) {
object.hover = false;
this.emit("blurNode",{node:object.id});
}
};
/**
* This is called when someone clicks on a node. either select or deselect it.
* If there is an existing selection and we don't want to append to it, clear the existing selection
*
* @param {Node || Edge} object
* @private
*/
exports._hoverObject = function(object) {
if (object.hover == false) {
object.hover = true;
this._addToHover(object);
if (object instanceof Node) {
this.emit("hoverNode",{node:object.id});
}
}
if (object instanceof Node) {
this._hoverConnectedEdges(object);
}
};
/**
* handles the selection part of the touch, only for navigation controls elements;
* Touch is triggered before tap, also before hold. Hold triggers after a while.
* This is the most responsive solution
*
* @param {Object} pointer
* @private
*/
exports._handleTouch = function(pointer) {
};
/**
* handles the selection part of the tap;
*
* @param {Object} pointer
* @private
*/
exports._handleTap = function(pointer) {
var node = this._getNodeAt(pointer);
if (node != null) {
this._selectObject(node, false);
}
else {
var edge = this._getEdgeAt(pointer);
if (edge != null) {
this._selectObject(edge, false);
}
else {
this._unselectAll();
}
}
var properties = this.getSelection();
properties['pointer'] = {
DOM: {x: pointer.x, y: pointer.y},
canvas: {x: this._XconvertDOMtoCanvas(pointer.x), y: this._YconvertDOMtoCanvas(pointer.y)}
}
this.emit("click", properties);
this._requestRedraw();
};
/**
* handles the selection part of the double tap and opens a cluster if needed
*
* @param {Object} pointer
* @private
*/
exports._handleDoubleTap = function(pointer) {
var node = this._getNodeAt(pointer);
if (node != null && node !== undefined) {
// we reset the areaCenter here so the opening of the node will occur
this.areaCenter = {"x" : this._XconvertDOMtoCanvas(pointer.x),
"y" : this._YconvertDOMtoCanvas(pointer.y)};
this.openCluster(node);
}
var properties = this.getSelection();
properties['pointer'] = {
DOM: {x: pointer.x, y: pointer.y},
canvas: {x: this._XconvertDOMtoCanvas(pointer.x), y: this._YconvertDOMtoCanvas(pointer.y)}
}
this.emit("doubleClick", properties);
};
/**
* Handle the onHold selection part
*
* @param pointer
* @private
*/
exports._handleOnHold = function(pointer) {
var node = this._getNodeAt(pointer);
if (node != null) {
this._selectObject(node,true);
}
else {
var edge = this._getEdgeAt(pointer);
if (edge != null) {
this._selectObject(edge,true);
}
}
this._requestRedraw();
};
/**
* handle the onRelease event. These functions are here for the navigation controls module
* and data manipulation module.
*
* @private
*/
exports._handleOnRelease = function(pointer) {
this._manipulationReleaseOverload(pointer);
this._navigationReleaseOverload(pointer);
};
exports._manipulationReleaseOverload = function (pointer) {};
exports._navigationReleaseOverload = function (pointer) {};
/**
*
* retrieve the currently selected objects
* @return {{nodes: Array.<String>, edges: Array.<String>}} selection
*/
exports.getSelection = function() {
var nodeIds = this.getSelectedNodes();
var edgeIds = this.getSelectedEdges();
return {nodes:nodeIds, edges:edgeIds};
};
/**
*
* retrieve the currently selected nodes
* @return {String[]} selection An array with the ids of the
* selected nodes.
*/
exports.getSelectedNodes = function() {
var idArray = [];
if (this.constants.selectable == true) {
for (var nodeId in this.selectionObj.nodes) {
if (this.selectionObj.nodes.hasOwnProperty(nodeId)) {
idArray.push(nodeId);
}
}
}
return idArray
};
/**
*
* retrieve the currently selected edges
* @return {Array} selection An array with the ids of the
* selected nodes.
*/
exports.getSelectedEdges = function() {
var idArray = [];
if (this.constants.selectable == true) {
for (var edgeId in this.selectionObj.edges) {
if (this.selectionObj.edges.hasOwnProperty(edgeId)) {
idArray.push(edgeId);
}
}
}
return idArray;
};
/**
* select zero or more nodes DEPRICATED
* @param {Number[] | String[]} selection An array with the ids of the
* selected nodes.
*/
exports.setSelection = function() {
console.log("setSelection is deprecated. Please use selectNodes instead.")
};
/**
* select zero or more nodes with the option to highlight edges
* @param {Number[] | String[]} selection An array with the ids of the
* selected nodes.
* @param {boolean} [highlightEdges]
*/
exports.selectNodes = function(selection, highlightEdges) {
var i, iMax, id;
if (!selection || (selection.length == undefined))
throw 'Selection must be an array with ids';
// first unselect any selected node
this._unselectAll(true);
for (i = 0, iMax = selection.length; i < iMax; i++) {
id = selection[i];
var node = this.body.nodes[id];
if (!node) {
throw new RangeError('Node with id "' + id + '" not found');
}
this._selectObject(node,true,true,highlightEdges,true);
}
this.redraw();
};
/**
* select zero or more edges
* @param {Number[] | String[]} selection An array with the ids of the
* selected nodes.
*/
exports.selectEdges = function(selection) {
var i, iMax, id;
if (!selection || (selection.length == undefined))
throw 'Selection must be an array with ids';
// first unselect any selected node
this._unselectAll(true);
for (i = 0, iMax = selection.length; i < iMax; i++) {
id = selection[i];
var edge = this.body.edges[id];
if (!edge) {
throw new RangeError('Edge with id "' + id + '" not found');
}
this._selectObject(edge,true,true,false,true);
}
this.redraw();
};
/**
* Validate the selection: remove ids of nodes which no longer exist
* @private
*/
exports._updateSelection = function () {
for(var nodeId in this.selectionObj.nodes) {
if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
if (!this.body.nodes.hasOwnProperty(nodeId)) {
delete this.selectionObj.nodes[nodeId];
}
}
}
for(var edgeId in this.selectionObj.edges) {
if(this.selectionObj.edges.hasOwnProperty(edgeId)) {
if (!this.body.edges.hasOwnProperty(edgeId)) {
delete this.selectionObj.edges[edgeId];
}
}
}
};

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

@ -22,6 +22,7 @@ class Canvas {
util.extend(this.options, this.defaultOptions);
this.body.emitter.once("resize", (obj) => {this.body.view.translation.x = obj.width * 0.5; this.body.view.translation.y = obj.height * 0.5;});
this.body.emitter.on("destroy", () => this.hammer.destroy());
this.pixelRatio = 1;
}
@ -227,4 +228,4 @@ class Canvas {
}
export {Canvas};
export default Canvas;

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

@ -265,4 +265,4 @@ class CanvasRenderer {
}
export {CanvasRenderer};
export default CanvasRenderer;

+ 24
- 32
lib/network/modules/Clustering.js View File

@ -35,7 +35,7 @@ class ClusterEngine {
for (var i = 0; i < nodesToCluster.length; i++) {
var node = this.body.nodes[nodesToCluster[i]];
this.clusterByConnection(node,options,{},{},true);
this.clusterByConnection(node,options,{},{},false);
}
this.body.emitter.emit('_dataChanged');
}
@ -44,9 +44,9 @@ class ClusterEngine {
/**
* loop over all nodes, check if they adhere to the condition and cluster if needed.
* @param options
* @param doNotUpdateCalculationNodes
* @param refreshData
*/
clusterByNodeData(options = {}, doNotUpdateCalculationNodes = false) {
clusterByNodeData(options = {}, refreshData = true) {
if (options.joinCondition === undefined) {throw new Error("Cannot call clusterByNodeData without a joinCondition function in the options.");}
// check if the options object is fine, append if needed
@ -64,16 +64,16 @@ class ClusterEngine {
}
}
this._cluster(childNodesObj, childEdgesObj, options, doNotUpdateCalculationNodes);
this._cluster(childNodesObj, childEdgesObj, options, refreshData);
}
/**
* Cluster all nodes in the network that have only 1 edge
* @param options
* @param doNotUpdateCalculationNodes
* @param refreshData
*/
clusterOutliers(options, doNotUpdateCalculationNodes) {
clusterOutliers(options, refreshData = true) {
options = this._checkOptions(options);
var clusters = [];
@ -106,10 +106,10 @@ class ClusterEngine {
}
for (var i = 0; i < clusters.length; i++) {
this._cluster(clusters[i].nodes, clusters[i].edges, options, true)
this._cluster(clusters[i].nodes, clusters[i].edges, options, false)
}
if (doNotUpdateCalculationNodes !== true) {
if (refreshData === true) {
this.body.emitter.emit('_dataChanged');
}
}
@ -118,9 +118,9 @@ class ClusterEngine {
*
* @param nodeId
* @param options
* @param doNotUpdateCalculationNodes
* @param refreshData
*/
clusterByConnection(nodeId, options, doNotUpdateCalculationNodes) {
clusterByConnection(nodeId, options, refreshData = true) {
// kill conditions
if (nodeId === undefined) {throw new Error("No nodeId supplied to clusterByConnection!");}
if (this.body.nodes[nodeId] === undefined) {throw new Error("The nodeId given to clusterByConnection does not exist!");}
@ -160,7 +160,7 @@ class ClusterEngine {
}
}
this._cluster(childNodesObj, childEdgesObj, options, doNotUpdateCalculationNodes);
this._cluster(childNodesObj, childEdgesObj, options, refreshData);
}
@ -256,10 +256,10 @@ class ClusterEngine {
* @param {Object} childNodesObj | object with node objects, id as keys, same as childNodes except it also contains a source node
* @param {Object} childEdgesObj | object with edge objects, id as keys
* @param {Array} options | object with {clusterNodeProperties, clusterEdgeProperties, processProperties}
* @param {Boolean} doNotUpdateCalculationNodes | when true, do not wrap up
* @param {Boolean} refreshData | when true, do not wrap up
* @private
*/
_cluster(childNodesObj, childEdgesObj, options, doNotUpdateCalculationNodes = false) {
_cluster(childNodesObj, childEdgesObj, options, refreshData = true) {
// kill condition: no children so cant cluster
if (Object.keys(childNodesObj).length == 0) {return;}
@ -362,17 +362,12 @@ class ClusterEngine {
this.body.edges[newEdges[i].id].connect();
}
// create bezier nodes for smooth curves if needed
this.body.emitter.emit("_newEdgesCreated");
// set ID to undefined so no duplicates arise
clusterNodeProperties.id = undefined;
// wrap up
if (doNotUpdateCalculationNodes !== true) {
if (refreshData === true) {
this.body.emitter.emit('_dataChanged');
}
}
@ -384,13 +379,13 @@ class ClusterEngine {
* @returns {*}
*/
isCluster(nodeId) {
if (this.body.nodes[nodeId] !== undefined) {
return this.body.nodes[nodeId].isCluster;
}
if (this.body.nodes[nodeId] !== undefined) {
return this.body.nodes[nodeId].isCluster;
}
else {
console.log("Node does not exist.")
return false;
}
console.log("Node does not exist.")
return false;
}
}
/**
@ -420,9 +415,9 @@ class ClusterEngine {
/**
* Open a cluster by calling this function.
* @param {String} clusterNodeId | the ID of the cluster node
* @param {Boolean} doNotUpdateCalculationNodes | wrap up afterwards if not true
* @param {Boolean} refreshData | wrap up afterwards if not true
*/
openCluster(clusterNodeId, doNotUpdateCalculationNodes) {
openCluster(clusterNodeId, refreshData = true) {
// kill conditions
if (clusterNodeId === undefined) {throw new Error("No clusterNodeId supplied to openCluster.");}
if (this.body.nodes[clusterNodeId] === undefined) {throw new Error("The clusterNodeId supplied to openCluster does not exist.");}
@ -465,9 +460,6 @@ class ClusterEngine {
}
}
this.body.emitter.emit("_newEdgesCreated",containedEdges);
var edgeIds = [];
for (var i = 0; i < node.edges.length; i++) {
edgeIds.push(node.edges[i].id);
@ -505,7 +497,7 @@ class ClusterEngine {
// remove clusterNode
delete this.body.nodes[clusterNodeId];
if (doNotUpdateCalculationNodes !== true) {
if (refreshData === true) {
this.body.emitter.emit('_dataChanged');
}
}
@ -617,4 +609,4 @@ class ClusterEngine {
}
export { ClusterEngine };
export default ClusterEngine;

+ 241
- 0
lib/network/modules/EdgesHandler.js View File

@ -0,0 +1,241 @@
/**
* Created by Alex on 3/4/2015.
*/
var util = require("../../util");
var DataSet = require('../../DataSet');
var DataView = require('../../DataView');
var Edge = require("./components/edges/EdgeMain");
class EdgesHandler {
constructor(body, images, groups) {
this.body = body;
this.images = images;
this.groups = groups;
this.edgesListeners = {
'add': (event, params) => {this.add(params.items);},
'update': (event, params) => {this.update(params.items);},
'remove': (event, params) => {this.remove(params.items);}
};
var customScalingFunction = function (min,max,total,value) {
if (max == min) {
return 0.5;
}
else {
var scale = 1 / (max - min);
return Math.max(0,(value - min)*scale);
}
};
this.options = {};
this.defaultOptions = {
customScalingFunction: customScalingFunction,
widthMin: 1, //
widthMax: 15,//
width: 1,
widthSelectionMultiplier: 2,
hoverWidth: 1.5,
value:1,
style: 'line',
color: {
color:'#848484',
highlight:'#848484',
hover: '#848484'
},
opacity:1.0,
fontColor: '#343434',
fontSize: 14, // px
fontFace: 'arial',
fontFill: 'white',
fontStrokeWidth: 0, // px
fontStrokeColor: 'white',
labelAlignment:'horizontal',
arrowScaleFactor: 1,
dash: {
length: 10,
gap: 5,
altLength: undefined
},
inheritColor: "from", // to, from, false, true (== from)
useGradients: false, // release in 4.0
smoothEdges: {
enabled: true,
dynamic: true,
type: "continuous",
roundness: 0.5
}
};
util.extend(this.options, this.defaultOptions);
}
setOptions(options) {
}
/**
* Load edges by reading the data table
* @param {Array | DataSet | DataView} edges The data containing the edges.
* @private
* @private
*/
setData(edges) {
var oldEdgesData = this.body.data.edges;
if (edges instanceof DataSet || edges instanceof DataView) {
this.body.data.edges = edges;
}
else if (Array.isArray(edges)) {
this.body.data.edges = new DataSet();
this.body.data.edges.add(edges);
}
else if (!edges) {
this.body.data.edges = new DataSet();
}
else {
throw new TypeError('Array or DataSet expected');
}
// TODO: is this null or undefined or false?
if (oldEdgesData) {
// unsubscribe from old dataset
util.forEach(this.edgesListeners, (callback, event) => {oldEdgesData.off(event, callback);});
}
// remove drawn edges
this.body.edges = {};
// TODO: is this null or undefined or false?
if (this.body.data.edges) {
// subscribe to new dataset
util.forEach(this.edgesListeners, (callback, event) => {this.body.data.edges.on(event, callback);});
// draw all new nodes
var ids = this.body.data.edges.getIds();
this.add(ids);
}
this.body.emitter.emit("_dataChanged");
}
/**
* Add edges
* @param {Number[] | String[]} ids
* @private
*/
add(ids) {
var edges = this.body.edges,
edgesData = this.body.data.edges;
for (var i = 0; i < ids.length; i++) {
var id = ids[i];
var oldEdge = edges[id];
if (oldEdge) {
oldEdge.disconnect();
}
var data = edgesData.get(id, {"showInternalIds" : true});
edges[id] = new Edge(data, this.body, this.options);
}
this.body.emitter.emit("_dataChanged");
}
/**
* Update existing edges, or create them when not yet existing
* @param {Number[] | String[]} ids
* @private
*/
update(ids) {
var edges = this.body.edges;
var edgesData = this.body.data.edges;
for (var i = 0; i < ids.length; i++) {
var id = ids[i];
var data = edgesData.get(id);
var edge = edges[id];
if (edge) {
// update edge
edge.disconnect();
edge.setProperties(data);
edge.connect();
}
else {
// create edge
edge = new Edge(data, this.body, this.options);
this.body.edges[id] = edge;
}
}
this.body.emitter.emit("_dataUpdated")
}
/**
* Remove existing edges. Non existing ids will be ignored
* @param {Number[] | String[]} ids
* @private
*/
remove(ids) {
var edges = this.body.edges;
for (var i = 0; i < ids.length; i++) {
var id = ids[i];
var edge = edges[id];
if (edge !== undefined) {
if (edge.via != null) {
delete this.body.supportNodes[edge.via.id];
}
edge.disconnect();
delete edges[id];
}
}
this.body.emitter.emit("_dataChanged");
}
/**
* Bezier curves require an anchor point to calculate the smooth flow. These points are nodes. These nodes are invisible but
* are used for the force calculation.
*
* @private
*/
createBezierNodes(specificEdges = this.body.edges) {
var changedData = false;
if (this.options.smoothEdges.enabled == true && this.options.smoothEdges.dynamic == true) {
for (var edgeId in specificEdges) {
if (specificEdges.hasOwnProperty(edgeId)) {
var edge = specificEdges[edgeId];
if (edge.via == null) {
changedData = true;
// TODO: move to nodes
var nodeId = "edgeId:".concat(edge.id);
var node = new Node(
{id:nodeId,
mass:1,
shape:'circle',
image:"",
internalMultiplier:1
},{},{},this.constants);
this.body.supportNodes[nodeId] = node;
edge.via = node;
edge.via.parentEdgeId = edge.id;
edge.positionBezierNode();
}
}
}
}
if (changedData === true) {
this.body.emitter.emit("_dataChanged");
}
}
}
export default EdgesHandler;

+ 3
- 3
lib/network/modules/InteractionHandler.js View File

@ -41,7 +41,7 @@ class InteractionHandler {
dragView: true,
zoomView: true,
hoverEnabled: false,
showNavigationIcons: true,
showNavigationIcons: false,
tooltip: {
delay: 300,
fontColor: 'black',
@ -53,7 +53,7 @@ class InteractionHandler {
}
},
keyboard: {
enabled: true,
enabled: false,
speed: {x: 10, y: 10, zoom: 0.02},
bindToWindow: true
}
@ -486,4 +486,4 @@ class InteractionHandler {
}
}
export {InteractionHandler};
export default InteractionHandler;

+ 421
- 0
lib/network/modules/LayoutEngine.js View File

@ -2,3 +2,424 @@
* Created by Alex on 3/3/2015.
*/
class LayoutEngine {
constructor(body) {
this.body = body;
}
setOptions(options) {
}
positionInitially(nodesArray) {
for (var i = 0; i < nodesArray.length; i++) {
let node = nodesArray[i];
if ((node.xFixed == false || node.yFixed == false) && (node.x === null || node.y === null)) {
var radius = 10 * 0.1*nodesArray.length + 10;
var angle = 2 * Math.PI * Math.random();
if (node.xFixed == false) {node.x = radius * Math.cos(angle);}
if (node.yFixed == false) {node.y = radius * Math.sin(angle);}
}
}
}
_resetLevels() {
for (var nodeId in this.body.nodes) {
if (this.body.nodes.hasOwnProperty(nodeId)) {
var node = this.body.nodes[nodeId];
if (node.preassignedLevel == false) {
node.level = -1;
node.hierarchyEnumerated = false;
}
}
}
}
/**
* This is the main function to layout the nodes in a hierarchical way.
* It checks if the node details are supplied correctly
*
* @private
*/
_setupHierarchicalLayout() {
if (this.constants.hierarchicalLayout.enabled == true && this.nodeIndices.length > 0) {
// get the size of the largest hubs and check if the user has defined a level for a node.
var hubsize = 0;
var node, nodeId;
var definedLevel = false;
var undefinedLevel = false;
for (nodeId in this.body.nodes) {
if (this.body.nodes.hasOwnProperty(nodeId)) {
node = this.body.nodes[nodeId];
if (node.level != -1) {
definedLevel = true;
}
else {
undefinedLevel = true;
}
if (hubsize < node.edges.length) {
hubsize = node.edges.length;
}
}
}
// if the user defined some levels but not all, alert and run without hierarchical layout
if (undefinedLevel == true && definedLevel == true) {
throw new Error("To use the hierarchical layout, nodes require either no predefined levels or levels have to be defined for all nodes.");
this.zoomExtent({duration:0},true,this.constants.clustering.enabled);
if (!this.constants.clustering.enabled) {
this.start();
}
}
else {
// setup the system to use hierarchical method.
this._changeConstants();
// define levels if undefined by the users. Based on hubsize
if (undefinedLevel == true) {
if (this.constants.hierarchicalLayout.layout == "hubsize") {
this._determineLevels(hubsize);
}
else {
this._determineLevelsDirected(false);
}
}
// check the distribution of the nodes per level.
var distribution = this._getDistribution();
// place the nodes on the canvas. This also stablilizes the system. Redraw in started automatically after stabilize.
this._placeNodesByHierarchy(distribution);
}
}
}
/**
* This function places the nodes on the canvas based on the hierarchial distribution.
*
* @param {Object} distribution | obtained by the function this._getDistribution()
* @private
*/
_placeNodesByHierarchy(distribution) {
var nodeId, node;
// start placing all the level 0 nodes first. Then recursively position their branches.
for (var level in distribution) {
if (distribution.hasOwnProperty(level)) {
for (nodeId in distribution[level].nodes) {
if (distribution[level].nodes.hasOwnProperty(nodeId)) {
node = distribution[level].nodes[nodeId];
if (this.constants.hierarchicalLayout.direction == "UD" || this.constants.hierarchicalLayout.direction == "DU") {
if (node.xFixed) {
node.x = distribution[level].minPos;
node.xFixed = false;
distribution[level].minPos += distribution[level].nodeSpacing;
}
}
else {
if (node.yFixed) {
node.y = distribution[level].minPos;
node.yFixed = false;
distribution[level].minPos += distribution[level].nodeSpacing;
}
}
this._placeBranchNodes(node.edges,node.id,distribution,node.level);
}
}
}
}
// stabilize the system after positioning. This function calls zoomExtent.
this._stabilize();
}
/**
* This function get the distribution of levels based on hubsize
*
* @returns {Object}
* @private
*/
_getDistribution() {
var distribution = {};
var nodeId, node, level;
// we fix Y because the hierarchy is vertical, we fix X so we do not give a node an x position for a second time.
// the fix of X is removed after the x value has been set.
for (nodeId in this.body.nodes) {
if (this.body.nodes.hasOwnProperty(nodeId)) {
node = this.body.nodes[nodeId];
node.xFixed = true;
node.yFixed = true;
if (this.constants.hierarchicalLayout.direction == "UD" || this.constants.hierarchicalLayout.direction == "DU") {
node.y = this.constants.hierarchicalLayout.levelSeparation*node.level;
}
else {
node.x = this.constants.hierarchicalLayout.levelSeparation*node.level;
}
if (distribution[node.level] === undefined) {
distribution[node.level] = {amount: 0, nodes: {}, minPos:0, nodeSpacing:0};
}
distribution[node.level].amount += 1;
distribution[node.level].nodes[nodeId] = node;
}
}
// determine the largest amount of nodes of all levels
var maxCount = 0;
for (level in distribution) {
if (distribution.hasOwnProperty(level)) {
if (maxCount < distribution[level].amount) {
maxCount = distribution[level].amount;
}
}
}
// set the initial position and spacing of each nodes accordingly
for (level in distribution) {
if (distribution.hasOwnProperty(level)) {
distribution[level].nodeSpacing = (maxCount + 1) * this.constants.hierarchicalLayout.nodeSpacing;
distribution[level].nodeSpacing /= (distribution[level].amount + 1);
distribution[level].minPos = distribution[level].nodeSpacing - (0.5 * (distribution[level].amount + 1) * distribution[level].nodeSpacing);
}
}
return distribution;
}
/**
* this function allocates nodes in levels based on the recursive branching from the largest hubs.
*
* @param hubsize
* @private
*/
_determineLevels(hubsize) {
var nodeId, node;
// determine hubs
for (nodeId in this.body.nodes) {
if (this.body.nodes.hasOwnProperty(nodeId)) {
node = this.body.nodes[nodeId];
if (node.edges.length == hubsize) {
node.level = 0;
}
}
}
// branch from hubs
for (nodeId in this.body.nodes) {
if (this.body.nodes.hasOwnProperty(nodeId)) {
node = this.body.nodes[nodeId];
if (node.level == 0) {
this._setLevel(1,node.edges,node.id);
}
}
}
}
/**
* this function allocates nodes in levels based on the direction of the edges
*
* @param hubsize
* @private
*/
_determineLevelsDirected() {
var nodeId, node, firstNode;
var minLevel = 10000;
// set first node to source
firstNode = this.body.nodes[this.nodeIndices[0]];
firstNode.level = minLevel;
this._setLevelDirected(minLevel,firstNode.edges,firstNode.id);
// get the minimum level
for (nodeId in this.body.nodes) {
if (this.body.nodes.hasOwnProperty(nodeId)) {
node = this.body.nodes[nodeId];
minLevel = node.level < minLevel ? node.level : minLevel;
}
}
// subtract the minimum from the set so we have a range starting from 0
for (nodeId in this.body.nodes) {
if (this.body.nodes.hasOwnProperty(nodeId)) {
node = this.body.nodes[nodeId];
node.level -= minLevel;
}
}
}
/**
* Since hierarchical layout does not support:
* - smooth curves (based on the physics),
* - clustering (based on dynamic node counts)
*
* We disable both features so there will be no problems.
*
* @private
*/
_changeConstants() {
this.constants.clustering.enabled = false;
this.constants.physics.barnesHut.enabled = false;
this.constants.physics.hierarchicalRepulsion.enabled = true;
this._loadSelectedForceSolver();
if (this.constants.smoothCurves.enabled == true) {
this.constants.smoothCurves.dynamic = false;
}
this._configureSmoothCurves();
var config = this.constants.hierarchicalLayout;
config.levelSeparation = Math.abs(config.levelSeparation);
if (config.direction == "RL" || config.direction == "DU") {
config.levelSeparation *= -1;
}
if (config.direction == "RL" || config.direction == "LR") {
if (this.constants.smoothCurves.enabled == true) {
this.constants.smoothCurves.type = "vertical";
}
}
else {
if (this.constants.smoothCurves.enabled == true) {
this.constants.smoothCurves.type = "horizontal";
}
}
}
/**
* This is a recursively called function to enumerate the branches from the largest hubs and place the nodes
* on a X position that ensures there will be no overlap.
*
* @param edges
* @param parentId
* @param distribution
* @param parentLevel
* @private
*/
_placeBranchNodes(edges, parentId, distribution, parentLevel) {
for (var i = 0; i < edges.length; i++) {
var childNode = null;
if (edges[i].toId == parentId) {
childNode = edges[i].from;
}
else {
childNode = edges[i].to;
}
// if a node is conneceted to another node on the same level (or higher (means lower level))!, this is not handled here.
var nodeMoved = false;
if (this.constants.hierarchicalLayout.direction == "UD" || this.constants.hierarchicalLayout.direction == "DU") {
if (childNode.xFixed && childNode.level > parentLevel) {
childNode.xFixed = false;
childNode.x = distribution[childNode.level].minPos;
nodeMoved = true;
}
}
else {
if (childNode.yFixed && childNode.level > parentLevel) {
childNode.yFixed = false;
childNode.y = distribution[childNode.level].minPos;
nodeMoved = true;
}
}
if (nodeMoved == true) {
distribution[childNode.level].minPos += distribution[childNode.level].nodeSpacing;
if (childNode.edges.length > 1) {
this._placeBranchNodes(childNode.edges,childNode.id,distribution,childNode.level);
}
}
}
}
/**
* this function is called recursively to enumerate the barnches of the largest hubs and give each node a level.
*
* @param level
* @param edges
* @param parentId
* @private
*/
_setLevel(level, edges, parentId) {
for (var i = 0; i < edges.length; i++) {
var childNode = null;
if (edges[i].toId == parentId) {
childNode = edges[i].from;
}
else {
childNode = edges[i].to;
}
if (childNode.level == -1 || childNode.level > level) {
childNode.level = level;
if (childNode.edges.length > 1) {
this._setLevel(level+1, childNode.edges, childNode.id);
}
}
}
}
/**
* this function is called recursively to enumerate the branched of the first node and give each node a level based on edge direction
*
* @param level
* @param edges
* @param parentId
* @private
*/
_setLevelDirected(level, edges, parentId) {
this.body.nodes[parentId].hierarchyEnumerated = true;
var childNode, direction;
for (var i = 0; i < edges.length; i++) {
direction = 1;
if (edges[i].toId == parentId) {
childNode = edges[i].from;
direction = -1;
}
else {
childNode = edges[i].to;
}
if (childNode.level == -1) {
childNode.level = level + direction;
}
}
for (var i = 0; i < edges.length; i++) {
if (edges[i].toId == parentId) {childNode = edges[i].from;}
else {childNode = edges[i].to;}
if (childNode.edges.length > 1 && childNode.hierarchyEnumerated === false) {
this._setLevelDirected(childNode.level, childNode.edges, childNode.id);
}
}
}
/**
* Unfix nodes
*
* @private
*/
_restoreNodes() {
for (var nodeId in this.body.nodes) {
if (this.body.nodes.hasOwnProperty(nodeId)) {
this.body.nodes[nodeId].xFixed = false;
this.body.nodes[nodeId].yFixed = false;
}
}
}
}
export default LayoutEngine;

+ 200
- 0
lib/network/modules/NodesHandler.js View File

@ -0,0 +1,200 @@
/**
* Created by Alex on 3/4/2015.
*/
var util = require("../../util");
var DataSet = require('../../DataSet');
var DataView = require('../../DataView');
var Node = require("./components/nodes/NodeMain");
class NodesHandler {
constructor(body, images, groups, layoutEngine) {
this.body = body;
this.images = images;
this.groups = groups;
this.layoutEngine = layoutEngine;
this.nodesListeners = {
'add': (event, params) => {this.add(params.items);},
'update': (event, params) => {this.update(params.items, params.data);},
'remove': (event, params) => {this.remove(params.items);}
};
var customScalingFunction = function (min,max,total,value) {
if (max == min) {
return 0.5;
}
else {
var scale = 1 / (max - min);
return Math.max(0,(value - min)*scale);
}
};
this.options = {};
this.defaultOptions = {
customScalingFunction: customScalingFunction,
mass: 1,
radiusMin: 10,
radiusMax: 30,
radius: 10,
shape: 'ellipse',
image: undefined,
widthMin: 16, // px
widthMax: 64, // px
fontColor: 'black',
fontSize: 14, // px
fontFace: 'verdana',
fontFill: undefined,
fontStrokeWidth: 0, // px
fontStrokeColor: '#ffffff',
fontDrawThreshold: 3,
scaleFontWithValue: false,
fontSizeMin: 14,
fontSizeMax: 30,
fontSizeMaxVisible: 30,
value: 1,
level: -1,
color: {
border: '#2B7CE9',
background: '#97C2FC',
highlight: {
border: '#2B7CE9',
background: '#D2E5FF'
},
hover: {
border: '#2B7CE9',
background: '#D2E5FF'
}
},
group: undefined,
borderWidth: 1,
borderWidthSelected: undefined
};
util.extend(this.options, this.defaultOptions);
}
setOptions(options) {
}
/**
* Set a data set with nodes for the network
* @param {Array | DataSet | DataView} nodes The data containing the nodes.
* @private
*/
setData(nodes) {
var oldNodesData = this.body.data.nodes;
if (nodes instanceof DataSet || nodes instanceof DataView) {
this.body.data.nodes = nodes;
}
else if (Array.isArray(nodes)) {
this.body.data.nodes = new DataSet();
this.body.data.nodes.add(nodes);
}
else if (!nodes) {
this.body.data.nodes = new DataSet();
}
else {
throw new TypeError('Array or DataSet expected');
}
if (oldNodesData) {
// unsubscribe from old dataset
util.forEach(this.nodesListeners, function (callback, event) {
oldNodesData.off(event, callback);
});
}
// remove drawn nodes
this.body.nodes = {};
if (this.body.data.nodes) {
// subscribe to new dataset
var me = this;
util.forEach(this.nodesListeners, function (callback, event) {
me.body.data.nodes.on(event, callback);
});
// draw all new nodes
var ids = this.body.data.nodes.getIds();
this.add(ids);
}
this.body.emitter.emit("_dataChanged");
}
/**
* Add nodes
* @param {Number[] | String[]} ids
* @private
*/
add(ids) {
var id;
var newNodes = [];
for (var i = 0; i < ids.length; i++) {
id = ids[i];
var data = this.body.data.nodes.get(id);
var node = new Node(data, this.images, this.groups, this.options);
newNodes.push(node);
this.body.nodes[id] = node; // note: this may replace an existing node
}
this.layoutEngine.positionInitially(newNodes);
this.body.emitter.emit("_dataChanged");
}
/**
* Update existing nodes, or create them when not yet existing
* @param {Number[] | String[]} ids
* @private
*/
update(ids, changedData) {
var nodes = this.body.nodes;
var dataChanged = false;
for (var i = 0; i < ids.length; i++) {
var id = ids[i];
var node = nodes[id];
var data = changedData[i];
if (node !== undefined) {
// update node
node.setProperties(data, this.constants);
}
else {
dataChanged = true;
// create node
node = new Node(properties, this.images, this.groups, this.constants);
nodes[id] = node;
}
}
if (dataChanged) {
this.body.emitter.emit("_dataChanged");
}
else {
this.body.emitter.emit("_dataUpdated");
}
}
/**
* Remove existing nodes. If nodes do not exist, the method will just ignore it.
* @param {Number[] | String[]} ids
* @private
*/
remove(ids) {
var nodes = this.body.nodes;
for (let i = 0; i < ids.length; i++) {
var id = ids[i];
delete nodes[id];
}
this.body.emitter.emit("_dataChanged");
}
}
export default NodesHandler;

+ 13
- 4
lib/network/modules/PhysicsEngine.js View File

@ -26,6 +26,7 @@ class PhysicsEngine {
this.stabilized = false;
this.stabilizationIterations = 0;
this.ready = false; // will be set to true if the stabilize
// default options
this.options = {};
@ -66,8 +67,14 @@ class PhysicsEngine {
}
util.extend(this.options, this.defaultOptions);
this.body.emitter.on("stabilize", () => {this.startSimulation();});
this.body.emitter.on("startSimulation", () => {this.stabilized = false; this.runSimulation();});
this.body.emitter.on("initPhysics", () => {this.initPhysics();});
this.body.emitter.on("resetPhysics", () => {this.stopSimulation(); this.ready = false;});
this.body.emitter.on("startSimulation", () => {
if (this.ready === true) {
this.stabilized = false;
this.runSimulation();
}
})
this.body.emitter.on("stopSimulation", () => {this.stopSimulation();});
}
@ -106,12 +113,14 @@ class PhysicsEngine {
this.modelOptions = options;
}
startSimulation() {
initPhysics() {
this.stabilized = false;
this.ready = true;
if (this.options.stabilization.enabled === true) {
this.stabilize();
}
else {
this.body.emitter.emit("zoomExtent", {duration:0}, true)
this.runSimulation();
}
}
@ -425,4 +434,4 @@ class PhysicsEngine {
}
export {PhysicsEngine};
export default PhysicsEngine;

+ 9
- 4
lib/network/modules/SelectionHandler.js View File

@ -2,14 +2,14 @@
* Created by Alex on 2/27/2015.
*/
var Node = require("../Node");
var Node = require("./components/nodes/NodeMain");
var util = require('../../util');
class SelectionHandler {
constructor(body, canvas) {
this.body = body;
this.canvas = canvas;
this.selectionObj = {nodes:[], edges:[]};
this.selectionObj = {nodes: [], edges: []};
this.options = {};
this.defaultOptions = {
@ -17,8 +17,13 @@ class SelectionHandler {
selectConnectedEdges: true
}
util.extend(this.options, this.defaultOptions);
this.body.emitter.on("_dataChanged", () => {
this.updateSelection()
});
}
setOptions(options) {
if (options !== undefined) {
util.deepExtend(this.options, options);
@ -592,7 +597,7 @@ class SelectionHandler {
* Validate the selection: remove ids of nodes which no longer exist
* @private
*/
_updateSelection() {
updateSelection() {
for (var nodeId in this.selectionObj.nodes) {
if (this.selectionObj.nodes.hasOwnProperty(nodeId)) {
if (!this.body.nodes.hasOwnProperty(nodeId)) {
@ -610,4 +615,4 @@ class SelectionHandler {
}
}
export {SelectionHandler};
export default SelectionHandler;

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

@ -124,12 +124,7 @@ class View {
range = this._getRange(options.nodes);
var numberOfNodes = this.body.nodeIndices.length;
if (this.options.smoothCurves == true) {
zoomLevel = 12.662 / (numberOfNodes + 7.4147) + 0.0964822; // this is obtained from fitting a dataset from 5 points with scale levels that looked good.
}
else {
zoomLevel = 30.5062972 / (numberOfNodes + 19.93597763) + 0.08413486; // this is obtained from fitting a dataset from 5 points with scale levels that looked good.
}
zoomLevel = 12.662 / (numberOfNodes + 7.4147) + 0.0964822; // this is obtained from fitting a dataset from 5 points with scale levels that looked good.
// correct for larger canvasses.
var factor = Math.min(this.canvas.frame.canvas.clientWidth / 600, this.canvas.frame.canvas.clientHeight / 600);
@ -331,4 +326,4 @@ class View {
}
export {View};
export default View;

+ 5
- 4
lib/network/modules/components/NavigationHandler.js View File

@ -18,6 +18,7 @@ class NavigationHandler {
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.body.emitter.on("destroy", () => {if (this.keycharm !== undefined) {this.keycharm.destroy();}});
this.options = {}
}
@ -141,10 +142,10 @@ class NavigationHandler {
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;}
_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;}

lib/network/Edge.js → lib/network/modules/components/edges/EdgeMain.js View File

@ -1,5 +1,5 @@
var util = require('../util');
var Node = require('./Node');
var util = require('../../../../util');
var Node = require('../nodes/NodeMain');
/**
* @class Edge
@ -16,15 +16,14 @@ var Node = require('./Node');
* @param {Object} constants An object with default values for
* example for the color
*/
function Edge (properties, body, networkConstants) {
function Edge (properties, body, globalOptions) {
if (body === undefined) {
throw "No body provided";
}
var fields = ['edges'];
var constants = util.selectiveBridgeObject(fields,networkConstants);
this.options = constants.edges;
this.options['smoothCurves'] = networkConstants['smoothCurves'];
this.options = util.bridgeObject(globalOptions);
console.log(this.options)
// TODO: fix this
//this.options['smoothCurves'] = networkConstants['smoothCurves'];
this.body = body;
// initialize variables

lib/network/Node.js → lib/network/modules/components/nodes/NodeMain.js View File

@ -1,4 +1,4 @@
var util = require('../util');
var util = require('../../../../util');
/**
* @class Node
@ -25,9 +25,8 @@ var util = require('../util');
* example for the color
*
*/
function Node(properties, imagelist, grouplist, networkConstants) {
var constants = util.selectiveBridgeObject(['nodes'],networkConstants);
this.options = constants.nodes;
function Node(properties, imagelist, grouplist, globalOptions) {
this.options = util.bridgeObject(globalOptions);
this.selected = false;
this.hover = false;
@ -42,7 +41,7 @@ function Node(properties, imagelist, grouplist, networkConstants) {
this.yFixed = false;
this.horizontalAlignLeft = true; // these are for the navigation controls
this.verticalAlignTop = true; // these are for the navigation controls
this.baseRadiusValue = networkConstants.nodes.radius;
this.baseRadiusValue = this.options.radius;
this.radiusFixed = false;
this.level = -1;
this.preassignedLevel = false;
@ -58,12 +57,10 @@ function Node(properties, imagelist, grouplist, networkConstants) {
this.y = null;
this.predefinedPosition = false; // used to check if initial zoomExtent should just take the range or approximate
// used for reverting to previous position on stabilization
this.previousState = {vx:0,vy:0,x:0,y:0};
this.fixedData = {x:null,y:null};
this.setProperties(properties, constants);
// TODO: global options should not be needed here.
this.setProperties(properties, globalOptions);
// variables to tell the node about the network.
this.networkScaleInv = 1;

+ 7
- 0
lib/util.js View File

@ -14,6 +14,13 @@ exports.isNumber = function(object) {
};
exports.recursiveDOMDelete = function(DOMobject) {
while (DOMobject.hasChildNodes() == true) {
exports.recursiveDOMDelete(DOMobject.firstChild);
DOMobject.removeChild(DOMobject.firstChild);
}
};
/**
* this function gives you a range between 0 and 1 based on the min and max values in the set, the total sum of all values and the current value.
*

Loading…
Cancel
Save