diff --git a/HISTORY.md b/HISTORY.md index c75421ac..d13c55f3 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -2,7 +2,7 @@ http://visjs.org -## not yet released, version 4.12.1-SNAPSHOT +## not yet released, version 4.13.0-SNAPSHOT ### Network @@ -10,7 +10,8 @@ http://visjs.org - Improved the hierarchical layout algorithm by adding a condensing method to remove whitespace. - Fixed #1556: Network throwing an error when clicking the "Edit" button on the manipulation toolbar. -= Fixed #1334 (again): Network now ignores scroll when interaction:zoomView is false. +- Fixed #1334 (again): Network now ignores scroll when interaction:zoomView is false. +- Added options to customize the hierarchical layout without the use of physics. ### Graph2d diff --git a/dist/vis.css b/dist/vis.css index 247ae3e9..7c6cbe10 100644 --- a/dist/vis.css +++ b/dist/vis.css @@ -133,7 +133,7 @@ input.vis-configuration.vis-config-rangeinput{ position:relative; top:-5px; width:60px; - height:13px; + /*height:13px;*/ padding:1px; margin:0; pointer-events:none; diff --git a/dist/vis.js b/dist/vis.js index 833ae536..11bfbc55 100644 --- a/dist/vis.js +++ b/dist/vis.js @@ -5,7 +5,7 @@ * A dynamic, browser-based visualization library. * * @version 4.12.1-SNAPSHOT - * @date 2016-01-15 + * @date 2016-01-18 * * @license * Copyright (C) 2011-2016 Almende B.V, http://almende.com @@ -11295,10 +11295,11 @@ return /******/ (function(modules) { // webpackBootstrap // propagate over all elements (until stopped) var elem = _firstTarget; while (elem && !stopped) { - if(elem.hammer){ + var elemHammer = elem.hammer; + if(elemHammer){ var _handlers; - for(var k = 0; k < elem.hammer.length; k++){ - _handlers = elem.hammer[k]._handlers[event.type]; + for(var k = 0; k < elemHammer.length; k++){ + _handlers = elemHammer[k]._handlers[event.type]; if(_handlers) for (var i = 0; i < _handlers.length && !stopped; i++) { _handlers[i](event); } @@ -23181,6 +23182,8 @@ return /******/ (function(modules) { // webpackBootstrap }, { key: '_hide', value: function _hide() { + var _this = this; + var storePrevious = arguments.length <= 0 || arguments[0] === undefined ? true : arguments[0]; // store the previous color for next time; @@ -23195,10 +23198,13 @@ return /******/ (function(modules) { // webpackBootstrap this.frame.style.display = 'none'; // call the closing callback, restoring the onclick method. - if (this.closeCallback !== undefined) { - this.closeCallback(); - this.closeCallback = undefined; - } + // this is in a setTimeout because it will trigger the show again before the click is done. + setTimeout(function () { + if (_this.closeCallback !== undefined) { + _this.closeCallback(); + _this.closeCallback = undefined; + } + }, 0); } /** @@ -23482,7 +23488,7 @@ return /******/ (function(modules) { // webpackBootstrap }, { key: '_bindHammer', value: function _bindHammer() { - var _this = this; + var _this2 = this; this.drag = {}; this.pinch = {}; @@ -23490,19 +23496,19 @@ return /******/ (function(modules) { // webpackBootstrap this.hammer.get('pinch').set({ enable: true }); hammerUtil.onTouch(this.hammer, function (event) { - _this._moveSelector(event); + _this2._moveSelector(event); }); this.hammer.on('tap', function (event) { - _this._moveSelector(event); + _this2._moveSelector(event); }); this.hammer.on('panstart', function (event) { - _this._moveSelector(event); + _this2._moveSelector(event); }); this.hammer.on('panmove', function (event) { - _this._moveSelector(event); + _this2._moveSelector(event); }); this.hammer.on('panend', function (event) { - _this._moveSelector(event); + _this2._moveSelector(event); }); } @@ -27844,7 +27850,6 @@ return /******/ (function(modules) { // webpackBootstrap var _this2 = this; if (options !== undefined) { - var errorFound = _sharedValidator2['default'].validate(options, _optionsJs.allOptions); if (errorFound === true) { console.log('%cErrors have been found in the supplied options object.', _sharedValidator.printStyle); @@ -40048,8 +40053,9 @@ return /******/ (function(modules) { // webpackBootstrap this.initialRandomSeed = Math.round(Math.random() * 1000000); this.randomSeed = this.initialRandomSeed; + this.setPhysics = false; this.options = {}; - this.optionsBackup = {}; + this.optionsBackup = { physics: {} }; this.defaultOptions = { randomSeed: undefined, @@ -40057,6 +40063,10 @@ return /******/ (function(modules) { // webpackBootstrap hierarchical: { enabled: false, levelSeparation: 150, + nodeSpacing: 100, + treeSpacing: 200, + blockShifting: true, + edgeMinimization: true, direction: 'UD', // UD, DU, LR, RL sortMethod: 'hubsize' // hubsize, directed } @@ -40127,17 +40137,19 @@ return /******/ (function(modules) { // webpackBootstrap if (this.options.hierarchical.enabled === true) { // set the physics if (allOptions.physics === undefined || allOptions.physics === true) { - allOptions.physics = { solver: 'hierarchicalRepulsion' }; - this.optionsBackup.physics = { solver: 'barnesHut' }; + allOptions.physics = { + enabled: this.optionsBackup.physics.enabled === undefined ? true : this.optionsBackup.physics.enabled, + solver: 'hierarchicalRepulsion' + }; + this.optionsBackup.physics.enabled = this.optionsBackup.physics.enabled === undefined ? true : this.optionsBackup.physics.enabled; + this.optionsBackup.physics.solver = this.optionsBackup.physics.solver || 'barnesHut'; } else if (typeof allOptions.physics === 'object') { - this.optionsBackup.physics = { solver: 'barnesHut' }; - if (allOptions.physics.solver !== undefined) { - this.optionsBackup.physics = { solver: allOptions.physics.solver }; - } - allOptions.physics['solver'] = 'hierarchicalRepulsion'; + this.optionsBackup.physics.enabled = allOptions.physics.enabled === undefined ? true : allOptions.physics.enabled; + this.optionsBackup.physics.solver = allOptions.physics.solver || 'barnesHut'; + allOptions.physics.solver = 'hierarchicalRepulsion'; } else if (allOptions.physics !== false) { - this.optionsBackup.physics = { solver: 'barnesHut' }; - allOptions.physics['solver'] = 'hierarchicalRepulsion'; + this.optionsBackup.physics.solver = 'barnesHut'; + allOptions.physics = { solver: 'hierarchicalRepulsion' }; } // get the type of static smooth curve in case it is required @@ -40181,6 +40193,7 @@ return /******/ (function(modules) { // webpackBootstrap // force all edges into static smooth curves. Only applies to edges that do not use the global options for smooth. this.body.emitter.emit('_forceDisableDynamicCurves', type); } + console.log(JSON.stringify(allOptions), JSON.stringify(this.optionsBackup)); return allOptions; } }, { @@ -40331,6 +40344,7 @@ return /******/ (function(modules) { // webpackBootstrap var node = undefined, nodeId = undefined; var definedLevel = false; + var definedPositions = true; var undefinedLevel = false; this.hierarchicalLevels = {}; this.lastNodeOnLevel = {}; @@ -40339,10 +40353,6 @@ return /******/ (function(modules) { // webpackBootstrap this.hierarchicalTrees = {}; this.treeIndex = -1; - this.whiteSpaceReductionFactor = 0.5; - this.nodeSpacing = 100; - this.treeSpacing = 2 * this.nodeSpacing; - this.distributionOrdering = {}; this.distributionIndex = {}; this.distributionOrderingPresence = {}; @@ -40350,6 +40360,9 @@ return /******/ (function(modules) { // webpackBootstrap for (nodeId in this.body.nodes) { if (this.body.nodes.hasOwnProperty(nodeId)) { node = this.body.nodes[nodeId]; + if (node.options.x === undefined && node.options.y === undefined) { + definedPositions = false; + } if (node.options.level !== undefined) { definedLevel = true; this.hierarchicalLevels[nodeId] = node.options.level; @@ -40385,7 +40398,7 @@ return /******/ (function(modules) { // webpackBootstrap this._placeNodesByHierarchy(distribution); // condense the whitespace. - this._condenseHierarchy(distribution); + this._condenseHierarchy(); // shift to center so gravity does not have to do much this._shiftToCenter(); @@ -40398,17 +40411,20 @@ return /******/ (function(modules) { // webpackBootstrap */ }, { key: '_condenseHierarchy', - value: function _condenseHierarchy(distribution) { + value: function _condenseHierarchy() { var _this2 = this; + // Global var in this scope to define when the movement has stopped. + var stillShifting = false; + var branches = {}; // first we have some methods to help shifting trees around. // the main method to shift the trees var shiftTrees = function shiftTrees() { var treeSizes = getTreeSizes(); for (var i = 0; i < treeSizes.length - 1; i++) { var diff = treeSizes[i].max - treeSizes[i + 1].min; - if (diff !== _this2.treeSpacing) { - shiftTree(i + 1, diff - _this2.treeSpacing); + if (diff !== _this2.options.hierarchical.treeSpacing) { + shiftTree(i + 1, diff - _this2.options.hierarchical.treeSpacing); } } }; @@ -40468,33 +40484,26 @@ return /******/ (function(modules) { // webpackBootstrap var maxLevel = arguments.length <= 1 || arguments[1] === undefined ? 1e9 : arguments[1]; var minSpace = 1e9; - var maxSpace = -1e9; + var maxSpace = 1e9; var min = 1e9; var max = -1e9; for (var branchNode in branchMap) { if (branchMap.hasOwnProperty(branchNode)) { var node = _this2.body.nodes[branchNode]; var level = _this2.hierarchicalLevels[node.id]; - var index = _this2.distributionIndex[node.id]; - var position = _this2._getPositionForHierarchy(_this2.body.nodes[node.id]); - - // if this is the node at the side, there is no previous node - if (index != 0) { - var prevNode = _this2.distributionOrdering[level][index - 1]; - if (branchMap[prevNode.id] === undefined) { - var prevPos = _this2._getPositionForHierarchy(prevNode); - minSpace = Math.min(minSpace, position - prevPos); - } - } + var position = _this2._getPositionForHierarchy(node); - // if this is the node at the end there is no next node - if (index != _this2.distributionOrdering[level].length - 1) { - var nextNode = _this2.distributionOrdering[level][index + 1]; - if (branchMap[nextNode.id] === undefined) { - var nextPos = _this2._getPositionForHierarchy(nextNode); - maxSpace = Math.max(maxSpace, nextPos - position); - } - } + // get the space around the node. + + var _getSpaceAroundNode2 = _this2._getSpaceAroundNode(node, branchMap); + + var _getSpaceAroundNode22 = _slicedToArray(_getSpaceAroundNode2, 2); + + var minSpaceNode = _getSpaceAroundNode22[0]; + var maxSpaceNode = _getSpaceAroundNode22[1]; + + minSpace = Math.min(minSpaceNode, minSpace); + maxSpace = Math.min(maxSpaceNode, maxSpace); // the width is only relevant for the levels two nodes have in common. This is why we filter on this. if (level <= maxLevel) { @@ -40504,9 +40513,6 @@ return /******/ (function(modules) { // webpackBootstrap } } - // if there was no next node, the max space is infinite (1e9 ~ close enough) - maxSpace = maxSpace < 0 ? 1e9 : maxSpace; - return [min, max, minSpace, maxSpace]; }; @@ -40556,10 +40562,10 @@ return /******/ (function(modules) { // webpackBootstrap var level = levels[i]; var levelNodes = _this2.distributionOrdering[level]; if (levelNodes.length > 1) { - for (var _i = 0; _i < levelNodes.length - 1; _i++) { - if (hasSameParent(levelNodes[_i], levelNodes[_i + 1]) === true) { - if (_this2.hierarchicalTrees[levelNodes[_i].id] === _this2.hierarchicalTrees[levelNodes[_i + 1].id]) { - callback(levelNodes[_i], levelNodes[_i + 1], centerParents); + for (var j = 0; j < levelNodes.length - 1; j++) { + if (hasSameParent(levelNodes[j], levelNodes[j + 1]) === true) { + if (_this2.hierarchicalTrees[levelNodes[j].id] === _this2.hierarchicalTrees[levelNodes[j + 1].id]) { + callback(levelNodes[j], levelNodes[j + 1], centerParents); } } } @@ -40567,9 +40573,6 @@ return /******/ (function(modules) { // webpackBootstrap } }; - // Global var in this scope to define when the movement has stopped. - var stillShifting = false; - // callback for shifting branches var branchShiftCallback = function branchShiftCallback(node1, node2) { var centerParent = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2]; @@ -40579,7 +40582,7 @@ return /******/ (function(modules) { // webpackBootstrap var pos2 = _this2._getPositionForHierarchy(node2); var diffAbs = Math.abs(pos2 - pos1); //console.log("NOW CHEcKING:", node1.id, node2.id, diffAbs); - if (diffAbs > _this2.nodeSpacing) { + if (diffAbs > _this2.options.hierarchical.nodeSpacing) { var branchNodes1 = {};branchNodes1[node1.id] = true; var branchNodes2 = {};branchNodes2[node2.id] = true; @@ -40609,11 +40612,11 @@ return /******/ (function(modules) { // webpackBootstrap //console.log(node1.id, getBranchBoundary(branchNodes1, maxLevel), node2.id, getBranchBoundary(branchNodes2, maxLevel), maxLevel); var diffBranch = Math.abs(max1 - min2); - if (diffBranch > _this2.nodeSpacing) { - var offset = max1 - min2 + _this2.nodeSpacing; - if (offset < -minSpace2 + _this2.nodeSpacing) { - offset = -minSpace2 + _this2.nodeSpacing; - //console.log("RESETTING OFFSET", max1 - min2 + this.nodeSpacing, -minSpace2, offset); + if (diffBranch > _this2.options.hierarchical.nodeSpacing) { + var offset = max1 - min2 + _this2.options.hierarchical.nodeSpacing; + if (offset < -minSpace2 + _this2.options.hierarchical.nodeSpacing) { + offset = -minSpace2 + _this2.options.hierarchical.nodeSpacing; + //console.log("RESETTING OFFSET", max1 - min2 + this.options.hierarchical.nodeSpacing, -minSpace2, offset); } if (offset < 0) { //console.log("SHIFTING", node2.id, offset); @@ -40627,43 +40630,169 @@ return /******/ (function(modules) { // webpackBootstrap //this.body.emitter.emit("_redraw");}) }; - // callback for shifting individual nodes - var unitShiftCallback = function unitShiftCallback(node1, node2, centerParent) { + var minimizeEdgeLength = function minimizeEdgeLength(iterations, node) { //window.CALLBACKS.push(() => { - var pos1 = _this2._getPositionForHierarchy(node1); - var pos2 = _this2._getPositionForHierarchy(node2); - var diffAbs = Math.abs(pos2 - pos1); - //console.log("NOW CHEcKING:", node1.id, node2.id, diffAbs); - if (diffAbs > _this2.nodeSpacing) { - var diff = (pos1 + _this2.nodeSpacing - pos2) * _this2.whiteSpaceReductionFactor; - if (diff != 0) { + // console.log("ts",node.id); + var nodeId = node.id; + var allEdges = node.edges; + var nodeLevel = _this2.hierarchicalLevels[node.id]; + + // gather constants + var C2 = _this2.options.hierarchical.levelSeparation * _this2.options.hierarchical.levelSeparation; + var referenceNodes = {}; + var aboveEdges = []; + for (var i = 0; i < allEdges.length; i++) { + var edge = allEdges[i]; + if (edge.toId != edge.fromId) { + var otherNode = edge.toId == nodeId ? edge.from : edge.to; + referenceNodes[allEdges[i].id] = otherNode; + if (_this2.hierarchicalLevels[otherNode.id] < nodeLevel) { + aboveEdges.push(edge); + } + } + } + + // differentiated sum of lengths based on only moving one node over one axis + var getFx = function getFx(point, edges) { + var sum = 0; + for (var i = 0; i < edges.length; i++) { + if (referenceNodes[edges[i].id] !== undefined) { + var a = _this2._getPositionForHierarchy(referenceNodes[edges[i].id]) - point; + sum += a / Math.sqrt(a * a + C2); + } + } + return sum; + }; + + // doubly differentiated sum of lengths based on only moving one node over one axis + var getDFx = function getDFx(point, edges) { + var sum = 0; + for (var i = 0; i < edges.length; i++) { + if (referenceNodes[edges[i].id] !== undefined) { + var a = _this2._getPositionForHierarchy(referenceNodes[edges[i].id]) - point; + sum -= C2 * Math.pow(a * a + C2, -1.5); + } + } + return sum; + }; + + var getGuess = function getGuess(iterations, edges) { + var guess = _this2._getPositionForHierarchy(node); + // Newton's method for optimization + var guessMap = {}; + for (var i = 0; i < iterations; i++) { + var fx = getFx(guess, edges); + var dfx = getDFx(guess, edges); + + // we limit the movement to avoid instability. + var limit = 40; + var ratio = Math.max(-limit, Math.min(limit, Math.round(fx / dfx))); + guess = guess - ratio; + // reduce duplicates + if (guessMap[guess] !== undefined) { + break; + } + guessMap[guess] = i; + } + return guess; + }; + + var moveBranch = function moveBranch(guess) { + // position node if there is space + var nodePosition = _this2._getPositionForHierarchy(node); + + // check movable area of the branch + if (branches[node.id] === undefined) { + var branchNodes = {}; + branchNodes[node.id] = true; + getBranchNodes(node, branchNodes); + branches[node.id] = branchNodes; + } + + var _getBranchBoundary4 = getBranchBoundary(branches[node.id]); + + var _getBranchBoundary42 = _slicedToArray(_getBranchBoundary4, 4); + + var minBranch = _getBranchBoundary42[0]; + var maxBranch = _getBranchBoundary42[1]; + var minSpaceBranch = _getBranchBoundary42[2]; + var maxSpaceBranch = _getBranchBoundary42[3]; + + var diff = guess - nodePosition; + + // check if we are allowed to move the node: + var branchOffset = 0; + if (diff > 0) { + branchOffset = Math.min(diff, maxSpaceBranch - _this2.options.hierarchical.nodeSpacing); + } else if (diff < 0) { + branchOffset = -Math.min(-diff, minSpaceBranch - _this2.options.hierarchical.nodeSpacing); + } + + if (branchOffset != 0) { + //console.log("moving branch:",branchOffset, maxSpaceBranch, minSpaceBranch) + _this2._shiftBlock(node.id, branchOffset); + //this.body.emitter.emit("_redraw"); stillShifting = true; } - var factor = node2.edges.length / (node1.edges.length + node2.edges.length); - _this2._setPositionForHierarchy(node2, pos2 + factor * diff, undefined, true); - _this2._setPositionForHierarchy(node1, pos1 - (1 - factor) * diff, undefined, true); - if (centerParent === true) { - _this2._centerParent(node2); + }; + + var moveNode = function moveNode(guess) { + var nodePosition = _this2._getPositionForHierarchy(node); + + // position node if there is space + + var _getSpaceAroundNode3 = _this2._getSpaceAroundNode(node); + + var _getSpaceAroundNode32 = _slicedToArray(_getSpaceAroundNode3, 2); + + var minSpace = _getSpaceAroundNode32[0]; + var maxSpace = _getSpaceAroundNode32[1]; + + var diff = guess - nodePosition; + // check if we are allowed to move the node: + var newPosition = nodePosition; + if (diff > 0) { + newPosition = Math.min(nodePosition + (maxSpace - _this2.options.hierarchical.nodeSpacing), guess); + } else if (diff < 0) { + newPosition = Math.max(nodePosition - (minSpace - _this2.options.hierarchical.nodeSpacing), guess); + } + + if (newPosition !== nodePosition) { + //console.log("moving Node:",diff, minSpace, maxSpace) + _this2._setPositionForHierarchy(node, newPosition, undefined, true); + //this.body.emitter.emit("_redraw"); + stillShifting = true; } - } - //this.body.emitter.emit("_redraw");}) + }; + + var guess = getGuess(iterations, aboveEdges); + moveBranch(guess); + guess = getGuess(iterations, allEdges); + moveNode(guess); + //}) }; - // method to shift all nodes closer together iteratively - var shiftUnitsCloser = function shiftUnitsCloser(iterations) { + // method to remove whitespace between branches. Because we do bottom up, we can center the parents. + var minimizeEdgeLengthBottomUp = function minimizeEdgeLengthBottomUp(iterations) { var levels = Object.keys(_this2.distributionOrdering); + levels = levels.reverse(); for (var i = 0; i < iterations; i++) { stillShifting = false; - shiftElementsCloser(unitShiftCallback, levels, false); + for (var j = 0; j < levels.length; j++) { + var level = levels[j]; + var levelNodes = _this2.distributionOrdering[level]; + for (var k = 0; k < levelNodes.length; k++) { + minimizeEdgeLength(1000, levelNodes[k]); + } + } if (stillShifting !== true) { - //console.log("FINISHED shiftUnitsCloser IN " + i); + //console.log("FINISHED minimizeEdgeLengthBottomUp IN " + i); break; } } - //console.log("FINISHED shiftUnitsCloser IN " + iterations); }; - // method to remove whitespace between branches. Because we do bottom up, we can center the parents. + //// method to remove whitespace between branches. Because we do bottom up, we can center the parents. var shiftBranchesCloserBottomUp = function shiftBranchesCloserBottomUp(iterations) { var levels = Object.keys(_this2.distributionOrdering); levels = levels.reverse(); @@ -40671,7 +40800,7 @@ return /******/ (function(modules) { // webpackBootstrap stillShifting = false; shiftElementsCloser(branchShiftCallback, levels, true); if (stillShifting !== true) { - //console.log("FINISHED shiftBranchesCloserBottomUp IN " + i); + //console.log("FINISHED shiftBranchesCloserBottomUp IN " + (i+1)); break; } } @@ -40679,18 +40808,64 @@ return /******/ (function(modules) { // webpackBootstrap // center all parents var centerAllParents = function centerAllParents() { - for (var node in _this2.body.nodes) { - _this2._centerParent(_this2.body.nodes[node]); + for (var nodeId in _this2.body.nodes) { + if (_this2.body.nodes.hasOwnProperty(nodeId)) _this2._centerParent(_this2.body.nodes[nodeId]); } }; // the actual work is done here. - shiftBranchesCloserBottomUp(5); - centerAllParents(); - shiftUnitsCloser(2); + if (this.options.hierarchical.blockShifting === true) { + shiftBranchesCloserBottomUp(5); + centerAllParents(); + } + + // minimize edge length + if (this.options.hierarchical.edgeMinimization === true) { + minimizeEdgeLengthBottomUp(20); + } + shiftTrees(); } + /** + * This gives the space around the node. IF a map is supplied, it will only check against nodes NOT in the map. + * This is used to only get the distances to nodes outside of a branch. + * @param node + * @param map + * @returns {*[]} + * @private + */ + }, { + key: '_getSpaceAroundNode', + value: function _getSpaceAroundNode(node, map) { + var useMap = true; + if (map === undefined) { + useMap = false; + } + var level = this.hierarchicalLevels[node.id]; + var index = this.distributionIndex[node.id]; + var position = this._getPositionForHierarchy(node); + var minSpace = 1e9; + var maxSpace = 1e9; + if (index !== 0) { + var prevNode = this.distributionOrdering[level][index - 1]; + if (useMap === true && map[prevNode.id] === undefined || useMap === false) { + var prevPos = this._getPositionForHierarchy(prevNode); + minSpace = position - prevPos; + } + } + + if (index != this.distributionOrdering[level].length - 1) { + var nextNode = this.distributionOrdering[level][index + 1]; + if (useMap === true && map[nextNode.id] === undefined || useMap === false) { + var nextPos = this._getPositionForHierarchy(nextNode); + maxSpace = Math.min(maxSpace, nextPos - position); + } + } + + return [minSpace, maxSpace]; + } + /** * We use this method to center a parent node and check if it does not cross other nodes when it does. * @param node @@ -40710,32 +40885,25 @@ return /******/ (function(modules) { // webpackBootstrap var maxPos = -1e9; var children = this.hierarchicalParents[parentId].children; if (children.length > 0) { - for (var _i2 = 0; _i2 < children.length; _i2++) { - var childNode = this.body.nodes[children[_i2]]; + for (var _i = 0; _i < children.length; _i++) { + var childNode = this.body.nodes[children[_i]]; minPos = Math.min(minPos, this._getPositionForHierarchy(childNode)); maxPos = Math.max(maxPos, this._getPositionForHierarchy(childNode)); } } - var level = this.hierarchicalLevels[parentId]; - var index = this.distributionIndex[parentId]; var position = this._getPositionForHierarchy(parentNode); - var minSpace = 1e9; - var maxSpace = 1e9; - if (index != 0) { - var prevNode = this.distributionOrdering[level][index - 1]; - var prevPos = this._getPositionForHierarchy(prevNode); - minSpace = position - prevPos; - } - if (index != this.distributionOrdering[level].length - 1) { - var nextNode = this.distributionOrdering[level][index + 1]; - var nextPos = this._getPositionForHierarchy(nextNode); - maxSpace = Math.min(maxSpace, nextPos - position); - } + var _getSpaceAroundNode4 = this._getSpaceAroundNode(parentNode); + + var _getSpaceAroundNode42 = _slicedToArray(_getSpaceAroundNode4, 2); + + var minSpace = _getSpaceAroundNode42[0]; + var maxSpace = _getSpaceAroundNode42[1]; var newPosition = 0.5 * (minPos + maxPos); - if (newPosition < position + maxSpace && newPosition > position - minSpace) { + var diff = position - newPosition; + if (diff < 0 && Math.abs(diff) < maxSpace - this.options.hierarchical.nodeSpacing || diff > 0 && Math.abs(diff) < minSpace - this.options.hierarchical.nodeSpacing) { this._setPositionForHierarchy(parentNode, newPosition, undefined, true); } } @@ -40764,7 +40932,7 @@ return /******/ (function(modules) { // webpackBootstrap for (var i = 0; i < nodeArray.length; i++) { var node = nodeArray[i]; if (this.positionedNodes[node.id] === undefined) { - this._setPositionForHierarchy(node, this.nodeSpacing * i, level); + this._setPositionForHierarchy(node, this.options.hierarchical.nodeSpacing * i, level); this.positionedNodes[node.id] = true; this._placeBranchNodes(node.id, level); } @@ -40967,8 +41135,6 @@ return /******/ (function(modules) { // webpackBootstrap /** * Update the bookkeeping of parent and child. - * @param parentNodeId - * @param childNodeId * @private */ }, { @@ -41079,15 +41245,15 @@ return /******/ (function(modules) { // webpackBootstrap if (i === 0) { pos = this._getPositionForHierarchy(this.body.nodes[parentId]); } else { - pos = this._getPositionForHierarchy(childNodes[i - 1]) + this.nodeSpacing; + pos = this._getPositionForHierarchy(childNodes[i - 1]) + this.options.hierarchical.nodeSpacing; } this._setPositionForHierarchy(childNode, pos, childNodeLevel); // if overlap has been detected, we shift the branch if (this.lastNodeOnLevel[childNodeLevel] !== undefined) { var previousPos = this._getPositionForHierarchy(this.body.nodes[this.lastNodeOnLevel[childNodeLevel]]); - if (pos - previousPos < this.nodeSpacing) { - var diff = previousPos + this.nodeSpacing - pos; + if (pos - previousPos < this.options.hierarchical.nodeSpacing) { + var diff = previousPos + this.options.hierarchical.nodeSpacing - pos; var sharedParent = this._findCommonParent(this.lastNodeOnLevel[childNodeLevel], childNode.id); this._shiftBlock(sharedParent.withChild, diff); } @@ -42620,6 +42786,10 @@ return /******/ (function(modules) { // webpackBootstrap hierarchical: { enabled: { boolean: boolean }, levelSeparation: { number: number }, + nodeSpacing: { number: number }, + treeSpacing: { number: number }, + blockShifting: { boolean: boolean }, + edgeMinimization: { boolean: boolean }, direction: { string: ['UD', 'DU', 'LR', 'RL'] }, // UD, DU, LR, RL sortMethod: { string: ['hubsize', 'directed'] }, // hubsize, directed __type__: { object: object, boolean: boolean } @@ -42919,6 +43089,10 @@ return /******/ (function(modules) { // webpackBootstrap hierarchical: { enabled: false, levelSeparation: [150, 20, 500, 5], + nodeSpacing: [100, 20, 500, 5], + treeSpacing: [200, 20, 500, 5], + blockShifting: true, + edgeMinimization: true, direction: ['UD', 'DU', 'LR', 'RL'], // UD, DU, LR, RL sortMethod: ['hubsize', 'directed'] // hubsize, directed } diff --git a/dist/vis.min.css b/dist/vis.min.css index bd6e1abe..ee0f437d 100644 --- a/dist/vis.min.css +++ b/dist/vis.min.css @@ -1 +1 @@ -.vis-background,.vis-labelset,.vis-timeline{overflow:hidden}.vis .overlay{position:absolute;top:0;left:0;width:100%;height:100%;z-index:10}.vis-active{box-shadow:0 0 10px #86d5f8}.vis [class*=span]{min-height:0;width:auto}div.vis-configuration{position:relative;display:block;float:left;font-size:12px}div.vis-configuration-wrapper{display:block;width:700px}div.vis-configuration.vis-config-option-container{display:block;width:495px;background-color:#fff;border:2px solid #f7f8fa;border-radius:4px;margin-top:20px;left:10px;padding-left:5px}div.vis-configuration.vis-config-button{display:block;width:495px;height:25px;vertical-align:middle;line-height:25px;background-color:#f7f8fa;border:2px solid #ceced0;border-radius:4px;margin-top:20px;left:10px;padding-left:5px;cursor:pointer;margin-bottom:30px}div.vis-configuration.vis-config-button.hover{background-color:#4588e6;border:2px solid #214373;color:#fff}div.vis-configuration.vis-config-item{display:block;float:left;width:495px;height:25px;vertical-align:middle;line-height:25px}div.vis-configuration.vis-config-item.vis-config-s2{left:10px;background-color:#f7f8fa;padding-left:5px;border-radius:3px}div.vis-configuration.vis-config-item.vis-config-s3{left:20px;background-color:#e4e9f0;padding-left:5px;border-radius:3px}div.vis-configuration.vis-config-item.vis-config-s4{left:30px;background-color:#cfd8e6;padding-left:5px;border-radius:3px}div.vis-configuration.vis-config-header{font-size:18px;font-weight:700}div.vis-configuration.vis-config-label{width:120px;height:25px;line-height:25px}div.vis-configuration.vis-config-label.vis-config-s3{width:110px}div.vis-configuration.vis-config-label.vis-config-s4{width:100px}div.vis-configuration.vis-config-colorBlock{top:1px;width:30px;height:19px;border:1px solid #444;border-radius:2px;padding:0;margin:0;cursor:pointer}input.vis-configuration.vis-config-checkbox{left:-5px}input.vis-configuration.vis-config-rangeinput{position:relative;top:-5px;width:60px;height:13px;padding:1px;margin:0;pointer-events:none}.vis-panel,.vis-timeline{padding:0;box-sizing:border-box}input.vis-configuration.vis-config-range{-webkit-appearance:none;border:0 solid #fff;background-color:rgba(0,0,0,0);width:300px;height:20px}input.vis-configuration.vis-config-range::-webkit-slider-runnable-track{width:300px;height:5px;background:#dedede;background:-moz-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#dedede),color-stop(99%,#c8c8c8));background:-webkit-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-o-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-ms-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:linear-gradient(to bottom,#dedede 0,#c8c8c8 99%);filter:progid:DXImageTransform.Microsoft.gradient( startColorstr='#dedede', endColorstr='#c8c8c8', GradientType=0 );border:1px solid #999;box-shadow:#aaa 0 0 3px 0;border-radius:3px}input.vis-configuration.vis-config-range::-webkit-slider-thumb{-webkit-appearance:none;border:1px solid #14334b;height:17px;width:17px;border-radius:50%;background:#3876c2;background:-moz-linear-gradient(top,#3876c2 0,#385380 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#3876c2),color-stop(100%,#385380));background:-webkit-linear-gradient(top,#3876c2 0,#385380 100%);background:-o-linear-gradient(top,#3876c2 0,#385380 100%);background:-ms-linear-gradient(top,#3876c2 0,#385380 100%);background:linear-gradient(to bottom,#3876c2 0,#385380 100%);filter:progid:DXImageTransform.Microsoft.gradient( startColorstr='#3876c2', endColorstr='#385380', GradientType=0 );box-shadow:#111927 0 0 1px 0;margin-top:-7px}input.vis-configuration.vis-config-range:focus{outline:0}input.vis-configuration.vis-config-range:focus::-webkit-slider-runnable-track{background:#9d9d9d;background:-moz-linear-gradient(top,#9d9d9d 0,#c8c8c8 99%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#9d9d9d),color-stop(99%,#c8c8c8));background:-webkit-linear-gradient(top,#9d9d9d 0,#c8c8c8 99%);background:-o-linear-gradient(top,#9d9d9d 0,#c8c8c8 99%);background:-ms-linear-gradient(top,#9d9d9d 0,#c8c8c8 99%);background:linear-gradient(to bottom,#9d9d9d 0,#c8c8c8 99%);filter:progid:DXImageTransform.Microsoft.gradient( startColorstr='#9d9d9d', endColorstr='#c8c8c8', GradientType=0 )}input.vis-configuration.vis-config-range::-moz-range-track{width:300px;height:10px;background:#dedede;background:-moz-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#dedede),color-stop(99%,#c8c8c8));background:-webkit-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-o-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-ms-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:linear-gradient(to bottom,#dedede 0,#c8c8c8 99%);filter:progid:DXImageTransform.Microsoft.gradient( startColorstr='#dedede', endColorstr='#c8c8c8', GradientType=0 );border:1px solid #999;box-shadow:#aaa 0 0 3px 0;border-radius:3px}input.vis-configuration.vis-config-range::-moz-range-thumb{border:none;height:16px;width:16px;border-radius:50%;background:#385380}input.vis-configuration.vis-config-range:-moz-focusring{outline:#fff solid 1px;outline-offset:-1px}input.vis-configuration.vis-config-range::-ms-track{width:300px;height:5px;background:0 0;border-color:transparent;border-width:6px 0;color:transparent}input.vis-configuration.vis-config-range::-ms-fill-lower{background:#777;border-radius:10px}input.vis-configuration.vis-config-range::-ms-fill-upper{background:#ddd;border-radius:10px}input.vis-configuration.vis-config-range::-ms-thumb{border:none;height:16px;width:16px;border-radius:50%;background:#385380}input.vis-configuration.vis-config-range:focus::-ms-fill-lower{background:#888}input.vis-configuration.vis-config-range:focus::-ms-fill-upper{background:#ccc}.vis-configuration-popup{position:absolute;background:rgba(57,76,89,.85);border:2px solid #f2faff;line-height:30px;height:30px;width:150px;text-align:center;color:#fff;font-size:14px;border-radius:4px;-webkit-transition:opacity .3s ease-in-out;-moz-transition:opacity .3s ease-in-out;transition:opacity .3s ease-in-out}.vis-configuration-popup:after,.vis-configuration-popup:before{left:100%;top:50%;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}.vis-configuration-popup:after{border-color:rgba(136,183,213,0);border-left-color:rgba(57,76,89,.85);border-width:8px;margin-top:-8px}.vis-configuration-popup:before{border-color:rgba(194,225,245,0);border-left-color:#f2faff;border-width:12px;margin-top:-12px}.vis-timeline{position:relative;border:1px solid #bfbfbf;margin:0}.vis-panel{position:absolute;margin:0}.vis-panel.vis-bottom,.vis-panel.vis-center,.vis-panel.vis-left,.vis-panel.vis-right,.vis-panel.vis-top{border:1px #bfbfbf}.vis-panel.vis-center,.vis-panel.vis-left,.vis-panel.vis-right{border-top-style:solid;border-bottom-style:solid;overflow:hidden}.vis-panel.vis-bottom,.vis-panel.vis-center,.vis-panel.vis-top{border-left-style:solid;border-right-style:solid}.vis-panel>.vis-content{position:relative}.vis-panel .vis-shadow{position:absolute;width:100%;height:1px;box-shadow:0 0 10px rgba(0,0,0,.8)}.vis-itemset,.vis-labelset,.vis-labelset .vis-label{position:relative;box-sizing:border-box}.vis-panel .vis-shadow.vis-top{top:-1px;left:0}.vis-panel .vis-shadow.vis-bottom{bottom:-1px;left:0}.vis-labelset .vis-label{left:0;top:0;width:100%;color:#4d4d4d;border-bottom:1px solid #bfbfbf}.vis-labelset .vis-label.draggable{cursor:pointer}.vis-labelset .vis-label:last-child{border-bottom:none}.vis-labelset .vis-label .vis-inner{display:inline-block;padding:5px}.vis-labelset .vis-label .vis-inner.vis-hidden{padding:0}.vis-itemset{padding:0;margin:0}.vis-itemset .vis-background,.vis-itemset .vis-foreground{position:absolute;width:100%;height:100%;overflow:visible}.vis-axis{position:absolute;width:100%;height:0;left:0;z-index:1}.vis-foreground .vis-group{position:relative;box-sizing:border-box;border-bottom:1px solid #bfbfbf}.vis-foreground .vis-group:last-child{border-bottom:none}.vis-overlay{position:absolute;top:0;left:0;width:100%;height:100%;z-index:10}.vis-item{position:absolute;color:#1A1A1A;border-color:#97B0F8;border-width:1px;background-color:#D5DDF6;display:inline-block}.vis-item.vis-point.vis-selected,.vis-item.vis-selected{background-color:#FFF785}.vis-item.vis-selected{border-color:#FFC200;z-index:2}.vis-editable.vis-selected{cursor:move}.vis-item.vis-box{text-align:center;border-style:solid;border-radius:2px}.vis-item.vis-point{background:0 0}.vis-item.vis-dot{position:absolute;padding:0;border-width:4px;border-style:solid;border-radius:4px}.vis-item.vis-range{border-style:solid;border-radius:2px;box-sizing:border-box}.vis-item.vis-background{border:none;background-color:rgba(213,221,246,.4);box-sizing:border-box;padding:0;margin:0}.vis-item .vis-item-overflow{position:relative;width:100%;height:100%;padding:0;margin:0;overflow:hidden}.vis-item.vis-range .vis-item-content{position:relative;display:inline-block}.vis-item.vis-background .vis-item-content{position:absolute;display:inline-block}.vis-item.vis-line{padding:0;position:absolute;width:0;border-left-width:1px;border-left-style:solid}.vis-item .vis-item-content{white-space:nowrap;box-sizing:border-box;padding:5px}.vis-item .vis-delete{background:url(img/timeline/delete.png) center no-repeat;position:absolute;width:24px;height:24px;top:-4px;right:-24px;cursor:pointer}.vis-item.vis-range .vis-drag-left{position:absolute;width:24px;max-width:20%;min-width:2px;height:100%;top:0;left:-4px;cursor:w-resize}.vis-item.vis-range .vis-drag-right{position:absolute;width:24px;max-width:20%;min-width:2px;height:100%;top:0;right:-4px;cursor:e-resize}.vis-range.vis-item.vis-readonly .vis-drag-left,.vis-range.vis-item.vis-readonly .vis-drag-right{cursor:auto}.vis-time-axis{position:relative;overflow:hidden}.vis-time-axis.vis-foreground{top:0;left:0;width:100%}.vis-time-axis.vis-background{position:absolute;top:0;left:0;width:100%;height:100%}.vis-time-axis .vis-text{position:absolute;color:#4d4d4d;padding:3px;overflow:hidden;box-sizing:border-box;white-space:nowrap}.vis-time-axis .vis-text.vis-measure{position:absolute;padding-left:0;padding-right:0;margin-left:0;margin-right:0;visibility:hidden}.vis-time-axis .vis-grid.vis-vertical{position:absolute;border-left:1px solid}.vis-time-axis .vis-grid.vis-minor{border-color:#e5e5e5}.vis-time-axis .vis-grid.vis-major{border-color:#bfbfbf}.vis-current-time{background-color:#FF7F6E;width:2px;z-index:1}.vis-custom-time{background-color:#6E94FF;width:2px;cursor:move;z-index:1}div.vis-network div.vis-close,div.vis-network div.vis-edit-mode div.vis-button,div.vis-network div.vis-manipulation div.vis-button{cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-webkit-touch-callout:none;-khtml-user-select:none}.vis-panel.vis-background.vis-horizontal .vis-grid.vis-horizontal{position:absolute;width:100%;height:0;border-bottom:1px solid}.vis-panel.vis-background.vis-horizontal .vis-grid.vis-minor{border-color:#e5e5e5}.vis-panel.vis-background.vis-horizontal .vis-grid.vis-major{border-color:#bfbfbf}.vis-data-axis .vis-y-axis.vis-major{width:100%;position:absolute;color:#4d4d4d;white-space:nowrap}.vis-data-axis .vis-y-axis.vis-major.vis-measure{padding:0;margin:0;border:0;visibility:hidden;width:auto}.vis-data-axis .vis-y-axis.vis-minor{position:absolute;width:100%;color:#bebebe;white-space:nowrap}.vis-data-axis .vis-y-axis.vis-minor.vis-measure{padding:0;margin:0;border:0;visibility:hidden;width:auto}.vis-data-axis .vis-y-axis.vis-title{position:absolute;color:#4d4d4d;white-space:nowrap;bottom:20px;text-align:center}.vis-data-axis .vis-y-axis.vis-title.vis-measure{padding:0;margin:0;visibility:hidden;width:auto}.vis-data-axis .vis-y-axis.vis-title.vis-left{bottom:0;-webkit-transform-origin:left top;-moz-transform-origin:left top;-ms-transform-origin:left top;-o-transform-origin:left top;transform-origin:left bottom;-webkit-transform:rotate(-90deg);-moz-transform:rotate(-90deg);-ms-transform:rotate(-90deg);-o-transform:rotate(-90deg);transform:rotate(-90deg)}.vis-data-axis .vis-y-axis.vis-title.vis-right{bottom:0;-webkit-transform-origin:right bottom;-moz-transform-origin:right bottom;-ms-transform-origin:right bottom;-o-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.vis-legend{background-color:rgba(247,252,255,.65);padding:5px;border:1px solid #b3b3b3;box-shadow:2px 2px 10px rgba(154,154,154,.55)}.vis-legend-text{white-space:nowrap;display:inline-block}.vis-graph-group0{fill:#4f81bd;fill-opacity:0;stroke-width:2px;stroke:#4f81bd}.vis-graph-group1{fill:#f79646;fill-opacity:0;stroke-width:2px;stroke:#f79646}.vis-graph-group2{fill:#8c51cf;fill-opacity:0;stroke-width:2px;stroke:#8c51cf}.vis-graph-group3{fill:#75c841;fill-opacity:0;stroke-width:2px;stroke:#75c841}.vis-graph-group4{fill:#ff0100;fill-opacity:0;stroke-width:2px;stroke:#ff0100}.vis-graph-group5{fill:#37d8e6;fill-opacity:0;stroke-width:2px;stroke:#37d8e6}.vis-graph-group6{fill:#042662;fill-opacity:0;stroke-width:2px;stroke:#042662}.vis-graph-group7{fill:#00ff26;fill-opacity:0;stroke-width:2px;stroke:#00ff26}.vis-graph-group8{fill:#f0f;fill-opacity:0;stroke-width:2px;stroke:#f0f}.vis-graph-group9{fill:#8f3938;fill-opacity:0;stroke-width:2px;stroke:#8f3938}.vis-timeline .vis-fill{fill-opacity:.1;stroke:none}.vis-timeline .vis-bar{fill-opacity:.5;stroke-width:1px}.vis-timeline .vis-point{stroke-width:2px;fill-opacity:1}.vis-timeline .vis-legend-background{stroke-width:1px;fill-opacity:.9;fill:#fff;stroke:#c2c2c2}.vis-timeline .vis-outline{stroke-width:1px;fill-opacity:1;fill:#fff;stroke:#e5e5e5}.vis-timeline .vis-icon-fill{fill-opacity:.3;stroke:none}div.vis-network div.vis-manipulation{border-width:0;border-bottom:1px;border-style:solid;border-color:#d6d9d8;background:#fff;background:-moz-linear-gradient(top,#fff 0,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fff),color-stop(48%,#fcfcfc),color-stop(50%,#fafafa),color-stop(100%,#fcfcfc));background:-webkit-linear-gradient(top,#fff 0,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%);background:-o-linear-gradient(top,#fff 0,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%);background:-ms-linear-gradient(top,#fff 0,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%);background:linear-gradient(to bottom,#fff 0,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%);filter:progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#fcfcfc', GradientType=0 );padding-top:4px;position:absolute;left:0;top:0;width:100%;height:28px}div.vis-network div.vis-edit-mode{position:absolute;left:0;top:5px;height:30px}div.vis-network div.vis-close{position:absolute;right:0;top:0;width:30px;height:30px;background-position:20px 3px;background-repeat:no-repeat;background-image:url(img/network/cross.png);user-select:none}div.vis-network div.vis-close:hover{opacity:.6}div.vis-network div.vis-edit-mode div.vis-button,div.vis-network div.vis-manipulation div.vis-button{float:left;font-family:verdana;font-size:12px;-moz-border-radius:15px;border-radius:15px;display:inline-block;background-position:0 0;background-repeat:no-repeat;height:24px;margin-left:10px;padding:0 8px;user-select:none}div.vis-network div.vis-manipulation div.vis-button:hover{box-shadow:1px 1px 8px rgba(0,0,0,.2)}div.vis-network div.vis-manipulation div.vis-button:active{box-shadow:1px 1px 8px rgba(0,0,0,.5)}div.vis-network div.vis-manipulation div.vis-button.vis-back{background-image:url(img/network/backIcon.png)}div.vis-network div.vis-manipulation div.vis-button.vis-none:hover{box-shadow:1px 1px 8px transparent;cursor:default}div.vis-network div.vis-manipulation div.vis-button.vis-none:active{box-shadow:1px 1px 8px transparent}div.vis-network div.vis-manipulation div.vis-button.vis-none{padding:0}div.vis-network div.vis-manipulation div.notification{margin:2px;font-weight:700}div.vis-network div.vis-manipulation div.vis-button.vis-add{background-image:url(img/network/addNodeIcon.png)}div.vis-network div.vis-edit-mode div.vis-button.vis-edit,div.vis-network div.vis-manipulation div.vis-button.vis-edit{background-image:url(img/network/editIcon.png)}div.vis-network div.vis-edit-mode div.vis-button.vis-edit.vis-edit-mode{background-color:#fcfcfc;border:1px solid #ccc}div.vis-network div.vis-manipulation div.vis-button.vis-connect{background-image:url(img/network/connectIcon.png)}div.vis-network div.vis-manipulation div.vis-button.vis-delete{background-image:url(img/network/deleteIcon.png)}div.vis-network div.vis-edit-mode div.vis-label,div.vis-network div.vis-manipulation div.vis-label{margin:0 0 0 23px;line-height:25px}div.vis-network div.vis-manipulation div.vis-separator-line{float:left;display:inline-block;width:1px;height:21px;background-color:#bdbdbd;margin:0 7px 0 15px}div.vis-network-tooltip{position:absolute;visibility:hidden;padding:5px;white-space:nowrap;font-family:verdana;font-size:14px;font-color:#000;background-color:#f5f4ed;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;border:1px solid #808074;box-shadow:3px 3px 10px rgba(0,0,0,.2);pointer-events:none}div.vis-network div.vis-navigation div.vis-button{width:34px;height:34px;-moz-border-radius:17px;border-radius:17px;position:absolute;display:inline-block;background-position:2px 2px;background-repeat:no-repeat;cursor:pointer;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}div.vis-network div.vis-navigation div.vis-button:hover{box-shadow:0 0 3px 3px rgba(56,207,21,.3)}div.vis-network div.vis-navigation div.vis-button:active{box-shadow:0 0 1px 3px rgba(56,207,21,.95)}div.vis-network div.vis-navigation div.vis-button.vis-up{background-image:url(img/network/upArrow.png);bottom:50px;left:55px}div.vis-network div.vis-navigation div.vis-button.vis-down{background-image:url(img/network/downArrow.png);bottom:10px;left:55px}div.vis-network div.vis-navigation div.vis-button.vis-left{background-image:url(img/network/leftArrow.png);bottom:10px;left:15px}div.vis-network div.vis-navigation div.vis-button.vis-right{background-image:url(img/network/rightArrow.png);bottom:10px;left:95px}div.vis-network div.vis-navigation div.vis-button.vis-zoomIn{background-image:url(img/network/plus.png);bottom:10px;right:15px}div.vis-network div.vis-navigation div.vis-button.vis-zoomOut{background-image:url(img/network/minus.png);bottom:10px;right:55px}div.vis-network div.vis-navigation div.vis-button.vis-zoomExtends{background-image:url(img/network/zoomExtends.png);bottom:50px;right:15px}div.vis-color-picker{position:absolute;top:0;left:30px;margin-top:-140px;margin-left:30px;width:310px;height:444px;z-index:1;padding:10px;border-radius:15px;background-color:#fff;display:none;box-shadow:rgba(0,0,0,.5) 0 0 10px 0}div.vis-color-picker div.vis-arrow{position:absolute;top:147px;left:5px}div.vis-color-picker div.vis-arrow::after,div.vis-color-picker div.vis-arrow::before{right:100%;top:50%;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}div.vis-color-picker div.vis-arrow:after{border-color:rgba(255,255,255,0);border-right-color:#fff;border-width:30px;margin-top:-30px}div.vis-color-picker div.vis-color{position:absolute;width:289px;height:289px;cursor:pointer}div.vis-color-picker div.vis-brightness{position:absolute;top:313px}div.vis-color-picker div.vis-opacity{position:absolute;top:350px}div.vis-color-picker div.vis-selector{position:absolute;top:137px;left:137px;width:15px;height:15px;border-radius:15px;border:1px solid #fff;background:#4c4c4c;background:-moz-linear-gradient(top,#4c4c4c 0,#595959 12%,#666 25%,#474747 39%,#2c2c2c 50%,#000 51%,#111 60%,#2b2b2b 76%,#1c1c1c 91%,#131313 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#4c4c4c),color-stop(12%,#595959),color-stop(25%,#666),color-stop(39%,#474747),color-stop(50%,#2c2c2c),color-stop(51%,#000),color-stop(60%,#111),color-stop(76%,#2b2b2b),color-stop(91%,#1c1c1c),color-stop(100%,#131313));background:-webkit-linear-gradient(top,#4c4c4c 0,#595959 12%,#666 25%,#474747 39%,#2c2c2c 50%,#000 51%,#111 60%,#2b2b2b 76%,#1c1c1c 91%,#131313 100%);background:-o-linear-gradient(top,#4c4c4c 0,#595959 12%,#666 25%,#474747 39%,#2c2c2c 50%,#000 51%,#111 60%,#2b2b2b 76%,#1c1c1c 91%,#131313 100%);background:-ms-linear-gradient(top,#4c4c4c 0,#595959 12%,#666 25%,#474747 39%,#2c2c2c 50%,#000 51%,#111 60%,#2b2b2b 76%,#1c1c1c 91%,#131313 100%);background:linear-gradient(to bottom,#4c4c4c 0,#595959 12%,#666 25%,#474747 39%,#2c2c2c 50%,#000 51%,#111 60%,#2b2b2b 76%,#1c1c1c 91%,#131313 100%);filter:progid:DXImageTransform.Microsoft.gradient( startColorstr='#4c4c4c', endColorstr='#131313', GradientType=0 )}div.vis-color-picker div.vis-initial-color,div.vis-color-picker div.vis-new-color{width:140px;height:20px;top:380px;font-size:10px;color:rgba(0,0,0,.4);line-height:20px;position:absolute;vertical-align:middle}div.vis-color-picker div.vis-new-color{border:1px solid rgba(0,0,0,.1);border-radius:5px;left:159px;text-align:right;padding-right:2px}div.vis-color-picker div.vis-initial-color{border:1px solid rgba(0,0,0,.1);border-radius:5px;left:10px;text-align:left;padding-left:2px}div.vis-color-picker div.vis-label{position:absolute;width:300px;left:10px}div.vis-color-picker div.vis-label.vis-brightness{top:300px}div.vis-color-picker div.vis-label.vis-opacity{top:338px}div.vis-color-picker div.vis-button{position:absolute;width:68px;height:25px;border-radius:10px;vertical-align:middle;text-align:center;line-height:25px;top:410px;border:2px solid #d9d9d9;background-color:#f7f7f7;cursor:pointer}div.vis-color-picker div.vis-button.vis-cancel{left:5px}div.vis-color-picker div.vis-button.vis-load{left:82px}div.vis-color-picker div.vis-button.vis-apply{left:159px}div.vis-color-picker div.vis-button.vis-save{left:236px}div.vis-color-picker input.vis-range{width:290px;height:20px} \ No newline at end of file +.vis-background,.vis-labelset,.vis-timeline{overflow:hidden}.vis .overlay{position:absolute;top:0;left:0;width:100%;height:100%;z-index:10}.vis-active{box-shadow:0 0 10px #86d5f8}.vis [class*=span]{min-height:0;width:auto}div.vis-configuration{position:relative;display:block;float:left;font-size:12px}div.vis-configuration-wrapper{display:block;width:700px}div.vis-configuration.vis-config-option-container{display:block;width:495px;background-color:#fff;border:2px solid #f7f8fa;border-radius:4px;margin-top:20px;left:10px;padding-left:5px}div.vis-configuration.vis-config-button{display:block;width:495px;height:25px;vertical-align:middle;line-height:25px;background-color:#f7f8fa;border:2px solid #ceced0;border-radius:4px;margin-top:20px;left:10px;padding-left:5px;cursor:pointer;margin-bottom:30px}div.vis-configuration.vis-config-button.hover{background-color:#4588e6;border:2px solid #214373;color:#fff}div.vis-configuration.vis-config-item{display:block;float:left;width:495px;height:25px;vertical-align:middle;line-height:25px}div.vis-configuration.vis-config-item.vis-config-s2{left:10px;background-color:#f7f8fa;padding-left:5px;border-radius:3px}div.vis-configuration.vis-config-item.vis-config-s3{left:20px;background-color:#e4e9f0;padding-left:5px;border-radius:3px}div.vis-configuration.vis-config-item.vis-config-s4{left:30px;background-color:#cfd8e6;padding-left:5px;border-radius:3px}div.vis-configuration.vis-config-header{font-size:18px;font-weight:700}div.vis-configuration.vis-config-label{width:120px;height:25px;line-height:25px}div.vis-configuration.vis-config-label.vis-config-s3{width:110px}div.vis-configuration.vis-config-label.vis-config-s4{width:100px}div.vis-configuration.vis-config-colorBlock{top:1px;width:30px;height:19px;border:1px solid #444;border-radius:2px;padding:0;margin:0;cursor:pointer}input.vis-configuration.vis-config-checkbox{left:-5px}input.vis-configuration.vis-config-rangeinput{position:relative;top:-5px;width:60px;padding:1px;margin:0;pointer-events:none}.vis-panel,.vis-timeline{padding:0;box-sizing:border-box}input.vis-configuration.vis-config-range{-webkit-appearance:none;border:0 solid #fff;background-color:rgba(0,0,0,0);width:300px;height:20px}input.vis-configuration.vis-config-range::-webkit-slider-runnable-track{width:300px;height:5px;background:#dedede;background:-moz-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#dedede),color-stop(99%,#c8c8c8));background:-webkit-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-o-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-ms-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:linear-gradient(to bottom,#dedede 0,#c8c8c8 99%);filter:progid:DXImageTransform.Microsoft.gradient( startColorstr='#dedede', endColorstr='#c8c8c8', GradientType=0 );border:1px solid #999;box-shadow:#aaa 0 0 3px 0;border-radius:3px}input.vis-configuration.vis-config-range::-webkit-slider-thumb{-webkit-appearance:none;border:1px solid #14334b;height:17px;width:17px;border-radius:50%;background:#3876c2;background:-moz-linear-gradient(top,#3876c2 0,#385380 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#3876c2),color-stop(100%,#385380));background:-webkit-linear-gradient(top,#3876c2 0,#385380 100%);background:-o-linear-gradient(top,#3876c2 0,#385380 100%);background:-ms-linear-gradient(top,#3876c2 0,#385380 100%);background:linear-gradient(to bottom,#3876c2 0,#385380 100%);filter:progid:DXImageTransform.Microsoft.gradient( startColorstr='#3876c2', endColorstr='#385380', GradientType=0 );box-shadow:#111927 0 0 1px 0;margin-top:-7px}input.vis-configuration.vis-config-range:focus{outline:0}input.vis-configuration.vis-config-range:focus::-webkit-slider-runnable-track{background:#9d9d9d;background:-moz-linear-gradient(top,#9d9d9d 0,#c8c8c8 99%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#9d9d9d),color-stop(99%,#c8c8c8));background:-webkit-linear-gradient(top,#9d9d9d 0,#c8c8c8 99%);background:-o-linear-gradient(top,#9d9d9d 0,#c8c8c8 99%);background:-ms-linear-gradient(top,#9d9d9d 0,#c8c8c8 99%);background:linear-gradient(to bottom,#9d9d9d 0,#c8c8c8 99%);filter:progid:DXImageTransform.Microsoft.gradient( startColorstr='#9d9d9d', endColorstr='#c8c8c8', GradientType=0 )}input.vis-configuration.vis-config-range::-moz-range-track{width:300px;height:10px;background:#dedede;background:-moz-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#dedede),color-stop(99%,#c8c8c8));background:-webkit-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-o-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-ms-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:linear-gradient(to bottom,#dedede 0,#c8c8c8 99%);filter:progid:DXImageTransform.Microsoft.gradient( startColorstr='#dedede', endColorstr='#c8c8c8', GradientType=0 );border:1px solid #999;box-shadow:#aaa 0 0 3px 0;border-radius:3px}input.vis-configuration.vis-config-range::-moz-range-thumb{border:none;height:16px;width:16px;border-radius:50%;background:#385380}input.vis-configuration.vis-config-range:-moz-focusring{outline:#fff solid 1px;outline-offset:-1px}input.vis-configuration.vis-config-range::-ms-track{width:300px;height:5px;background:0 0;border-color:transparent;border-width:6px 0;color:transparent}input.vis-configuration.vis-config-range::-ms-fill-lower{background:#777;border-radius:10px}input.vis-configuration.vis-config-range::-ms-fill-upper{background:#ddd;border-radius:10px}input.vis-configuration.vis-config-range::-ms-thumb{border:none;height:16px;width:16px;border-radius:50%;background:#385380}input.vis-configuration.vis-config-range:focus::-ms-fill-lower{background:#888}input.vis-configuration.vis-config-range:focus::-ms-fill-upper{background:#ccc}.vis-configuration-popup{position:absolute;background:rgba(57,76,89,.85);border:2px solid #f2faff;line-height:30px;height:30px;width:150px;text-align:center;color:#fff;font-size:14px;border-radius:4px;-webkit-transition:opacity .3s ease-in-out;-moz-transition:opacity .3s ease-in-out;transition:opacity .3s ease-in-out}.vis-configuration-popup:after,.vis-configuration-popup:before{left:100%;top:50%;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}.vis-configuration-popup:after{border-color:rgba(136,183,213,0);border-left-color:rgba(57,76,89,.85);border-width:8px;margin-top:-8px}.vis-configuration-popup:before{border-color:rgba(194,225,245,0);border-left-color:#f2faff;border-width:12px;margin-top:-12px}.vis-timeline{position:relative;border:1px solid #bfbfbf;margin:0}.vis-panel{position:absolute;margin:0}.vis-panel.vis-bottom,.vis-panel.vis-center,.vis-panel.vis-left,.vis-panel.vis-right,.vis-panel.vis-top{border:1px #bfbfbf}.vis-panel.vis-center,.vis-panel.vis-left,.vis-panel.vis-right{border-top-style:solid;border-bottom-style:solid;overflow:hidden}.vis-panel.vis-bottom,.vis-panel.vis-center,.vis-panel.vis-top{border-left-style:solid;border-right-style:solid}.vis-panel>.vis-content{position:relative}.vis-panel .vis-shadow{position:absolute;width:100%;height:1px;box-shadow:0 0 10px rgba(0,0,0,.8)}.vis-itemset,.vis-labelset,.vis-labelset .vis-label{position:relative;box-sizing:border-box}.vis-panel .vis-shadow.vis-top{top:-1px;left:0}.vis-panel .vis-shadow.vis-bottom{bottom:-1px;left:0}.vis-labelset .vis-label{left:0;top:0;width:100%;color:#4d4d4d;border-bottom:1px solid #bfbfbf}.vis-labelset .vis-label.draggable{cursor:pointer}.vis-labelset .vis-label:last-child{border-bottom:none}.vis-labelset .vis-label .vis-inner{display:inline-block;padding:5px}.vis-labelset .vis-label .vis-inner.vis-hidden{padding:0}.vis-itemset{padding:0;margin:0}.vis-itemset .vis-background,.vis-itemset .vis-foreground{position:absolute;width:100%;height:100%;overflow:visible}.vis-axis{position:absolute;width:100%;height:0;left:0;z-index:1}.vis-foreground .vis-group{position:relative;box-sizing:border-box;border-bottom:1px solid #bfbfbf}.vis-foreground .vis-group:last-child{border-bottom:none}.vis-overlay{position:absolute;top:0;left:0;width:100%;height:100%;z-index:10}.vis-item{position:absolute;color:#1A1A1A;border-color:#97B0F8;border-width:1px;background-color:#D5DDF6;display:inline-block}.vis-item.vis-point.vis-selected,.vis-item.vis-selected{background-color:#FFF785}.vis-item.vis-selected{border-color:#FFC200;z-index:2}.vis-editable.vis-selected{cursor:move}.vis-item.vis-box{text-align:center;border-style:solid;border-radius:2px}.vis-item.vis-point{background:0 0}.vis-item.vis-dot{position:absolute;padding:0;border-width:4px;border-style:solid;border-radius:4px}.vis-item.vis-range{border-style:solid;border-radius:2px;box-sizing:border-box}.vis-item.vis-background{border:none;background-color:rgba(213,221,246,.4);box-sizing:border-box;padding:0;margin:0}.vis-item .vis-item-overflow{position:relative;width:100%;height:100%;padding:0;margin:0;overflow:hidden}.vis-item.vis-range .vis-item-content{position:relative;display:inline-block}.vis-item.vis-background .vis-item-content{position:absolute;display:inline-block}.vis-item.vis-line{padding:0;position:absolute;width:0;border-left-width:1px;border-left-style:solid}.vis-item .vis-item-content{white-space:nowrap;box-sizing:border-box;padding:5px}.vis-item .vis-delete{background:url(img/timeline/delete.png) center no-repeat;position:absolute;width:24px;height:24px;top:-4px;right:-24px;cursor:pointer}.vis-item.vis-range .vis-drag-left{position:absolute;width:24px;max-width:20%;min-width:2px;height:100%;top:0;left:-4px;cursor:w-resize}.vis-item.vis-range .vis-drag-right{position:absolute;width:24px;max-width:20%;min-width:2px;height:100%;top:0;right:-4px;cursor:e-resize}.vis-range.vis-item.vis-readonly .vis-drag-left,.vis-range.vis-item.vis-readonly .vis-drag-right{cursor:auto}.vis-time-axis{position:relative;overflow:hidden}.vis-time-axis.vis-foreground{top:0;left:0;width:100%}.vis-time-axis.vis-background{position:absolute;top:0;left:0;width:100%;height:100%}.vis-time-axis .vis-text{position:absolute;color:#4d4d4d;padding:3px;overflow:hidden;box-sizing:border-box;white-space:nowrap}.vis-time-axis .vis-text.vis-measure{position:absolute;padding-left:0;padding-right:0;margin-left:0;margin-right:0;visibility:hidden}.vis-time-axis .vis-grid.vis-vertical{position:absolute;border-left:1px solid}.vis-time-axis .vis-grid.vis-minor{border-color:#e5e5e5}.vis-time-axis .vis-grid.vis-major{border-color:#bfbfbf}.vis-current-time{background-color:#FF7F6E;width:2px;z-index:1}.vis-custom-time{background-color:#6E94FF;width:2px;cursor:move;z-index:1}div.vis-network div.vis-close,div.vis-network div.vis-edit-mode div.vis-button,div.vis-network div.vis-manipulation div.vis-button{cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-webkit-touch-callout:none;-khtml-user-select:none}.vis-panel.vis-background.vis-horizontal .vis-grid.vis-horizontal{position:absolute;width:100%;height:0;border-bottom:1px solid}.vis-panel.vis-background.vis-horizontal .vis-grid.vis-minor{border-color:#e5e5e5}.vis-panel.vis-background.vis-horizontal .vis-grid.vis-major{border-color:#bfbfbf}.vis-data-axis .vis-y-axis.vis-major{width:100%;position:absolute;color:#4d4d4d;white-space:nowrap}.vis-data-axis .vis-y-axis.vis-major.vis-measure{padding:0;margin:0;border:0;visibility:hidden;width:auto}.vis-data-axis .vis-y-axis.vis-minor{position:absolute;width:100%;color:#bebebe;white-space:nowrap}.vis-data-axis .vis-y-axis.vis-minor.vis-measure{padding:0;margin:0;border:0;visibility:hidden;width:auto}.vis-data-axis .vis-y-axis.vis-title{position:absolute;color:#4d4d4d;white-space:nowrap;bottom:20px;text-align:center}.vis-data-axis .vis-y-axis.vis-title.vis-measure{padding:0;margin:0;visibility:hidden;width:auto}.vis-data-axis .vis-y-axis.vis-title.vis-left{bottom:0;-webkit-transform-origin:left top;-moz-transform-origin:left top;-ms-transform-origin:left top;-o-transform-origin:left top;transform-origin:left bottom;-webkit-transform:rotate(-90deg);-moz-transform:rotate(-90deg);-ms-transform:rotate(-90deg);-o-transform:rotate(-90deg);transform:rotate(-90deg)}.vis-data-axis .vis-y-axis.vis-title.vis-right{bottom:0;-webkit-transform-origin:right bottom;-moz-transform-origin:right bottom;-ms-transform-origin:right bottom;-o-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.vis-legend{background-color:rgba(247,252,255,.65);padding:5px;border:1px solid #b3b3b3;box-shadow:2px 2px 10px rgba(154,154,154,.55)}.vis-legend-text{white-space:nowrap;display:inline-block}.vis-graph-group0{fill:#4f81bd;fill-opacity:0;stroke-width:2px;stroke:#4f81bd}.vis-graph-group1{fill:#f79646;fill-opacity:0;stroke-width:2px;stroke:#f79646}.vis-graph-group2{fill:#8c51cf;fill-opacity:0;stroke-width:2px;stroke:#8c51cf}.vis-graph-group3{fill:#75c841;fill-opacity:0;stroke-width:2px;stroke:#75c841}.vis-graph-group4{fill:#ff0100;fill-opacity:0;stroke-width:2px;stroke:#ff0100}.vis-graph-group5{fill:#37d8e6;fill-opacity:0;stroke-width:2px;stroke:#37d8e6}.vis-graph-group6{fill:#042662;fill-opacity:0;stroke-width:2px;stroke:#042662}.vis-graph-group7{fill:#00ff26;fill-opacity:0;stroke-width:2px;stroke:#00ff26}.vis-graph-group8{fill:#f0f;fill-opacity:0;stroke-width:2px;stroke:#f0f}.vis-graph-group9{fill:#8f3938;fill-opacity:0;stroke-width:2px;stroke:#8f3938}.vis-timeline .vis-fill{fill-opacity:.1;stroke:none}.vis-timeline .vis-bar{fill-opacity:.5;stroke-width:1px}.vis-timeline .vis-point{stroke-width:2px;fill-opacity:1}.vis-timeline .vis-legend-background{stroke-width:1px;fill-opacity:.9;fill:#fff;stroke:#c2c2c2}.vis-timeline .vis-outline{stroke-width:1px;fill-opacity:1;fill:#fff;stroke:#e5e5e5}.vis-timeline .vis-icon-fill{fill-opacity:.3;stroke:none}div.vis-network div.vis-manipulation{border-width:0;border-bottom:1px;border-style:solid;border-color:#d6d9d8;background:#fff;background:-moz-linear-gradient(top,#fff 0,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fff),color-stop(48%,#fcfcfc),color-stop(50%,#fafafa),color-stop(100%,#fcfcfc));background:-webkit-linear-gradient(top,#fff 0,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%);background:-o-linear-gradient(top,#fff 0,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%);background:-ms-linear-gradient(top,#fff 0,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%);background:linear-gradient(to bottom,#fff 0,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%);filter:progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#fcfcfc', GradientType=0 );padding-top:4px;position:absolute;left:0;top:0;width:100%;height:28px}div.vis-network div.vis-edit-mode{position:absolute;left:0;top:5px;height:30px}div.vis-network div.vis-close{position:absolute;right:0;top:0;width:30px;height:30px;background-position:20px 3px;background-repeat:no-repeat;background-image:url(img/network/cross.png);user-select:none}div.vis-network div.vis-close:hover{opacity:.6}div.vis-network div.vis-edit-mode div.vis-button,div.vis-network div.vis-manipulation div.vis-button{float:left;font-family:verdana;font-size:12px;-moz-border-radius:15px;border-radius:15px;display:inline-block;background-position:0 0;background-repeat:no-repeat;height:24px;margin-left:10px;padding:0 8px;user-select:none}div.vis-network div.vis-manipulation div.vis-button:hover{box-shadow:1px 1px 8px rgba(0,0,0,.2)}div.vis-network div.vis-manipulation div.vis-button:active{box-shadow:1px 1px 8px rgba(0,0,0,.5)}div.vis-network div.vis-manipulation div.vis-button.vis-back{background-image:url(img/network/backIcon.png)}div.vis-network div.vis-manipulation div.vis-button.vis-none:hover{box-shadow:1px 1px 8px transparent;cursor:default}div.vis-network div.vis-manipulation div.vis-button.vis-none:active{box-shadow:1px 1px 8px transparent}div.vis-network div.vis-manipulation div.vis-button.vis-none{padding:0}div.vis-network div.vis-manipulation div.notification{margin:2px;font-weight:700}div.vis-network div.vis-manipulation div.vis-button.vis-add{background-image:url(img/network/addNodeIcon.png)}div.vis-network div.vis-edit-mode div.vis-button.vis-edit,div.vis-network div.vis-manipulation div.vis-button.vis-edit{background-image:url(img/network/editIcon.png)}div.vis-network div.vis-edit-mode div.vis-button.vis-edit.vis-edit-mode{background-color:#fcfcfc;border:1px solid #ccc}div.vis-network div.vis-manipulation div.vis-button.vis-connect{background-image:url(img/network/connectIcon.png)}div.vis-network div.vis-manipulation div.vis-button.vis-delete{background-image:url(img/network/deleteIcon.png)}div.vis-network div.vis-edit-mode div.vis-label,div.vis-network div.vis-manipulation div.vis-label{margin:0 0 0 23px;line-height:25px}div.vis-network div.vis-manipulation div.vis-separator-line{float:left;display:inline-block;width:1px;height:21px;background-color:#bdbdbd;margin:0 7px 0 15px}div.vis-network-tooltip{position:absolute;visibility:hidden;padding:5px;white-space:nowrap;font-family:verdana;font-size:14px;font-color:#000;background-color:#f5f4ed;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;border:1px solid #808074;box-shadow:3px 3px 10px rgba(0,0,0,.2);pointer-events:none}div.vis-network div.vis-navigation div.vis-button{width:34px;height:34px;-moz-border-radius:17px;border-radius:17px;position:absolute;display:inline-block;background-position:2px 2px;background-repeat:no-repeat;cursor:pointer;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}div.vis-network div.vis-navigation div.vis-button:hover{box-shadow:0 0 3px 3px rgba(56,207,21,.3)}div.vis-network div.vis-navigation div.vis-button:active{box-shadow:0 0 1px 3px rgba(56,207,21,.95)}div.vis-network div.vis-navigation div.vis-button.vis-up{background-image:url(img/network/upArrow.png);bottom:50px;left:55px}div.vis-network div.vis-navigation div.vis-button.vis-down{background-image:url(img/network/downArrow.png);bottom:10px;left:55px}div.vis-network div.vis-navigation div.vis-button.vis-left{background-image:url(img/network/leftArrow.png);bottom:10px;left:15px}div.vis-network div.vis-navigation div.vis-button.vis-right{background-image:url(img/network/rightArrow.png);bottom:10px;left:95px}div.vis-network div.vis-navigation div.vis-button.vis-zoomIn{background-image:url(img/network/plus.png);bottom:10px;right:15px}div.vis-network div.vis-navigation div.vis-button.vis-zoomOut{background-image:url(img/network/minus.png);bottom:10px;right:55px}div.vis-network div.vis-navigation div.vis-button.vis-zoomExtends{background-image:url(img/network/zoomExtends.png);bottom:50px;right:15px}div.vis-color-picker{position:absolute;top:0;left:30px;margin-top:-140px;margin-left:30px;width:310px;height:444px;z-index:1;padding:10px;border-radius:15px;background-color:#fff;display:none;box-shadow:rgba(0,0,0,.5) 0 0 10px 0}div.vis-color-picker div.vis-arrow{position:absolute;top:147px;left:5px}div.vis-color-picker div.vis-arrow::after,div.vis-color-picker div.vis-arrow::before{right:100%;top:50%;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}div.vis-color-picker div.vis-arrow:after{border-color:rgba(255,255,255,0);border-right-color:#fff;border-width:30px;margin-top:-30px}div.vis-color-picker div.vis-color{position:absolute;width:289px;height:289px;cursor:pointer}div.vis-color-picker div.vis-brightness{position:absolute;top:313px}div.vis-color-picker div.vis-opacity{position:absolute;top:350px}div.vis-color-picker div.vis-selector{position:absolute;top:137px;left:137px;width:15px;height:15px;border-radius:15px;border:1px solid #fff;background:#4c4c4c;background:-moz-linear-gradient(top,#4c4c4c 0,#595959 12%,#666 25%,#474747 39%,#2c2c2c 50%,#000 51%,#111 60%,#2b2b2b 76%,#1c1c1c 91%,#131313 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#4c4c4c),color-stop(12%,#595959),color-stop(25%,#666),color-stop(39%,#474747),color-stop(50%,#2c2c2c),color-stop(51%,#000),color-stop(60%,#111),color-stop(76%,#2b2b2b),color-stop(91%,#1c1c1c),color-stop(100%,#131313));background:-webkit-linear-gradient(top,#4c4c4c 0,#595959 12%,#666 25%,#474747 39%,#2c2c2c 50%,#000 51%,#111 60%,#2b2b2b 76%,#1c1c1c 91%,#131313 100%);background:-o-linear-gradient(top,#4c4c4c 0,#595959 12%,#666 25%,#474747 39%,#2c2c2c 50%,#000 51%,#111 60%,#2b2b2b 76%,#1c1c1c 91%,#131313 100%);background:-ms-linear-gradient(top,#4c4c4c 0,#595959 12%,#666 25%,#474747 39%,#2c2c2c 50%,#000 51%,#111 60%,#2b2b2b 76%,#1c1c1c 91%,#131313 100%);background:linear-gradient(to bottom,#4c4c4c 0,#595959 12%,#666 25%,#474747 39%,#2c2c2c 50%,#000 51%,#111 60%,#2b2b2b 76%,#1c1c1c 91%,#131313 100%);filter:progid:DXImageTransform.Microsoft.gradient( startColorstr='#4c4c4c', endColorstr='#131313', GradientType=0 )}div.vis-color-picker div.vis-initial-color,div.vis-color-picker div.vis-new-color{width:140px;height:20px;top:380px;font-size:10px;color:rgba(0,0,0,.4);line-height:20px;position:absolute;vertical-align:middle}div.vis-color-picker div.vis-new-color{border:1px solid rgba(0,0,0,.1);border-radius:5px;left:159px;text-align:right;padding-right:2px}div.vis-color-picker div.vis-initial-color{border:1px solid rgba(0,0,0,.1);border-radius:5px;left:10px;text-align:left;padding-left:2px}div.vis-color-picker div.vis-label{position:absolute;width:300px;left:10px}div.vis-color-picker div.vis-label.vis-brightness{top:300px}div.vis-color-picker div.vis-label.vis-opacity{top:338px}div.vis-color-picker div.vis-button{position:absolute;width:68px;height:25px;border-radius:10px;vertical-align:middle;text-align:center;line-height:25px;top:410px;border:2px solid #d9d9d9;background-color:#f7f7f7;cursor:pointer}div.vis-color-picker div.vis-button.vis-cancel{left:5px}div.vis-color-picker div.vis-button.vis-load{left:82px}div.vis-color-picker div.vis-button.vis-apply{left:159px}div.vis-color-picker div.vis-button.vis-save{left:236px}div.vis-color-picker input.vis-range{width:290px;height:20px} \ No newline at end of file diff --git a/docs/network/layout.html b/docs/network/layout.html index c43a838c..08f1eca9 100644 --- a/docs/network/layout.html +++ b/docs/network/layout.html @@ -105,8 +105,12 @@ var options = { hierarchical: { enabled:false, levelSeparation: 150, - direction: 'UD', // UD, DU, LR, RL - sortMethod: 'hubsize' // hubsize, directed + nodeSpacing: 100, + treeSpacing: 200, + blockShifting: true, + edgeMinimization: true, + direction: 'UD', // UD, DU, LR, RL + sortMethod: 'hubsize' // hubsize, directed } } } @@ -132,6 +136,12 @@ network.setOptions(options); hierarchicalObject or BooleanObject When true, the layout engine positions the nodes in a hierarchical fashion using default settings. For customization you can supply an object. hierarchical.enabledBooleanfalse Toggle the usage of the hierarchical layout system. If this option is not defined, it is set to true if any of the properties in this object are defined. hierarchical.levelSeparationNumber150 The distance between the different levels. + hierarchical.nodeSpacingNumber100 Minimum distance between nodes on the free axis. This is only for the initial layout. If you enable physics, the node distance there will be the effective node distance. + hierarchical.treeSpacingNumber200 Distance between different trees (independent networks). This is only for the initial layout. If you enable physics, the repulsion model will denote the distance between the trees. + hierarchical.blockShiftingBooleantrue Method for reducing whitespace. Can be used alone or together with edge minimization. Each node will check for whitespace and will shift + it's branch along with it for as far as it can, respecting the nodeSpacing on any level. This is mainly for the initial layout. If you enable physics, they layout will be determined by the physics. This will greatly speed up the stabilization time though! + hierarchical.edgeMinimizationBooleantrue Method for reducing whitespace. Can be used alone or together with block shifting. Enabling block shifting will usually speed up the layout process. + Each node will try to move along its free axis to reduce the total length of it's edges. This is mainly for the initial layout. If you enable physics, they layout will be determined by the physics. This will greatly speed up the stabilization time though! hierarchical.directionString'UD' The direction of the hierarchical layout. The available options are: UD, DU, LR, RL. To simplify: up-down, down-up, left-right, right-left. hierarchical.sortMethodString'hubsize' The algorithm used to ascertain the levels of the nodes based on the data. The possible options are: hubsize, directed.

Hubsize takes the nodes with the most edges and puts them at the top. From that the rest of the hierarchy is evaluated.

diff --git a/examples/network/datasources/largeHierarchicalDataset.js b/examples/network/datasources/largeHierarchicalDataset.js new file mode 100644 index 00000000..ddbb524e --- /dev/null +++ b/examples/network/datasources/largeHierarchicalDataset.js @@ -0,0 +1,2 @@ +var nodes = [{id:0,label:0},{id:1,label:1},{id:2,label:2},{id:3,label:3},{id:4,label:4},{id:5,label:5},{id:6,label:6},{id:7,label:7},{id:8,label:8},{id:9,label:9},{id:10,label:10},{id:11,label:11},{id:12,label:12},{id:13,label:13},{id:14,label:14},{id:15,label:15},{id:16,label:16},{id:17,label:17},{id:18,label:18},{id:19,label:19},{id:20,label:20},{id:21,label:21},{id:22,label:22},{id:23,label:23},{id:24,label:24},{id:25,label:25},{id:26,label:26},{id:27,label:27},{id:28,label:28},{id:29,label:29},{id:30,label:30},{id:31,label:31},{id:32,label:32},{id:33,label:33},{id:34,label:34},{id:35,label:35},{id:36,label:36},{id:37,label:37},{id:38,label:38},{id:39,label:39},{id:40,label:40},{id:41,label:41},{id:42,label:42},{id:43,label:43},{id:44,label:44},{id:45,label:45},{id:46,label:46},{id:47,label:47},{id:48,label:48},{id:49,label:49},{id:50,label:50},{id:51,label:51},{id:52,label:52},{id:53,label:53},{id:54,label:54},{id:55,label:55},{id:56,label:56},{id:57,label:57},{id:58,label:58},{id:59,label:59},{id:60,label:60},{id:61,label:61},{id:62,label:62},{id:63,label:63},{id:64,label:64},{id:65,label:65},{id:66,label:66},{id:67,label:67},{id:68,label:68},{id:69,label:69},{id:70,label:70},{id:71,label:71},{id:72,label:72},{id:73,label:73},{id:74,label:74},{id:75,label:75},{id:76,label:76},{id:77,label:77},{id:78,label:78},{id:79,label:79},{id:80,label:80},{id:81,label:81},{id:82,label:82},{id:83,label:83},{id:84,label:84},{id:85,label:85},{id:86,label:86},{id:87,label:87},{id:88,label:88},{id:89,label:89},{id:90,label:90},{id:91,label:91},{id:92,label:92},{id:93,label:93},{id:94,label:94},{id:95,label:95},{id:96,label:96},{id:97,label:97},{id:98,label:98},{id:99,label:99},{id:100,label:100},{id:101,label:101},{id:102,label:102},{id:103,label:103},{id:104,label:104},{id:105,label:105},{id:106,label:106},{id:107,label:107},{id:108,label:108},{id:109,label:109},{id:110,label:110},{id:111,label:111},{id:112,label:112},{id:113,label:113},{id:114,label:114},{id:115,label:115},{id:116,label:116},{id:117,label:117},{id:118,label:118},{id:119,label:119},{id:120,label:120},{id:121,label:121},{id:122,label:122},{id:123,label:123},{id:124,label:124},{id:125,label:125},{id:126,label:126},{id:127,label:127},{id:128,label:128},{id:129,label:129},{id:130,label:130},{id:131,label:131},{id:132,label:132},{id:133,label:133},{id:134,label:134},{id:135,label:135},{id:136,label:136},{id:137,label:137},{id:138,label:138},{id:139,label:139},{id:140,label:140},{id:141,label:141},{id:142,label:142},{id:143,label:143},{id:144,label:144},{id:145,label:145},{id:146,label:146},{id:147,label:147},{id:148,label:148},{id:149,label:149},{id:150,label:150},{id:151,label:151},{id:152,label:152},{id:153,label:153},{id:154,label:154},{id:155,label:155},{id:156,label:156},{id:157,label:157},{id:158,label:158},{id:159,label:159},{id:160,label:160},{id:161,label:161},{id:162,label:162},{id:163,label:163},{id:164,label:164},{id:165,label:165},{id:166,label:166},{id:167,label:167},{id:168,label:168},{id:169,label:169},{id:170,label:170},{id:171,label:171},{id:172,label:172},{id:173,label:173},{id:174,label:174},{id:175,label:175},{id:176,label:176},{id:177,label:177},{id:178,label:178},{id:179,label:179},{id:180,label:180},{id:181,label:181},{id:182,label:182},{id:183,label:183},{id:184,label:184},{id:185,label:185},{id:186,label:186},{id:187,label:187},{id:188,label:188},{id:189,label:189},{id:190,label:190},{id:191,label:191},{id:192,label:192},{id:193,label:193},{id:194,label:194},{id:195,label:195},{id:196,label:196},{id:197,label:197},{id:198,label:198},{id:199,label:199},{id:200,label:200},{id:201,label:201},{id:202,label:202},{id:203,label:203},{id:204,label:204},{id:205,label:205},{id:206,label:206},{id:207,label:207},{id:208,label:208},{id:209,label:209},{id:210,label:210},{id:211,label:211},{id:212,label:212},{id:213,label:213},{id:214,label:214},{id:215,label:215},{id:216,label:216},{id:217,label:217},{id:218,label:218},{id:219,label:219},{id:220,label:220},{id:221,label:221},{id:222,label:222},{id:223,label:223},{id:224,label:224},{id:225,label:225},{id:226,label:226},{id:227,label:227},{id:228,label:228},{id:229,label:229},{id:230,label:230},{id:231,label:231},{id:232,label:232},{id:233,label:233},{id:234,label:234},{id:235,label:235},{id:236,label:236},{id:237,label:237},{id:238,label:238},{id:239,label:239},{id:240,label:240},{id:241,label:241},{id:242,label:242},{id:243,label:243},{id:244,label:244},{id:245,label:245},{id:246,label:246},{id:247,label:247},{id:248,label:248},{id:249,label:249},{id:250,label:250},{id:251,label:251},{id:252,label:252},{id:253,label:253},{id:254,label:254},{id:255,label:255},{id:256,label:256},{id:257,label:257},{id:258,label:258},{id:259,label:259},{id:260,label:260},{id:261,label:261},{id:262,label:262},{id:263,label:263},{id:264,label:264},{id:265,label:265},{id:266,label:266},{id:267,label:267},{id:268,label:268},{id:269,label:269},{id:270,label:270},{id:271,label:271},{id:272,label:272},{id:273,label:273},{id:274,label:274},{id:275,label:275},{id:276,label:276},{id:277,label:277},{id:278,label:278},{id:279,label:279},{id:280,label:280},{id:281,label:281},{id:282,label:282},{id:283,label:283},{id:284,label:284},{id:285,label:285},{id:286,label:286},{id:287,label:287},{id:288,label:288},{id:289,label:289},{id:290,label:290},{id:291,label:291},{id:292,label:292},{id:293,label:293},{id:294,label:294},{id:295,label:295},{id:296,label:296},{id:297,label:297},{id:298,label:298},{id:299,label:299},{id:300,label:300},{id:301,label:301},{id:302,label:302},{id:303,label:303},{id:304,label:304},{id:305,label:305},{id:306,label:306},{id:307,label:307},{id:308,label:308},{id:309,label:309},{id:310,label:310},{id:311,label:311},{id:312,label:312},{id:313,label:313},{id:314,label:314},{id:315,label:315},{id:316,label:316},{id:317,label:317},{id:318,label:318},{id:319,label:319},{id:320,label:320},{id:321,label:321},{id:322,label:322},{id:323,label:323},{id:324,label:324},{id:325,label:325},{id:326,label:326},{id:327,label:327},{id:328,label:328},{id:329,label:329},{id:330,label:330},{id:331,label:331},{id:332,label:332},{id:333,label:333},{id:334,label:334},{id:335,label:335},{id:336,label:336},{id:337,label:337},{id:338,label:338},{id:339,label:339},{id:340,label:340},{id:341,label:341},{id:342,label:342},{id:343,label:343},{id:344,label:344},{id:345,label:345},{id:346,label:346},{id:347,label:347}]; +var edges = [{from:331,to:0,id:"e0"},{from:331,to:1,id:"e1"},{from:302,to:2,id:"e2"},{from:321,to:3,id:"e3"},{from:323,to:4,id:"e4"},{from:326,to:5,id:"e5"},{from:24,to:6,id:"e6"},{from:327,to:7,id:"e7"},{from:50,to:8,id:"e8"},{from:275,to:9,id:"e9"},{from:327,to:10,id:"e10"},{from:30,to:11,id:"e11"},{from:327,to:12,id:"e12"},{from:270,to:13,id:"e13"},{from:204,to:14,id:"e14"},{from:42,to:15,id:"e15"},{from:140,to:16,id:"e16"},{from:327,to:17,id:"e17"},{from:80,to:18,id:"e18"},{from:24,to:19,id:"e19"},{from:60,to:20,id:"e20"},{from:323,to:21,id:"e21"},{from:328,to:22,id:"e22"},{from:58,to:23,id:"e23"},{from:0,to:24,id:"e24"},{from:50,to:25,id:"e25"},{from:50,to:26,id:"e26"},{from:36,to:27,id:"e27"},{from:36,to:28,id:"e28"},{from:303,to:29,id:"e29"},{from:1,to:30,id:"e30"},{from:326,to:31,id:"e31"},{from:81,to:32,id:"e32"},{from:60,to:33,id:"e33"},{from:62,to:34,id:"e34"},{from:24,to:35,id:"e35"},{from:319,to:36,id:"e36"},{from:58,to:37,id:"e37"},{from:58,to:38,id:"e38"},{from:80,to:39,id:"e39"},{from:35,to:40,id:"e40"},{from:73,to:41,id:"e41"},{from:327,to:42,id:"e42"},{from:301,to:43,id:"e43"},{from:36,to:44,id:"e44"},{from:322,to:45,id:"e45"},{from:69,to:46,id:"e46"},{from:329,to:47,id:"e47"},{from:199,to:48,id:"e48"},{from:321,to:49,id:"e49"},{from:331,to:50,id:"e50"},{from:70,to:51,id:"e51"},{from:329,to:52,id:"e52"},{from:140,to:53,id:"e53"},{from:1,to:54,id:"e54"},{from:330,to:55,id:"e55"},{from:304,to:56,id:"e56"},{from:214,to:57,id:"e57"},{from:84,to:58,id:"e58"},{from:141,to:59,id:"e59"},{from:36,to:60,id:"e60"},{from:323,to:61,id:"e61"},{from:326,to:62,id:"e62"},{from:323,to:63,id:"e63"},{from:328,to:64,id:"e64"},{from:331,to:65,id:"e65"},{from:140,to:66,id:"e66"},{from:24,to:67,id:"e67"},{from:324,to:68,id:"e68"},{from:326,to:69,id:"e69"},{from:323,to:70,id:"e70"},{from:283,to:71,id:"e71"},{from:27,to:72,id:"e72"},{from:50,to:73,id:"e73"},{from:325,to:74,id:"e74"},{from:58,to:75,id:"e75"},{from:323,to:76,id:"e76"},{from:15,to:77,id:"e77"},{from:70,to:78,id:"e78"},{from:22,to:79,id:"e79"},{from:328,to:80,id:"e80"},{from:0,to:81,id:"e81"},{from:322,to:82,id:"e82"},{from:326,to:83,id:"e83"},{from:325,to:84,id:"e84"},{from:331,to:85,id:"e85"},{from:184,to:86,id:"e86"},{from:250,to:87,id:"e87"},{from:321,to:88,id:"e88"},{from:322,to:89,id:"e89"},{from:326,to:90,id:"e90"},{from:162,to:91,id:"e91"},{from:162,to:92,id:"e92"},{from:99,to:93,id:"e93"},{from:320,to:94,id:"e94"},{from:326,to:95,id:"e95"},{from:0,to:96,id:"e96"},{from:326,to:97,id:"e97"},{from:327,to:98,id:"e98"},{from:0,to:99,id:"e99"},{from:327,to:100,id:"e100"},{from:0,to:101,id:"e101"},{from:0,to:102,id:"e102"},{from:328,to:103,id:"e103"},{from:256,to:104,id:"e104"},{from:326,to:105,id:"e105"},{from:81,to:106,id:"e106"},{from:322,to:107,id:"e107"},{from:326,to:108,id:"e108"},{from:8,to:109,id:"e109"},{from:204,to:110,id:"e110"},{from:163,to:111,id:"e111"},{from:330,to:112,id:"e112"},{from:330,to:113,id:"e113"},{from:324,to:114,id:"e114"},{from:42,to:115,id:"e115"},{from:328,to:116,id:"e116"},{from:331,to:117,id:"e117"},{from:321,to:118,id:"e118"},{from:141,to:119,id:"e119"},{from:321,to:120,id:"e120"},{from:330,to:121,id:"e121"},{from:324,to:122,id:"e122"},{from:199,to:123,id:"e123"},{from:302,to:124,id:"e124"},{from:328,to:125,id:"e125"},{from:307,to:126,id:"e126"},{from:321,to:127,id:"e127"},{from:329,to:128,id:"e128"},{from:0,to:129,id:"e129"},{from:331,to:130,id:"e130"},{from:287,to:131,id:"e131"},{from:322,to:132,id:"e132"},{from:1,to:133,id:"e133"},{from:304,to:134,id:"e134"},{from:295,to:135,id:"e135"},{from:42,to:136,id:"e136"},{from:104,to:137,id:"e137"},{from:321,to:138,id:"e138"},{from:204,to:139,id:"e139"},{from:109,to:140,id:"e140"},{from:324,to:141,id:"e141"},{from:70,to:142,id:"e142"},{from:73,to:143,id:"e143"},{from:304,to:144,id:"e144"},{from:0,to:145,id:"e145"},{from:0,to:146,id:"e146"},{from:327,to:147,id:"e147"},{from:141,to:148,id:"e148"},{from:323,to:149,id:"e149"},{from:184,to:150,id:"e150"},{from:324,to:151,id:"e151"},{from:330,to:152,id:"e152"},{from:75,to:153,id:"e153"},{from:328,to:154,id:"e154"},{from:60,to:155,id:"e155"},{from:331,to:156,id:"e156"},{from:153,to:157,id:"e157"},{from:214,to:158,id:"e158"},{from:129,to:159,id:"e159"},{from:331,to:160,id:"e160"},{from:324,to:161,id:"e161"},{from:322,to:162,id:"e162"},{from:195,to:163,id:"e163"},{from:323,to:164,id:"e164"},{from:275,to:165,id:"e165"},{from:58,to:166,id:"e166"},{from:321,to:167,id:"e167"},{from:325,to:168,id:"e168"},{from:324,to:169,id:"e169"},{from:330,to:170,id:"e170"},{from:330,to:171,id:"e171"},{from:331,to:172,id:"e172"},{from:1,to:173,id:"e173"},{from:50,to:174,id:"e174"},{from:327,to:175,id:"e175"},{from:331,to:176,id:"e176"},{from:324,to:177,id:"e177"},{from:204,to:178,id:"e178"},{from:330,to:179,id:"e179"},{from:330,to:180,id:"e180"},{from:50,to:181,id:"e181"},{from:323,to:182,id:"e182"},{from:106,to:183,id:"e183"},{from:70,to:184,id:"e184"},{from:58,to:185,id:"e185"},{from:0,to:186,id:"e186"},{from:321,to:187,id:"e187"},{from:304,to:188,id:"e188"},{from:307,to:189,id:"e189"},{from:140,to:190,id:"e190"},{from:104,to:191,id:"e191"},{from:50,to:192,id:"e192"},{from:60,to:193,id:"e193"},{from:27,to:194,id:"e194"},{from:297,to:195,id:"e195"},{from:321,to:196,id:"e196"},{from:27,to:197,id:"e197"},{from:1,to:198,id:"e198"},{from:321,to:199,id:"e199"},{from:75,to:200,id:"e200"},{from:30,to:201,id:"e201"},{from:50,to:202,id:"e202"},{from:325,to:203,id:"e203"},{from:173,to:204,id:"e204"},{from:307,to:205,id:"e205"},{from:275,to:206,id:"e206"},{from:275,to:207,id:"e207"},{from:331,to:208,id:"e208"},{from:109,to:209,id:"e209"},{from:0,to:210,id:"e210"},{from:327,to:211,id:"e211"},{from:275,to:212,id:"e212"},{from:304,to:213,id:"e213"},{from:104,to:214,id:"e214"},{from:327,to:215,id:"e215"},{from:53,to:216,id:"e216"},{from:60,to:217,id:"e217"},{from:60,to:218,id:"e218"},{from:109,to:219,id:"e219"},{from:192,to:220,id:"e220"},{from:275,to:221,id:"e221"},{from:326,to:222,id:"e222"},{from:250,to:223,id:"e223"},{from:325,to:224,id:"e224"},{from:321,to:225,id:"e225"},{from:1,to:226,id:"e226"},{from:323,to:227,id:"e227"},{from:322,to:228,id:"e228"},{from:327,to:229,id:"e229"},{from:256,to:230,id:"e230"},{from:250,to:231,id:"e231"},{from:330,to:232,id:"e232"},{from:36,to:233,id:"e233"},{from:328,to:234,id:"e234"},{from:323,to:235,id:"e235"},{from:327,to:236,id:"e236"},{from:328,to:237,id:"e237"},{from:250,to:238,id:"e238"},{from:326,to:239,id:"e239"},{from:327,to:240,id:"e240"},{from:329,to:241,id:"e241"},{from:329,to:242,id:"e242"},{from:109,to:243,id:"e243"},{from:323,to:244,id:"e244"},{from:220,to:245,id:"e245"},{from:326,to:246,id:"e246"},{from:267,to:247,id:"e247"},{from:250,to:248,id:"e248"},{from:256,to:249,id:"e249"},{from:322,to:250,id:"e250"},{from:322,to:251,id:"e251"},{from:322,to:252,id:"e252"},{from:109,to:253,id:"e253"},{from:101,to:254,id:"e254"},{from:328,to:255,id:"e255"},{from:331,to:256,id:"e256"},{from:327,to:257,id:"e257"},{from:24,to:258,id:"e258"},{from:124,to:259,id:"e259"},{from:324,to:260,id:"e260"},{from:322,to:261,id:"e261"},{from:322,to:262,id:"e262"},{from:321,to:263,id:"e263"},{from:283,to:264,id:"e264"},{from:318,to:265,id:"e265"},{from:30,to:266,id:"e266"},{from:8,to:267,id:"e267"},{from:140,to:268,id:"e268"},{from:322,to:269,id:"e269"},{from:24,to:270,id:"e270"},{from:9,to:271,id:"e271"},{from:322,to:272,id:"e272"},{from:99,to:273,id:"e273"},{from:24,to:274,id:"e274"},{from:282,to:275,id:"e275"},{from:250,to:276,id:"e276"},{from:70,to:277,id:"e277"},{from:328,to:278,id:"e278"},{from:250,to:279,id:"e279"},{from:50,to:280,id:"e280"},{from:250,to:281,id:"e281"},{from:173,to:282,id:"e282"},{from:320,to:283,id:"e283"},{from:320,to:284,id:"e284"},{from:250,to:285,id:"e285"},{from:325,to:286,id:"e286"},{from:1,to:287,id:"e287"},{from:1,to:288,id:"e288"},{from:109,to:289,id:"e289"},{from:50,to:290,id:"e290"},{from:250,to:291,id:"e291"},{from:195,to:292,id:"e292"},{from:320,to:293,id:"e293"},{from:331,to:294,id:"e294"},{from:331,to:295,id:"e295"},{from:101,to:296,id:"e296"},{from:58,to:297,id:"e297"},{from:24,to:298,id:"e298"},{from:291,to:299,id:"e299"},{from:302,to:300,id:"e300"},{from:323,to:301,id:"e301"},{from:226,to:302,id:"e302"},{from:53,to:303,id:"e303"},{from:110,to:304,id:"e304"},{from:163,to:305,id:"e305"},{from:324,to:306,id:"e306"},{from:304,to:307,id:"e307"},{from:322,to:308,id:"e308"},{from:140,to:309,id:"e309"},{from:323,to:310,id:"e310"},{from:0,to:311,id:"e311"},{from:250,to:312,id:"e312"},{from:30,to:313,id:"e313"},{from:58,to:314,id:"e314"},{from:104,to:315,id:"e315"},{from:75,to:316,id:"e316"},{from:323,to:317,id:"e317"},{from:321,to:318,id:"e318"},{from:256,to:319,id:"e319"},{from:250,to:320,id:"e320"},{from:330,to:321,id:"e321"},{from:327,to:322,id:"e322"},{from:326,to:323,id:"e323"},{from:328,to:324,id:"e324"},{from:328,to:325,id:"e325"},{from:327,to:326,id:"e326"},{from:0,to:327,id:"e327"},{from:70,to:328,id:"e328"},{from:327,to:329,id:"e329"},{from:324,to:330,id:"e330"},{from:69,to:332,id:"e331"},{from:346,to:333,id:"e332"},{from:346,to:334,id:"e333"},{from:337,to:335,id:"e334"},{from:106,to:336,id:"e335"},{from:341,to:337,id:"e336"},{from:341,to:338,id:"e337"},{from:346,to:339,id:"e338"},{from:337,to:340,id:"e339"},{from:334,to:341,id:"e340"},{from:334,to:342,id:"e341"},{from:334,to:343,id:"e342"},{from:334,to:344,id:"e343"},{from:84,to:345,id:"e344"},{from:14,to:346,id:"e345"},{from:331,to:347,id:"e346"}] \ No newline at end of file diff --git a/examples/network/exampleUtil.js b/examples/network/exampleUtil.js index 3ace1e63..1621a9a5 100644 --- a/examples/network/exampleUtil.js +++ b/examples/network/exampleUtil.js @@ -75,11 +75,15 @@ function seededRandom() { return x - Math.floor(x); } -function getScaleFreeNetworkSeeded(nodeCount) { +function getScaleFreeNetworkSeeded(nodeCount, seed) { + if (seed) { + randomSeed = Number(seed); + } var nodes = []; var edges = []; var connectionCount = []; - randomSeed = 764; + var edgesId = 0; + // randomly create some nodes and edges for (var i = 0; i < nodeCount; i++) { @@ -95,6 +99,7 @@ function getScaleFreeNetworkSeeded(nodeCount) { var from = i; var to = 0; edges.push({ + id: edgesId++, from: from, to: to }); @@ -115,6 +120,7 @@ function getScaleFreeNetworkSeeded(nodeCount) { var from = i; var to = j; edges.push({ + id: edgesId++, from: from, to: to }); diff --git a/examples/network/layout/hierarchicalLayoutWithoutPhysics.html b/examples/network/layout/hierarchicalLayoutWithoutPhysics.html new file mode 100644 index 00000000..dfa32f3b --- /dev/null +++ b/examples/network/layout/hierarchicalLayoutWithoutPhysics.html @@ -0,0 +1,85 @@ + + + + Hierarchical Layout without Physics + + + + + + +

Hierarchical Layout without Physics

+The hierarchical layout can now be controlled without the use of physics. This is much quicker. The options for this are:

+ + + + + + + + + + + + + + + + + + + + + + +
levelSeparationDistance between levels.
nodeSpacingMinimum distance between nodes on the free axis.
treeSpacingDistance between different trees (independent networks).
blockShiftingMethod for reducing whitespace. Can be used alone or together with edge minimization. Each node will check for whitespace and will shift + it's branch along with it for as far as it can, respecting the nodeSpacing on any level.
edgeMinimizationMethod for reducing whitespace. Can be used alone or together with block shifting. Enabling block shifting will usually speed up the layout process. + Each node will try to move along its free axis to reduce the total length of it's edges.
+

+Play with the settings below the network and see how the layout changes! +
+ + + \ No newline at end of file diff --git a/lib/network/Network.js b/lib/network/Network.js index 546a0eb6..68d10c76 100644 --- a/lib/network/Network.js +++ b/lib/network/Network.js @@ -144,7 +144,6 @@ Emitter(Network.prototype); */ Network.prototype.setOptions = function (options) { if (options !== undefined) { - let errorFound = Validator.validate(options, allOptions); if (errorFound === true) { console.log('%cErrors have been found in the supplied options object.', printStyle); diff --git a/lib/network/modules/LayoutEngine.js b/lib/network/modules/LayoutEngine.js index 162b6dea..6585483f 100644 --- a/lib/network/modules/LayoutEngine.js +++ b/lib/network/modules/LayoutEngine.js @@ -9,8 +9,9 @@ class LayoutEngine { this.initialRandomSeed = Math.round(Math.random() * 1000000); this.randomSeed = this.initialRandomSeed; + this.setPhysics = false; this.options = {}; - this.optionsBackup = {}; + this.optionsBackup = {physics:{}}; this.defaultOptions = { randomSeed: undefined, @@ -18,6 +19,10 @@ class LayoutEngine { hierarchical: { enabled:false, levelSeparation: 150, + nodeSpacing: 100, + treeSpacing: 200, + blockShifting: true, + edgeMinimization: true, direction: 'UD', // UD, DU, LR, RL sortMethod: 'hubsize' // hubsize, directed } @@ -82,19 +87,21 @@ class LayoutEngine { if (this.options.hierarchical.enabled === true) { // set the physics if (allOptions.physics === undefined || allOptions.physics === true) { - allOptions.physics = {solver: 'hierarchicalRepulsion'}; - this.optionsBackup.physics = {solver:'barnesHut'}; + allOptions.physics = { + enabled:this.optionsBackup.physics.enabled === undefined ? true : this.optionsBackup.physics.enabled, + solver:'hierarchicalRepulsion' + }; + this.optionsBackup.physics.enabled = this.optionsBackup.physics.enabled === undefined ? true : this.optionsBackup.physics.enabled; + this.optionsBackup.physics.solver = this.optionsBackup.physics.solver || 'barnesHut'; } else if (typeof allOptions.physics === 'object') { - this.optionsBackup.physics = {solver:'barnesHut'}; - if (allOptions.physics.solver !== undefined) { - this.optionsBackup.physics = {solver:allOptions.physics.solver}; - } - allOptions.physics['solver'] = 'hierarchicalRepulsion'; + this.optionsBackup.physics.enabled = allOptions.physics.enabled === undefined ? true : allOptions.physics.enabled; + this.optionsBackup.physics.solver = allOptions.physics.solver || 'barnesHut'; + allOptions.physics.solver = 'hierarchicalRepulsion'; } else if (allOptions.physics !== false) { - this.optionsBackup.physics = {solver:'barnesHut'}; - allOptions.physics['solver'] = 'hierarchicalRepulsion'; + this.optionsBackup.physics.solver ='barnesHut'; + allOptions.physics = {solver:'hierarchicalRepulsion'}; } // get the type of static smooth curve in case it is required @@ -125,7 +132,7 @@ class LayoutEngine { this.optionsBackup.edges = { smooth: allOptions.edges.smooth.enabled === undefined ? true : allOptions.edges.smooth.enabled, - type:allOptions.edges.smooth.type === undefined ? 'dynamic' : allOptions.edges.smooth.type, + type: allOptions.edges.smooth.type === undefined ? 'dynamic' : allOptions.edges.smooth.type, roundness: allOptions.edges.smooth.roundness === undefined ? 0.5 : allOptions.edges.smooth.roundness, forceDirection: allOptions.edges.smooth.forceDirection === undefined ? false : allOptions.edges.smooth.forceDirection }; @@ -141,6 +148,7 @@ class LayoutEngine { // force all edges into static smooth curves. Only applies to edges that do not use the global options for smooth. this.body.emitter.emit('_forceDisableDynamicCurves', type); } + console.log(JSON.stringify(allOptions), JSON.stringify(this.optionsBackup)); return allOptions; } @@ -282,6 +290,7 @@ class LayoutEngine { // get the size of the largest hubs and check if the user has defined a level for a node. let node, nodeId; let definedLevel = false; + let definedPositions = true; let undefinedLevel = false; this.hierarchicalLevels = {}; this.lastNodeOnLevel = {}; @@ -290,10 +299,6 @@ class LayoutEngine { this.hierarchicalTrees = {}; this.treeIndex = -1; - this.whiteSpaceReductionFactor = 0.5; - this.nodeSpacing = 100; - this.treeSpacing = 2 * this.nodeSpacing; - this.distributionOrdering = {}; this.distributionIndex = {}; this.distributionOrderingPresence = {}; @@ -302,6 +307,9 @@ class LayoutEngine { for (nodeId in this.body.nodes) { if (this.body.nodes.hasOwnProperty(nodeId)) { node = this.body.nodes[nodeId]; + if (node.options.x === undefined && node.options.y === undefined) { + definedPositions = false; + } if (node.options.level !== undefined) { definedLevel = true; this.hierarchicalLevels[nodeId] = node.options.level; @@ -331,7 +339,6 @@ class LayoutEngine { } } - // check the distribution of the nodes per level. let distribution = this._getDistribution(); @@ -342,7 +349,7 @@ class LayoutEngine { this._placeNodesByHierarchy(distribution); // condense the whitespace. - this._condenseHierarchy(distribution); + this._condenseHierarchy(); // shift to center so gravity does not have to do much this._shiftToCenter(); @@ -353,15 +360,18 @@ class LayoutEngine { /** * @private */ - _condenseHierarchy(distribution) { + _condenseHierarchy() { + // Global var in this scope to define when the movement has stopped. + let stillShifting = false; + let branches = {}; // first we have some methods to help shifting trees around. // the main method to shift the trees let shiftTrees = () => { let treeSizes = getTreeSizes(); for (let i = 0; i < treeSizes.length - 1; i++) { let diff = treeSizes[i].max - treeSizes[i+1].min; - if (diff !== this.treeSpacing) { - shiftTree(i + 1, diff - this.treeSpacing); + if (diff !== this.options.hierarchical.treeSpacing) { + shiftTree(i + 1, diff - this.options.hierarchical.treeSpacing); } } }; @@ -420,33 +430,19 @@ class LayoutEngine { // we use min max terminology because width and height can interchange depending on the direction of the layout let getBranchBoundary = (branchMap, maxLevel = 1e9) => { let minSpace = 1e9; - let maxSpace = -1e9; + let maxSpace = 1e9; let min = 1e9; let max = -1e9; for (let branchNode in branchMap) { if (branchMap.hasOwnProperty(branchNode)) { let node = this.body.nodes[branchNode]; let level = this.hierarchicalLevels[node.id]; - let index = this.distributionIndex[node.id]; - let position = this._getPositionForHierarchy(this.body.nodes[node.id]); - - // if this is the node at the side, there is no previous node - if (index != 0) { - let prevNode = this.distributionOrdering[level][index - 1]; - if (branchMap[prevNode.id] === undefined) { - let prevPos = this._getPositionForHierarchy(prevNode); - minSpace = Math.min(minSpace, position - prevPos); - } - } + let position = this._getPositionForHierarchy(node); - // if this is the node at the end there is no next node - if (index != this.distributionOrdering[level].length - 1) { - let nextNode = this.distributionOrdering[level][index + 1]; - if (branchMap[nextNode.id] === undefined) { - let nextPos = this._getPositionForHierarchy(nextNode); - maxSpace = Math.max(maxSpace, nextPos - position); - } - } + // get the space around the node. + let [minSpaceNode, maxSpaceNode] = this._getSpaceAroundNode(node,branchMap); + minSpace = Math.min(minSpaceNode, minSpace); + maxSpace = Math.min(maxSpaceNode, maxSpace); // the width is only relevant for the levels two nodes have in common. This is why we filter on this. if (level <= maxLevel) { @@ -456,9 +452,6 @@ class LayoutEngine { } } - // if there was no next node, the max space is infinite (1e9 ~ close enough) - maxSpace = maxSpace < 0 ? 1e9 : maxSpace; - return [min, max, minSpace, maxSpace]; }; @@ -502,27 +495,22 @@ class LayoutEngine { return false; }; - // condense elements. These can be nodes or branches depending on the callback. let shiftElementsCloser = (callback, levels, centerParents) => { for (let i = 0; i < levels.length; i++) { let level = levels[i]; let levelNodes = this.distributionOrdering[level]; if (levelNodes.length > 1) { - for (let i = 0; i < levelNodes.length - 1; i++) { - if (hasSameParent(levelNodes[i],levelNodes[i+1]) === true) { - if (this.hierarchicalTrees[levelNodes[i].id] === this.hierarchicalTrees[levelNodes[i+1].id]) { - callback(levelNodes[i],levelNodes[i+1], centerParents); + for (let j = 0; j < levelNodes.length - 1; j++) { + if (hasSameParent(levelNodes[j],levelNodes[j+1]) === true) { + if (this.hierarchicalTrees[levelNodes[j].id] === this.hierarchicalTrees[levelNodes[j+1].id]) { + callback(levelNodes[j],levelNodes[j+1], centerParents); } }} } } }; - - // Global var in this scope to define when the movement has stopped. - let stillShifting = false; - // callback for shifting branches let branchShiftCallback = (node1, node2, centerParent = false) => { //window.CALLBACKS.push(() => { @@ -530,7 +518,7 @@ class LayoutEngine { let pos2 = this._getPositionForHierarchy(node2); let diffAbs = Math.abs(pos2 - pos1); //console.log("NOW CHEcKING:", node1.id, node2.id, diffAbs); - if (diffAbs > this.nodeSpacing) { + if (diffAbs > this.options.hierarchical.nodeSpacing) { let branchNodes1 = {}; branchNodes1[node1.id] = true; let branchNodes2 = {}; branchNodes2[node2.id] = true; @@ -544,11 +532,11 @@ class LayoutEngine { //console.log(node1.id, getBranchBoundary(branchNodes1, maxLevel), node2.id, getBranchBoundary(branchNodes2, maxLevel), maxLevel); let diffBranch = Math.abs(max1 - min2); - if (diffBranch > this.nodeSpacing) { - let offset = max1 - min2 + this.nodeSpacing; - if (offset < -minSpace2 + this.nodeSpacing) { - offset = -minSpace2 + this.nodeSpacing; - //console.log("RESETTING OFFSET", max1 - min2 + this.nodeSpacing, -minSpace2, offset); + if (diffBranch > this.options.hierarchical.nodeSpacing) { + let offset = max1 - min2 + this.options.hierarchical.nodeSpacing; + if (offset < -minSpace2 + this.options.hierarchical.nodeSpacing) { + offset = -minSpace2 + this.options.hierarchical.nodeSpacing; + //console.log("RESETTING OFFSET", max1 - min2 + this.options.hierarchical.nodeSpacing, -minSpace2, offset); } if (offset < 0) { //console.log("SHIFTING", node2.id, offset); @@ -564,43 +552,156 @@ class LayoutEngine { //this.body.emitter.emit("_redraw");}) }; - // callback for shifting individual nodes - let unitShiftCallback = (node1, node2, centerParent) => { + let minimizeEdgeLength = (iterations, node) => { //window.CALLBACKS.push(() => { - let pos1 = this._getPositionForHierarchy(node1); - let pos2 = this._getPositionForHierarchy(node2); - let diffAbs = Math.abs(pos2 - pos1); - //console.log("NOW CHEcKING:", node1.id, node2.id, diffAbs); - if (diffAbs > this.nodeSpacing) { - let diff = (pos1 + this.nodeSpacing - pos2) * this.whiteSpaceReductionFactor; - if (diff != 0) { + // console.log("ts",node.id); + let nodeId = node.id; + let allEdges = node.edges; + let nodeLevel = this.hierarchicalLevels[node.id]; + + // gather constants + let C2 = this.options.hierarchical.levelSeparation * this.options.hierarchical.levelSeparation; + let referenceNodes = {}; + let aboveEdges = []; + for (let i = 0; i < allEdges.length; i++) { + let edge = allEdges[i]; + if (edge.toId != edge.fromId) { + let otherNode = edge.toId == nodeId ? edge.from : edge.to; + referenceNodes[allEdges[i].id] = otherNode; + if (this.hierarchicalLevels[otherNode.id] < nodeLevel) { + aboveEdges.push(edge); + } + } + } + + // differentiated sum of lengths based on only moving one node over one axis + let getFx = (point, edges) => { + let sum = 0; + for (let i = 0; i < edges.length; i++) { + if (referenceNodes[edges[i].id] !== undefined) { + let a = this._getPositionForHierarchy(referenceNodes[edges[i].id]) - point; + sum += a / Math.sqrt(a * a + C2); + } + } + return sum; + }; + + // doubly differentiated sum of lengths based on only moving one node over one axis + let getDFx = (point, edges) => { + let sum = 0; + for (let i = 0; i < edges.length; i++) { + if (referenceNodes[edges[i].id] !== undefined) { + let a = this._getPositionForHierarchy(referenceNodes[edges[i].id]) - point; + sum -= (C2 * Math.pow(a * a + C2, -1.5)); + } + } + return sum; + }; + + let getGuess = (iterations, edges) => { + let guess = this._getPositionForHierarchy(node); + // Newton's method for optimization + let guessMap = {}; + for (let i = 0; i < iterations; i++) { + let fx = getFx(guess, edges); + let dfx = getDFx(guess, edges); + + // we limit the movement to avoid instability. + let limit = 40; + let ratio = Math.max(-limit, Math.min(limit, Math.round(fx/dfx))); + guess = guess - ratio; + // reduce duplicates + if (guessMap[guess] !== undefined) { + break; + } + guessMap[guess] = i; + } + return guess; + }; + + let moveBranch = (guess) => { + // position node if there is space + let nodePosition = this._getPositionForHierarchy(node); + + // check movable area of the branch + if (branches[node.id] === undefined) { + let branchNodes = {}; + branchNodes[node.id] = true; + getBranchNodes(node, branchNodes); + branches[node.id] = branchNodes; + } + let [minBranch, maxBranch, minSpaceBranch, maxSpaceBranch] = getBranchBoundary(branches[node.id]); + + let diff = guess - nodePosition; + + // check if we are allowed to move the node: + let branchOffset = 0; + if (diff > 0) { + branchOffset = Math.min(diff, maxSpaceBranch - this.options.hierarchical.nodeSpacing); + } + else if (diff < 0) { + branchOffset = -Math.min(-diff, minSpaceBranch - this.options.hierarchical.nodeSpacing); + } + + if (branchOffset != 0) { + //console.log("moving branch:",branchOffset, maxSpaceBranch, minSpaceBranch) + this._shiftBlock(node.id, branchOffset); + //this.body.emitter.emit("_redraw"); stillShifting = true; } - let factor = node2.edges.length / (node1.edges.length + node2.edges.length); - this._setPositionForHierarchy(node2, pos2 + factor * diff, undefined, true); - this._setPositionForHierarchy(node1, pos1 - (1-factor) * diff, undefined, true); - if (centerParent === true) { - this._centerParent(node2); + }; + + let moveNode = (guess) => { + let nodePosition = this._getPositionForHierarchy(node); + + // position node if there is space + let [minSpace, maxSpace] = this._getSpaceAroundNode(node); + let diff = guess - nodePosition; + // check if we are allowed to move the node: + let newPosition = nodePosition; + if (diff > 0) { + newPosition = Math.min(nodePosition + (maxSpace - this.options.hierarchical.nodeSpacing), guess); } - } - //this.body.emitter.emit("_redraw");}) + else if (diff < 0) { + newPosition = Math.max(nodePosition - (minSpace - this.options.hierarchical.nodeSpacing), guess); + } + + if (newPosition !== nodePosition) { + //console.log("moving Node:",diff, minSpace, maxSpace) + this._setPositionForHierarchy(node, newPosition, undefined, true); + //this.body.emitter.emit("_redraw"); + stillShifting = true; + } + }; + + let guess = getGuess(iterations, aboveEdges); + moveBranch(guess); + guess = getGuess(iterations, allEdges); + moveNode(guess); + //}) }; - // method to shift all nodes closer together iteratively - let shiftUnitsCloser = (iterations) => { + // method to remove whitespace between branches. Because we do bottom up, we can center the parents. + let minimizeEdgeLengthBottomUp = (iterations) => { let levels = Object.keys(this.distributionOrdering); + levels = levels.reverse(); for (let i = 0; i < iterations; i++) { stillShifting = false; - shiftElementsCloser(unitShiftCallback, levels, false); + for (let j = 0; j < levels.length; j++) { + let level = levels[j]; + let levelNodes = this.distributionOrdering[level]; + for (let k = 0; k < levelNodes.length; k++) { + minimizeEdgeLength(1000, levelNodes[k]); + } + } if (stillShifting !== true) { - //console.log("FINISHED shiftUnitsCloser IN " + i); + //console.log("FINISHED minimizeEdgeLengthBottomUp IN " + i); break; } } - //console.log("FINISHED shiftUnitsCloser IN " + iterations); }; - // method to remove whitespace between branches. Because we do bottom up, we can center the parents. + //// method to remove whitespace between branches. Because we do bottom up, we can center the parents. let shiftBranchesCloserBottomUp = (iterations) => { let levels = Object.keys(this.distributionOrdering); levels = levels.reverse(); @@ -608,7 +709,7 @@ class LayoutEngine { stillShifting = false; shiftElementsCloser(branchShiftCallback, levels, true); if (stillShifting !== true) { - //console.log("FINISHED shiftBranchesCloserBottomUp IN " + i); + //console.log("FINISHED shiftBranchesCloserBottomUp IN " + (i+1)); break; } } @@ -616,18 +717,62 @@ class LayoutEngine { // center all parents let centerAllParents = () => { - for (let node in this.body.nodes) { - this._centerParent(this.body.nodes[node]); + for (let nodeId in this.body.nodes) { + if (this.body.nodes.hasOwnProperty(nodeId)) + this._centerParent(this.body.nodes[nodeId]); } }; // the actual work is done here. - shiftBranchesCloserBottomUp(5); - centerAllParents(); - shiftUnitsCloser(2); + if (this.options.hierarchical.blockShifting === true) { + shiftBranchesCloserBottomUp(5); + centerAllParents(); + } + + // minimize edge length + if (this.options.hierarchical.edgeMinimization === true) { + minimizeEdgeLengthBottomUp(20); + } + shiftTrees(); } + /** + * This gives the space around the node. IF a map is supplied, it will only check against nodes NOT in the map. + * This is used to only get the distances to nodes outside of a branch. + * @param node + * @param map + * @returns {*[]} + * @private + */ + _getSpaceAroundNode(node, map) { + let useMap = true; + if (map === undefined) { + useMap = false; + } + let level = this.hierarchicalLevels[node.id]; + let index = this.distributionIndex[node.id]; + let position = this._getPositionForHierarchy(node); + let minSpace = 1e9; + let maxSpace = 1e9; + if (index !== 0) { + let prevNode = this.distributionOrdering[level][index - 1]; + if ((useMap === true && map[prevNode.id] === undefined) || useMap === false) { + let prevPos = this._getPositionForHierarchy(prevNode); + minSpace = position - prevPos; + } + } + + if (index != this.distributionOrdering[level].length - 1) { + let nextNode = this.distributionOrdering[level][index + 1]; + if ((useMap === true && map[nextNode.id] === undefined) || useMap === false) { + let nextPos = this._getPositionForHierarchy(nextNode); + maxSpace = Math.min(maxSpace, nextPos - position); + } + } + + return [minSpace, maxSpace]; + } /** * We use this method to center a parent node and check if it does not cross other nodes when it does. @@ -653,26 +798,11 @@ class LayoutEngine { } } - let level = this.hierarchicalLevels[parentId]; - let index = this.distributionIndex[parentId]; let position = this._getPositionForHierarchy(parentNode); - let minSpace = 1e9; - let maxSpace = 1e9; - if (index != 0) { - let prevNode = this.distributionOrdering[level][index - 1]; - let prevPos = this._getPositionForHierarchy(prevNode); - minSpace = position - prevPos; - } - - - if (index != this.distributionOrdering[level].length - 1) { - let nextNode = this.distributionOrdering[level][index + 1]; - let nextPos = this._getPositionForHierarchy(nextNode); - maxSpace = Math.min(maxSpace, nextPos - position); - } - + let [minSpace, maxSpace] = this._getSpaceAroundNode(parentNode); let newPosition = 0.5 * (minPos + maxPos); - if (newPosition < position + maxSpace && newPosition > position - minSpace) { + let diff = position - newPosition; + if ((diff < 0 && Math.abs(diff) < maxSpace - this.options.hierarchical.nodeSpacing) || (diff > 0 && Math.abs(diff) < minSpace - this.options.hierarchical.nodeSpacing)) { this._setPositionForHierarchy(parentNode, newPosition, undefined, true); } } @@ -701,7 +831,7 @@ class LayoutEngine { for (let i = 0; i < nodeArray.length; i++) { let node = nodeArray[i]; if (this.positionedNodes[node.id] === undefined) { - this._setPositionForHierarchy(node, this.nodeSpacing * i, level); + this._setPositionForHierarchy(node, this.options.hierarchical.nodeSpacing * i, level); this.positionedNodes[node.id] = true; this._placeBranchNodes(node.id, level); } @@ -892,8 +1022,6 @@ class LayoutEngine { /** * Update the bookkeeping of parent and child. - * @param parentNodeId - * @param childNodeId * @private */ _generateMap() { @@ -993,14 +1121,14 @@ class LayoutEngine { // we get the X or Y values we need and store them in pos and previousPos. The get and set make sure we get X or Y if (i === 0) {pos = this._getPositionForHierarchy(this.body.nodes[parentId]);} - else {pos = this._getPositionForHierarchy(childNodes[i-1]) + this.nodeSpacing;} + else {pos = this._getPositionForHierarchy(childNodes[i-1]) + this.options.hierarchical.nodeSpacing;} this._setPositionForHierarchy(childNode, pos, childNodeLevel); // if overlap has been detected, we shift the branch if (this.lastNodeOnLevel[childNodeLevel] !== undefined) { let previousPos = this._getPositionForHierarchy(this.body.nodes[this.lastNodeOnLevel[childNodeLevel]]); - if (pos - previousPos < this.nodeSpacing) { - let diff = (previousPos + this.nodeSpacing) - pos; + if (pos - previousPos < this.options.hierarchical.nodeSpacing) { + let diff = (previousPos + this.options.hierarchical.nodeSpacing) - pos; let sharedParent = this._findCommonParent(this.lastNodeOnLevel[childNodeLevel], childNode.id); this._shiftBlock(sharedParent.withChild, diff); } diff --git a/lib/network/options.js b/lib/network/options.js index 58082b9a..59904163 100644 --- a/lib/network/options.js +++ b/lib/network/options.js @@ -123,6 +123,10 @@ let allOptions = { hierarchical: { enabled: { boolean }, levelSeparation: { number }, + nodeSpacing: { number }, + treeSpacing: { number }, + blockShifting: { boolean }, + edgeMinimization: { boolean }, direction: { string: ['UD', 'DU', 'LR', 'RL'] }, // UD, DU, LR, RL sortMethod: { string: ['hubsize', 'directed'] }, // hubsize, directed __type__: { object, boolean } @@ -423,6 +427,10 @@ let configureOptions = { hierarchical: { enabled: false, levelSeparation: [150, 20, 500, 5], + nodeSpacing: [100, 20, 500, 5], + treeSpacing: [200, 20, 500, 5], + blockShifting: true, + edgeMinimization: true, direction: ['UD', 'DU', 'LR', 'RL'], // UD, DU, LR, RL sortMethod: ['hubsize', 'directed'] // hubsize, directed } diff --git a/lib/shared/ColorPicker.js b/lib/shared/ColorPicker.js index 7c54b93d..adbfe10b 100644 --- a/lib/shared/ColorPicker.js +++ b/lib/shared/ColorPicker.js @@ -168,10 +168,13 @@ class ColorPicker { this.frame.style.display = 'none'; // call the closing callback, restoring the onclick method. - if (this.closeCallback !== undefined) { - this.closeCallback(); - this.closeCallback = undefined; - } + // this is in a setTimeout because it will trigger the show again before the click is done. + setTimeout(() => { + if (this.closeCallback !== undefined) { + this.closeCallback(); + this.closeCallback = undefined; + } + },0); } diff --git a/lib/shared/Configurator.js b/lib/shared/Configurator.js index 6890d6c1..e564bc2d 100644 --- a/lib/shared/Configurator.js +++ b/lib/shared/Configurator.js @@ -695,7 +695,6 @@ class Configurator { } } return optionsObj; - } _printOptions() { diff --git a/lib/shared/configuration.css b/lib/shared/configuration.css index d8becc87..8ce7106c 100644 --- a/lib/shared/configuration.css +++ b/lib/shared/configuration.css @@ -111,7 +111,7 @@ input.vis-configuration.vis-config-rangeinput{ position:relative; top:-5px; width:60px; - height:13px; + /*height:13px;*/ padding:1px; margin:0; pointer-events:none; diff --git a/test/networkTest.html b/test/networkTest.html index 10420aa7..a7ec8161 100644 --- a/test/networkTest.html +++ b/test/networkTest.html @@ -15,58 +15,10 @@

Network Test

- - - - -