Browse Source

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

flowchartTest
Alex de Mulder 10 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