From d856af1e556164271963a5e6a60c1622bed3cb8f Mon Sep 17 00:00:00 2001 From: Alex de Mulder Date: Tue, 1 Dec 2015 11:49:16 +0100 Subject: [PATCH] Cleaned up code, possibilities for future custom sorting of levels. Whitespace cleaning, options for layout, documentation and examples are still required. --- dist/vis.js | 538 +++++++++++++++------------- lib/network/NetworkUtil.js | 25 +- lib/network/modules/Clustering.js | 39 +- lib/network/modules/LayoutEngine.js | 147 ++++---- test/networkTest.html | 2 +- 5 files changed, 403 insertions(+), 348 deletions(-) diff --git a/dist/vis.js b/dist/vis.js index 6995a610..58052a55 100644 --- a/dist/vis.js +++ b/dist/vis.js @@ -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; diff --git a/lib/network/NetworkUtil.js b/lib/network/NetworkUtil.js index 44523e63..2dae68c7 100644 --- a/lib/network/NetworkUtil.js +++ b/lib/network/NetworkUtil.js @@ -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; \ No newline at end of file diff --git a/lib/network/modules/Clustering.js b/lib/network/modules/Clustering.js index 82656a83..6c706dba 100644 --- a/lib/network/modules/Clustering.js +++ b/lib/network/modules/Clustering.js @@ -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. diff --git a/lib/network/modules/LayoutEngine.js b/lib/network/modules/LayoutEngine.js index a07a52ed..87a35cec 100644 --- a/lib/network/modules/LayoutEngine.js +++ b/lib/network/modules/LayoutEngine.js @@ -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); } + + } diff --git a/test/networkTest.html b/test/networkTest.html index f3cdbdfe..74b609f8 100644 --- a/test/networkTest.html +++ b/test/networkTest.html @@ -51,7 +51,7 @@ sortMethod: "directed" } }, - physics: false,//{stabilization:false}, + physics: false, //{stabilization:false}, configure:"layout, physics" }; network = new vis.Network(container, data, options);