Browse Source

first modularizing experiment. Made body object containing data nodes, emitter, support functions. ClusterEngine is the first module.

flowchartTest
Alex de Mulder 9 years ago
parent
commit
093cb73de5
13 changed files with 7703 additions and 7269 deletions
  1. +6747
    -6963
      dist/vis.js
  2. +1
    -1
      dist/vis.min.css
  3. +4
    -4
      examples/network/39_newClustering.html
  4. +6
    -6
      lib/network/Edge.js
  5. +182
    -133
      lib/network/Network.js
  6. +0
    -15
      lib/network/mixins/ClusterMixin.js
  7. +27
    -27
      lib/network/mixins/HierarchicalLayoutMixin.js
  8. +33
    -34
      lib/network/mixins/ManipulationMixin.js
  9. +6
    -6
      lib/network/mixins/MixinLoader.js
  10. +52
    -52
      lib/network/mixins/SectorsMixin.js
  11. +8
    -8
      lib/network/mixins/SelectionMixin.js
  12. +17
    -20
      lib/network/mixins/physics/PhysicsMixin.js
  13. +620
    -0
      lib/network/modules/Clustering.js

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


+ 1
- 1
dist/vis.min.css
File diff suppressed because it is too large
View File


+ 4
- 4
examples/network/39_newClustering.html View File

@ -77,8 +77,8 @@
}
// network.clusterByNodeData(clusterOptionsByData)
// network.clusterOutliers({clusterNodeProperties: {borderWidth:8}})
network.clusterByConnection(2, clusterOptions);
network.clustering.clusterOutliers({clusterNodeProperties: {borderWidth:8}})
// network.clusterByConnection(2, clusterOptions);
// network.clusterByConnection(9, {
// joinCondition:function(parentOptions,childOptions) {return true;},
@ -89,8 +89,8 @@
// });
network.on("select", function(params) {
if (params.nodes.length == 1) {
if (network.isCluster(params.nodes[0]) == true) {
network.openCluster(params.nodes[0])
if (network.clustering.isCluster(params.nodes[0]) == true) {
network.clustering.openCluster(params.nodes[0])
}
}
})

+ 6
- 6
lib/network/Edge.js View File

@ -16,9 +16,9 @@ var Node = require('./Node');
* @param {Object} constants An object with default values for
* example for the color
*/
function Edge (properties, network, networkConstants) {
if (!network) {
throw "No network provided";
function Edge (properties, body, networkConstants) {
if (body === undefined) {
throw "No body provided";
}
var fields = ['edges','physics'];
var constants = util.selectiveBridgeObject(fields,networkConstants);
@ -28,7 +28,7 @@ function Edge (properties, network, networkConstants) {
this.options['smoothCurves'] = networkConstants['smoothCurves'];
this.network = network;
this.body = body;
// initialize variables
this.id = undefined;
@ -135,8 +135,8 @@ Edge.prototype.setProperties = function(properties) {
Edge.prototype.connect = function () {
this.disconnect();
this.from = this.network.nodes[this.fromId] || null;
this.to = this.network.nodes[this.toId] || null;
this.from = this.body.nodes[this.fromId] || null;
this.to = this.body.nodes[this.toId] || null;
this.connected = (this.from !== null && this.to !== null);
if (this.connected === true) {

+ 182
- 133
lib/network/Network.js View File

@ -19,6 +19,9 @@ var locales = require('./locales');
// Load custom shapes into CanvasRenderingContext2D
require('./shapes');
import { ClusterEngine } from './modules/Clustering'
/**
* @constructor Network
* Create a network visualization, displaying nodes and edges.
@ -61,6 +64,9 @@ function Network (container, data, options) {
return Math.max(0,(value - min)*scale);
}
};
// set constant values
this.defaultOptions = {
nodes: {
@ -223,9 +229,31 @@ function Network (container, data, options) {
useDefaultGroups: true
};
this.constants = util.extend({}, this.defaultOptions);
// containers for nodes and edges
this.body = {
sectors: {},
nodeIndices: [],
nodes: {},
edges: {},
data: {
nodes: null, // A DataSet or DataView
edges: null // A DataSet or DataView
},
functions:{
createNode: this._createNode.bind(this),
createEdge: this._createEdge.bind(this)
},
emitter: {
on: this.on.bind(this),
off: this.off.bind(this),
emit: this.emit.bind(this)
}
};
this.pixelRatio = 1;
this.hoverObj = {nodes:{},edges:{}};
this.controlNodesActive = false;
this.navigationHammers = [];
@ -246,11 +274,11 @@ function Network (container, data, options) {
this.redrawRequested = false;
// Node variables
var network = this;
var me = this;
this.groups = new Groups(); // object with groups
this.images = new Images(); // object with images
this.images.setOnloadCallback(function (status) {
network._requestRedraw();
me._requestRedraw();
});
// keyboard navigation variables
@ -272,7 +300,6 @@ function Network (container, data, options) {
// load the selection system. (mandatory, required by Network)
this._loadHierarchySystem();
// apply options
this._setTranslation(this.frame.clientWidth / 2, this.frame.clientHeight / 2);
this._setScale(1);
@ -286,20 +313,7 @@ function Network (container, data, options) {
this.stabilizationIterations = null;
this.draggingNodes = false;
// containers for nodes and edges
this.body = {
calculationNodes: {},
calculationNodeIndices: {},
nodeIndices: {},
nodes: {},
edges: {}
}
this.calculationNodes = {};
this.calculationNodeIndices = [];
this.nodeIndices = []; // array with all the indices of the nodes. Used to speed up forces calculation
this.nodes = {}; // object with Node objects
this.edges = {}; // object with Edge objects
this.clustering = new ClusterEngine(this.body);
// position and scale variables and objects
this.canvasTopLeft = {"x": 0,"y": 0}; // coordinates of the top left of the canvas. they will be set during _redraw.
@ -307,37 +321,33 @@ function Network (container, data, options) {
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
// datasets or dataviews
this.nodesData = null; // A DataSet or DataView
this.edgesData = null; // A DataSet or DataView
// create event listeners used to subscribe on the DataSets of the nodes and edges
this.nodesListeners = {
'add': function (event, params) {
network._addNodes(params.items);
network.start();
me._addNodes(params.items);
me.start();
},
'update': function (event, params) {
network._updateNodes(params.items, params.data);
network.start();
me._updateNodes(params.items, params.data);
me.start();
},
'remove': function (event, params) {
network._removeNodes(params.items);
network.start();
me._removeNodes(params.items);
me.start();
}
};
this.edgesListeners = {
'add': function (event, params) {
network._addEdges(params.items);
network.start();
me._addEdges(params.items);
me.start();
},
'update': function (event, params) {
network._updateEdges(params.items);
network.start();
me._updateEdges(params.items);
me.start();
},
'remove': function (event, params) {
network._removeEdges(params.items);
network.start();
me._removeEdges(params.items);
me.start();
}
};
@ -363,12 +373,35 @@ function Network (container, data, options) {
this.initializing = false;
}
var me = this;
this.on("_dataChanged", function () {
console.log("here")
me._updateNodeIndexList();
me._updateCalculationNodes();
me._markAllEdgesAsDirty();
if (me.initializing !== true) {
me.moving = true;
me.start();
}
})
this.on("_newEdgesCreated", this._createBezierNodes.bind(this));
this.on("stabilizationIterationsDone", function () {this.initializing = false; this.start();}.bind(this));
}
// Extend Network with an Emitter mixin
Emitter(Network.prototype);
Network.prototype._createNode = function(properties) {
return new Node(properties, this.images, this.groups, this.constants)
}
Network.prototype._createEdge = function(properties) {
return new Edge(properties, this.body, this.constants)
}
/**
* Determine if the browser requires a setTimeout or a requestAnimationFrame. This was required because
* some implementations (safari and IE9) did not support requestAnimationFrame
@ -420,7 +453,7 @@ Network.prototype._getRange = function(specificNodes) {
var minY = 1e9, maxY = -1e9, minX = 1e9, maxX = -1e9, node;
if (specificNodes.length > 0) {
for (var i = 0; i < specificNodes.length; i++) {
node = this.nodes[specificNodes[i]];
node = this.body.nodes[specificNodes[i]];
if (minX > (node.boundingBox.left)) {
minX = node.boundingBox.left;
}
@ -436,9 +469,9 @@ Network.prototype._getRange = function(specificNodes) {
}
}
else {
for (var nodeId in this.nodes) {
if (this.nodes.hasOwnProperty(nodeId)) {
node = this.nodes[nodeId];
for (var nodeId in this.body.nodes) {
if (this.body.nodes.hasOwnProperty(nodeId)) {
node = this.body.nodes[nodeId];
if (minX > (node.boundingBox.left)) {
minX = node.boundingBox.left;
}
@ -495,22 +528,22 @@ Network.prototype.zoomExtent = function(options, initialZoom, disableStart) {
if (initialZoom == true) {
// check if more than half of the nodes have a predefined position. If so, we use the range, not the approximation.
var positionDefined = 0;
for (var nodeId in this.nodes) {
if (this.nodes.hasOwnProperty(nodeId)) {
var node = this.nodes[nodeId];
for (var nodeId in this.body.nodes) {
if (this.body.nodes.hasOwnProperty(nodeId)) {
var node = this.body.nodes[nodeId];
if (node.predefinedPosition == true) {
positionDefined += 1;
}
}
}
if (positionDefined > 0.5 * this.nodeIndices.length) {
if (positionDefined > 0.5 * this.body.nodeIndices.length) {
this.zoomExtent(options,false,disableStart);
return;
}
range = this._getRange(options.nodes);
var numberOfNodes = this.nodeIndices.length;
var numberOfNodes = this.body.nodeIndices.length;
if (this.constants.smoothCurves == true) {
if (this.constants.clustering.enabled == true &&
numberOfNodes >= this.constants.clustering.initialMaxNodes) {
@ -568,12 +601,12 @@ Network.prototype.zoomExtent = function(options, initialZoom, disableStart) {
/**
* Update the this.nodeIndices with the most recent node index list
* Update the this.body.nodeIndices with the most recent node index list
* @private
*/
Network.prototype._updateNodeIndexList = function() {
this._clearNodeIndexList();
this.nodeIndices = Object.keys(this.nodes);
this.body.nodeIndices = Object.keys(this.body.nodes);
};
@ -1446,7 +1479,7 @@ Network.prototype._checkShowPopup = function (pointer) {
if (this.popupObj == undefined) {
// search the nodes for overlap, select the top one in case of multiple nodes
var nodes = this.nodes;
var nodes = this.body.nodes;
var overlappingNodes = [];
for (id in nodes) {
if (nodes.hasOwnProperty(id)) {
@ -1462,7 +1495,7 @@ Network.prototype._checkShowPopup = function (pointer) {
if (overlappingNodes.length > 0) {
// if there are overlapping nodes, select the last one, this is the
// one which is drawn on top of the others
this.popupObj = this.nodes[overlappingNodes[overlappingNodes.length - 1]];
this.popupObj = this.body.nodes[overlappingNodes[overlappingNodes.length - 1]];
// if you hover over a node, the title of the edge is not supposed to be shown.
nodeUnderCursor = true;
}
@ -1470,7 +1503,7 @@ Network.prototype._checkShowPopup = function (pointer) {
if (this.popupObj === undefined && nodeUnderCursor == false) {
// search the edges for overlap
var edges = this.edges;
var edges = this.body.edges;
var overlappingEdges = [];
for (id in edges) {
if (edges.hasOwnProperty(id)) {
@ -1483,7 +1516,7 @@ Network.prototype._checkShowPopup = function (pointer) {
}
if (overlappingEdges.length > 0) {
this.popupObj = this.edges[overlappingEdges[overlappingEdges.length - 1]];
this.popupObj = this.body.edges[overlappingEdges[overlappingEdges.length - 1]];
popupType = "edge";
}
}
@ -1530,7 +1563,7 @@ Network.prototype._checkHidePopup = function (pointer) {
var stillOnObj = false;
if (this.popup.popupTargetType == 'node') {
stillOnObj = this.nodes[this.popup.popupTargetId].isOverlappingWith(pointerObj);
stillOnObj = this.body.nodes[this.popup.popupTargetId].isOverlappingWith(pointerObj);
if (stillOnObj === true) {
var overNode = this._getNodeAt(pointer);
stillOnObj = overNode.id == this.popup.popupTargetId;
@ -1538,7 +1571,7 @@ Network.prototype._checkHidePopup = function (pointer) {
}
else {
if (this._getNodeAt(pointer) === null) {
stillOnObj = this.edges[this.popup.popupTargetId].isOverlappingWith(pointerObj);
stillOnObj = this.body.edges[this.popup.popupTargetId].isOverlappingWith(pointerObj);
}
}
@ -1601,17 +1634,17 @@ Network.prototype.setSize = function(width, height) {
* @private
*/
Network.prototype._setNodes = function(nodes) {
var oldNodesData = this.nodesData;
var oldNodesData = this.body.data.nodes;
if (nodes instanceof DataSet || nodes instanceof DataView) {
this.nodesData = nodes;
this.body.data.nodes = nodes;
}
else if (Array.isArray(nodes)) {
this.nodesData = new DataSet();
this.nodesData.add(nodes);
this.body.data.nodes = new DataSet();
this.body.data.nodes.add(nodes);
}
else if (!nodes) {
this.nodesData = new DataSet();
this.body.data.nodes = new DataSet();
}
else {
throw new TypeError('Array or DataSet expected');
@ -1625,17 +1658,17 @@ Network.prototype._setNodes = function(nodes) {
}
// remove drawn nodes
this.nodes = {};
this.body.nodes = {};
if (this.nodesData) {
if (this.body.data.nodes) {
// subscribe to new dataset
var me = this;
util.forEach(this.nodesListeners, function (callback, event) {
me.nodesData.on(event, callback);
me.body.data.nodes.on(event, callback);
});
// draw all new nodes
var ids = this.nodesData.getIds();
var ids = this.body.data.nodes.getIds();
this._addNodes(ids);
}
this._updateSelection();
@ -1650,9 +1683,9 @@ Network.prototype._addNodes = function(ids) {
var id;
for (var i = 0, len = ids.length; i < len; i++) {
id = ids[i];
var data = this.nodesData.get(id);
var data = this.body.data.nodes.get(id);
var node = new Node(data, this.images, this.groups, this.constants);
this.nodes[id] = node; // note: this may replace an existing node
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();
@ -1669,7 +1702,7 @@ Network.prototype._addNodes = function(ids) {
}
this._updateCalculationNodes();
this._reconnectEdges();
this._updateValueRange(this.nodes);
this._updateValueRange(this.body.nodes);
};
/**
@ -1678,7 +1711,7 @@ Network.prototype._addNodes = function(ids) {
* @private
*/
Network.prototype._updateNodes = function(ids,changedData) {
var nodes = this.nodes;
var nodes = this.body.nodes;
for (var i = 0, len = ids.length; i < len; i++) {
var id = ids[i];
var node = nodes[id];
@ -1705,8 +1738,8 @@ Network.prototype._updateNodes = function(ids,changedData) {
Network.prototype._markAllEdgesAsDirty = function() {
for (var edgeId in this.edges) {
this.edges[edgeId].colorDirty = true;
for (var edgeId in this.body.edges) {
this.body.edges[edgeId].colorDirty = true;
}
}
@ -1716,13 +1749,13 @@ Network.prototype._markAllEdgesAsDirty = function() {
* @private
*/
Network.prototype._removeNodes = function(ids) {
var nodes = this.nodes;
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.nodes[ids[i]].unselect();
this._removeFromSelection(this.nodes[ids[i]]);
this.body.nodes[ids[i]].unselect();
this._removeFromSelection(this.body.nodes[ids[i]]);
}
}
@ -1751,17 +1784,17 @@ Network.prototype._removeNodes = function(ids) {
* @private
*/
Network.prototype._setEdges = function(edges) {
var oldEdgesData = this.edgesData;
var oldEdgesData = this.body.data.edges;
if (edges instanceof DataSet || edges instanceof DataView) {
this.edgesData = edges;
this.body.data.edges = edges;
}
else if (Array.isArray(edges)) {
this.edgesData = new DataSet();
this.edgesData.add(edges);
this.body.data.edges = new DataSet();
this.body.data.edges.add(edges);
}
else if (!edges) {
this.edgesData = new DataSet();
this.body.data.edges = new DataSet();
}
else {
throw new TypeError('Array or DataSet expected');
@ -1775,17 +1808,17 @@ Network.prototype._setEdges = function(edges) {
}
// remove drawn edges
this.edges = {};
this.body.edges = {};
if (this.edgesData) {
if (this.body.data.edges) {
// subscribe to new dataset
var me = this;
util.forEach(this.edgesListeners, function (callback, event) {
me.edgesData.on(event, callback);
me.body.data.edges.on(event, callback);
});
// draw all new nodes
var ids = this.edgesData.getIds();
var ids = this.body.data.edges.getIds();
this._addEdges(ids);
}
@ -1798,8 +1831,8 @@ Network.prototype._setEdges = function(edges) {
* @private
*/
Network.prototype._addEdges = function (ids) {
var edges = this.edges,
edgesData = this.edgesData;
var edges = this.body.edges,
edgesData = this.body.data.edges;
for (var i = 0, len = ids.length; i < len; i++) {
var id = ids[i];
@ -1810,7 +1843,7 @@ Network.prototype._addEdges = function (ids) {
}
var data = edgesData.get(id, {"showInternalIds" : true});
edges[id] = new Edge(data, this, this.constants);
edges[id] = new Edge(data, this.body, this.constants);
}
this.moving = true;
this._updateValueRange(edges);
@ -1828,8 +1861,8 @@ Network.prototype._addEdges = function (ids) {
* @private
*/
Network.prototype._updateEdges = function (ids) {
var edges = this.edges,
edgesData = this.edgesData;
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];
@ -1838,13 +1871,13 @@ Network.prototype._updateEdges = function (ids) {
if (edge) {
// update edge
edge.disconnect();
edge.setProperties(data, this.constants);
edge.setProperties(data);
edge.connect();
}
else {
// create edge
edge = new Edge(data, this, this.constants);
this.edges[id] = edge;
edge = new Edge(data, this.body, this.constants);
this.body.edges[id] = edge;
}
}
@ -1863,7 +1896,7 @@ Network.prototype._updateEdges = function (ids) {
* @private
*/
Network.prototype._removeEdges = function (ids) {
var edges = this.edges;
var edges = this.body.edges;
// remove from selection
for (var i = 0, len = ids.length; i < len; i++) {
@ -1878,7 +1911,7 @@ Network.prototype._removeEdges = function (ids) {
var edge = edges[id];
if (edge) {
if (edge.via != null) {
delete this.sectors['support']['nodes'][edge.via.id];
delete this.body.sectors['support']['nodes'][edge.via.id];
}
edge.disconnect();
delete edges[id];
@ -1900,8 +1933,8 @@ Network.prototype._removeEdges = function (ids) {
*/
Network.prototype._reconnectEdges = function() {
var id,
nodes = this.nodes,
edges = this.edges;
nodes = this.body.nodes,
edges = this.body.edges;
for (id in nodes) {
if (nodes.hasOwnProperty(id)) {
nodes[id].edges = [];
@ -2168,7 +2201,7 @@ Network.prototype._drawNodes = function(ctx,alwaysShow) {
}
// first draw the unselected nodes
var nodes = this.nodes;
var nodes = this.body.nodes;
var selected = [];
for (var id in nodes) {
@ -2200,7 +2233,7 @@ Network.prototype._drawNodes = function(ctx,alwaysShow) {
* @private
*/
Network.prototype._drawEdges = function(ctx) {
var edges = this.edges;
var edges = this.body.edges;
for (var id in edges) {
if (edges.hasOwnProperty(id)) {
var edge = edges[id];
@ -2219,7 +2252,7 @@ Network.prototype._drawEdges = function(ctx) {
* @private
*/
Network.prototype._drawControlNodes = function(ctx) {
var edges = this.edges;
var edges = this.body.edges;
for (var id in edges) {
if (edges.hasOwnProperty(id)) {
edges[id]._drawControlNodes(ctx);
@ -2276,7 +2309,7 @@ Network.prototype._finalizeStabilization = function() {
* @private
*/
Network.prototype._freezeDefinedNodes = function() {
var nodes = this.nodes;
var nodes = this.body.nodes;
for (var id in nodes) {
if (nodes.hasOwnProperty(id)) {
if (nodes[id].x != null && nodes[id].y != null) {
@ -2295,7 +2328,7 @@ Network.prototype._freezeDefinedNodes = function() {
* @private
*/
Network.prototype._restoreFrozenNodes = function() {
var nodes = this.nodes;
var nodes = this.body.nodes;
for (var id in nodes) {
if (nodes.hasOwnProperty(id)) {
if (nodes[id].fixedData.x != null) {
@ -2314,7 +2347,7 @@ Network.prototype._restoreFrozenNodes = function() {
* @private
*/
Network.prototype._isMoving = function(vmin) {
var nodes = this.nodes;
var nodes = this.body.nodes;
for (var id in nodes) {
if (nodes[id] !== undefined) {
if (nodes[id].isMoving(vmin) == true) {
@ -2334,7 +2367,7 @@ Network.prototype._isMoving = function(vmin) {
*/
Network.prototype._discreteStepNodes = function() {
var interval = this.physicsDiscreteStepsize;
var nodes = this.nodes;
var nodes = this.body.nodes;
var nodeId;
var nodesPresent = false;
@ -2369,7 +2402,7 @@ Network.prototype._discreteStepNodes = function() {
Network.prototype._revertPhysicsState = function() {
var nodes = this.nodes;
var nodes = this.body.nodes;
for (var nodeId in nodes) {
if (nodes.hasOwnProperty(nodeId)) {
nodes[nodeId].revertPosition();
@ -2567,20 +2600,20 @@ Network.prototype._configureSmoothCurves = function(disableStart) {
if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
this._createBezierNodes();
// cleanup unused support nodes
for (var nodeId in this.sectors['support']['nodes']) {
if (this.sectors['support']['nodes'].hasOwnProperty(nodeId)) {
if (this.edges[this.sectors['support']['nodes'][nodeId].parentEdgeId] === undefined) {
delete this.sectors['support']['nodes'][nodeId];
for (var nodeId in this.body.sectors['support']['nodes']) {
if (this.body.sectors['support']['nodes'].hasOwnProperty(nodeId)) {
if (this.body.edges[this.body.sectors['support']['nodes'][nodeId].parentEdgeId] === undefined) {
delete this.body.sectors['support']['nodes'][nodeId];
}
}
}
}
else {
// delete the support nodes
this.sectors['support']['nodes'] = {};
for (var edgeId in this.edges) {
if (this.edges.hasOwnProperty(edgeId)) {
this.edges[edgeId].via = null;
this.body.sectors['support']['nodes'] = {};
for (var edgeId in this.body.edges) {
if (this.body.edges.hasOwnProperty(edgeId)) {
this.body.edges[edgeId].via = null;
}
}
}
@ -2601,8 +2634,9 @@ Network.prototype._configureSmoothCurves = function(disableStart) {
* @private
*/
Network.prototype._createBezierNodes = function(specificEdges) {
console.log('specifics', specificEdges)
if (specificEdges === undefined) {
specificEdges = this.edges;
specificEdges = this.body.edges;
}
if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
for (var edgeId in specificEdges) {
@ -2610,14 +2644,14 @@ Network.prototype._createBezierNodes = function(specificEdges) {
var edge = specificEdges[edgeId];
if (edge.via == null) {
var nodeId = "edgeId:".concat(edge.id);
this.sectors['support']['nodes'][nodeId] = new Node(
this.body.sectors['support']['nodes'][nodeId] = new Node(
{id:nodeId,
mass:1,
shape:'circle',
image:"",
internalMultiplier:1
},{},{},this.constants);
edge.via = this.sectors['support']['nodes'][nodeId];
edge.via = this.body.sectors['support']['nodes'][nodeId];
edge.via.parentEdgeId = edge.id;
edge.positionBezierNode();
}
@ -2652,17 +2686,17 @@ Network.prototype.storePosition = function() {
*/
Network.prototype.storePositions = function() {
var dataArray = [];
for (var nodeId in this.nodes) {
if (this.nodes.hasOwnProperty(nodeId)) {
var node = this.nodes[nodeId];
var allowedToMoveX = !this.nodes.xFixed;
var allowedToMoveY = !this.nodes.yFixed;
if (this.nodesData._data[nodeId].x != Math.round(node.x) || this.nodesData._data[nodeId].y != Math.round(node.y)) {
for (var nodeId in this.body.nodes) {
if (this.body.nodes.hasOwnProperty(nodeId)) {
var node = this.body.nodes[nodeId];
var allowedToMoveX = !this.body.nodes.xFixed;
var allowedToMoveY = !this.body.nodes.yFixed;
if (this.body.data.nodes._data[nodeId].x != Math.round(node.x) || this.body.data.nodes._data[nodeId].y != Math.round(node.y)) {
dataArray.push({id:nodeId,x:Math.round(node.x),y:Math.round(node.y),allowedToMoveX:allowedToMoveX,allowedToMoveY:allowedToMoveY});
}
}
}
this.nodesData.update(dataArray);
this.body.data.nodes.update(dataArray);
};
/**
@ -2673,23 +2707,23 @@ Network.prototype.getPositions = function(ids) {
if (ids !== undefined) {
if (Array.isArray(ids) == true) {
for (var i = 0; i < ids.length; i++) {
if (this.nodes[ids[i]] !== undefined) {
var node = this.nodes[ids[i]];
if (this.body.nodes[ids[i]] !== undefined) {
var node = this.body.nodes[ids[i]];
dataArray[ids[i]] = {x: Math.round(node.x), y: Math.round(node.y)};
}
}
}
else {
if (this.nodes[ids] !== undefined) {
var node = this.nodes[ids];
if (this.body.nodes[ids] !== undefined) {
var node = this.body.nodes[ids];
dataArray[ids] = {x: Math.round(node.x), y: Math.round(node.y)};
}
}
}
else {
for (var nodeId in this.nodes) {
if (this.nodes.hasOwnProperty(nodeId)) {
var node = this.nodes[nodeId];
for (var nodeId in this.body.nodes) {
if (this.body.nodes.hasOwnProperty(nodeId)) {
var node = this.body.nodes[nodeId];
dataArray[nodeId] = {x: Math.round(node.x), y: Math.round(node.y)};
}
}
@ -2706,11 +2740,11 @@ Network.prototype.getPositions = function(ids) {
* @param {Number} [options]
*/
Network.prototype.focusOnNode = function (nodeId, options) {
if (this.nodes.hasOwnProperty(nodeId)) {
if (this.body.nodes.hasOwnProperty(nodeId)) {
if (options === undefined) {
options = {};
}
var nodePosition = {x: this.nodes[nodeId].x, y: this.nodes[nodeId].y};
var nodePosition = {x: this.body.nodes[nodeId].x, y: this.body.nodes[nodeId].y};
options.position = nodePosition;
options.lockedOnNode = nodeId;
@ -2821,7 +2855,7 @@ Network.prototype.animateView = function (options) {
* @private
*/
Network.prototype._lockedRedraw = function () {
var nodePosition = {x: this.nodes[this.lockedOnNodeId].x, y: this.nodes[this.lockedOnNodeId].y};
var nodePosition = {x: this.body.nodes[this.lockedOnNodeId].x, y: this.body.nodes[this.lockedOnNodeId].y};
var viewCenter = this.DOMtoCanvas({x: 0.5 * this.frame.canvas.clientWidth, y: 0.5 * this.frame.canvas.clientHeight});
var distanceFromCenter = { // offset from view, distance view has to change by these x and y to center the node
x: viewCenter.x - nodePosition.x,
@ -2909,6 +2943,21 @@ Network.prototype.getScale = function () {
};
/**
* Check if a node is a cluster.
* @param nodeId
* @returns {*}
*/
Network.prototype.isCluster = function(nodeId) {
if (this.body.nodes[nodeId] !== undefined) {
return this.body.nodes[nodeId].isCluster;
}
else {
console.log("Node does not exist.")
return false;
}
};
/**
* Returns the scale
* @returns {Number}
@ -2919,15 +2968,15 @@ Network.prototype.getCenterCoordinates = function () {
Network.prototype.getBoundingBox = function(nodeId) {
if (this.nodes[nodeId] !== undefined) {
return this.nodes[nodeId].boundingBox;
if (this.body.nodes[nodeId] !== undefined) {
return this.body.nodes[nodeId].boundingBox;
}
}
Network.prototype.getConnectedNodes = function(nodeId) {
var nodeList = [];
if (this.nodes[nodeId] !== undefined) {
var node = this.nodes[nodeId];
if (this.body.nodes[nodeId] !== undefined) {
var node = this.body.nodes[nodeId];
var nodeObj = {nodeId : true}; // used to quickly check if node already exists
for (var i = 0; i < node.edges.length; i++) {
var edge = node.edges[i];
@ -2951,8 +3000,8 @@ Network.prototype.getConnectedNodes = function(nodeId) {
Network.prototype.getEdgesFromNode = function(nodeId) {
var edgesList = [];
if (this.nodes[nodeId] !== undefined) {
var node = this.nodes[nodeId];
if (this.body.nodes[nodeId] !== undefined) {
var node = this.body.nodes[nodeId];
for (var i = 0; i < node.edges.length; i++) {
edgesList.push(node.edges[i].id);
}

+ 0
- 15
lib/network/mixins/ClusterMixin.js View File

@ -377,21 +377,6 @@ exports._cluster = function(childNodesObj, childEdgesObj, options, doNotUpdateCa
}
/**
* Check if a node is a cluster.
* @param nodeId
* @returns {*}
*/
exports.isCluster = function(nodeId) {
if (this.nodes[nodeId] !== undefined) {
return this.nodes[nodeId].isCluster;
}
else {
console.log("Node does not exist.")
return false;
}
}
/**
* get the position of the cluster node based on what's inside

+ 27
- 27
lib/network/mixins/HierarchicalLayoutMixin.js View File

@ -1,7 +1,7 @@
exports._resetLevels = function() {
for (var nodeId in this.nodes) {
if (this.nodes.hasOwnProperty(nodeId)) {
var node = this.nodes[nodeId];
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;
@ -24,9 +24,9 @@ exports._setupHierarchicalLayout = function() {
var definedLevel = false;
var undefinedLevel = false;
for (nodeId in this.nodes) {
if (this.nodes.hasOwnProperty(nodeId)) {
node = this.nodes[nodeId];
for (nodeId in this.body.nodes) {
if (this.body.nodes.hasOwnProperty(nodeId)) {
node = this.body.nodes[nodeId];
if (node.level != -1) {
definedLevel = true;
}
@ -126,9 +126,9 @@ exports._getDistribution = function() {
// 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.nodes) {
if (this.nodes.hasOwnProperty(nodeId)) {
node = this.nodes[nodeId];
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") {
@ -178,9 +178,9 @@ exports._determineLevels = function(hubsize) {
var nodeId, node;
// determine hubs
for (nodeId in this.nodes) {
if (this.nodes.hasOwnProperty(nodeId)) {
node = this.nodes[nodeId];
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;
}
@ -188,9 +188,9 @@ exports._determineLevels = function(hubsize) {
}
// branch from hubs
for (nodeId in this.nodes) {
if (this.nodes.hasOwnProperty(nodeId)) {
node = this.nodes[nodeId];
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);
}
@ -211,22 +211,22 @@ exports._determineLevelsDirected = function() {
var minLevel = 10000;
// set first node to source
firstNode = this.nodes[this.nodeIndices[0]];
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.nodes) {
if (this.nodes.hasOwnProperty(nodeId)) {
node = this.nodes[nodeId];
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.nodes) {
if (this.nodes.hasOwnProperty(nodeId)) {
node = this.nodes[nodeId];
for (nodeId in this.body.nodes) {
if (this.body.nodes.hasOwnProperty(nodeId)) {
node = this.body.nodes[nodeId];
node.level -= minLevel;
}
}
@ -354,7 +354,7 @@ exports._setLevel = function(level, edges, parentId) {
* @private
*/
exports._setLevelDirected = function(level, edges, parentId) {
this.nodes[parentId].hierarchyEnumerated = true;
this.body.nodes[parentId].hierarchyEnumerated = true;
var childNode, direction;
for (var i = 0; i < edges.length; i++) {
direction = 1;
@ -387,10 +387,10 @@ exports._setLevelDirected = function(level, edges, parentId) {
* @private
*/
exports._restoreNodes = function() {
for (var nodeId in this.nodes) {
if (this.nodes.hasOwnProperty(nodeId)) {
this.nodes[nodeId].xFixed = false;
this.nodes[nodeId].yFixed = false;
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;
}
}
};

+ 33
- 34
lib/network/mixins/ManipulationMixin.js View File

@ -15,8 +15,8 @@ exports._clearManipulatorBar = function() {
this._cleanManipulatorHammers();
this._manipulationReleaseOverload = function () {};
delete this.sectors['support']['nodes']['targetNode'];
delete this.sectors['support']['nodes']['targetViaNode'];
delete this.body.sectors['support']['nodes']['targetNode'];
delete this.body.sectors['support']['nodes']['targetViaNode'];
this.controlNodesActive = false;
this.freezeSimulationEnabled = false;
};
@ -99,7 +99,7 @@ exports._createManipulatorBar = function() {
this._restoreOverloadedFunctions();
// resume calculation
this.freezeSimulationEnabled = false;
this.freezeSimulation(false);
// reset global variables
this.blockConnectingEdgeSelection = false;
@ -284,7 +284,6 @@ exports._createAddEdgeToolbar = function() {
var locale = this.constants.locales[this.constants.locale];
this._unselectAll();
this.forceAppendSelection = false;
this.blockConnectingEdgeSelection = true;
@ -407,7 +406,7 @@ exports._selectControlNode = function(pointer) {
this.selectedControlNode = this.edgeBeingEdited._getSelectedControlNode(this._XconvertDOMtoCanvas(pointer.x),this._YconvertDOMtoCanvas(pointer.y));
if (this.selectedControlNode !== null) {
this.selectedControlNode.select();
this.freezeSimulationEnabled = true;
this.freezeSimulation(true);
}
this._redraw();
};
@ -451,7 +450,7 @@ exports._releaseControlNode = function(pointer) {
else {
this.edgeBeingEdited._restoreControlNodes();
}
this.freezeSimulationEnabled = false;
this.freezeSimulation(false);
this._redraw();
};
@ -466,22 +465,22 @@ exports._handleConnect = function(pointer) {
var node = this._getNodeAt(pointer);
if (node != null) {
if (node.clusterSize > 1) {
if (this.isCluster(node.id) == true) {
alert(this.constants.locales[this.constants.locale]['createEdgeError'])
}
else {
this._selectObject(node,false);
var supportNodes = this.sectors['support']['nodes'];
var supportNodes = this.body.sectors['support']['nodes'];
// create a node the temporary line can look at
supportNodes['targetNode'] = new Node({id:'targetNode'},{},{},this.constants);
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.edges['connectionEdge'] = new Edge({id:"connectionEdge",from:node.id,to:targetNode.id}, this, this.constants);
var connectionEdge = this.edges['connectionEdge'];
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,
@ -493,15 +492,14 @@ exports._handleConnect = function(pointer) {
connectionEdge.to = targetNode;
this.cachedFunctions["_handleOnDrag"] = this._handleOnDrag;
var me = this;
this._handleOnDrag = function(event) {
var pointer = this._getPointer(event.gesture.center);
var connectionEdge = this.edges['connectionEdge'];
connectionEdge.to.x = this._XconvertDOMtoCanvas(pointer.x);
connectionEdge.to.y = this._YconvertDOMtoCanvas(pointer.y);
var pointer = me._getPointer(event.gesture.center);
var connectionEdge = me.body.edges['connectionEdge'];
connectionEdge.to.x = me._XconvertDOMtoCanvas(pointer.x);
connectionEdge.to.y = me._YconvertDOMtoCanvas(pointer.y);
me._redraw();
};
this.moving = true;
this.start();
}
}
}
@ -515,16 +513,16 @@ exports._finishConnect = function(event) {
delete this.cachedFunctions["_handleOnDrag"];
// remember the edge id
var connectFromId = this.edges['connectionEdge'].fromId;
var connectFromId = this.body.edges['connectionEdge'].fromId;
// remove the temporary nodes and edge
delete this.edges['connectionEdge'];
delete this.sectors['support']['nodes']['targetNode'];
delete this.sectors['support']['nodes']['targetViaNode'];
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 (node.clusterSize > 1) {
if (this.isCluster(node.id) === true) {
alert(this.constants.locales[this.constants.locale]["createEdgeError"])
}
else {
@ -548,7 +546,7 @@ exports._addNode = function() {
if (this.triggerFunctions.add.length == 2) {
var me = this;
this.triggerFunctions.add(defaultData, function(finalizedData) {
me.nodesData.add(finalizedData);
me.body.data.nodes.add(finalizedData);
me._createManipulatorBar();
me.moving = true;
me.start();
@ -562,7 +560,7 @@ exports._addNode = function() {
}
}
else {
this.nodesData.add(defaultData);
this.body.data.nodes.add(defaultData);
this._createManipulatorBar();
this.moving = true;
this.start();
@ -583,7 +581,7 @@ exports._createEdge = function(sourceNodeId,targetNodeId) {
if (this.triggerFunctions.connect.length == 2) {
var me = this;
this.triggerFunctions.connect(defaultData, function(finalizedData) {
me.edgesData.add(finalizedData);
me.body.data.edges.add(finalizedData);
me.moving = true;
me.start();
});
@ -595,7 +593,7 @@ exports._createEdge = function(sourceNodeId,targetNodeId) {
}
}
else {
this.edgesData.add(defaultData);
this.body.data.edges.add(defaultData);
this.moving = true;
this.start();
}
@ -610,11 +608,12 @@ exports._createEdge = function(sourceNodeId,targetNodeId) {
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.edgesData.update(finalizedData);
me.body.data.edges.update(finalizedData);
me.moving = true;
me.start();
});
@ -626,7 +625,7 @@ exports._editEdge = function(sourceNodeId,targetNodeId) {
}
}
else {
this.edgesData.update(defaultData);
this.body.data.edges.update(defaultData);
this.moving = true;
this.start();
}
@ -656,7 +655,7 @@ exports._editNode = function() {
if (this.triggerFunctions.edit.length == 2) {
var me = this;
this.triggerFunctions.edit(data, function (finalizedData) {
me.nodesData.update(finalizedData);
me.body.data.nodes.update(finalizedData);
me._createManipulatorBar();
me.moving = true;
me.start();
@ -689,8 +688,8 @@ exports._deleteSelected = function() {
var data = {nodes: selectedNodes, edges: selectedEdges};
if (this.triggerFunctions.del.length == 2) {
this.triggerFunctions.del(data, function (finalizedData) {
me.edgesData.remove(finalizedData.edges);
me.nodesData.remove(finalizedData.nodes);
me.body.data.edges.remove(finalizedData.edges);
me.body.data.nodes.remove(finalizedData.nodes);
me._unselectAll();
me.moving = true;
me.start();
@ -701,8 +700,8 @@ exports._deleteSelected = function() {
}
}
else {
this.edgesData.remove(selectedEdges);
this.nodesData.remove(selectedNodes);
this.body.data.edges.remove(selectedEdges);
this.body.data.nodes.remove(selectedNodes);
this._unselectAll();
this.moving = true;
this.start();

+ 6
- 6
lib/network/mixins/MixinLoader.js View File

@ -70,22 +70,22 @@ exports._loadClusterSystem = function () {
* @private
*/
exports._loadSectorSystem = function () {
this.sectors = {};
this.body.sectors = {};
this.activeSector = ["default"];
this.sectors["active"] = {};
this.sectors["active"]["default"] = {"nodes": {},
this.body.sectors["active"] = {};
this.body.sectors["active"]["default"] = {"nodes": {},
"edges": {},
"nodeIndices": [],
"formationScale": 1.0,
"drawingNode": undefined };
this.sectors["frozen"] = {};
this.sectors["support"] = {"nodes": {},
this.body.sectors["frozen"] = {};
this.body.sectors["support"] = {"nodes": {},
"edges": {},
"nodeIndices": [],
"formationScale": 1.0,
"drawingNode": undefined };
this.nodeIndices = this.sectors["active"]["default"]["nodeIndices"]; // the node indices list is used to speed up the computation of the repulsion fields
this.body.nodeIndices = this.body.sectors["active"]["default"]["nodeIndices"]; // the node indices list is used to speed up the computation of the repulsion fields
this._loadMixin(SectorsMixin);
};

+ 52
- 52
lib/network/mixins/SectorsMixin.js View File

@ -16,9 +16,9 @@ var Node = require('../Node');
* @private
*/
exports._putDataInSector = function() {
this.sectors["active"][this._sector()].nodes = this.nodes;
this.sectors["active"][this._sector()].edges = this.edges;
this.sectors["active"][this._sector()].nodeIndices = this.nodeIndices;
this.body.sectors["active"][this._sector()].nodes = this.body.nodes;
this.body.sectors["active"][this._sector()].edges = this.body.edges;
this.body.sectors["active"][this._sector()].nodeIndices = this.body.nodeIndices;
};
@ -49,9 +49,9 @@ exports._switchToSector = function(sectorId, sectorType) {
* @private
*/
exports._switchToActiveSector = function(sectorId) {
this.nodeIndices = this.sectors["active"][sectorId]["nodeIndices"];
this.nodes = this.sectors["active"][sectorId]["nodes"];
this.edges = this.sectors["active"][sectorId]["edges"];
this.body.nodeIndices = this.body.sectors["active"][sectorId]["nodeIndices"];
this.body.nodes = this.body.sectors["active"][sectorId]["nodes"];
this.body.edges = this.body.sectors["active"][sectorId]["edges"];
};
@ -62,9 +62,9 @@ exports._switchToActiveSector = function(sectorId) {
* @private
*/
exports._switchToSupportSector = function() {
this.nodeIndices = this.sectors["support"]["nodeIndices"];
this.nodes = this.sectors["support"]["nodes"];
this.edges = this.sectors["support"]["edges"];
this.body.nodeIndices = this.body.sectors["support"]["nodeIndices"];
this.body.nodes = this.body.sectors["support"]["nodes"];
this.body.edges = this.body.sectors["support"]["edges"];
};
@ -76,9 +76,9 @@ exports._switchToSupportSector = function() {
* @private
*/
exports._switchToFrozenSector = function(sectorId) {
this.nodeIndices = this.sectors["frozen"][sectorId]["nodeIndices"];
this.nodes = this.sectors["frozen"][sectorId]["nodes"];
this.edges = this.sectors["frozen"][sectorId]["edges"];
this.body.nodeIndices = this.body.sectors["frozen"][sectorId]["nodeIndices"];
this.body.nodes = this.body.sectors["frozen"][sectorId]["nodes"];
this.body.edges = this.body.sectors["frozen"][sectorId]["edges"];
};
@ -153,21 +153,21 @@ exports._forgetLastSector = function() {
*/
exports._createNewSector = function(newId) {
// create the new sector
this.sectors["active"][newId] = {"nodes":{},
this.body.sectors["active"][newId] = {"nodes":{},
"edges":{},
"nodeIndices":[],
"formationScale": this.scale,
"drawingNode": undefined};
// create the new sector render node. This gives visual feedback that you are in a new sector.
this.sectors["active"][newId]['drawingNode'] = new Node(
this.body.sectors["active"][newId]['drawingNode'] = new Node(
{id:newId,
color: {
background: "#eaefef",
border: "495c5e"
}
},{},{},this.constants);
this.sectors["active"][newId]['drawingNode'].clusterSize = 2;
this.body.sectors["active"][newId]['drawingNode'].clusterSize = 2;
};
@ -179,7 +179,7 @@ exports._createNewSector = function(newId) {
* @private
*/
exports._deleteActiveSector = function(sectorId) {
delete this.sectors["active"][sectorId];
delete this.body.sectors["active"][sectorId];
};
@ -191,7 +191,7 @@ exports._deleteActiveSector = function(sectorId) {
* @private
*/
exports._deleteFrozenSector = function(sectorId) {
delete this.sectors["frozen"][sectorId];
delete this.body.sectors["frozen"][sectorId];
};
@ -204,7 +204,7 @@ exports._deleteFrozenSector = function(sectorId) {
*/
exports._freezeSector = function(sectorId) {
// we move the set references from the active to the frozen stack.
this.sectors["frozen"][sectorId] = this.sectors["active"][sectorId];
this.body.sectors["frozen"][sectorId] = this.body.sectors["active"][sectorId];
// we have moved the sector data into the frozen set, we now remove it from the active set
this._deleteActiveSector(sectorId);
@ -220,7 +220,7 @@ exports._freezeSector = function(sectorId) {
*/
exports._activateSector = function(sectorId) {
// we move the set references from the frozen to the active stack.
this.sectors["active"][sectorId] = this.sectors["frozen"][sectorId];
this.body.sectors["active"][sectorId] = this.body.sectors["frozen"][sectorId];
// we have moved the sector data into the active set, we now remove it from the frozen stack
this._deleteFrozenSector(sectorId);
@ -238,22 +238,22 @@ exports._activateSector = function(sectorId) {
*/
exports._mergeThisWithFrozen = function(sectorId) {
// copy all nodes
for (var nodeId in this.nodes) {
if (this.nodes.hasOwnProperty(nodeId)) {
this.sectors["frozen"][sectorId]["nodes"][nodeId] = this.nodes[nodeId];
for (var nodeId in this.body.nodes) {
if (this.body.nodes.hasOwnProperty(nodeId)) {
this.body.sectors["frozen"][sectorId]["nodes"][nodeId] = this.body.nodes[nodeId];
}
}
// copy all edges (if not fully clustered, else there are no edges)
for (var edgeId in this.edges) {
if (this.edges.hasOwnProperty(edgeId)) {
this.sectors["frozen"][sectorId]["edges"][edgeId] = this.edges[edgeId];
for (var edgeId in this.body.edges) {
if (this.body.edges.hasOwnProperty(edgeId)) {
this.body.sectors["frozen"][sectorId]["edges"][edgeId] = this.body.edges[edgeId];
}
}
// merge the nodeIndices
for (var i = 0; i < this.nodeIndices.length; i++) {
this.sectors["frozen"][sectorId]["nodeIndices"].push(this.nodeIndices[i]);
for (var i = 0; i < this.body.nodeIndices.length; i++) {
this.body.sectors["frozen"][sectorId]["nodeIndices"].push(this.body.nodeIndices[i]);
}
};
@ -280,7 +280,7 @@ exports._addSector = function(node) {
var sector = this._sector();
// // this should allow me to select nodes from a frozen set.
// if (this.sectors['active'][sector]["nodes"].hasOwnProperty(node.id)) {
// if (this.body.sectors['active'][sector]["nodes"].hasOwnProperty(node.id)) {
// console.log("the node is part of the active sector");
// }
// else {
@ -288,7 +288,7 @@ exports._addSector = function(node) {
// }
// when we switch to a new sector, we remove the node that will be expanded from the current nodes list.
delete this.nodes[node.id];
delete this.body.nodes[node.id];
var unqiueIdentifier = util.randomUUID();
@ -305,7 +305,7 @@ exports._addSector = function(node) {
this._switchToSector(this._sector());
// finally we add the node we removed from our previous active sector to the new active sector
this.nodes[node.id] = node;
this.body.nodes[node.id] = node;
};
@ -321,9 +321,9 @@ exports._collapseSector = function() {
// we cannot collapse the default sector
if (sector != "default") {
if ((this.nodeIndices.length == 1) ||
(this.sectors["active"][sector]["drawingNode"].width*this.scale < this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientWidth) ||
(this.sectors["active"][sector]["drawingNode"].height*this.scale < this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientHeight)) {
if ((this.body.nodeIndices.length == 1) ||
(this.body.sectors["active"][sector]["drawingNode"].width*this.scale < this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientWidth) ||
(this.body.sectors["active"][sector]["drawingNode"].height*this.scale < this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientHeight)) {
var previousSector = this._previousSector();
// we collapse the sector back to a single cluster
@ -368,8 +368,8 @@ exports._collapseSector = function() {
exports._doInAllActiveSectors = function(runFunction,argument) {
var returnValues = [];
if (argument === undefined) {
for (var sector in this.sectors["active"]) {
if (this.sectors["active"].hasOwnProperty(sector)) {
for (var sector in this.body.sectors["active"]) {
if (this.body.sectors["active"].hasOwnProperty(sector)) {
// switch the global references to those of this sector
this._switchToActiveSector(sector);
returnValues.push( this[runFunction]() );
@ -377,8 +377,8 @@ exports._doInAllActiveSectors = function(runFunction,argument) {
}
}
else {
for (var sector in this.sectors["active"]) {
if (this.sectors["active"].hasOwnProperty(sector)) {
for (var sector in this.body.sectors["active"]) {
if (this.body.sectors["active"].hasOwnProperty(sector)) {
// switch the global references to those of this sector
this._switchToActiveSector(sector);
var args = Array.prototype.splice.call(arguments, 1);
@ -439,8 +439,8 @@ exports._doInSupportSector = function(runFunction,argument) {
*/
exports._doInAllFrozenSectors = function(runFunction,argument) {
if (argument === undefined) {
for (var sector in this.sectors["frozen"]) {
if (this.sectors["frozen"].hasOwnProperty(sector)) {
for (var sector in this.body.sectors["frozen"]) {
if (this.body.sectors["frozen"].hasOwnProperty(sector)) {
// switch the global references to those of this sector
this._switchToFrozenSector(sector);
this[runFunction]();
@ -448,8 +448,8 @@ exports._doInAllFrozenSectors = function(runFunction,argument) {
}
}
else {
for (var sector in this.sectors["frozen"]) {
if (this.sectors["frozen"].hasOwnProperty(sector)) {
for (var sector in this.body.sectors["frozen"]) {
if (this.body.sectors["frozen"].hasOwnProperty(sector)) {
// switch the global references to those of this sector
this._switchToFrozenSector(sector);
var args = Array.prototype.splice.call(arguments, 1);
@ -495,15 +495,15 @@ exports._doInAllSectors = function(runFunction,argument) {
/**
* This clears the nodeIndices list. We cannot use this.nodeIndices = [] because we would break the link with the
* active sector. Thus we clear the nodeIndices in the active sector, then reconnect the this.nodeIndices to it.
* This clears the nodeIndices list. We cannot use this.body.nodeIndices = [] because we would break the link with the
* active sector. Thus we clear the nodeIndices in the active sector, then reconnect the this.body.nodeIndices to it.
*
* @private
*/
exports._clearNodeIndexList = function() {
var sector = this._sector();
this.sectors["active"][sector]["nodeIndices"] = [];
this.nodeIndices = this.sectors["active"][sector]["nodeIndices"];
this.body.sectors["active"][sector]["nodeIndices"] = [];
this.body.nodeIndices = this.body.sectors["active"][sector]["nodeIndices"];
};
@ -516,16 +516,16 @@ exports._clearNodeIndexList = function() {
*/
exports._drawSectorNodes = function(ctx,sectorType) {
var minY = 1e9, maxY = -1e9, minX = 1e9, maxX = -1e9, node;
for (var sector in this.sectors[sectorType]) {
if (this.sectors[sectorType].hasOwnProperty(sector)) {
if (this.sectors[sectorType][sector]["drawingNode"] !== undefined) {
for (var sector in this.body.sectors[sectorType]) {
if (this.body.sectors[sectorType].hasOwnProperty(sector)) {
if (this.body.sectors[sectorType][sector]["drawingNode"] !== undefined) {
this._switchToSector(sector,sectorType);
minY = 1e9; maxY = -1e9; minX = 1e9; maxX = -1e9;
for (var nodeId in this.nodes) {
if (this.nodes.hasOwnProperty(nodeId)) {
node = this.nodes[nodeId];
for (var nodeId in this.body.nodes) {
if (this.body.nodes.hasOwnProperty(nodeId)) {
node = this.body.nodes[nodeId];
node.resize(ctx);
if (minX > node.x - 0.5 * node.width) {minX = node.x - 0.5 * node.width;}
if (maxX < node.x + 0.5 * node.width) {maxX = node.x + 0.5 * node.width;}
@ -533,7 +533,7 @@ exports._drawSectorNodes = function(ctx,sectorType) {
if (maxY < node.y + 0.5 * node.height) {maxY = node.y + 0.5 * node.height;}
}
}
node = this.sectors[sectorType][sector]["drawingNode"];
node = this.body.sectors[sectorType][sector]["drawingNode"];
node.x = 0.5 * (maxX + minX);
node.y = 0.5 * (maxY + minY);
node.width = 2 * (node.x - minX);

+ 8
- 8
lib/network/mixins/SelectionMixin.js View File

@ -8,7 +8,7 @@ var Node = require('../Node');
* @private
*/
exports._getNodesOverlappingWith = function(object, overlappingNodes) {
var nodes = this.nodes;
var nodes = this.body.nodes;
for (var nodeId in nodes) {
if (nodes.hasOwnProperty(nodeId)) {
if (nodes[nodeId].isOverlappingWith(object)) {
@ -66,7 +66,7 @@ exports._getNodeAt = function (pointer) {
// 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.nodes[overlappingNodes[overlappingNodes.length - 1]];
return this.body.nodes[overlappingNodes[overlappingNodes.length - 1]];
}
else {
return null;
@ -81,7 +81,7 @@ exports._getNodeAt = function (pointer) {
* @private
*/
exports._getEdgesOverlappingWith = function (object, overlappingEdges) {
var edges = this.edges;
var edges = this.body.edges;
for (var edgeId in edges) {
if (edges.hasOwnProperty(edgeId)) {
if (edges[edgeId].isOverlappingWith(object)) {
@ -117,7 +117,7 @@ exports._getEdgeAt = function(pointer) {
var overlappingEdges = this._getAllEdgesOverlappingWith(positionObject);
if (overlappingEdges.length > 0) {
return this.edges[overlappingEdges[overlappingEdges.length - 1]];
return this.body.edges[overlappingEdges[overlappingEdges.length - 1]];
}
else {
return null;
@ -650,7 +650,7 @@ exports.selectNodes = function(selection, highlightEdges) {
for (i = 0, iMax = selection.length; i < iMax; i++) {
id = selection[i];
var node = this.nodes[id];
var node = this.body.nodes[id];
if (!node) {
throw new RangeError('Node with id "' + id + '" not found');
}
@ -677,7 +677,7 @@ exports.selectEdges = function(selection) {
for (i = 0, iMax = selection.length; i < iMax; i++) {
id = selection[i];
var edge = this.edges[id];
var edge = this.body.edges[id];
if (!edge) {
throw new RangeError('Edge with id "' + id + '" not found');
}
@ -693,14 +693,14 @@ exports.selectEdges = function(selection) {
exports._updateSelection = function () {
for(var nodeId in this.selectionObj.nodes) {
if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
if (!this.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.edges.hasOwnProperty(edgeId)) {
if (!this.body.edges.hasOwnProperty(edgeId)) {
delete this.selectionObj.edges[edgeId];
}
}

+ 17
- 20
lib/network/mixins/physics/PhysicsMixin.js View File

@ -67,8 +67,8 @@ exports._loadSelectedForceSolver = function () {
*/
exports._initializeForceCalculation = function () {
// stop calculation if there is only one node
if (this.nodeIndices.length == 1) {
this.nodes[this.nodeIndices[0]]._setForce(0, 0);
if (this.calculationNodeIndices.length == 1) {
this.body.nodes[this.calculationNodeIndices[0]]._setForce(0, 0);
}
else {
// we now start the force calculation
@ -110,7 +110,7 @@ exports._calculateForces = function () {
* Smooth curves are created by adding invisible nodes in the center of the edges. These nodes are also
* handled in the calculateForces function. We then use a quadratic curve with the center node as control.
* This function joins the datanodes and invisible (called support) nodes into one object.
* We do this so we do not contaminate this.nodes with the support nodes.
* We do this so we do not contaminate this.body.nodes with the support nodes.
*
* @private
*/
@ -119,15 +119,15 @@ exports._updateCalculationNodes = function () {
this.calculationNodes = {};
this.calculationNodeIndices = [];
for (var nodeId in this.nodes) {
if (this.nodes.hasOwnProperty(nodeId)) {
this.calculationNodes[nodeId] = this.nodes[nodeId];
for (var nodeId in this.body.nodes) {
if (this.body.nodes.hasOwnProperty(nodeId)) {
this.calculationNodes[nodeId] = this.body.nodes[nodeId];
}
}
var supportNodes = this.sectors['support']['nodes'];
var supportNodes = this.body.sectors['support']['nodes'];
for (var supportNodeId in supportNodes) {
if (supportNodes.hasOwnProperty(supportNodeId)) {
if (this.edges.hasOwnProperty(supportNodes[supportNodeId].parentEdgeId)) {
if (this.body.edges.hasOwnProperty(supportNodes[supportNodeId].parentEdgeId)) {
this.calculationNodes[supportNodeId] = supportNodes[supportNodeId];
}
else {
@ -136,15 +136,11 @@ exports._updateCalculationNodes = function () {
}
}
for (var idx in this.calculationNodes) {
if (this.calculationNodes.hasOwnProperty(idx)) {
this.calculationNodeIndices.push(idx);
}
}
this.calculationNodeIndices = Object.keys(this.calculationNodes);
}
else {
this.calculationNodes = this.nodes;
this.calculationNodeIndices = this.nodeIndices;
this.calculationNodes = this.body.nodes;
this.calculationNodeIndices = this.body.nodeIndices;
}
};
@ -191,7 +187,7 @@ exports._calculateGravitationalForces = function () {
exports._calculateSpringForces = function () {
var edgeLength, edge, edgeId;
var dx, dy, fx, fy, springForce, distance;
var edges = this.edges;
var edges = this.body.edges;
// forces caused by the edges, modelled as springs
for (edgeId in edges) {
@ -199,7 +195,7 @@ exports._calculateSpringForces = function () {
edge = edges[edgeId];
if (edge.connected === true) {
// only calculate forces if nodes are in the same sector
if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) {
if (this.body.nodes.hasOwnProperty(edge.toId) && this.body.nodes.hasOwnProperty(edge.fromId)) {
edgeLength = edge.physics.springLength;
dx = (edge.from.x - edge.to.x);
@ -236,15 +232,16 @@ exports._calculateSpringForces = function () {
*/
exports._calculateSpringForcesWithSupport = function () {
var edgeLength, edge, edgeId;
var edges = this.edges;
var edges = this.body.edges;
var calculationNodes = this.calculationNodes;
// forces caused by the edges, modelled as springs
for (edgeId in edges) {
if (edges.hasOwnProperty(edgeId)) {
edge = edges[edgeId];
if (edge.connected === true) {
// only calculate forces if nodes are in the same sector
if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) {
if (calculationNodes[edge.toId] !== undefined && calculationNodes[edge.fromId] !== undefined) {
if (edge.via != null) {
var node1 = edge.to;
var node2 = edge.via;

+ 620
- 0
lib/network/modules/Clustering.js View File

@ -0,0 +1,620 @@
/**
* Created by Alex on 24-Feb-15.
*/
var util = require("../../util");
class ClusterEngine {
constructor(body, options={}) {
this.body = body;
this.clusteredNodes = {};
}
/**
*
* @param hubsize
* @param options
*/
clusterByConnectionCount(hubsize, options) {
if (hubsize === undefined) {
hubsize = this._getHubSize();
}
else if (tyepof(hubsize) == "object") {
options = this._checkOptions(hubsize);
hubsize = this._getHubSize();
}
var nodesToCluster = [];
for (var i = 0; i < this.body.nodeIndices.length; i++) {
var node = this.body.nodes[this.body.nodeIndices[i]];
if (node.edges.length >= hubsize) {
nodesToCluster.push(node.id);
}
}
for (var i = 0; i < nodesToCluster.length; i++) {
var node = this.body.nodes[nodesToCluster[i]];
this.clusterByConnection(node,options,{},{},true);
}
this.body.emitter.emit('_dataChanged');
}
/**
* loop over all nodes, check if they adhere to the condition and cluster if needed.
* @param options
* @param doNotUpdateCalculationNodes
*/
clusterByNodeData(options = {}, doNotUpdateCalculationNodes=false) {
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
options = this._checkOptions(options);
var childNodesObj = {};
var childEdgesObj = {}
// collect the nodes that will be in the cluster
for (var i = 0; i < this.body.nodeIndices.length; i++) {
var nodeId = this.body.nodeIndices[i];
var clonedOptions = this._cloneOptions(nodeId);
if (options.joinCondition(clonedOptions) == true) {
childNodesObj[nodeId] = this.body.nodes[nodeId];
}
}
this._cluster(childNodesObj, childEdgesObj, options, doNotUpdateCalculationNodes);
}
/**
* Cluster all nodes in the network that have only 1 edge
* @param options
* @param doNotUpdateCalculationNodes
*/
clusterOutliers(options, doNotUpdateCalculationNodes) {
options = this._checkOptions(options);
var clusters = []
// collect the nodes that will be in the cluster
for (var i = 0; i < this.body.nodeIndices.length; i++) {
var childNodesObj = {};
var childEdgesObj = {};
var nodeId = this.body.nodeIndices[i];
if (this.body.nodes[nodeId].edges.length == 1) {
var edge = this.body.nodes[nodeId].edges[0];
var childNodeId = this._getConnectedId(edge, nodeId);
if (childNodeId != nodeId) {
if (options.joinCondition === undefined) {
childNodesObj[nodeId] = this.body.nodes[nodeId];
childNodesObj[childNodeId] = this.body.nodes[childNodeId];
}
else {
var clonedOptions = this._cloneOptions(nodeId);
if (options.joinCondition(clonedOptions) == true) {
childNodesObj[nodeId] = this.body.nodes[nodeId];
}
clonedOptions = this._cloneOptions(childNodeId);
if (options.joinCondition(clonedOptions) == true) {
childNodesObj[childNodeId] = this.body.nodes[childNodeId];
}
}
clusters.push({nodes:childNodesObj, edges:childEdgesObj})
}
}
}
for (var i = 0; i < clusters.length; i++) {
this._cluster(clusters[i].nodes, clusters[i].edges, options, true)
}
if (doNotUpdateCalculationNodes !== true) {
this.body.emitter.emit('_dataChanged');
}
}
/**
*
* @param nodeId
* @param options
* @param doNotUpdateCalculationNodes
*/
clusterByConnection(nodeId, options, doNotUpdateCalculationNodes) {
// 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!");}
var node = this.body.nodes[nodeId];
options = this._checkOptions(options, node);
if (options.clusterNodeProperties.x === undefined) {options.clusterNodeProperties.x = node.x; options.clusterNodeProperties.allowedToMoveX = !node.xFixed;}
if (options.clusterNodeProperties.y === undefined) {options.clusterNodeProperties.y = node.y; options.clusterNodeProperties.allowedToMoveY = !node.yFixed;}
var childNodesObj = {};
var childEdgesObj = {}
var parentNodeId = node.id;
var parentClonedOptions = this._cloneOptions(parentNodeId);
childNodesObj[parentNodeId] = node;
// collect the nodes that will be in the cluster
for (var i = 0; i < node.edges.length; i++) {
var edge = node.edges[i];
var childNodeId = this._getConnectedId(edge, parentNodeId);
if (childNodeId !== parentNodeId) {
if (options.joinCondition === undefined) {
childEdgesObj[edge.id] = edge;
childNodesObj[childNodeId] = this.body.nodes[childNodeId];
}
else {
// clone the options and insert some additional parameters that could be interesting.
var childClonedOptions = this._cloneOptions(childNodeId);
if (options.joinCondition(parentClonedOptions, childClonedOptions) == true) {
childEdgesObj[edge.id] = edge;
childNodesObj[childNodeId] = this.body.nodes[childNodeId];
}
}
}
else {
childEdgesObj[edge.id] = edge;
}
}
this._cluster(childNodesObj, childEdgesObj, options, doNotUpdateCalculationNodes);
}
/**
* This returns a clone of the options or properties of the edge or node to be used for construction of new edges or check functions for new nodes.
* @param objId
* @param type
* @returns {{}}
* @private
*/
_cloneOptions(objId, type) {
var clonedOptions = {};
if (type === undefined || type == 'node') {
util.deepExtend(clonedOptions, this.body.nodes[objId].options, true);
util.deepExtend(clonedOptions, this.body.nodes[objId].properties, true);
clonedOptions.amountOfConnections = this.body.nodes[objId].edges.length;
}
else {
util.deepExtend(clonedOptions, this.body.edges[objId].properties, true);
}
return clonedOptions;
}
/**
* This function creates the edges that will be attached to the cluster.
*
* @param childNodesObj
* @param childEdgesObj
* @param newEdges
* @param options
* @private
*/
_createClusterEdges (childNodesObj, childEdgesObj, newEdges, options) {
var edge, childNodeId, childNode;
var childKeys = Object.keys(childNodesObj);
for (var i = 0; i < childKeys.length; i++) {
childNodeId = childKeys[i];
childNode = childNodesObj[childNodeId];
// mark all edges for removal from global and construct new edges from the cluster to others
for (var j = 0; j < childNode.edges.length; j++) {
edge = childNode.edges[j];
childEdgesObj[edge.id] = edge;
var otherNodeId = edge.toId;
var otherOnTo = true;
if (edge.toId != childNodeId) {
otherNodeId = edge.toId;
otherOnTo = true;
}
else if (edge.fromId != childNodeId) {
otherNodeId = edge.fromId;
otherOnTo = false;
}
if (childNodesObj[otherNodeId] === undefined) {
var clonedOptions = this._cloneOptions(edge.id, 'edge');
util.deepExtend(clonedOptions, options.clusterEdgeProperties);
if (otherOnTo === true) {
clonedOptions.from = options.clusterNodeProperties.id;
clonedOptions.to = otherNodeId;
}
else {
clonedOptions.from = otherNodeId;
clonedOptions.to = options.clusterNodeProperties.id;
}
clonedOptions.id = 'clusterEdge:' + util.randomUUID();
newEdges.push(this.body.functions.createEdge(clonedOptions))
}
}
}
}
/**
* This function checks the options that can be supplied to the different cluster functions
* for certain fields and inserts defaults if needed
* @param options
* @returns {*}
* @private
*/
_checkOptions(options = {}) {
if (options.clusterEdgeProperties === undefined) {options.clusterEdgeProperties = {};}
if (options.clusterNodeProperties === undefined) {options.clusterNodeProperties = {};}
return options;
}
/**
*
* @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
* @private
*/
_cluster(childNodesObj, childEdgesObj, options, doNotUpdateCalculationNodes = false) {
// kill condition: no children so cant cluster
if (Object.keys(childNodesObj).length == 0) {return;}
// check if we have an unique id;
if (options.clusterNodeProperties.id === undefined) {options.clusterNodeProperties.id = 'cluster:' + util.randomUUID();}
var clusterId = options.clusterNodeProperties.id;
// create the new edges that will connect to the cluster
var newEdges = [];
this._createClusterEdges(childNodesObj, childEdgesObj, newEdges, options);
// construct the clusterNodeProperties
var clusterNodeProperties = options.clusterNodeProperties;
if (options.processProperties !== undefined) {
// get the childNode options
var childNodesOptions = [];
for (var nodeId in childNodesObj) {
var clonedOptions = this._cloneOptions(nodeId);
childNodesOptions.push(clonedOptions);
}
// get clusterproperties based on childNodes
var childEdgesOptions = [];
for (var edgeId in childEdgesObj) {
var clonedOptions = this._cloneOptions(edgeId, 'edge');
childEdgesOptions.push(clonedOptions);
}
clusterNodeProperties = options.processProperties(clusterNodeProperties, childNodesOptions, childEdgesOptions);
if (!clusterNodeProperties) {
throw new Error("The processClusterProperties function does not return properties!");
}
}
if (clusterNodeProperties.label === undefined) {
clusterNodeProperties.label = 'cluster';
}
// give the clusterNode a postion if it does not have one.
var pos = undefined;
if (clusterNodeProperties.x === undefined) {
pos = this._getClusterPosition(childNodesObj);
clusterNodeProperties.x = pos.x;
clusterNodeProperties.allowedToMoveX = true;
}
if (clusterNodeProperties.x === undefined) {
if (pos === undefined) {
pos = this._getClusterPosition(childNodesObj);
}
clusterNodeProperties.y = pos.y;
clusterNodeProperties.allowedToMoveY = true;
}
// force the ID to remain the same
clusterNodeProperties.id = clusterId;
// create the clusterNode
var clusterNode = this.body.functions.createNode(clusterNodeProperties);
clusterNode.isCluster = true;
clusterNode.containedNodes = childNodesObj;
clusterNode.containedEdges = childEdgesObj;
// delete contained edges from global
for (var edgeId in childEdgesObj) {
if (childEdgesObj.hasOwnProperty(edgeId)) {
if (this.body.edges[edgeId] !== undefined) {
if (this.body.edges[edgeId].via !== null) {
var viaId = this.body.edges[edgeId].via.id;
if (viaId) {
this.body.edges[edgeId].via = null
delete this.body.sectors['support']['nodes'][viaId];
}
}
this.body.edges[edgeId].disconnect();
delete this.body.edges[edgeId];
}
}
}
// remove contained nodes from global
for (var nodeId in childNodesObj) {
if (childNodesObj.hasOwnProperty(nodeId)) {
this.clusteredNodes[nodeId] = {clusterId:clusterNodeProperties.id, node: this.body.nodes[nodeId]};
delete this.body.nodes[nodeId];
}
}
// finally put the cluster node into global
this.body.nodes[clusterNodeProperties.id] = clusterNode;
// push new edges to global
for (var i = 0; i < newEdges.length; i++) {
this.body.edges[newEdges[i].id] = newEdges[i];
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) {
this.body.emitter.emit('_dataChanged');
}
}
/**
* Check if a node is a cluster.
* @param nodeId
* @returns {*}
*/
isCluster(nodeId) {
if (this.body.nodes[nodeId] !== undefined) {
return this.body.nodes[nodeId].isCluster;
}
else {
console.log("Node does not exist.")
return false;
}
}
/**
* get the position of the cluster node based on what's inside
* @param {object} childNodesObj | object with node objects, id as keys
* @returns {{x: number, y: number}}
* @private
*/
_getClusterPosition(childNodesObj) {
var childKeys = Object.keys(childNodesObj);
var minX = childNodesObj[childKeys[0]].x;
var maxX = childNodesObj[childKeys[0]].x;
var minY = childNodesObj[childKeys[0]].y;
var maxY = childNodesObj[childKeys[0]].y;
var node;
for (var i = 0; i < childKeys.lenght; i++) {
node = childNodesObj[childKeys[0]];
minX = node.x < minX ? node.x : minX;
maxX = node.x > maxX ? node.x : maxX;
minY = node.y < minY ? node.y : minY;
maxY = node.y > maxY ? node.y : maxY;
}
return {x: 0.5*(minX + maxX), y: 0.5*(minY + maxY)};
}
/**
* 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
*/
openCluster(clusterNodeId, doNotUpdateCalculationNodes) {
// 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.");}
if (this.body.nodes[clusterNodeId].containedNodes === undefined) {console.log("The node:" + clusterNodeId + " is not a cluster."); return};
var node = this.body.nodes[clusterNodeId];
var containedNodes = node.containedNodes;
var containedEdges = node.containedEdges;
// release nodes
for (var nodeId in containedNodes) {
if (containedNodes.hasOwnProperty(nodeId)) {
this.body.nodes[nodeId] = containedNodes[nodeId];
// inherit position
this.body.nodes[nodeId].x = node.x;
this.body.nodes[nodeId].y = node.y;
// inherit speed
this.body.nodes[nodeId].vx = node.vx;
this.body.nodes[nodeId].vy = node.vy;
delete this.clusteredNodes[nodeId];
}
}
// release edges
for (var edgeId in containedEdges) {
if (containedEdges.hasOwnProperty(edgeId)) {
this.body.edges[edgeId] = containedEdges[edgeId];
this.body.edges[edgeId].connect();
var edge = this.body.edges[edgeId];
if (edge.connected === false) {
if (this.clusteredNodes[edge.fromId] !== undefined) {
this._connectEdge(edge, edge.fromId, true);
}
if (this.clusteredNodes[edge.toId] !== undefined) {
this._connectEdge(edge, edge.toId, false);
}
}
}
}
this.body.emitter.emit("_newEdgesCreated",containedEdges);
var edgeIds = [];
for (var i = 0; i < node.edges.length; i++) {
edgeIds.push(node.edges[i].id);
}
// remove edges in clusterNode
for (var i = 0; i < edgeIds.length; i++) {
var edge = this.body.edges[edgeIds[i]];
// if the edge should have been connected to a contained node
if (edge.fromArray.length > 0 && edge.fromId == clusterNodeId) {
// the node in the from array was contained in the cluster
if (this.body.nodes[edge.fromArray[0].id] !== undefined) {
this._connectEdge(edge, edge.fromArray[0].id, true);
}
}
else if (edge.toArray.length > 0 && edge.toId == clusterNodeId) {
// the node in the to array was contained in the cluster
if (this.body.nodes[edge.toArray[0].id] !== undefined) {
this._connectEdge(edge, edge.toArray[0].id, false);
}
}
else {
var edgeId = edgeIds[i];
var viaId = this.body.edges[edgeId].via.id;
if (viaId) {
this.body.edges[edgeId].via = null
delete this.body.sectors['support']['nodes'][viaId];
}
// this removes the edge from node.edges, which is why edgeIds is formed
this.body.edges[edgeId].disconnect();
delete this.body.edges[edgeId];
}
}
// remove clusterNode
delete this.body.nodes[clusterNodeId];
if (doNotUpdateCalculationNodes !== true) {
this.body.emitter.emit('_dataChanged');
}
}
/**
* Connect an edge that was previously contained from cluster A to cluster B if the node that it was originally connected to
* is currently residing in cluster B
* @param edge
* @param nodeId
* @param from
* @private
*/
_connectEdge(edge, nodeId, from) {
var clusterStack = this._getClusterStack(nodeId);
if (from == true) {
edge.from = clusterStack[clusterStack.length - 1];
edge.fromId = clusterStack[clusterStack.length - 1].id;
clusterStack.pop()
edge.fromArray = clusterStack;
}
else {
edge.to = clusterStack[clusterStack.length - 1];
edge.toId = clusterStack[clusterStack.length - 1].id;
clusterStack.pop();
edge.toArray = clusterStack;
}
edge.connect();
}
/**
* Get the stack clusterId's that a certain node resides in. cluster A -> cluster B -> cluster C -> node
* @param nodeId
* @returns {Array}
* @private
*/
_getClusterStack(nodeId) {
var stack = [];
var max = 100;
var counter = 0;
while (this.clusteredNodes[nodeId] !== undefined && counter < max) {
stack.push(this.clusteredNodes[nodeId].node);
nodeId = this.clusteredNodes[nodeId].clusterId;
counter++;
}
stack.push(this.body.nodes[nodeId]);
return stack;
}
/**
* Get the Id the node is connected to
* @param edge
* @param nodeId
* @returns {*}
* @private
*/
_getConnectedId(edge, nodeId) {
if (edge.toId != nodeId) {
return edge.toId;
}
else if (edge.fromId != nodeId) {
return edge.fromId;
}
else {
return edge.fromId;
}
}
/**
* We determine how many connections denote an important hub.
* We take the mean + 2*std as the important hub size. (Assuming a normal distribution of data, ~2.2%)
*
* @private
*/
_getHubSize() {
var average = 0;
var averageSquared = 0;
var hubCounter = 0;
var largestHub = 0;
for (var i = 0; i < this.body.nodeIndices.length; i++) {
var node = this.body.nodes[this.body.nodeIndices[i]];
if (node.edges.length > largestHub) {
largestHub = node.edges.length;
}
average += node.edges.length;
averageSquared += Math.pow(node.edges.length,2);
hubCounter += 1;
}
average = average / hubCounter;
averageSquared = averageSquared / hubCounter;
var variance = averageSquared - Math.pow(average,2);
var standardDeviation = Math.sqrt(variance);
var hubThreshold = Math.floor(average + 2*standardDeviation);
// always have at least one to cluster
if (hubThreshold > largestHub) {
hubThreshold = largestHub;
}
return hubThreshold;
};
}
export { ClusterEngine };

Loading…
Cancel
Save