Browse Source

Cleaned up code, possibilities for future custom sorting of levels. Whitespace cleaning, options for layout, documentation and examples are still required.

fixDataView
Alex de Mulder 8 years ago
parent
commit
d856af1e55
5 changed files with 403 additions and 348 deletions
  1. +288
    -250
      dist/vis.js
  2. +24
    -1
      lib/network/NetworkUtil.js
  3. +9
    -30
      lib/network/modules/Clustering.js
  4. +81
    -66
      lib/network/modules/LayoutEngine.js
  5. +1
    -1
      test/networkTest.html

+ 288
- 250
dist/vis.js View File

@ -5,7 +5,7 @@
* A dynamic, browser-based visualization library.
*
* @version 4.10.1-SNAPSHOT
* @date 2015-11-30
* @date 2015-12-01
*
* @license
* Copyright (C) 2011-2015 Almende B.V, http://almende.com
@ -18429,6 +18429,9 @@ return /******/ (function(modules) { // webpackBootstrap
restack = true;
}
// recalculate the height of the subgroups
this._calculateSubGroupHeights();
// reposition visible items vertically
if (typeof this.itemSet.options.order === 'function') {
// a custom order function
@ -18496,6 +18499,25 @@ return /******/ (function(modules) { // webpackBootstrap
return resized;
};
/**
* recalculate the height of the subgroups
* @private
*/
Group.prototype._calculateSubGroupHeights = function () {
if (Object.keys(this.subgroups).length > 0) {
var me = this;
this.resetSubgroups();
util.forEach(this.visibleItems, function (item) {
if (item.data.subgroup !== undefined) {
me.subgroups[item.data.subgroup].height = Math.max(me.subgroups[item.data.subgroup].height, item.height);
me.subgroups[item.data.subgroup].visible = true;
}
});
}
};
/**
* recalculate the height of the group
* @param {{item: {horizontal: number, vertical: number}, axis: number}} margin
@ -18506,20 +18528,12 @@ return /******/ (function(modules) { // webpackBootstrap
// recalculate the height of the group
var height;
var visibleItems = this.visibleItems;
//var visibleSubgroups = [];
//this.visibleSubgroups = 0;
this.resetSubgroups();
var me = this;
if (visibleItems.length > 0) {
var min = visibleItems[0].top;
var max = visibleItems[0].top + visibleItems[0].height;
util.forEach(visibleItems, function (item) {
min = Math.min(min, item.top);
max = Math.max(max, item.top + item.height);
if (item.data.subgroup !== undefined) {
me.subgroups[item.data.subgroup].height = Math.max(me.subgroups[item.data.subgroup].height, item.height);
me.subgroups[item.data.subgroup].visible = true;
}
});
if (min > margin.axis) {
// there is an empty gap between the lowest item and the axis
@ -27047,15 +27061,15 @@ return /******/ (function(modules) { // webpackBootstrap
var _modulesClustering2 = _interopRequireDefault(_modulesClustering);
var _modulesCanvasRenderer = __webpack_require__(101);
var _modulesCanvasRenderer = __webpack_require__(102);
var _modulesCanvasRenderer2 = _interopRequireDefault(_modulesCanvasRenderer);
var _modulesCanvas = __webpack_require__(102);
var _modulesCanvas = __webpack_require__(103);
var _modulesCanvas2 = _interopRequireDefault(_modulesCanvas);
var _modulesView = __webpack_require__(103);
var _modulesView = __webpack_require__(104);
var _modulesView2 = _interopRequireDefault(_modulesView);
@ -35167,7 +35181,11 @@ return /******/ (function(modules) { // webpackBootstrap
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
var _componentsNodesCluster = __webpack_require__(100);
var _NetworkUtil = __webpack_require__(100);
var _NetworkUtil2 = _interopRequireDefault(_NetworkUtil);
var _componentsNodesCluster = __webpack_require__(101);
var _componentsNodesCluster2 = _interopRequireDefault(_componentsNodesCluster);
@ -35252,7 +35270,7 @@ return /******/ (function(modules) { // webpackBootstrap
for (var i = 0; i < this.body.nodeIndices.length; i++) {
var nodeId = this.body.nodeIndices[i];
var node = this.body.nodes[nodeId];
var clonedOptions = this._cloneOptions(node);
var clonedOptions = _NetworkUtil2['default']._cloneOptions(node);
if (options.joinCondition(clonedOptions) === true) {
childNodesObj[nodeId] = this.body.nodes[nodeId];
@ -35322,7 +35340,7 @@ return /******/ (function(modules) { // webpackBootstrap
childNodesObj[childNodeId] = this.body.nodes[childNodeId];
usedNodes[nodeId] = true;
} else {
var clonedOptions = this._cloneOptions(this.body.nodes[nodeId]);
var clonedOptions = _NetworkUtil2['default']._cloneOptions(this.body.nodes[nodeId]);
if (options.joinCondition(clonedOptions) === true) {
childEdgesObj[edge.id] = edge;
childNodesObj[nodeId] = this.body.nodes[nodeId];
@ -35414,7 +35432,7 @@ return /******/ (function(modules) { // webpackBootstrap
var childNodesObj = {};
var childEdgesObj = {};
var parentNodeId = node.id;
var parentClonedOptions = this._cloneOptions(node);
var parentClonedOptions = _NetworkUtil2['default']._cloneOptions(node);
childNodesObj[parentNodeId] = node;
// collect the nodes that will be in the cluster
@ -35431,7 +35449,7 @@ return /******/ (function(modules) { // webpackBootstrap
childNodesObj[childNodeId] = this.body.nodes[childNodeId];
} else {
// clone the options and insert some additional parameters that could be interesting.
var childClonedOptions = this._cloneOptions(this.body.nodes[childNodeId]);
var childClonedOptions = _NetworkUtil2['default']._cloneOptions(this.body.nodes[childNodeId]);
if (options.joinCondition(parentClonedOptions, childClonedOptions) === true) {
childEdgesObj[edge.id] = edge;
childNodesObj[childNodeId] = this.body.nodes[childNodeId];
@ -35448,28 +35466,6 @@ return /******/ (function(modules) { // webpackBootstrap
this._cluster(childNodesObj, childEdgesObj, options, refreshData);
}
/**
* This returns a clone of the options or options 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
*/
}, {
key: '_cloneOptions',
value: function _cloneOptions(item, type) {
var clonedOptions = {};
if (type === undefined || type === 'node') {
util.deepExtend(clonedOptions, item.options, true);
clonedOptions.x = item.x;
clonedOptions.y = item.y;
clonedOptions.amountOfConnections = item.edges.length;
} else {
util.deepExtend(clonedOptions, item.options, true);
}
return clonedOptions;
}
/**
* This function creates the edges that will be attached to the cluster
* It looks for edges that are connected to the nodes from the "outside' of the cluster.
@ -35532,7 +35528,7 @@ return /******/ (function(modules) { // webpackBootstrap
for (var j = 0; j < createEdges.length; j++) {
var _edge = createEdges[j].edge;
// copy the options of the edge we will replace
var clonedOptions = this._cloneOptions(_edge, 'edge');
var clonedOptions = _NetworkUtil2['default']._cloneOptions(_edge, 'edge');
// make sure the properties of clusterEdges are superimposed on it
util.deepExtend(clonedOptions, clusterEdgeProperties);
@ -35613,7 +35609,7 @@ return /******/ (function(modules) { // webpackBootstrap
var childNodesOptions = [];
for (var nodeId in childNodesObj) {
if (childNodesObj.hasOwnProperty(nodeId)) {
var clonedOptions = this._cloneOptions(childNodesObj[nodeId]);
var clonedOptions = _NetworkUtil2['default']._cloneOptions(childNodesObj[nodeId]);
childNodesOptions.push(clonedOptions);
}
}
@ -35624,7 +35620,7 @@ return /******/ (function(modules) { // webpackBootstrap
if (childEdgesObj.hasOwnProperty(edgeId)) {
// these cluster edges will be removed on creation of the cluster.
if (edgeId.substr(0, 12) !== "clusterEdge:") {
var clonedOptions = this._cloneOptions(childEdgesObj[edgeId], 'edge');
var clonedOptions = _NetworkUtil2['default']._cloneOptions(childEdgesObj[edgeId], 'edge');
childEdgesOptions.push(clonedOptions);
}
}
@ -35858,7 +35854,7 @@ return /******/ (function(modules) { // webpackBootstrap
}
// clone the options and apply the cluster options to them
var clonedOptions = this._cloneOptions(transferEdge, 'edge');
var clonedOptions = _NetworkUtil2['default']._cloneOptions(transferEdge, 'edge');
util.deepExtend(clonedOptions, otherCluster.clusterEdgeProperties);
// apply the edge specific options to it.
@ -36005,6 +36001,145 @@ return /******/ (function(modules) { // webpackBootstrap
/***/ },
/* 100 */
/***/ function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var util = __webpack_require__(1);
var NetworkUtil = (function () {
function NetworkUtil() {
_classCallCheck(this, NetworkUtil);
}
/**
* Find the center position of the network considering the bounding boxes
* @private
*/
_createClass(NetworkUtil, null, [{
key: "_getRange",
value: function _getRange(allNodes) {
var specificNodes = arguments.length <= 1 || arguments[1] === undefined ? [] : arguments[1];
var minY = 1e9,
maxY = -1e9,
minX = 1e9,
maxX = -1e9,
node;
if (specificNodes.length > 0) {
for (var i = 0; i < specificNodes.length; i++) {
node = allNodes[specificNodes[i]];
if (minX > node.shape.boundingBox.left) {
minX = node.shape.boundingBox.left;
}
if (maxX < node.shape.boundingBox.right) {
maxX = node.shape.boundingBox.right;
}
if (minY > node.shape.boundingBox.top) {
minY = node.shape.boundingBox.top;
} // top is negative, bottom is positive
if (maxY < node.shape.boundingBox.bottom) {
maxY = node.shape.boundingBox.bottom;
} // top is negative, bottom is positive
}
}
if (minX === 1e9 && maxX === -1e9 && minY === 1e9 && maxY === -1e9) {
minY = 0, maxY = 0, minX = 0, maxX = 0;
}
return { minX: minX, maxX: maxX, minY: minY, maxY: maxY };
}
/**
* Find the center position of the network
* @private
*/
}, {
key: "_getRangeCore",
value: function _getRangeCore(allNodes) {
var specificNodes = arguments.length <= 1 || arguments[1] === undefined ? [] : arguments[1];
var minY = 1e9,
maxY = -1e9,
minX = 1e9,
maxX = -1e9,
node;
if (specificNodes.length > 0) {
for (var i = 0; i < specificNodes.length; i++) {
node = allNodes[specificNodes[i]];
if (minX > node.x) {
minX = node.x;
}
if (maxX < node.x) {
maxX = node.x;
}
if (minY > node.y) {
minY = node.y;
} // top is negative, bottom is positive
if (maxY < node.y) {
maxY = node.y;
} // top is negative, bottom is positive
}
}
if (minX === 1e9 && maxX === -1e9 && minY === 1e9 && maxY === -1e9) {
minY = 0, maxY = 0, minX = 0, maxX = 0;
}
return { minX: minX, maxX: maxX, minY: minY, maxY: maxY };
}
/**
* @param {object} range = {minX: minX, maxX: maxX, minY: minY, maxY: maxY};
* @returns {{x: number, y: number}}
* @private
*/
}, {
key: "_findCenter",
value: function _findCenter(range) {
return { x: 0.5 * (range.maxX + range.minX),
y: 0.5 * (range.maxY + range.minY) };
}
/**
* This returns a clone of the options or options of the edge or node to be used for construction of new edges or check functions for new nodes.
* @param item
* @param type
* @returns {{}}
* @private
*/
}, {
key: "_cloneOptions",
value: function _cloneOptions(item, type) {
var clonedOptions = {};
if (type === undefined || type === 'node') {
util.deepExtend(clonedOptions, item.options, true);
clonedOptions.x = item.x;
clonedOptions.y = item.y;
clonedOptions.amountOfConnections = item.edges.length;
} else {
util.deepExtend(clonedOptions, item.options, true);
}
return clonedOptions;
}
}]);
return NetworkUtil;
})();
exports["default"] = NetworkUtil;
module.exports = exports["default"];
/***/ },
/* 101 */
/***/ function(module, exports, __webpack_require__) {
'use strict';
@ -36049,7 +36184,7 @@ return /******/ (function(modules) { // webpackBootstrap
module.exports = exports['default'];
/***/ },
/* 101 */
/* 102 */
/***/ function(module, exports, __webpack_require__) {
'use strict';
@ -36441,7 +36576,7 @@ return /******/ (function(modules) { // webpackBootstrap
module.exports = exports['default'];
/***/ },
/* 102 */
/* 103 */
/***/ function(module, exports, __webpack_require__) {
'use strict';
@ -36876,7 +37011,7 @@ return /******/ (function(modules) { // webpackBootstrap
module.exports = exports['default'];
/***/ },
/* 103 */
/* 104 */
/***/ function(module, exports, __webpack_require__) {
'use strict';
@ -36891,7 +37026,7 @@ return /******/ (function(modules) { // webpackBootstrap
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
var _NetworkUtil = __webpack_require__(104);
var _NetworkUtil = __webpack_require__(100);
var _NetworkUtil2 = _interopRequireDefault(_NetworkUtil);
@ -37220,121 +37355,6 @@ return /******/ (function(modules) { // webpackBootstrap
exports['default'] = View;
module.exports = exports['default'];
/***/ },
/* 104 */
/***/ function(module, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var NetworkUtil = (function () {
function NetworkUtil() {
_classCallCheck(this, NetworkUtil);
}
/**
* Find the center position of the network considering the bounding boxes
* @private
*/
_createClass(NetworkUtil, null, [{
key: "_getRange",
value: function _getRange(allNodes) {
var specificNodes = arguments.length <= 1 || arguments[1] === undefined ? [] : arguments[1];
var minY = 1e9,
maxY = -1e9,
minX = 1e9,
maxX = -1e9,
node;
if (specificNodes.length > 0) {
for (var i = 0; i < specificNodes.length; i++) {
node = allNodes[specificNodes[i]];
if (minX > node.shape.boundingBox.left) {
minX = node.shape.boundingBox.left;
}
if (maxX < node.shape.boundingBox.right) {
maxX = node.shape.boundingBox.right;
}
if (minY > node.shape.boundingBox.top) {
minY = node.shape.boundingBox.top;
} // top is negative, bottom is positive
if (maxY < node.shape.boundingBox.bottom) {
maxY = node.shape.boundingBox.bottom;
} // top is negative, bottom is positive
}
}
if (minX === 1e9 && maxX === -1e9 && minY === 1e9 && maxY === -1e9) {
minY = 0, maxY = 0, minX = 0, maxX = 0;
}
return { minX: minX, maxX: maxX, minY: minY, maxY: maxY };
}
/**
* Find the center position of the network
* @private
*/
}, {
key: "_getRangeCore",
value: function _getRangeCore(allNodes) {
var specificNodes = arguments.length <= 1 || arguments[1] === undefined ? [] : arguments[1];
var minY = 1e9,
maxY = -1e9,
minX = 1e9,
maxX = -1e9,
node;
if (specificNodes.length > 0) {
for (var i = 0; i < specificNodes.length; i++) {
node = allNodes[specificNodes[i]];
if (minX > node.x) {
minX = node.x;
}
if (maxX < node.x) {
maxX = node.x;
}
if (minY > node.y) {
minY = node.y;
} // top is negative, bottom is positive
if (maxY < node.y) {
maxY = node.y;
} // top is negative, bottom is positive
}
}
if (minX === 1e9 && maxX === -1e9 && minY === 1e9 && maxY === -1e9) {
minY = 0, maxY = 0, minX = 0, maxX = 0;
}
return { minX: minX, maxX: maxX, minY: minY, maxY: maxY };
}
/**
* @param {object} range = {minX: minX, maxX: maxX, minY: minY, maxY: maxY};
* @returns {{x: number, y: number}}
* @private
*/
}, {
key: "_findCenter",
value: function _findCenter(range) {
return { x: 0.5 * (range.maxX + range.minX),
y: 0.5 * (range.maxY + range.minY) };
}
}]);
return NetworkUtil;
})();
exports["default"] = NetworkUtil;
module.exports = exports["default"];
/***/ },
/* 105 */
/***/ function(module, exports, __webpack_require__) {
@ -39329,7 +39349,7 @@ return /******/ (function(modules) { // webpackBootstrap
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
var _NetworkUtil = __webpack_require__(104);
var _NetworkUtil = __webpack_require__(100);
var _NetworkUtil2 = _interopRequireDefault(_NetworkUtil);
@ -39656,8 +39676,10 @@ return /******/ (function(modules) { // webpackBootstrap
if (undefinedLevel === true) {
if (this.options.hierarchical.sortMethod === 'hubsize') {
this._determineLevelsByHubsize();
} else if (this.options.hierarchical.sortMethod === 'directed' || 'direction') {
} else if (this.options.hierarchical.sortMethod === 'directed') {
this._determineLevelsDirected();
} else if (this.options.hierarchical.sortMethod === 'custom') {
this._determineLevelsCustomCallback();
}
}
@ -39784,20 +39806,31 @@ return /******/ (function(modules) { // webpackBootstrap
}, {
key: '_determineLevelsByHubsize',
value: function _determineLevelsByHubsize() {
var nodeId = undefined,
node = undefined;
var _this2 = this;
var hubSize = 1;
var levelDownstream = function levelDownstream(nodeA, nodeB) {
if (_this2.hierarchicalLevels[nodeB.id] === undefined) {
// set initial level
if (_this2.hierarchicalLevels[nodeA.id] === undefined) {
_this2.hierarchicalLevels[nodeA.id] = 0;
}
// set level
_this2.hierarchicalLevels[nodeB.id] = _this2.hierarchicalLevels[nodeA.id] + 1;
}
};
while (hubSize > 0) {
// determine hubs
hubSize = this._getHubSize();
if (hubSize === 0) break;
for (nodeId in this.body.nodes) {
for (var nodeId in this.body.nodes) {
if (this.body.nodes.hasOwnProperty(nodeId)) {
node = this.body.nodes[nodeId];
var node = this.body.nodes[nodeId];
if (node.edges.length === hubSize) {
this._setLevelByHubsize(0, node);
this._crawlNetwork(levelDownstream, nodeId);
}
}
}
@ -39805,28 +39838,33 @@ return /******/ (function(modules) { // webpackBootstrap
}
/**
* this function is called recursively to enumerate the barnches of the largest hubs and give each node a level.
*
* @param level
* @param edges
* @param parentId
* TODO: release feature
* @private
*/
}, {
key: '_setLevelByHubsize',
value: function _setLevelByHubsize(level, node) {
if (this.hierarchicalLevels[node.id] !== undefined) return;
key: '_determineLevelsCustomCallback',
value: function _determineLevelsCustomCallback() {
var _this3 = this;
var childNode = undefined;
this.hierarchicalLevels[node.id] = level;
for (var i = 0; i < node.edges.length; i++) {
if (node.edges[i].toId === node.id) {
childNode = node.edges[i].from;
} else {
childNode = node.edges[i].to;
var minLevel = 100000;
// TODO: this should come from options.
var customCallback = function customCallback(nodeA, nodeB, edge) {};
var levelByDirection = function levelByDirection(nodeA, nodeB, edge) {
var levelA = _this3.hierarchicalLevels[nodeA.id];
// set initial level
if (levelA === undefined) {
_this3.hierarchicalLevels[nodeA.id] = minLevel;
}
this._setLevelByHubsize(level + 1, childNode, node.id);
}
var diff = customCallback(_NetworkUtil2['default']._cloneOptions(nodeA, 'node'), _NetworkUtil2['default']._cloneOptions(nodeB, 'node'), _NetworkUtil2['default']._cloneOptions(edge, 'edge'));
_this3.hierarchicalLevels[nodeB.id] = _this3.hierarchicalLevels[nodeA.id] + diff;
};
this._crawlNetwork(levelByDirection);
this._setMinLevelToZero();
}
/**
@ -39838,60 +39876,43 @@ return /******/ (function(modules) { // webpackBootstrap
}, {
key: '_determineLevelsDirected',
value: function _determineLevelsDirected() {
var nodeId = undefined,
node = undefined;
var minLevel = 10000;
var _this4 = this;
// set first node to source
for (nodeId in this.body.nodes) {
if (this.body.nodes.hasOwnProperty(nodeId)) {
node = this.body.nodes[nodeId];
this._setLevelDirected(minLevel, node);
var minLevel = 10000;
var levelByDirection = function levelByDirection(nodeA, nodeB, edge) {
var levelA = _this4.hierarchicalLevels[nodeA.id];
// set initial level
if (levelA === undefined) {
_this4.hierarchicalLevels[nodeA.id] = minLevel;
}
if (edge.toId == nodeB.id) {
_this4.hierarchicalLevels[nodeB.id] = _this4.hierarchicalLevels[nodeA.id] + 1;
} else {
_this4.hierarchicalLevels[nodeB.id] = _this4.hierarchicalLevels[nodeA.id] - 1;
}
}
};
this._crawlNetwork(levelByDirection);
this._setMinLevelToZero();
}
}, {
key: '_setMinLevelToZero',
value: function _setMinLevelToZero() {
var minLevel = undefined;
// get the minimum level
for (nodeId in this.body.nodes) {
for (var nodeId in this.body.nodes) {
if (this.body.nodes.hasOwnProperty(nodeId)) {
minLevel = Math.min(this.hierarchicalLevels[nodeId], minLevel);
}
}
// subtract the minimum from the set so we have a range starting from 0
for (nodeId in this.body.nodes) {
for (var nodeId in this.body.nodes) {
if (this.body.nodes.hasOwnProperty(nodeId)) {
this.hierarchicalLevels[nodeId] -= minLevel;
}
}
}
/**
* this function is called recursively to enumerate the branched of the first node and give each node a level based on edge direction
*
* @param level
* @param edges
* @param parentId
* @private
*/
}, {
key: '_setLevelDirected',
value: function _setLevelDirected(level, node, parentId) {
if (this.hierarchicalLevels[node.id] !== undefined) return;
var childNode = undefined;
this.hierarchicalLevels[node.id] = level;
for (var i = 0; i < node.edges.length; i++) {
if (node.edges[i].toId === node.id) {
childNode = node.edges[i].from;
this._setLevelDirected(level - 1, childNode, node.id);
} else {
childNode = node.edges[i].to;
this._setLevelDirected(level + 1, childNode, node.id);
}
}
}
/**
* Update the bookkeeping of parent and child.
* @param parentNodeId
@ -39901,29 +39922,36 @@ return /******/ (function(modules) { // webpackBootstrap
}, {
key: '_generateMap',
value: function _generateMap() {
var _this2 = this;
var _this5 = this;
var fillInRelations = function fillInRelations(parentNode, childNode) {
if (_this2.hierarchicalLevels[childNode.id] > _this2.hierarchicalLevels[parentNode.id]) {
if (_this5.hierarchicalLevels[childNode.id] > _this5.hierarchicalLevels[parentNode.id]) {
var parentNodeId = parentNode.id;
var childNodeId = childNode.id;
if (_this2.hierarchicalParents[parentNodeId] === undefined) {
_this2.hierarchicalParents[parentNodeId] = { children: [], amount: 0 };
if (_this5.hierarchicalParents[parentNodeId] === undefined) {
_this5.hierarchicalParents[parentNodeId] = { children: [], amount: 0 };
}
_this2.hierarchicalParents[parentNodeId].children.push(childNodeId);
if (_this2.hierarchicalChildren[childNodeId] === undefined) {
_this2.hierarchicalChildren[childNodeId] = { parents: [], amount: 0 };
_this5.hierarchicalParents[parentNodeId].children.push(childNodeId);
if (_this5.hierarchicalChildren[childNodeId] === undefined) {
_this5.hierarchicalChildren[childNodeId] = { parents: [], amount: 0 };
}
_this2.hierarchicalChildren[childNodeId].parents.push(parentNodeId);
_this5.hierarchicalChildren[childNodeId].parents.push(parentNodeId);
}
};
this._crawlNetwork(fillInRelations);
}
/**
* Crawl over the entire network and use a callback on each node couple that is connected to eachother.
* @param callback | will receive nodeA nodeB and the connecting edge. A and B are unique.
* @param startingNodeId
* @private
*/
}, {
key: '_crawlNetwork',
value: function _crawlNetwork() {
var callback = arguments.length <= 0 || arguments[0] === undefined ? function () {} : arguments[0];
value: function _crawlNetwork(callback, startingNodeId) {
if (callback === undefined) callback = function () {};
var progress = {};
var crawler = function crawler(node) {
@ -39938,15 +39966,25 @@ return /******/ (function(modules) { // webpackBootstrap
}
if (node.id !== childNode.id) {
callback(node, childNode);
callback(node, childNode, node.edges[i]);
crawler(childNode);
}
}
}
};
for (var i = 0; i < this.body.nodeIndices.length; i++) {
var node = this.body.nodes[this.body.nodeIndices[i]];
// we can crawl from a specific node or over all nodes.
if (startingNodeId === undefined) {
for (var i = 0; i < this.body.nodeIndices.length; i++) {
var node = this.body.nodes[this.body.nodeIndices[i]];
crawler(node);
}
} else {
var node = this.body.nodes[startingNodeId];
if (node === undefined) {
console.error("Node not found:", startingNodeId);
return;
}
crawler(node);
}
}
@ -40057,34 +40095,34 @@ return /******/ (function(modules) { // webpackBootstrap
}, {
key: '_findCommonParent',
value: function _findCommonParent(childA, childB) {
var _this3 = this;
var _this6 = this;
var parents = {};
var iterateParents = function iterateParents(parents, child) {
if (_this3.hierarchicalChildren[child] !== undefined) {
for (var i = 0; i < _this3.hierarchicalChildren[child].parents.length; i++) {
var _parent = _this3.hierarchicalChildren[child].parents[i];
if (_this6.hierarchicalChildren[child] !== undefined) {
for (var i = 0; i < _this6.hierarchicalChildren[child].parents.length; i++) {
var _parent = _this6.hierarchicalChildren[child].parents[i];
parents[_parent] = true;
iterateParents(parents, _parent);
}
}
};
var findParent = function findParent(_x2, _x3) {
var findParent = function findParent(_x, _x2) {
var _again = true;
_function: while (_again) {
var parents = _x2,
child = _x3;
var parents = _x,
child = _x2;
_again = false;
if (_this3.hierarchicalChildren[child] !== undefined) {
for (var i = 0; i < _this3.hierarchicalChildren[child].parents.length; i++) {
var _parent2 = _this3.hierarchicalChildren[child].parents[i];
if (_this6.hierarchicalChildren[child] !== undefined) {
for (var i = 0; i < _this6.hierarchicalChildren[child].parents.length; i++) {
var _parent2 = _this6.hierarchicalChildren[child].parents[i];
if (parents[_parent2] !== undefined) {
return { foundParent: _parent2, withChild: child };
}
_x2 = parents;
_x3 = _parent2;
_x = parents;
_x2 = _parent2;
_again = true;
i = _parent2 = undefined;
continue _function;

+ 24
- 1
lib/network/NetworkUtil.js View File

@ -1,4 +1,4 @@
let util = require("../util");
class NetworkUtil {
constructor() {}
@ -72,6 +72,29 @@ class NetworkUtil {
return {x: (0.5 * (range.maxX + range.minX)),
y: (0.5 * (range.maxY + range.minY))};
}
/**
* This returns a clone of the options or options of the edge or node to be used for construction of new edges or check functions for new nodes.
* @param item
* @param type
* @returns {{}}
* @private
*/
static _cloneOptions(item, type) {
let clonedOptions = {};
if (type === undefined || type === 'node') {
util.deepExtend(clonedOptions, item.options, true);
clonedOptions.x = item.x;
clonedOptions.y = item.y;
clonedOptions.amountOfConnections = item.edges.length;
}
else {
util.deepExtend(clonedOptions, item.options, true);
}
return clonedOptions;
}
}
export default NetworkUtil;

+ 9
- 30
lib/network/modules/Clustering.js View File

@ -1,4 +1,5 @@
let util = require("../../util");
import NetworkUtil from '../NetworkUtil';
import Cluster from './components/nodes/Cluster'
class ClusterEngine {
@ -67,7 +68,7 @@ class ClusterEngine {
for (let i = 0; i < this.body.nodeIndices.length; i++) {
let nodeId = this.body.nodeIndices[i];
let node = this.body.nodes[nodeId];
let clonedOptions = this._cloneOptions(node);
let clonedOptions = NetworkUtil._cloneOptions(node);
if (options.joinCondition(clonedOptions) === true) {
childNodesObj[nodeId] = this.body.nodes[nodeId];
@ -131,7 +132,7 @@ class ClusterEngine {
usedNodes[nodeId] = true;
}
else {
let clonedOptions = this._cloneOptions(this.body.nodes[nodeId]);
let clonedOptions = NetworkUtil._cloneOptions(this.body.nodes[nodeId]);
if (options.joinCondition(clonedOptions) === true) {
childEdgesObj[edge.id] = edge;
childNodesObj[nodeId] = this.body.nodes[nodeId];
@ -207,7 +208,7 @@ class ClusterEngine {
let childNodesObj = {};
let childEdgesObj = {};
let parentNodeId = node.id;
let parentClonedOptions = this._cloneOptions(node);
let parentClonedOptions = NetworkUtil._cloneOptions(node);
childNodesObj[parentNodeId] = node;
// collect the nodes that will be in the cluster
@ -225,7 +226,7 @@ class ClusterEngine {
}
else {
// clone the options and insert some additional parameters that could be interesting.
let childClonedOptions = this._cloneOptions(this.body.nodes[childNodeId]);
let childClonedOptions = NetworkUtil._cloneOptions(this.body.nodes[childNodeId]);
if (options.joinCondition(parentClonedOptions, childClonedOptions) === true) {
childEdgesObj[edge.id] = edge;
childNodesObj[childNodeId] = this.body.nodes[childNodeId];
@ -244,28 +245,6 @@ class ClusterEngine {
}
/**
* This returns a clone of the options or options 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(item, type) {
let clonedOptions = {};
if (type === undefined || type === 'node') {
util.deepExtend(clonedOptions, item.options, true);
clonedOptions.x = item.x;
clonedOptions.y = item.y;
clonedOptions.amountOfConnections = item.edges.length;
}
else {
util.deepExtend(clonedOptions, item.options, true);
}
return clonedOptions;
}
/**
* This function creates the edges that will be attached to the cluster
* It looks for edges that are connected to the nodes from the "outside' of the cluster.
@ -322,7 +301,7 @@ class ClusterEngine {
for (let j = 0; j < createEdges.length; j++) {
let edge = createEdges[j].edge;
// copy the options of the edge we will replace
let clonedOptions = this._cloneOptions(edge, 'edge');
let clonedOptions = NetworkUtil._cloneOptions(edge, 'edge');
// make sure the properties of clusterEdges are superimposed on it
util.deepExtend(clonedOptions, clusterEdgeProperties);
@ -390,7 +369,7 @@ class ClusterEngine {
let childNodesOptions = [];
for (let nodeId in childNodesObj) {
if (childNodesObj.hasOwnProperty(nodeId)) {
let clonedOptions = this._cloneOptions(childNodesObj[nodeId]);
let clonedOptions = NetworkUtil._cloneOptions(childNodesObj[nodeId]);
childNodesOptions.push(clonedOptions);
}
}
@ -401,7 +380,7 @@ class ClusterEngine {
if (childEdgesObj.hasOwnProperty(edgeId)) {
// these cluster edges will be removed on creation of the cluster.
if (edgeId.substr(0, 12) !== "clusterEdge:") {
let clonedOptions = this._cloneOptions(childEdgesObj[edgeId], 'edge');
let clonedOptions = NetworkUtil._cloneOptions(childEdgesObj[edgeId], 'edge');
childEdgesOptions.push(clonedOptions);
}
}
@ -623,7 +602,7 @@ class ClusterEngine {
}
// clone the options and apply the cluster options to them
let clonedOptions = this._cloneOptions(transferEdge, 'edge');
let clonedOptions = NetworkUtil._cloneOptions(transferEdge, 'edge');
util.deepExtend(clonedOptions, otherCluster.clusterEdgeProperties);
// apply the edge specific options to it.

+ 81
- 66
lib/network/modules/LayoutEngine.js View File

@ -316,9 +316,12 @@ class LayoutEngine {
if (this.options.hierarchical.sortMethod === 'hubsize') {
this._determineLevelsByHubsize();
}
else if (this.options.hierarchical.sortMethod === 'directed' || 'direction') {
else if (this.options.hierarchical.sortMethod === 'directed') {
this._determineLevelsDirected();
}
else if (this.options.hierarchical.sortMethod === 'custom') {
this._determineLevelsCustomCallback();
}
}
@ -440,53 +443,65 @@ class LayoutEngine {
* @private
*/
_determineLevelsByHubsize() {
let nodeId, node;
let hubSize = 1;
let levelDownstream = (nodeA, nodeB) => {
if (this.hierarchicalLevels[nodeB.id] === undefined) {
// set initial level
if (this.hierarchicalLevels[nodeA.id] === undefined) {
this.hierarchicalLevels[nodeA.id] = 0;
}
// set level
this.hierarchicalLevels[nodeB.id] = this.hierarchicalLevels[nodeA.id] + 1;
}
}
while (hubSize > 0) {
// determine hubs
hubSize = this._getHubSize();
if (hubSize === 0)
break;
for (nodeId in this.body.nodes) {
for (let nodeId in this.body.nodes) {
if (this.body.nodes.hasOwnProperty(nodeId)) {
node = this.body.nodes[nodeId];
let node = this.body.nodes[nodeId];
if (node.edges.length === hubSize) {
this._setLevelByHubsize(0, node);
this._crawlNetwork(levelDownstream,nodeId);
}
}
}
}
}
/**
* this function is called recursively to enumerate the barnches of the largest hubs and give each node a level.
*
* @param level
* @param edges
* @param parentId
* TODO: release feature
* @private
*/
_setLevelByHubsize(level, node) {
if (this.hierarchicalLevels[node.id] !== undefined)
return;
_determineLevelsCustomCallback() {
let minLevel = 100000;
// TODO: this should come from options.
let customCallback = function(nodeA, nodeB, edge) {
let childNode;
this.hierarchicalLevels[node.id] = level;
for (let i = 0; i < node.edges.length; i++) {
if (node.edges[i].toId === node.id) {
childNode = node.edges[i].from;
}
else {
childNode = node.edges[i].to;
}
this._setLevelByHubsize(level + 1, childNode, node.id);
}
}
let levelByDirection = (nodeA, nodeB, edge) => {
let levelA = this.hierarchicalLevels[nodeA.id];
// set initial level
if (levelA === undefined) {this.hierarchicalLevels[nodeA.id] = minLevel;}
let diff = customCallback(
NetworkUtil._cloneOptions(nodeA,'node'),
NetworkUtil._cloneOptions(nodeB,'node'),
NetworkUtil._cloneOptions(edge,'edge')
);
this.hierarchicalLevels[nodeB.id] = this.hierarchicalLevels[nodeA.id] + diff;
}
this._crawlNetwork(levelByDirection);
this._setMinLevelToZero();
}
/**
* this function allocates nodes in levels based on the direction of the edges
@ -495,26 +510,33 @@ class LayoutEngine {
* @private
*/
_determineLevelsDirected() {
let nodeId, node;
let minLevel = 10000;
// set first node to source
for (nodeId in this.body.nodes) {
if (this.body.nodes.hasOwnProperty(nodeId)) {
node = this.body.nodes[nodeId];
this._setLevelDirected(minLevel,node);
let levelByDirection = (nodeA, nodeB, edge) => {
let levelA = this.hierarchicalLevels[nodeA.id];
// set initial level
if (levelA === undefined) {this.hierarchicalLevels[nodeA.id] = minLevel;}
if (edge.toId == nodeB.id) {
this.hierarchicalLevels[nodeB.id] = this.hierarchicalLevels[nodeA.id] + 1;
}
else {
this.hierarchicalLevels[nodeB.id] = this.hierarchicalLevels[nodeA.id] - 1;
}
}
this._crawlNetwork(levelByDirection)
this._setMinLevelToZero();
}
_setMinLevelToZero() {
let minLevel;
// get the minimum level
for (nodeId in this.body.nodes) {
for (let nodeId in this.body.nodes) {
if (this.body.nodes.hasOwnProperty(nodeId)) {
minLevel = Math.min(this.hierarchicalLevels[nodeId], minLevel);
}
}
// subtract the minimum from the set so we have a range starting from 0
for (nodeId in this.body.nodes) {
for (let nodeId in this.body.nodes) {
if (this.body.nodes.hasOwnProperty(nodeId)) {
this.hierarchicalLevels[nodeId] -= minLevel;
}
@ -522,34 +544,6 @@ class LayoutEngine {
}
/**
* this function is called recursively to enumerate the branched of the first node and give each node a level based on edge direction
*
* @param level
* @param edges
* @param parentId
* @private
*/
_setLevelDirected(level, node, parentId) {
if (this.hierarchicalLevels[node.id] !== undefined)
return;
let childNode;
this.hierarchicalLevels[node.id] = level;
for (let i = 0; i < node.edges.length; i++) {
if (node.edges[i].toId === node.id) {
childNode = node.edges[i].from;
this._setLevelDirected(level - 1, childNode, node.id);
}
else {
childNode = node.edges[i].to;
this._setLevelDirected(level + 1, childNode, node.id);
}
}
}
/**
* Update the bookkeeping of parent and child.
* @param parentNodeId
@ -575,7 +569,14 @@ class LayoutEngine {
this._crawlNetwork(fillInRelations);
}
_crawlNetwork(callback = function() {}) {
/**
* Crawl over the entire network and use a callback on each node couple that is connected to eachother.
* @param callback | will receive nodeA nodeB and the connecting edge. A and B are unique.
* @param startingNodeId
* @private
*/
_crawlNetwork(callback = function() {}, startingNodeId) {
let progress = {};
let crawler = (node) => {
if (progress[node.id] === undefined) {
@ -586,17 +587,31 @@ class LayoutEngine {
else {childNode = node.edges[i].to;}
if (node.id !== childNode.id) {
callback(node, childNode);
callback(node, childNode, node.edges[i]);
crawler(childNode);
}
}
}
}
for (let i = 0; i < this.body.nodeIndices.length; i++) {
let node = this.body.nodes[this.body.nodeIndices[i]];
// we can crawl from a specific node or over all nodes.
if (startingNodeId === undefined) {
for (let i = 0; i < this.body.nodeIndices.length; i++) {
let node = this.body.nodes[this.body.nodeIndices[i]];
crawler(node);
}
}
else {
let node = this.body.nodes[startingNodeId];
if (node === undefined) {
console.error("Node not found:", startingNodeId);
return;
}
crawler(node);
}
}

+ 1
- 1
test/networkTest.html View File

@ -51,7 +51,7 @@
sortMethod: "directed"
}
},
physics: false,//{stabilization:false},
physics: false, //{stabilization:false},
configure:"layout, physics"
};
network = new vis.Network(container, data, options);

Loading…
Cancel
Save