diff --git a/dist/vis.js b/dist/vis.js index 4ae53c82..28fc9181 100644 --- a/dist/vis.js +++ b/dist/vis.js @@ -5,7 +5,7 @@ * A dynamic, browser-based visualization library. * * @version 4.0.0-SNAPSHOT - * @date 2015-05-20 + * @date 2015-05-21 * * @license * Copyright (C) 2011-2014 Almende B.V, http://almende.com @@ -1331,10 +1331,12 @@ return /******/ (function(modules) { // webpackBootstrap delete mergeTarget[option]; } else { if (options[option] !== undefined) { - if (typeof options[option] == 'boolean') { + if (typeof options[option] === 'boolean') { mergeTarget[option].enabled = options[option]; } else { - mergeTarget[option].enabled = true; + if (options[option].enabled === undefined) { + mergeTarget[option].enabled = true; + } for (var prop in options[option]) { if (options[option].hasOwnProperty(prop)) { mergeTarget[option][prop] = options[option][prop]; @@ -3154,7 +3156,7 @@ return /******/ (function(modules) { // webpackBootstrap 'use strict'; - var Emitter = __webpack_require__(43); + var Emitter = __webpack_require__(69); var DataSet = __webpack_require__(3); var DataView = __webpack_require__(4); var util = __webpack_require__(1); @@ -6317,23 +6319,23 @@ return /******/ (function(modules) { // webpackBootstrap 'use strict'; - var Emitter = __webpack_require__(43); + var Emitter = __webpack_require__(69); var Hammer = __webpack_require__(41); var util = __webpack_require__(1); var DataSet = __webpack_require__(3); var DataView = __webpack_require__(4); var Range = __webpack_require__(17); - var Core = __webpack_require__(44); + var Core = __webpack_require__(43); var TimeAxis = __webpack_require__(35); var CurrentTime = __webpack_require__(26); var CustomTime = __webpack_require__(27); var ItemSet = __webpack_require__(32); - var Configurator = __webpack_require__(45); - var Validator = __webpack_require__(46)['default']; - var printStyle = __webpack_require__(46).printStyle; - var allOptions = __webpack_require__(47).allOptions; - var configureOptions = __webpack_require__(47).configureOptions; + var Configurator = __webpack_require__(44); + var Validator = __webpack_require__(45)['default']; + var printStyle = __webpack_require__(45).printStyle; + var allOptions = __webpack_require__(46).allOptions; + var configureOptions = __webpack_require__(46).configureOptions; /** * Create a timeline visualization @@ -6758,23 +6760,23 @@ return /******/ (function(modules) { // webpackBootstrap 'use strict'; - var Emitter = __webpack_require__(43); + var Emitter = __webpack_require__(69); var Hammer = __webpack_require__(41); var util = __webpack_require__(1); var DataSet = __webpack_require__(3); var DataView = __webpack_require__(4); var Range = __webpack_require__(17); - var Core = __webpack_require__(44); + var Core = __webpack_require__(43); var TimeAxis = __webpack_require__(35); var CurrentTime = __webpack_require__(26); var CustomTime = __webpack_require__(27); var LineGraph = __webpack_require__(34); - var Configurator = __webpack_require__(45); - var Validator = __webpack_require__(46)['default']; - var printStyle = __webpack_require__(46).printStyle; - var allOptions = __webpack_require__(48).allOptions; - var configureOptions = __webpack_require__(48).configureOptions; + var Configurator = __webpack_require__(44); + var Validator = __webpack_require__(45)['default']; + var printStyle = __webpack_require__(45).printStyle; + var allOptions = __webpack_require__(47).allOptions; + var configureOptions = __webpack_require__(47).configureOptions; /** * Create a timeline visualization @@ -7776,7 +7778,7 @@ return /******/ (function(modules) { // webpackBootstrap 'use strict'; var util = __webpack_require__(1); - var hammerUtil = __webpack_require__(49); + var hammerUtil = __webpack_require__(48); var moment = __webpack_require__(40); var Component = __webpack_require__(25); var DateUtil = __webpack_require__(15); @@ -10524,7 +10526,7 @@ return /******/ (function(modules) { // webpackBootstrap var util = __webpack_require__(1); var Component = __webpack_require__(25); var moment = __webpack_require__(40); - var locales = __webpack_require__(50); + var locales = __webpack_require__(49); /** * A current time bar @@ -10701,7 +10703,7 @@ return /******/ (function(modules) { // webpackBootstrap var util = __webpack_require__(1); var Component = __webpack_require__(25); var moment = __webpack_require__(40); - var locales = __webpack_require__(50); + var locales = __webpack_require__(49); /** * A custom time bar @@ -11542,9 +11544,9 @@ return /******/ (function(modules) { // webpackBootstrap var util = __webpack_require__(1); var DOMutil = __webpack_require__(2); - var Line = __webpack_require__(51); - var Bar = __webpack_require__(52); - var Points = __webpack_require__(53); + var Line = __webpack_require__(50); + var Bar = __webpack_require__(51); + var Points = __webpack_require__(52); /** * /** @@ -14215,8 +14217,8 @@ return /******/ (function(modules) { // webpackBootstrap var DataAxis = __webpack_require__(28); var GraphGroup = __webpack_require__(29); var Legend = __webpack_require__(33); - var BarFunctions = __webpack_require__(52); - var LineFunctions = __webpack_require__(51); + var BarFunctions = __webpack_require__(51); + var LineFunctions = __webpack_require__(50); var UNGROUPED = '__ungrouped__'; // reserved group id for ungrouped items @@ -15625,68 +15627,68 @@ return /******/ (function(modules) { // webpackBootstrap function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - var _modulesGroups = __webpack_require__(54); + var _modulesGroups = __webpack_require__(53); var _modulesGroups2 = _interopRequireDefault(_modulesGroups); - var _modulesNodesHandler = __webpack_require__(55); + var _modulesNodesHandler = __webpack_require__(54); var _modulesNodesHandler2 = _interopRequireDefault(_modulesNodesHandler); - var _modulesEdgesHandler = __webpack_require__(56); + var _modulesEdgesHandler = __webpack_require__(55); var _modulesEdgesHandler2 = _interopRequireDefault(_modulesEdgesHandler); - var _modulesPhysicsEngine = __webpack_require__(57); + var _modulesPhysicsEngine = __webpack_require__(56); var _modulesPhysicsEngine2 = _interopRequireDefault(_modulesPhysicsEngine); - var _modulesClustering = __webpack_require__(58); + var _modulesClustering = __webpack_require__(57); var _modulesClustering2 = _interopRequireDefault(_modulesClustering); - var _modulesCanvasRenderer = __webpack_require__(59); + var _modulesCanvasRenderer = __webpack_require__(58); var _modulesCanvasRenderer2 = _interopRequireDefault(_modulesCanvasRenderer); - var _modulesCanvas = __webpack_require__(60); + var _modulesCanvas = __webpack_require__(59); var _modulesCanvas2 = _interopRequireDefault(_modulesCanvas); - var _modulesView = __webpack_require__(61); + var _modulesView = __webpack_require__(60); var _modulesView2 = _interopRequireDefault(_modulesView); - var _modulesInteractionHandler = __webpack_require__(62); + var _modulesInteractionHandler = __webpack_require__(61); var _modulesInteractionHandler2 = _interopRequireDefault(_modulesInteractionHandler); - var _modulesSelectionHandler = __webpack_require__(63); + var _modulesSelectionHandler = __webpack_require__(62); var _modulesSelectionHandler2 = _interopRequireDefault(_modulesSelectionHandler); - var _modulesLayoutEngine = __webpack_require__(64); + var _modulesLayoutEngine = __webpack_require__(63); var _modulesLayoutEngine2 = _interopRequireDefault(_modulesLayoutEngine); - var _modulesManipulationSystem = __webpack_require__(65); + var _modulesManipulationSystem = __webpack_require__(64); var _modulesManipulationSystem2 = _interopRequireDefault(_modulesManipulationSystem); - var _sharedConfigurator = __webpack_require__(45); + var _sharedConfigurator = __webpack_require__(44); var _sharedConfigurator2 = _interopRequireDefault(_sharedConfigurator); - var _sharedValidator = __webpack_require__(46); + var _sharedValidator = __webpack_require__(45); var _sharedValidator2 = _interopRequireDefault(_sharedValidator); - var _optionsJs = __webpack_require__(66); + var _optionsJs = __webpack_require__(65); // Load custom shapes into CanvasRenderingContext2D - __webpack_require__(67); + __webpack_require__(66); - var Emitter = __webpack_require__(43); + var Emitter = __webpack_require__(69); var Hammer = __webpack_require__(41); var util = __webpack_require__(1); var DataSet = __webpack_require__(3); @@ -15694,8 +15696,8 @@ return /******/ (function(modules) { // webpackBootstrap var dotparser = __webpack_require__(38); var gephiParser = __webpack_require__(39); var Images = __webpack_require__(37); - var Activator = __webpack_require__(68); - var locales = __webpack_require__(69); + var Activator = __webpack_require__(67); + var locales = __webpack_require__(68); /** * @constructor Network @@ -15829,7 +15831,7 @@ return /******/ (function(modules) { // webpackBootstrap this.nodesHandler.setOptions(options.nodes); this.edgesHandler.setOptions(options.edges); this.physics.setOptions(options.physics); - this.manipulation.setOptions(options.manipulation, options); // manipulation uses the locales in the globals + this.manipulation.setOptions(options.manipulation, options, this.options); // manipulation uses the locales in the globals this.interactionHandler.setOptions(options.interaction); this.renderer.setOptions(options.interaction); // options for rendering are in interaction @@ -16084,127 +16086,127 @@ return /******/ (function(modules) { // webpackBootstrap }; Network.prototype.setSize = function () { - this.canvas.setSize.apply(this.canvas, arguments); + return this.canvas.setSize.apply(this.canvas, arguments); }; Network.prototype.canvasToDOM = function () { - this.canvas.canvasToDOM.apply(this.canvas, arguments); + return this.canvas.canvasToDOM.apply(this.canvas, arguments); }; Network.prototype.DOMtoCanvas = function () { - this.canvas.setSize.DOMtoCanvas(this.canvas, arguments); + return this.canvas.setSize.DOMtoCanvas(this.canvas, arguments); }; Network.prototype.findNode = function () { - this.clustering.findNode.apply(this.clustering, arguments); + return this.clustering.findNode.apply(this.clustering, arguments); }; Network.prototype.isCluster = function () { - this.clustering.isCluster.apply(this.clustering, arguments); + return this.clustering.isCluster.apply(this.clustering, arguments); }; Network.prototype.openCluster = function () { - this.clustering.openCluster.apply(this.clustering, arguments); + return this.clustering.openCluster.apply(this.clustering, arguments); }; Network.prototype.cluster = function () { - this.clustering.cluster.apply(this.clustering, arguments); + return this.clustering.cluster.apply(this.clustering, arguments); }; Network.prototype.clusterByConnection = function () { - this.clustering.clusterByConnection.apply(this.clustering, arguments); + return this.clustering.clusterByConnection.apply(this.clustering, arguments); }; Network.prototype.clusterByHubsize = function () { - this.clustering.clusterByHubsize.apply(this.clustering, arguments); + return this.clustering.clusterByHubsize.apply(this.clustering, arguments); }; Network.prototype.clusterOutliers = function () { - this.clustering.clusterOutliers.apply(this.clustering, arguments); + return this.clustering.clusterOutliers.apply(this.clustering, arguments); }; Network.prototype.getSeed = function () { - this.layoutEngine.getSeed.apply(this.layoutEngine, arguments); + return this.layoutEngine.getSeed.apply(this.layoutEngine, arguments); }; Network.prototype.enableEditMode = function () { - this.manipulation.enableEditMode.apply(this.manipulation, arguments); + return this.manipulation.enableEditMode.apply(this.manipulation, arguments); }; Network.prototype.disableEditMode = function () { - this.manipulation.disableEditMode.apply(this.manipulation, arguments); + return this.manipulation.disableEditMode.apply(this.manipulation, arguments); }; Network.prototype.addNodeMode = function () { - this.manipulation.addNodeMode.apply(this.manipulation, arguments); + return this.manipulation.addNodeMode.apply(this.manipulation, arguments); }; Network.prototype.editNodeMode = function () { - this.manipulation.editNodeMode.apply(this.manipulation, arguments); + return this.manipulation.editNodeMode.apply(this.manipulation, arguments); }; Network.prototype.addEdgeMode = function () { - this.manipulation.addEdgeMode.apply(this.manipulation, arguments); + return this.manipulation.addEdgeMode.apply(this.manipulation, arguments); }; Network.prototype.editEdgeMode = function () { - this.manipulation.editEdgeMode.apply(this.manipulation, arguments); + return this.manipulation.editEdgeMode.apply(this.manipulation, arguments); }; Network.prototype.deleteSelected = function () { - this.manipulation.deleteSelected.apply(this.manipulation, arguments); + return this.manipulation.deleteSelected.apply(this.manipulation, arguments); }; Network.prototype.getPositions = function () { - this.nodesHandler.getPositions.apply(this.nodesHandler, arguments); + return this.nodesHandler.getPositions.apply(this.nodesHandler, arguments); }; Network.prototype.storePositions = function () { - this.nodesHandler.storePositions.apply(this.nodesHandler, arguments); + return this.nodesHandler.storePositions.apply(this.nodesHandler, arguments); }; Network.prototype.getBoundingBox = function () { - this.nodesHandler.getBoundingBox.apply(this.nodesHandler, arguments); + return this.nodesHandler.getBoundingBox.apply(this.nodesHandler, arguments); }; Network.prototype.getConnectedNodes = function () { - this.nodesHandler.getConnectedNodes.apply(this.nodesHandler, arguments); + return this.nodesHandler.getConnectedNodes.apply(this.nodesHandler, arguments); }; Network.prototype.getEdges = function () { - this.nodesHandler.getEdges.apply(this.nodesHandler, arguments); + return this.nodesHandler.getEdges.apply(this.nodesHandler, arguments); }; Network.prototype.startSimulation = function () { - this.physics.startSimulation.apply(this.physics, arguments); + return this.physics.startSimulation.apply(this.physics, arguments); }; Network.prototype.stopSimulation = function () { - this.physics.stopSimulation.apply(this.physics, arguments); + return this.physics.stopSimulation.apply(this.physics, arguments); }; Network.prototype.stabilize = function () { - this.physics.stabilize.apply(this.physics, arguments); + return this.physics.stabilize.apply(this.physics, arguments); }; Network.prototype.getSelection = function () { - this.selectionHandler.getSelection.apply(this.selectionHandler, arguments); + return this.selectionHandler.getSelection.apply(this.selectionHandler, arguments); }; Network.prototype.getSelectedNodes = function () { - this.selectionHandler.getSelectedNodes.apply(this.selectionHandler, arguments); + return this.selectionHandler.getSelectedNodes.apply(this.selectionHandler, arguments); }; Network.prototype.getSelectedEdges = function () { - this.selectionHandler.getSelectedEdges.apply(this.selectionHandler, arguments); + return this.selectionHandler.getSelectedEdges.apply(this.selectionHandler, arguments); }; Network.prototype.getNodeAt = function () { - this.selectionHandler.getNodeAt.apply(this.selectionHandler, arguments); + return this.selectionHandler.getNodeAt.apply(this.selectionHandler, arguments); }; Network.prototype.getEdgeAt = function () { - this.selectionHandler.getEdgeAt.apply(this.selectionHandler, arguments); + return this.selectionHandler.getEdgeAt.apply(this.selectionHandler, arguments); }; Network.prototype.selectNodes = function () { - this.selectionHandler.selectNodes.apply(this.selectionHandler, arguments); + return this.selectionHandler.selectNodes.apply(this.selectionHandler, arguments); }; Network.prototype.selectEdges = function () { - this.selectionHandler.selectEdges.apply(this.selectionHandler, arguments); + return this.selectionHandler.selectEdges.apply(this.selectionHandler, arguments); }; Network.prototype.unselectAll = function () { - this.selectionHandler.unselectAll.apply(this.selectionHandler, arguments); + return this.selectionHandler.unselectAll.apply(this.selectionHandler, arguments); }; Network.prototype.redraw = function () { - this.renderer.redraw.apply(this.renderer, arguments); + return this.renderer.redraw.apply(this.renderer, arguments); }; Network.prototype.getScale = function () { - this.view.getScale.apply(this.view, arguments); + return this.view.getScale.apply(this.view, arguments); }; Network.prototype.getPosition = function () { - this.view.getPosition.apply(this.view, arguments); + return this.view.getPosition.apply(this.view, arguments); }; Network.prototype.fit = function () { - this.view.fit.apply(this.view, arguments); + return this.view.fit.apply(this.view, arguments); }; Network.prototype.moveTo = function () { - this.view.moveTo.apply(this.view, arguments); + return this.view.moveTo.apply(this.view, arguments); }; Network.prototype.focus = function () { - this.view.focus.apply(this.view, arguments); + return this.view.focus.apply(this.view, arguments); }; Network.prototype.releaseNode = function () { - this.view.releaseNode.apply(this.view, arguments); + return this.view.releaseNode.apply(this.view, arguments); }; module.exports = Network; @@ -17427,188 +17429,18 @@ return /******/ (function(modules) { // webpackBootstrap /* 43 */ /***/ function(module, exports, __webpack_require__) { - - /** - * Expose `Emitter`. - */ - - module.exports = Emitter; - - /** - * Initialize a new `Emitter`. - * - * @api public - */ - - function Emitter(obj) { - if (obj) return mixin(obj); - }; - - /** - * Mixin the emitter properties. - * - * @param {Object} obj - * @return {Object} - * @api private - */ - - function mixin(obj) { - for (var key in Emitter.prototype) { - obj[key] = Emitter.prototype[key]; - } - return obj; - } - - /** - * Listen on the given `event` with `fn`. - * - * @param {String} event - * @param {Function} fn - * @return {Emitter} - * @api public - */ - - Emitter.prototype.on = - Emitter.prototype.addEventListener = function(event, fn){ - this._callbacks = this._callbacks || {}; - (this._callbacks[event] = this._callbacks[event] || []) - .push(fn); - return this; - }; - - /** - * Adds an `event` listener that will be invoked a single - * time then automatically removed. - * - * @param {String} event - * @param {Function} fn - * @return {Emitter} - * @api public - */ - - Emitter.prototype.once = function(event, fn){ - var self = this; - this._callbacks = this._callbacks || {}; - - function on() { - self.off(event, on); - fn.apply(this, arguments); - } - - on.fn = fn; - this.on(event, on); - return this; - }; - - /** - * Remove the given callback for `event` or all - * registered callbacks. - * - * @param {String} event - * @param {Function} fn - * @return {Emitter} - * @api public - */ - - Emitter.prototype.off = - Emitter.prototype.removeListener = - Emitter.prototype.removeAllListeners = - Emitter.prototype.removeEventListener = function(event, fn){ - this._callbacks = this._callbacks || {}; - - // all - if (0 == arguments.length) { - this._callbacks = {}; - return this; - } - - // specific event - var callbacks = this._callbacks[event]; - if (!callbacks) return this; - - // remove all handlers - if (1 == arguments.length) { - delete this._callbacks[event]; - return this; - } - - // remove specific handler - var cb; - for (var i = 0; i < callbacks.length; i++) { - cb = callbacks[i]; - if (cb === fn || cb.fn === fn) { - callbacks.splice(i, 1); - break; - } - } - return this; - }; - - /** - * Emit `event` with the given args. - * - * @param {String} event - * @param {Mixed} ... - * @return {Emitter} - */ - - Emitter.prototype.emit = function(event){ - this._callbacks = this._callbacks || {}; - var args = [].slice.call(arguments, 1) - , callbacks = this._callbacks[event]; - - if (callbacks) { - callbacks = callbacks.slice(0); - for (var i = 0, len = callbacks.length; i < len; ++i) { - callbacks[i].apply(this, args); - } - } - - return this; - }; - - /** - * Return array of callbacks for `event`. - * - * @param {String} event - * @return {Array} - * @api public - */ - - Emitter.prototype.listeners = function(event){ - this._callbacks = this._callbacks || {}; - return this._callbacks[event] || []; - }; - - /** - * Check if this emitter has `event` handlers. - * - * @param {String} event - * @return {Boolean} - * @api public - */ - - Emitter.prototype.hasListeners = function(event){ - return !! this.listeners(event).length; - }; - - -/***/ }, -/* 44 */ -/***/ function(module, exports, __webpack_require__) { - 'use strict'; - var Emitter = __webpack_require__(43); + var Emitter = __webpack_require__(69); var Hammer = __webpack_require__(41); - var hammerUtil = __webpack_require__(49); + var hammerUtil = __webpack_require__(48); var util = __webpack_require__(1); var DataSet = __webpack_require__(3); var DataView = __webpack_require__(4); var Range = __webpack_require__(17); var ItemSet = __webpack_require__(32); var TimeAxis = __webpack_require__(35); - var Activator = __webpack_require__(68); + var Activator = __webpack_require__(67); var DateUtil = __webpack_require__(15); var CustomTime = __webpack_require__(27); @@ -18560,7 +18392,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = Core; /***/ }, -/* 45 */ +/* 44 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -19218,7 +19050,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 46 */ +/* 45 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -19422,16 +19254,14 @@ return /******/ (function(modules) { // webpackBootstrap value: function findInOptions(option, options, path) { var recursive = arguments[3] === undefined ? false : arguments[3]; - //console.log(option, options, path) var min = 1000000000; var closestMatch = ''; var closestMatchPath = []; var lowerCaseOption = option.toLowerCase(); var indexMatch = undefined; for (var op in options) { - var type = Validator.getType(options[op]); var distance = undefined; - if (type === 'object' && recursive === true) { + if (options[op].__type__ !== undefined && recursive === true) { var result = Validator.findInOptions(option, options[op], util.copyAndExtendArray(path, op)); if (min > result.distance) { closestMatch = result.closestMatch; @@ -19539,7 +19369,7 @@ return /******/ (function(modules) { // webpackBootstrap // item is a function, which is allowed /***/ }, -/* 47 */ +/* 46 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -19756,7 +19586,7 @@ return /******/ (function(modules) { // webpackBootstrap exports.configureOptions = configureOptions; /***/ }, -/* 48 */ +/* 47 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -20027,7 +19857,7 @@ return /******/ (function(modules) { // webpackBootstrap exports.configureOptions = configureOptions; /***/ }, -/* 49 */ +/* 48 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -20099,7 +19929,7 @@ return /******/ (function(modules) { // webpackBootstrap exports.offRelease = exports.offTouch; /***/ }, -/* 50 */ +/* 49 */ /***/ function(module, exports, __webpack_require__) { // English @@ -20121,13 +19951,13 @@ return /******/ (function(modules) { // webpackBootstrap exports['nl_BE'] = exports['nl']; /***/ }, -/* 51 */ +/* 50 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; var DOMutil = __webpack_require__(2); - var Points = __webpack_require__(53); + var Points = __webpack_require__(52); function Line(groupId, options) { this.groupId = groupId; @@ -20416,13 +20246,13 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = Line; /***/ }, -/* 52 */ +/* 51 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; var DOMutil = __webpack_require__(2); - var Points = __webpack_require__(53); + var Points = __webpack_require__(52); function Bargraph(groupId, options) { this.groupId = groupId; @@ -20664,7 +20494,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = Bargraph; /***/ }, -/* 53 */ +/* 52 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -20711,7 +20541,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = Points; /***/ }, -/* 54 */ +/* 53 */ /***/ function(module, exports, __webpack_require__) { "use strict"; @@ -20853,7 +20683,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports["default"]; /***/ }, -/* 55 */ +/* 54 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -20868,11 +20698,11 @@ return /******/ (function(modules) { // webpackBootstrap function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - var _componentsNode = __webpack_require__(85); + var _componentsNode = __webpack_require__(74); var _componentsNode2 = _interopRequireDefault(_componentsNode); - var _componentsSharedLabel = __webpack_require__(91); + var _componentsSharedLabel = __webpack_require__(75); var _componentsSharedLabel2 = _interopRequireDefault(_componentsSharedLabel); @@ -21326,7 +21156,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 56 */ +/* 55 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -21341,11 +21171,11 @@ return /******/ (function(modules) { // webpackBootstrap function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - var _componentsEdge = __webpack_require__(86); + var _componentsEdge = __webpack_require__(76); var _componentsEdge2 = _interopRequireDefault(_componentsEdge); - var _componentsSharedLabel = __webpack_require__(91); + var _componentsSharedLabel = __webpack_require__(75); var _componentsSharedLabel2 = _interopRequireDefault(_componentsSharedLabel); @@ -21745,7 +21575,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 57 */ +/* 56 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -21760,35 +21590,35 @@ return /******/ (function(modules) { // webpackBootstrap function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - var _componentsPhysicsBarnesHutSolver = __webpack_require__(74); + var _componentsPhysicsBarnesHutSolver = __webpack_require__(77); var _componentsPhysicsBarnesHutSolver2 = _interopRequireDefault(_componentsPhysicsBarnesHutSolver); - var _componentsPhysicsRepulsionSolver = __webpack_require__(75); + var _componentsPhysicsRepulsionSolver = __webpack_require__(78); var _componentsPhysicsRepulsionSolver2 = _interopRequireDefault(_componentsPhysicsRepulsionSolver); - var _componentsPhysicsHierarchicalRepulsionSolver = __webpack_require__(76); + var _componentsPhysicsHierarchicalRepulsionSolver = __webpack_require__(79); var _componentsPhysicsHierarchicalRepulsionSolver2 = _interopRequireDefault(_componentsPhysicsHierarchicalRepulsionSolver); - var _componentsPhysicsSpringSolver = __webpack_require__(77); + var _componentsPhysicsSpringSolver = __webpack_require__(80); var _componentsPhysicsSpringSolver2 = _interopRequireDefault(_componentsPhysicsSpringSolver); - var _componentsPhysicsHierarchicalSpringSolver = __webpack_require__(78); + var _componentsPhysicsHierarchicalSpringSolver = __webpack_require__(81); var _componentsPhysicsHierarchicalSpringSolver2 = _interopRequireDefault(_componentsPhysicsHierarchicalSpringSolver); - var _componentsPhysicsCentralGravitySolver = __webpack_require__(79); + var _componentsPhysicsCentralGravitySolver = __webpack_require__(82); var _componentsPhysicsCentralGravitySolver2 = _interopRequireDefault(_componentsPhysicsCentralGravitySolver); - var _componentsPhysicsFA2BasedRepulsionSolver = __webpack_require__(80); + var _componentsPhysicsFA2BasedRepulsionSolver = __webpack_require__(83); var _componentsPhysicsFA2BasedRepulsionSolver2 = _interopRequireDefault(_componentsPhysicsFA2BasedRepulsionSolver); - var _componentsPhysicsFA2BasedCentralGravitySolver = __webpack_require__(81); + var _componentsPhysicsFA2BasedCentralGravitySolver = __webpack_require__(84); var _componentsPhysicsFA2BasedCentralGravitySolver2 = _interopRequireDefault(_componentsPhysicsFA2BasedCentralGravitySolver); @@ -21809,6 +21639,7 @@ return /******/ (function(modules) { // webpackBootstrap this.renderTimer = undefined; this.stabilized = false; + this.startedStabilization = false; this.stabilizationIterations = 0; this.ready = false; // will be set to true if the stabilize @@ -22029,11 +21860,8 @@ return /******/ (function(modules) { // webpackBootstrap // The event is triggered on the next tick, to prevent the case that // it is fired while initializing the Network, in which case you would not // be able to catch it - this.stabilizationIterations = 0; this.startedStabilization = false; - this._emitStabilized(); - } else { - this.stabilizationIterations = 0; + //this._emitStabilized(); } this.stopSimulation(); } @@ -22046,6 +21874,7 @@ return /******/ (function(modules) { // webpackBootstrap if (this.stabilizationIterations > 1) { setTimeout(function () { _this2.body.emitter.emit('stabilized', { iterations: _this2.stabilizationIterations }); + _this2.stabilizationIterations = 0; }, 0); } } @@ -22288,6 +22117,8 @@ return /******/ (function(modules) { // webpackBootstrap * @private */ value: function stabilize() { + var _this3 = this; + var iterations = arguments[0] === undefined ? this.options.stabilization.iterations : arguments[0]; if (typeof iterations !== 'number') { @@ -22306,8 +22137,6 @@ return /******/ (function(modules) { // webpackBootstrap // block redraw requests this.body.emitter.emit('_blockRedrawRequests'); - this.body.emitter.emit('startStabilizing'); - this.startedStabilization = true; this.targetIterations = iterations; // start the stabilization @@ -22316,7 +22145,9 @@ return /******/ (function(modules) { // webpackBootstrap } this.stabilizationIterations = 0; - setTimeout(this._stabilizationBatch.bind(this), 0); + setTimeout(function () { + return _this3._stabilizationBatch(); + }, 0); } }, { key: '_stabilizationBatch', @@ -22350,6 +22181,12 @@ return /******/ (function(modules) { // webpackBootstrap this.body.emitter.emit('stabilizationIterationsDone'); this.body.emitter.emit('_requestRedraw'); + if (this.stabilized === true) { + this._emitStabilized(); + } else { + this.startSimulation(); + } + this.ready = true; } }]); @@ -22361,7 +22198,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 58 */ +/* 57 */ /***/ function(module, exports, __webpack_require__) { "use strict"; @@ -22376,7 +22213,7 @@ return /******/ (function(modules) { // webpackBootstrap function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - var _componentsNodesCluster = __webpack_require__(82); + var _componentsNodesCluster = __webpack_require__(85); var _componentsNodesCluster2 = _interopRequireDefault(_componentsNodesCluster); @@ -22424,8 +22261,7 @@ return /******/ (function(modules) { // webpackBootstrap } for (var i = 0; i < nodesToCluster.length; i++) { - var node = this.body.nodes[nodesToCluster[i]]; - this.clusterByConnection(node, options, false); + this.clusterByConnection(nodesToCluster[i], options, false); } this.body.emitter.emit("_dataChanged"); } @@ -22454,9 +22290,16 @@ return /******/ (function(modules) { // webpackBootstrap // collect the nodes that will be in the cluster for (var i = 0; i < this.body.nodeIndices.length; i++) { var nodeId = this.body.nodeIndices[i]; - var clonedOptions = this._cloneOptions(nodeId); + var node = this.body.nodes[nodeId]; + var clonedOptions = this._cloneOptions(node); if (options.joinCondition(clonedOptions) === true) { childNodesObj[nodeId] = this.body.nodes[nodeId]; + + // collect the nodes that will be in the cluster + for (var _i = 0; _i < node.edges.length; _i++) { + var edge = node.edges[_i]; + childEdgesObj[edge.id] = edge; + } } } @@ -22482,19 +22325,23 @@ return /******/ (function(modules) { // webpackBootstrap var childEdgesObj = {}; var nodeId = this.body.nodeIndices[i]; if (this.body.nodes[nodeId].edges.length === 1) { + // this is an outlier var edge = this.body.nodes[nodeId].edges[0]; var childNodeId = this._getConnectedId(edge, nodeId); - if (childNodeId != nodeId) { + if (childNodeId !== nodeId) { if (options.joinCondition === undefined) { + childEdgesObj[edge.id] = edge; childNodesObj[nodeId] = this.body.nodes[nodeId]; childNodesObj[childNodeId] = this.body.nodes[childNodeId]; } else { - var clonedOptions = this._cloneOptions(nodeId); + var clonedOptions = this._cloneOptions(this.body.nodes[nodeId]); if (options.joinCondition(clonedOptions) === true) { + childEdgesObj[edge.id] = edge; childNodesObj[nodeId] = this.body.nodes[nodeId]; } - clonedOptions = this._cloneOptions(childNodeId); + clonedOptions = this._cloneOptions(this.body.nodes[childNodeId]); if (options.joinCondition(clonedOptions) === true) { + childEdgesObj[edge.id] = edge; childNodesObj[childNodeId] = this.body.nodes[childNodeId]; } } @@ -22548,7 +22395,7 @@ return /******/ (function(modules) { // webpackBootstrap var childNodesObj = {}; var childEdgesObj = {}; var parentNodeId = node.id; - var parentClonedOptions = this._cloneOptions(parentNodeId); + var parentClonedOptions = this._cloneOptions(node); childNodesObj[parentNodeId] = node; // collect the nodes that will be in the cluster @@ -22562,7 +22409,7 @@ return /******/ (function(modules) { // webpackBootstrap childNodesObj[childNodeId] = this.body.nodes[childNodeId]; } else { // clone the options and insert some additional parameters that could be interesting. - var childClonedOptions = this._cloneOptions(childNodeId); + var childClonedOptions = this._cloneOptions(this.body.nodes[childNodeId]); if (options.joinCondition(parentClonedOptions, childClonedOptions) === true) { childEdgesObj[edge.id] = edge; childNodesObj[childNodeId] = this.body.nodes[childNodeId]; @@ -22585,15 +22432,15 @@ return /******/ (function(modules) { // webpackBootstrap * @returns {{}} * @private */ - value: function _cloneOptions(objId, type) { + value: function _cloneOptions(item, type) { var clonedOptions = {}; if (type === undefined || type === "node") { - util.deepExtend(clonedOptions, this.body.nodes[objId].options, true); - clonedOptions.x = this.body.nodes[objId].x; - clonedOptions.y = this.body.nodes[objId].y; - clonedOptions.amountOfConnections = this.body.nodes[objId].edges.length; + util.deepExtend(clonedOptions, item.options, true); + clonedOptions.x = item.x; + clonedOptions.y = item.y; + clonedOptions.amountOfConnections = item.edges.length; } else { - util.deepExtend(clonedOptions, this.body.edges[objId].options, true); + util.deepExtend(clonedOptions, item.options, true); } return clonedOptions; } @@ -22609,41 +22456,42 @@ return /******/ (function(modules) { // webpackBootstrap * @param options * @private */ - value: function _createClusterEdges(childNodesObj, childEdgesObj, newEdges, options) { + value: function _createClusterEdges(childNodesObj, childEdgesObj, newEdges, clusterNodeProperties, clusterEdgeProperties) { var edge = undefined, childNodeId = undefined, - childNode = undefined; + childNode = undefined, + toId = undefined, + fromId = undefined, + otherNodeId = undefined; var childKeys = Object.keys(childNodesObj); for (var i = 0; i < childKeys.length; i++) { childNodeId = childKeys[i]; childNode = childNodesObj[childNodeId]; - // mark all edges for removal from global and construct new edges from the cluster to others + // construct new edges from the cluster to others for (var j = 0; j < childNode.edges.length; j++) { edge = childNode.edges[j]; childEdgesObj[edge.id] = edge; - var otherNodeId = edge.toId; - var otherOnTo = true; - if (edge.toId != childNodeId) { - otherNodeId = edge.toId; - otherOnTo = true; - } else if (edge.fromId != childNodeId) { - otherNodeId = edge.fromId; - otherOnTo = false; + // childNodeId position will be replaced by the cluster. + if (edge.toId == childNodeId) { + // this is a double equals because ints and strings can be interchanged here. + toId = clusterNodeProperties.id; + fromId = edge.fromId; + otherNodeId = fromId; + } else { + toId = edge.toId; + fromId = clusterNodeProperties.id; + otherNodeId = toId; } + // if the node connected to the cluster is also in the cluster we do not need a new edge. if (childNodesObj[otherNodeId] === undefined) { - var clonedOptions = this._cloneOptions(edge.id, "edge"); - util.deepExtend(clonedOptions, options.clusterEdgeProperties); - if (otherOnTo === true) { - clonedOptions.from = options.clusterNodeProperties.id; - clonedOptions.to = otherNodeId; - } else { - clonedOptions.from = otherNodeId; - clonedOptions.to = options.clusterNodeProperties.id; - } + var clonedOptions = this._cloneOptions(edge, "edge"); + util.deepExtend(clonedOptions, clusterEdgeProperties); + clonedOptions.from = fromId; + clonedOptions.to = toId; clonedOptions.id = "clusterEdge:" + util.randomUUID(); newEdges.push(this.body.functions.createEdge(clonedOptions)); } @@ -22691,34 +22539,36 @@ return /******/ (function(modules) { // webpackBootstrap return; } + var clusterNodeProperties = util.deepExtend({}, options.clusterNodeProperties); + // check if we have an unique id; - if (options.clusterNodeProperties.id === undefined) { - options.clusterNodeProperties.id = "cluster:" + util.randomUUID(); + if (clusterNodeProperties.id === undefined) { + clusterNodeProperties.id = "cluster:" + util.randomUUID(); } - var clusterId = options.clusterNodeProperties.id; + var clusterId = clusterNodeProperties.id; // construct the clusterNodeProperties - var clusterNodeProperties = options.clusterNodeProperties; if (options.processProperties !== undefined) { // get the childNode options var childNodesOptions = []; for (var nodeId in childNodesObj) { - var clonedOptions = this._cloneOptions(nodeId); + var clonedOptions = this._cloneOptions(childNodesObj[nodeId]); childNodesOptions.push(clonedOptions); } // get clusterproperties based on childNodes var childEdgesOptions = []; for (var edgeId in childEdgesObj) { - var clonedOptions = this._cloneOptions(edgeId, "edge"); + var clonedOptions = this._cloneOptions(childEdgesObj[edgeId], "edge"); childEdgesOptions.push(clonedOptions); } clusterNodeProperties = options.processProperties(clusterNodeProperties, childNodesOptions, childEdgesOptions); if (!clusterNodeProperties) { - throw new Error("The processClusterProperties function does not return properties!"); + throw new Error("The processProperties function does not return properties!"); } } + if (clusterNodeProperties.label === undefined) { clusterNodeProperties.label = "cluster"; } @@ -22744,13 +22594,15 @@ return /******/ (function(modules) { // webpackBootstrap clusterNode.isCluster = true; clusterNode.containedNodes = childNodesObj; clusterNode.containedEdges = childEdgesObj; + // cache a copy from the cluster edge properties if we have to reconnect others later on + clusterNode.clusterEdgeProperties = options.clusterEdgeProperties; // finally put the cluster node into global this.body.nodes[clusterNodeProperties.id] = clusterNode; // create the new edges that will connect to the cluster var newEdges = []; - this._createClusterEdges(childNodesObj, childEdgesObj, newEdges, options); + this._createClusterEdges(childNodesObj, childEdgesObj, newEdges, clusterNodeProperties, options.clusterEdgeProperties); // disable the childEdges for (var edgeId in childEdgesObj) { @@ -22818,13 +22670,14 @@ return /******/ (function(modules) { // webpackBootstrap var minY = childNodesObj[childKeys[0]].y; var maxY = childNodesObj[childKeys[0]].y; var node = undefined; - for (var i = 0; i < childKeys.lenght; i++) { - node = childNodesObj[childKeys[0]]; + for (var i = 1; i < childKeys.length; i++) { + node = childNodesObj[childKeys[i]]; minX = node.x < minX ? node.x : minX; maxX = node.x > maxX ? node.x : maxX; minY = node.y < minY ? node.y : minY; maxY = node.y > maxY ? node.y : maxY; } + return { x: 0.5 * (minX + maxX), y: 0.5 * (minY + maxY) }; } }, { @@ -22876,9 +22729,44 @@ return /******/ (function(modules) { // webpackBootstrap // release edges for (var edgeId in containedEdges) { if (containedEdges.hasOwnProperty(edgeId)) { - var edge = this.body.edges[edgeId]; - edge.options.hidden = false; - edge.togglePhysics(true); + var edge = containedEdges[edgeId]; + // if this edge was a temporary edge and it's connected nodes do not exist anymore, we remove it from the data + if (this.body.nodes[edge.fromId] === undefined || this.body.nodes[edge.toId] === undefined) { + edge.edgeType.cleanup(); + // this removes the edge from node.edges, which is why edgeIds is formed + edge.disconnect(); + delete this.body.edges[edgeId]; + } else { + // one of the nodes connected to this edge is in a cluster. We give the edge to that cluster and make a new temporary edge. + if (this.clusteredNodes[edge.fromId] !== undefined || this.clusteredNodes[edge.toId] !== undefined) { + var fromId = undefined, + toId = undefined; + var clusteredNode = this.clusteredNodes[edge.fromId] || this.clusteredNodes[edge.toId]; + var clusterId = clusteredNode.clusterId; + var _clusterNode = this.body.nodes[clusterId]; + _clusterNode.containedEdges[edgeId] = edge; + + if (this.clusteredNodes[edge.fromId] !== undefined) { + fromId = clusterId; + toId = edge.toId; + } else { + fromId = edge.fromId; + toId = clusterId; + } + + var clonedOptions = this._cloneOptions(edge, "edge"); + var id = "clusterEdge:" + util.randomUUID(); + util.deepExtend(clonedOptions, _clusterNode.clusterEdgeProperties); + util.deepExtend(clonedOptions, { from: fromId, to: toId, hidden: false, physics: true, id: id }); + var newEdge = this.body.functions.createEdge(clonedOptions); + + this.body.edges[id] = newEdge; + this.body.edges[id].connect(); + } else { + edge.options.hidden = false; + edge.togglePhysics(true); + } + } } } @@ -23013,7 +22901,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports["default"]; /***/ }, -/* 59 */ +/* 58 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -23202,13 +23090,15 @@ return /******/ (function(modules) { // webpackBootstrap var h = this.canvas.frame.canvas.clientHeight; ctx.clearRect(0, 0, w, h); - this.body.emitter.emit('beforeDrawing', ctx); - // set scaling and translation ctx.save(); ctx.translate(this.body.view.translation.x, this.body.view.translation.y); ctx.scale(this.body.view.scale, this.body.view.scale); + ctx.beginPath(); + this.body.emitter.emit('beforeDrawing', ctx); + ctx.closePath(); + if (hidden === false) { if (this.dragging === false || this.dragging === true && this.options.hideEdgesOnDrag === false) { this._drawEdges(ctx); @@ -23223,10 +23113,10 @@ return /******/ (function(modules) { // webpackBootstrap this._drawControlNodes(ctx); } + ctx.beginPath(); //this.physics.nodesSolver._debug(ctx,"#F00F0F"); - this.body.emitter.emit('afterDrawing', ctx); - + ctx.closePath(); // restore original scaling and translation ctx.restore(); @@ -23391,7 +23281,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 60 */ +/* 59 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -23405,7 +23295,7 @@ return /******/ (function(modules) { // webpackBootstrap function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } var Hammer = __webpack_require__(41); - var hammerUtil = __webpack_require__(49); + var hammerUtil = __webpack_require__(48); var util = __webpack_require__(1); @@ -23759,7 +23649,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 61 */ +/* 60 */ /***/ function(module, exports, __webpack_require__) { "use strict"; @@ -24161,7 +24051,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports["default"]; /***/ }, -/* 62 */ +/* 61 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -24176,11 +24066,11 @@ return /******/ (function(modules) { // webpackBootstrap function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - var _componentsNavigationHandler = __webpack_require__(83); + var _componentsNavigationHandler = __webpack_require__(86); var _componentsNavigationHandler2 = _interopRequireDefault(_componentsNavigationHandler); - var _componentsPopup = __webpack_require__(84); + var _componentsPopup = __webpack_require__(87); var _componentsPopup2 = _interopRequireDefault(_componentsPopup); @@ -24867,7 +24757,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 63 */ +/* 62 */ /***/ function(module, exports, __webpack_require__) { "use strict"; @@ -24880,8 +24770,8 @@ return /******/ (function(modules) { // webpackBootstrap function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - var Node = __webpack_require__(85); - var Edge = __webpack_require__(86); + var Node = __webpack_require__(74); + var Edge = __webpack_require__(76); var util = __webpack_require__(1); var SelectionHandler = (function () { @@ -25587,7 +25477,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports["default"]; /***/ }, -/* 64 */ +/* 63 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -25649,7 +25539,7 @@ return /******/ (function(modules) { // webpackBootstrap util.mergeOptions(this.options, options, 'hierarchical'); if (options.randomSeed !== undefined) { - this.randomSeed = options.randomSeed; + this.initialRandomSeed = options.randomSeed; } if (this.options.hierarchical.enabled === true) { @@ -25734,6 +25624,7 @@ return /******/ (function(modules) { // webpackBootstrap key: 'positionInitially', value: function positionInitially(nodesArray) { if (this.options.hierarchical.enabled !== true) { + this.randomSeed = this.initialRandomSeed; for (var i = 0; i < nodesArray.length; i++) { var node = nodesArray[i]; if (!node.isFixed() && (node.x === undefined || node.y === undefined)) { @@ -26083,7 +25974,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 65 */ +/* 64 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -26098,7 +25989,7 @@ return /******/ (function(modules) { // webpackBootstrap var util = __webpack_require__(1); var Hammer = __webpack_require__(41); - var hammerUtil = __webpack_require__(49); + var hammerUtil = __webpack_require__(48); /** * clears the toolbar div element of children @@ -26181,14 +26072,18 @@ return /******/ (function(modules) { // webpackBootstrap * Set the Options * @param options */ - value: function setOptions(options, allOptions) { + value: function setOptions(options, allOptions, globalOptions) { if (allOptions !== undefined) { if (allOptions.locale !== undefined) { this.options.locale = allOptions.locale; - }; + } else { + this.options.locale = globalOptions.locale; + } if (allOptions.locales !== undefined) { this.options.locales = allOptions.locales; - }; + } else { + this.options.locales = globalOptions.locales; + } } if (options !== undefined) { @@ -26559,6 +26454,7 @@ return /******/ (function(modules) { // webpackBootstrap _this3.body.data.edges.remove(finalizedData.edges); _this3.body.data.nodes.remove(finalizedData.nodes); _this3.body.emitter.emit('startSimulation'); + _this3.showManipulatorToolbar(); } }); } else { @@ -26568,6 +26464,7 @@ return /******/ (function(modules) { // webpackBootstrap this.body.data.edges.remove(selectedEdges); this.body.data.nodes.remove(selectedNodes); this.body.emitter.emit('startSimulation'); + this.showManipulatorToolbar(); } } }, { @@ -27272,7 +27169,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 66 */ +/* 65 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -27446,7 +27343,7 @@ return /******/ (function(modules) { // webpackBootstrap size: { number: number }, // px face: { string: string }, background: { string: string }, - stroke: { number: number }, // px + strokeWidth: { number: number }, // px strokeColor: { string: string }, __type__: { object: object, string: string } }, @@ -27748,7 +27645,7 @@ return /******/ (function(modules) { // webpackBootstrap exports.configureOptions = configureOptions; /***/ }, -/* 67 */ +/* 66 */ /***/ function(module, exports, __webpack_require__) { /** @@ -27764,6 +27661,7 @@ return /******/ (function(modules) { // webpackBootstrap CanvasRenderingContext2D.prototype.circle = function (x, y, r) { this.beginPath(); this.arc(x, y, r, 0, 2 * Math.PI, false); + this.closePath(); }; /** @@ -27775,6 +27673,7 @@ return /******/ (function(modules) { // webpackBootstrap CanvasRenderingContext2D.prototype.square = function (x, y, r) { this.beginPath(); this.rect(x - r, y - r, r * 2, r * 2); + this.closePath(); }; /** @@ -27890,6 +27789,7 @@ return /******/ (function(modules) { // webpackBootstrap this.arc(x + r, y + h - r, r, r2d * 90, r2d * 180, false); this.lineTo(x, y + r); this.arc(x + r, y + r, r, r2d * 180, r2d * 270, false); + this.closePath(); }; /** @@ -27915,6 +27815,7 @@ return /******/ (function(modules) { // webpackBootstrap this.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); this.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); this.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + this.closePath(); }; /** @@ -27968,7 +27869,6 @@ return /******/ (function(modules) { // webpackBootstrap var yt = y - length * Math.sin(angle); // inner tail - // TODO: allow to customize different shapes var xi = x - length * 0.9 * Math.cos(angle); var yi = y - length * 0.9 * Math.sin(angle); @@ -28032,13 +27932,13 @@ return /******/ (function(modules) { // webpackBootstrap } /***/ }, -/* 68 */ +/* 67 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var keycharm = __webpack_require__(87); - var Emitter = __webpack_require__(43); + var keycharm = __webpack_require__(88); + var Emitter = __webpack_require__(69); var Hammer = __webpack_require__(41); var util = __webpack_require__(1); @@ -28185,7 +28085,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = Activator; /***/ }, -/* 69 */ +/* 68 */ /***/ function(module, exports, __webpack_require__) { // English @@ -28228,6 +28128,176 @@ return /******/ (function(modules) { // webpackBootstrap exports['nl_NL'] = exports['nl']; exports['nl_BE'] = exports['nl']; +/***/ }, +/* 69 */ +/***/ function(module, exports, __webpack_require__) { + + + /** + * Expose `Emitter`. + */ + + module.exports = Emitter; + + /** + * Initialize a new `Emitter`. + * + * @api public + */ + + function Emitter(obj) { + if (obj) return mixin(obj); + }; + + /** + * Mixin the emitter properties. + * + * @param {Object} obj + * @return {Object} + * @api private + */ + + function mixin(obj) { + for (var key in Emitter.prototype) { + obj[key] = Emitter.prototype[key]; + } + return obj; + } + + /** + * Listen on the given `event` with `fn`. + * + * @param {String} event + * @param {Function} fn + * @return {Emitter} + * @api public + */ + + Emitter.prototype.on = + Emitter.prototype.addEventListener = function(event, fn){ + this._callbacks = this._callbacks || {}; + (this._callbacks[event] = this._callbacks[event] || []) + .push(fn); + return this; + }; + + /** + * Adds an `event` listener that will be invoked a single + * time then automatically removed. + * + * @param {String} event + * @param {Function} fn + * @return {Emitter} + * @api public + */ + + Emitter.prototype.once = function(event, fn){ + var self = this; + this._callbacks = this._callbacks || {}; + + function on() { + self.off(event, on); + fn.apply(this, arguments); + } + + on.fn = fn; + this.on(event, on); + return this; + }; + + /** + * Remove the given callback for `event` or all + * registered callbacks. + * + * @param {String} event + * @param {Function} fn + * @return {Emitter} + * @api public + */ + + Emitter.prototype.off = + Emitter.prototype.removeListener = + Emitter.prototype.removeAllListeners = + Emitter.prototype.removeEventListener = function(event, fn){ + this._callbacks = this._callbacks || {}; + + // all + if (0 == arguments.length) { + this._callbacks = {}; + return this; + } + + // specific event + var callbacks = this._callbacks[event]; + if (!callbacks) return this; + + // remove all handlers + if (1 == arguments.length) { + delete this._callbacks[event]; + return this; + } + + // remove specific handler + var cb; + for (var i = 0; i < callbacks.length; i++) { + cb = callbacks[i]; + if (cb === fn || cb.fn === fn) { + callbacks.splice(i, 1); + break; + } + } + return this; + }; + + /** + * Emit `event` with the given args. + * + * @param {String} event + * @param {Mixed} ... + * @return {Emitter} + */ + + Emitter.prototype.emit = function(event){ + this._callbacks = this._callbacks || {}; + var args = [].slice.call(arguments, 1) + , callbacks = this._callbacks[event]; + + if (callbacks) { + callbacks = callbacks.slice(0); + for (var i = 0, len = callbacks.length; i < len; ++i) { + callbacks[i].apply(this, args); + } + } + + return this; + }; + + /** + * Return array of callbacks for `event`. + * + * @param {String} event + * @return {Array} + * @api public + */ + + Emitter.prototype.listeners = function(event){ + this._callbacks = this._callbacks || {}; + return this._callbacks[event] || []; + }; + + /** + * Check if this emitter has `event` handlers. + * + * @param {String} event + * @return {Boolean} + * @api public + */ + + Emitter.prototype.hasListeners = function(event){ + return !! this.listeners(event).length; + }; + + /***/ }, /* 70 */ /***/ function(module, exports, __webpack_require__) { @@ -31315,7 +31385,7 @@ return /******/ (function(modules) { // webpackBootstrap return _moment; })); - /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(89)(module))) + /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(107)(module))) /***/ }, /* 71 */ @@ -34001,7 +34071,7 @@ return /******/ (function(modules) { // webpackBootstrap prefixed: prefixed }); - if ("function" == TYPE_FUNCTION && __webpack_require__(90)) { + if ("function" == TYPE_FUNCTION && __webpack_require__(108)) { !(__WEBPACK_AMD_DEFINE_RESULT__ = function() { return Hammer; }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); @@ -34029,7 +34099,7 @@ return /******/ (function(modules) { // webpackBootstrap function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } var Hammer = __webpack_require__(41); - var hammerUtil = __webpack_require__(49); + var hammerUtil = __webpack_require__(48); var util = __webpack_require__(1); var ColorPicker = (function () { @@ -34598,982 +34668,1388 @@ return /******/ (function(modules) { // webpackBootstrap /* 74 */ /***/ function(module, exports, __webpack_require__) { - "use strict"; + 'use strict'; - Object.defineProperty(exports, "__esModule", { + Object.defineProperty(exports, '__esModule', { value: true }); - var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - var BarnesHutSolver = (function () { - function BarnesHutSolver(body, physicsBody, options) { - _classCallCheck(this, BarnesHutSolver); + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + + var _sharedLabel = __webpack_require__(75); + + var _sharedLabel2 = _interopRequireDefault(_sharedLabel); + + var _nodesShapesBox = __webpack_require__(90); + + var _nodesShapesBox2 = _interopRequireDefault(_nodesShapesBox); + + var _nodesShapesCircle = __webpack_require__(91); + + var _nodesShapesCircle2 = _interopRequireDefault(_nodesShapesCircle); + + var _nodesShapesCircularImage = __webpack_require__(92); + + var _nodesShapesCircularImage2 = _interopRequireDefault(_nodesShapesCircularImage); + + var _nodesShapesDatabase = __webpack_require__(93); + + var _nodesShapesDatabase2 = _interopRequireDefault(_nodesShapesDatabase); + + var _nodesShapesDiamond = __webpack_require__(94); + + var _nodesShapesDiamond2 = _interopRequireDefault(_nodesShapesDiamond); + + var _nodesShapesDot = __webpack_require__(95); + + var _nodesShapesDot2 = _interopRequireDefault(_nodesShapesDot); + + var _nodesShapesEllipse = __webpack_require__(96); + + var _nodesShapesEllipse2 = _interopRequireDefault(_nodesShapesEllipse); + + var _nodesShapesIcon = __webpack_require__(97); + + var _nodesShapesIcon2 = _interopRequireDefault(_nodesShapesIcon); + + var _nodesShapesImage = __webpack_require__(98); + + var _nodesShapesImage2 = _interopRequireDefault(_nodesShapesImage); + + var _nodesShapesSquare = __webpack_require__(99); + + var _nodesShapesSquare2 = _interopRequireDefault(_nodesShapesSquare); + + var _nodesShapesStar = __webpack_require__(100); + + var _nodesShapesStar2 = _interopRequireDefault(_nodesShapesStar); + + var _nodesShapesText = __webpack_require__(101); + + var _nodesShapesText2 = _interopRequireDefault(_nodesShapesText); + + var _nodesShapesTriangle = __webpack_require__(102); + + var _nodesShapesTriangle2 = _interopRequireDefault(_nodesShapesTriangle); + + var _nodesShapesTriangleDown = __webpack_require__(103); + + var _nodesShapesTriangleDown2 = _interopRequireDefault(_nodesShapesTriangleDown); + + var _sharedValidator = __webpack_require__(45); + + var _sharedValidator2 = _interopRequireDefault(_sharedValidator); + + var util = __webpack_require__(1); + + /** + * @class Node + * A node. A node can be connected to other nodes via one or multiple edges. + * @param {object} options An object containing options for the node. All + * options are optional, except for the id. + * {number} id Id of the node. Required + * {string} label Text label for the node + * {number} x Horizontal position of the node + * {number} y Vertical position of the node + * {string} shape Node shape, available: + * "database", "circle", "ellipse", + * "box", "image", "text", "dot", + * "star", "triangle", "triangleDown", + * "square", "icon" + * {string} image An image url + * {string} title An title text, can be HTML + * {anytype} group A group name or number + * @param {Network.Images} imagelist A list with images. Only needed + * when the node has an image + * @param {Network.Groups} grouplist A list with groups. Needed for + * retrieving group options + * @param {Object} constants An object with default values for + * example for the color + * + */ + var Node = (function () { + function Node(options, body, imagelist, grouplist, globalOptions) { + _classCallCheck(this, Node); + + this.options = util.bridgeObject(globalOptions); this.body = body; - this.physicsBody = physicsBody; - this.barnesHutTree; + + this.edges = []; // all edges connected to this node + + // set defaults for the options + this.id = undefined; + this.imagelist = imagelist; + this.grouplist = grouplist; + + // state options + this.x = undefined; + this.y = undefined; + this.baseSize = this.options.size; + this.baseFontSize = this.options.font.size; + this.predefinedPosition = false; // used to check if initial fit should just take the range or approximate + this.selected = false; + this.hover = false; + + this.labelModule = new _sharedLabel2['default'](this.body, this.options); this.setOptions(options); } - _createClass(BarnesHutSolver, [{ - key: "setOptions", - value: function setOptions(options) { - this.options = options; - this.thetaInversed = 1 / this.options.theta; - this.overlapAvoidanceFactor = 1 - Math.max(0, Math.min(1, this.options.avoidOverlap)); // if 1 then min distance = 0.5, if 0.5 then min distance = 0.5 + 0.5*node.shape.radius + _createClass(Node, [{ + key: 'attachEdge', + + /** + * Attach a edge to the node + * @param {Edge} edge + */ + value: function attachEdge(edge) { + if (this.edges.indexOf(edge) === -1) { + this.edges.push(edge); + } } }, { - key: "solve", + key: 'detachEdge', /** - * This function calculates the forces the nodes apply on eachother based on a gravitational model. - * The Barnes Hut method is used to speed up this N-body simulation. - * - * @private + * Detach a edge from the node + * @param {Edge} edge */ - value: function solve() { - if (this.options.gravitationalConstant !== 0 && this.physicsBody.physicsNodeIndices.length > 0) { - var node = undefined; - var nodes = this.body.nodes; - var nodeIndices = this.physicsBody.physicsNodeIndices; - var nodeCount = nodeIndices.length; - - // create the tree - var barnesHutTree = this._formBarnesHutTree(nodes, nodeIndices); - - // for debugging - this.barnesHutTree = barnesHutTree; - - // place the nodes one by one recursively - for (var i = 0; i < nodeCount; i++) { - node = nodes[nodeIndices[i]]; - if (node.options.mass > 0) { - // starting with root is irrelevant, it never passes the BarnesHutSolver condition - this._getForceContribution(barnesHutTree.root.children.NW, node); - this._getForceContribution(barnesHutTree.root.children.NE, node); - this._getForceContribution(barnesHutTree.root.children.SW, node); - this._getForceContribution(barnesHutTree.root.children.SE, node); - } - } + value: function detachEdge(edge) { + var index = this.edges.indexOf(edge); + if (index != -1) { + this.edges.splice(index, 1); } } }, { - key: "_getForceContribution", + key: 'togglePhysics', /** - * This function traverses the barnesHutTree. It checks when it can approximate distant nodes with their center of mass. - * If a region contains a single node, we check if it is not itself, then we apply the force. - * - * @param parentBranch - * @param node - * @private + * Enable or disable the physics. + * @param status */ - value: function _getForceContribution(parentBranch, node) { - // we get no force contribution from an empty region - if (parentBranch.childrenCount > 0) { - var dx = undefined, - dy = undefined, - distance = undefined; - - // get the distance from the center of mass to the node. - dx = parentBranch.centerOfMass.x - node.x; - dy = parentBranch.centerOfMass.y - node.y; - distance = Math.sqrt(dx * dx + dy * dy); - - // BarnesHutSolver condition - // original condition : s/d < theta = passed === d/s > 1/theta = passed - // calcSize = 1/s --> d * 1/s > 1/theta = passed - if (distance * parentBranch.calcSize > this.thetaInversed) { - this._calculateForces(distance, dx, dy, node, parentBranch); - } else { - // Did not pass the condition, go into children if available - if (parentBranch.childrenCount === 4) { - this._getForceContribution(parentBranch.children.NW, node); - this._getForceContribution(parentBranch.children.NE, node); - this._getForceContribution(parentBranch.children.SW, node); - this._getForceContribution(parentBranch.children.SE, node); - } else { - // parentBranch must have only one node, if it was empty we wouldnt be here - if (parentBranch.children.data.id != node.id) { - // if it is not self - this._calculateForces(distance, dx, dy, node, parentBranch); - } - } - } - } + value: function togglePhysics(status) { + this.options.physics = status; } }, { - key: "_calculateForces", + key: 'setOptions', /** - * Calculate the forces based on the distance. - * - * @param distance - * @param dx - * @param dy - * @param node - * @param parentBranch - * @private + * Set or overwrite options for the node + * @param {Object} options an object with options + * @param {Object} constants and object with default, global options */ - value: function _calculateForces(distance, dx, dy, node, parentBranch) { - if (distance === 0) { - distance = 0.1 * Math.random(); - dx = distance; + value: function setOptions(options) { + if (!options) { + return; } - - if (this.overlapAvoidanceFactor < 1) { - distance = Math.max(0.1 + this.overlapAvoidanceFactor * node.shape.radius, distance - node.shape.radius); + // basic options + if (options.id !== undefined) { + this.id = options.id; } - // the dividing by the distance cubed instead of squared allows us to get the fx and fy components without sines and cosines - // it is shorthand for gravityforce with distance squared and fx = dx/distance * gravityForce - var gravityForce = this.options.gravitationalConstant * parentBranch.mass * node.options.mass / Math.pow(distance, 3); - var fx = dx * gravityForce; - var fy = dy * gravityForce; + if (this.id === undefined) { + throw 'Node must have an id'; + } - this.physicsBody.forces[node.id].x += fx; - this.physicsBody.forces[node.id].y += fy; - } - }, { - key: "_formBarnesHutTree", + if (options.x !== undefined) { + this.x = parseInt(options.x);this.predefinedPosition = true; + } + if (options.y !== undefined) { + this.y = parseInt(options.y);this.predefinedPosition = true; + } + if (options.size !== undefined) { + this.baseSize = options.size; + } + if (options.value !== undefined) { + options.value = parseInt(options.value); + } - /** - * This function constructs the barnesHut tree recursively. It creates the root, splits it and starts placing the nodes. - * - * @param nodes - * @param nodeIndices - * @private - */ - value: function _formBarnesHutTree(nodes, nodeIndices) { - var node = undefined; - var nodeCount = nodeIndices.length; + // this transforms all shorthands into fully defined options + Node.parseOptions(this.options, options, true); - var minX = nodes[nodeIndices[0]].x; - var minY = nodes[nodeIndices[0]].y; - var maxX = nodes[nodeIndices[0]].x; - var maxY = nodes[nodeIndices[0]].y; + // copy group options + if (typeof options.group === 'number' || typeof options.group === 'string' && options.group != '') { + var groupObj = this.grouplist.get(options.group); + util.deepExtend(this.options, groupObj); + // the color object needs to be completely defined. Since groups can partially overwrite the colors, we parse it again, just in case. + this.options.color = util.parseColor(this.options.color); + } - // get the range of the nodes - for (var i = 1; i < nodeCount; i++) { - var x = nodes[nodeIndices[i]].x; - var y = nodes[nodeIndices[i]].y; - if (nodes[nodeIndices[i]].options.mass > 0) { - if (x < minX) { - minX = x; - } - if (x > maxX) { - maxX = x; - } - if (y < minY) { - minY = y; - } - if (y > maxY) { - maxY = y; - } + // load the images + if (this.options.image !== undefined && this.options.image != '') { + if (this.imagelist) { + this.imageObj = this.imagelist.load(this.options.image, this.options.brokenImage); + } else { + throw 'No imagelist provided'; } } - // make the range a square - var sizeDiff = Math.abs(maxX - minX) - Math.abs(maxY - minY); // difference between X and Y - if (sizeDiff > 0) { - minY -= 0.5 * sizeDiff; - maxY += 0.5 * sizeDiff; - } // xSize > ySize - else { - minX += 0.5 * sizeDiff; - maxX -= 0.5 * sizeDiff; - } // xSize < ySize - var minimumTreeSize = 0.00001; - var rootSize = Math.max(minimumTreeSize, Math.abs(maxX - minX)); - var halfRootSize = 0.5 * rootSize; - var centerX = 0.5 * (minX + maxX), - centerY = 0.5 * (minY + maxY); + this.updateShape(); + this.updateLabelModule(); - // construct the barnesHutTree - var barnesHutTree = { - root: { - centerOfMass: { x: 0, y: 0 }, - mass: 0, - range: { - minX: centerX - halfRootSize, maxX: centerX + halfRootSize, - minY: centerY - halfRootSize, maxY: centerY + halfRootSize - }, - size: rootSize, - calcSize: 1 / rootSize, - children: { data: null }, - maxWidth: 0, - level: 0, - childrenCount: 4 - } - }; - this._splitBranch(barnesHutTree.root); - - // place the nodes one by one recursively - for (var i = 0; i < nodeCount; i++) { - node = nodes[nodeIndices[i]]; - if (node.options.mass > 0) { - this._placeInTree(barnesHutTree.root, node); - } + // reset the size of the node, this can be changed + this._reset(); + } + }, { + key: 'updateLabelModule', + value: function updateLabelModule() { + if (this.options.label === undefined || this.options.label === null) { + this.options.label = ''; + } + this.labelModule.setOptions(this.options, true); + if (this.labelModule.baseSize !== undefined) { + this.baseFontSize = this.labelModule.baseSize; + } + } + }, { + key: 'updateShape', + value: function updateShape() { + // choose draw method depending on the shape + switch (this.options.shape) { + case 'box': + this.shape = new _nodesShapesBox2['default'](this.options, this.body, this.labelModule); + break; + case 'circle': + this.shape = new _nodesShapesCircle2['default'](this.options, this.body, this.labelModule); + break; + case 'circularImage': + this.shape = new _nodesShapesCircularImage2['default'](this.options, this.body, this.labelModule, this.imageObj); + break; + case 'database': + this.shape = new _nodesShapesDatabase2['default'](this.options, this.body, this.labelModule); + break; + case 'diamond': + this.shape = new _nodesShapesDiamond2['default'](this.options, this.body, this.labelModule); + break; + case 'dot': + this.shape = new _nodesShapesDot2['default'](this.options, this.body, this.labelModule); + break; + case 'ellipse': + this.shape = new _nodesShapesEllipse2['default'](this.options, this.body, this.labelModule); + break; + case 'icon': + this.shape = new _nodesShapesIcon2['default'](this.options, this.body, this.labelModule); + break; + case 'image': + this.shape = new _nodesShapesImage2['default'](this.options, this.body, this.labelModule, this.imageObj); + break; + case 'square': + this.shape = new _nodesShapesSquare2['default'](this.options, this.body, this.labelModule); + break; + case 'star': + this.shape = new _nodesShapesStar2['default'](this.options, this.body, this.labelModule); + break; + case 'text': + this.shape = new _nodesShapesText2['default'](this.options, this.body, this.labelModule); + break; + case 'triangle': + this.shape = new _nodesShapesTriangle2['default'](this.options, this.body, this.labelModule); + break; + case 'triangleDown': + this.shape = new _nodesShapesTriangleDown2['default'](this.options, this.body, this.labelModule); + break; + default: + this.shape = new _nodesShapesEllipse2['default'](this.options, this.body, this.labelModule); + break; } + this._reset(); + } + }, { + key: 'select', - // make global - return barnesHutTree; + /** + * select this node + */ + value: function select() { + this.selected = true; + this._reset(); } }, { - key: "_updateBranchMass", + key: 'unselect', /** - * this updates the mass of a branch. this is increased by adding a node. - * - * @param parentBranch - * @param node + * unselect this node + */ + value: function unselect() { + this.selected = false; + this._reset(); + } + }, { + key: '_reset', + + /** + * Reset the calculated size of the node, forces it to recalculate its size * @private */ - value: function _updateBranchMass(parentBranch, node) { - var totalMass = parentBranch.mass + node.options.mass; - var totalMassInv = 1 / totalMass; + value: function _reset() { + this.shape.width = undefined; + this.shape.height = undefined; + } + }, { + key: 'getTitle', - parentBranch.centerOfMass.x = parentBranch.centerOfMass.x * parentBranch.mass + node.x * node.options.mass; - parentBranch.centerOfMass.x *= totalMassInv; + /** + * get the title of this node. + * @return {string} title The title of the node, or undefined when no title + * has been set. + */ + value: function getTitle() { + return this.options.title; + } + }, { + key: 'distanceToBorder', - parentBranch.centerOfMass.y = parentBranch.centerOfMass.y * parentBranch.mass + node.y * node.options.mass; - parentBranch.centerOfMass.y *= totalMassInv; + /** + * Calculate the distance to the border of the Node + * @param {CanvasRenderingContext2D} ctx + * @param {Number} angle Angle in radians + * @returns {number} distance Distance to the border in pixels + */ + value: function distanceToBorder(ctx, angle) { + return this.shape.distanceToBorder(ctx, angle); + } + }, { + key: 'isFixed', - parentBranch.mass = totalMass; - var biggestSize = Math.max(Math.max(node.height, node.radius), node.width); - parentBranch.maxWidth = parentBranch.maxWidth < biggestSize ? biggestSize : parentBranch.maxWidth; + /** + * Check if this node has a fixed x and y position + * @return {boolean} true if fixed, false if not + */ + value: function isFixed() { + return this.options.fixed.x && this.options.fixed.y; } }, { - key: "_placeInTree", + key: 'isSelected', /** - * determine in which branch the node will be placed. - * - * @param parentBranch - * @param node - * @param skipMassUpdate - * @private + * check if this node is selecte + * @return {boolean} selected True if node is selected, else false */ - value: function _placeInTree(parentBranch, node, skipMassUpdate) { - if (skipMassUpdate != true || skipMassUpdate === undefined) { - // update the mass of the branch. - this._updateBranchMass(parentBranch, node); - } + value: function isSelected() { + return this.selected; + } + }, { + key: 'getValue', - if (parentBranch.children.NW.range.maxX > node.x) { - // in NW or SW - if (parentBranch.children.NW.range.maxY > node.y) { - // in NW - this._placeInRegion(parentBranch, node, "NW"); - } else { - // in SW - this._placeInRegion(parentBranch, node, "SW"); + /** + * Retrieve the value of the node. Can be undefined + * @return {Number} value + */ + value: function getValue() { + return this.options.value; + } + }, { + key: 'setValueRange', + + /** + * Adjust the value range of the node. The node will adjust it's size + * based on its value. + * @param {Number} min + * @param {Number} max + */ + value: function setValueRange(min, max, total) { + if (this.options.value !== undefined) { + var scale = this.options.scaling.customScalingFunction(min, max, total, this.options.value); + var sizeDiff = this.options.scaling.max - this.options.scaling.min; + if (this.options.scaling.label.enabled === true) { + var fontDiff = this.options.scaling.label.max - this.options.scaling.label.min; + this.options.font.size = this.options.scaling.label.min + scale * fontDiff; } + this.options.size = this.options.scaling.min + scale * sizeDiff; } else { - // in NE or SE - if (parentBranch.children.NW.range.maxY > node.y) { - // in NE - this._placeInRegion(parentBranch, node, "NE"); - } else { - // in SE - this._placeInRegion(parentBranch, node, "SE"); - } + this.options.size = this.baseSize; + this.options.font.size = this.baseFontSize; } } }, { - key: "_placeInRegion", + key: 'draw', /** - * actually place the node in a region (or branch) - * - * @param parentBranch - * @param node - * @param region - * @private + * Draw this node in the given canvas + * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d"); + * @param {CanvasRenderingContext2D} ctx */ - value: function _placeInRegion(parentBranch, node, region) { - switch (parentBranch.children[region].childrenCount) { - case 0: - // place node here - parentBranch.children[region].children.data = node; - parentBranch.children[region].childrenCount = 1; - this._updateBranchMass(parentBranch.children[region], node); - break; - case 1: - // convert into children - // if there are two nodes exactly overlapping (on init, on opening of cluster etc.) - // we move one node a pixel and we do not put it in the tree. - if (parentBranch.children[region].children.data.x === node.x && parentBranch.children[region].children.data.y === node.y) { - node.x += Math.random(); - node.y += Math.random(); - } else { - this._splitBranch(parentBranch.children[region]); - this._placeInTree(parentBranch.children[region], node); - } - break; - case 4: - // place in branch - this._placeInTree(parentBranch.children[region], node); - break; - } + value: function draw(ctx) { + this.shape.draw(ctx, this.x, this.y, this.selected, this.hover); } }, { - key: "_splitBranch", + key: 'updateBoundingBox', /** - * this function splits a branch into 4 sub branches. If the branch contained a node, we place it in the subbranch - * after the split is complete. - * - * @param parentBranch - * @private + * Update the bounding box of the shape */ - value: function _splitBranch(parentBranch) { - // if the branch is shaded with a node, replace the node in the new subset. - var containedNode = null; - if (parentBranch.childrenCount === 1) { - containedNode = parentBranch.children.data; - parentBranch.mass = 0; - parentBranch.centerOfMass.x = 0; - parentBranch.centerOfMass.y = 0; - } - parentBranch.childrenCount = 4; - parentBranch.children.data = null; - this._insertRegion(parentBranch, "NW"); - this._insertRegion(parentBranch, "NE"); - this._insertRegion(parentBranch, "SW"); - this._insertRegion(parentBranch, "SE"); - - if (containedNode != null) { - this._placeInTree(parentBranch, containedNode); - } + value: function updateBoundingBox() { + this.shape.updateBoundingBox(this.x, this.y); } }, { - key: "_insertRegion", + key: 'resize', /** - * This function subdivides the region into four new segments. - * Specifically, this inserts a single new segment. - * It fills the children section of the parentBranch - * - * @param parentBranch - * @param region - * @param parentRange - * @private + * Recalculate the size of this node in the given canvas + * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d"); + * @param {CanvasRenderingContext2D} ctx */ - value: function _insertRegion(parentBranch, region) { - var minX = undefined, - maxX = undefined, - minY = undefined, - maxY = undefined; - var childSize = 0.5 * parentBranch.size; - switch (region) { - case "NW": - minX = parentBranch.range.minX; - maxX = parentBranch.range.minX + childSize; - minY = parentBranch.range.minY; - maxY = parentBranch.range.minY + childSize; - break; - case "NE": - minX = parentBranch.range.minX + childSize; - maxX = parentBranch.range.maxX; - minY = parentBranch.range.minY; - maxY = parentBranch.range.minY + childSize; - break; - case "SW": - minX = parentBranch.range.minX; - maxX = parentBranch.range.minX + childSize; - minY = parentBranch.range.minY + childSize; - maxY = parentBranch.range.maxY; - break; - case "SE": - minX = parentBranch.range.minX + childSize; - maxX = parentBranch.range.maxX; - minY = parentBranch.range.minY + childSize; - maxY = parentBranch.range.maxY; - break; - } - - parentBranch.children[region] = { - centerOfMass: { x: 0, y: 0 }, - mass: 0, - range: { minX: minX, maxX: maxX, minY: minY, maxY: maxY }, - size: 0.5 * parentBranch.size, - calcSize: 2 * parentBranch.calcSize, - children: { data: null }, - maxWidth: 0, - level: parentBranch.level + 1, - childrenCount: 0 - }; + value: function resize(ctx) { + this.shape.resize(ctx); } }, { - key: "_debug", - - //--------------------------- DEBUGGING BELOW ---------------------------// + key: 'isOverlappingWith', /** - * This function is for debugging purposed, it draws the tree. - * - * @param ctx - * @param color - * @private + * Check if this object is overlapping with the provided object + * @param {Object} obj an object with parameters left, top, right, bottom + * @return {boolean} True if location is located on node */ - value: function _debug(ctx, color) { - if (this.barnesHutTree !== undefined) { - - ctx.lineWidth = 1; - - this._drawBranch(this.barnesHutTree.root, ctx, color); - } + value: function isOverlappingWith(obj) { + return this.shape.left < obj.right && this.shape.left + this.shape.width > obj.left && this.shape.top < obj.bottom && this.shape.top + this.shape.height > obj.top; } }, { - key: "_drawBranch", + key: 'isBoundingBoxOverlappingWith', /** - * This function is for debugging purposes. It draws the branches recursively. - * - * @param branch - * @param ctx - * @param color - * @private + * Check if this object is overlapping with the provided object + * @param {Object} obj an object with parameters left, top, right, bottom + * @return {boolean} True if location is located on node */ - value: function _drawBranch(branch, ctx, color) { - if (color === undefined) { - color = "#FF0000"; - } + value: function isBoundingBoxOverlappingWith(obj) { + return this.shape.boundingBox.left < obj.right && this.shape.boundingBox.right > obj.left && this.shape.boundingBox.top < obj.bottom && this.shape.boundingBox.bottom > obj.top; + } + }], [{ + key: 'parseOptions', - if (branch.childrenCount === 4) { - this._drawBranch(branch.children.NW, ctx); - this._drawBranch(branch.children.NE, ctx); - this._drawBranch(branch.children.SE, ctx); - this._drawBranch(branch.children.SW, ctx); - } - ctx.strokeStyle = color; - ctx.beginPath(); - ctx.moveTo(branch.range.minX, branch.range.minY); - ctx.lineTo(branch.range.maxX, branch.range.minY); - ctx.stroke(); + /** + * This process all possible shorthands in the new options and makes sure that the parentOptions are fully defined. + * Static so it can also be used by the handler. + * @param parentOptions + * @param newOptions + */ + value: function parseOptions(parentOptions, newOptions) { + var allowDeletion = arguments[2] === undefined ? false : arguments[2]; - ctx.beginPath(); - ctx.moveTo(branch.range.maxX, branch.range.minY); - ctx.lineTo(branch.range.maxX, branch.range.maxY); - ctx.stroke(); + var fields = ['color', 'font', 'fixed', 'shadow']; + util.selectiveNotDeepExtend(fields, parentOptions, newOptions, allowDeletion); - ctx.beginPath(); - ctx.moveTo(branch.range.maxX, branch.range.maxY); - ctx.lineTo(branch.range.minX, branch.range.maxY); - ctx.stroke(); + // merge the shadow options into the parent. + util.mergeOptions(parentOptions, newOptions, 'shadow'); - ctx.beginPath(); - ctx.moveTo(branch.range.minX, branch.range.maxY); - ctx.lineTo(branch.range.minX, branch.range.minY); - ctx.stroke(); + // individual shape newOptions + if (newOptions.color !== undefined && newOptions.color !== null) { + var parsedColor = util.parseColor(newOptions.color); + util.fillIfDefined(parentOptions.color, parsedColor); + } else if (allowDeletion === true && newOptions.color === null) { + parentOptions.color = undefined; + delete parentOptions.color; + } - /* - if (branch.mass > 0) { - ctx.circle(branch.centerOfMass.x, branch.centerOfMass.y, 3*branch.mass); - ctx.stroke(); - } - */ + // handle the fixed options + if (newOptions.fixed !== undefined && newOptions.fixed !== null) { + if (typeof newOptions.fixed === 'boolean') { + parentOptions.fixed.x = newOptions.fixed; + parentOptions.fixed.y = newOptions.fixed; + } else { + if (newOptions.fixed.x !== undefined && typeof newOptions.fixed.x === 'boolean') { + parentOptions.fixed.x = newOptions.fixed.x; + } + if (newOptions.fixed.y !== undefined && typeof newOptions.fixed.y === 'boolean') { + parentOptions.fixed.y = newOptions.fixed.y; + } + } + } + + // handle the font options + if (newOptions.font !== undefined) { + _sharedLabel2['default'].parseOptions(parentOptions.font, newOptions); + } + + // handle the scaling options, specifically the label part + if (newOptions.scaling !== undefined) { + util.mergeOptions(parentOptions.scaling, newOptions.scaling, 'label'); + } } }]); - return BarnesHutSolver; + return Node; })(); - exports["default"] = BarnesHutSolver; - module.exports = exports["default"]; + exports['default'] = Node; + module.exports = exports['default']; /***/ }, /* 75 */ /***/ function(module, exports, __webpack_require__) { - "use strict"; + 'use strict'; - Object.defineProperty(exports, "__esModule", { + Object.defineProperty(exports, '__esModule', { value: true }); - var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + function _slicedToArray(arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i['return']) _i['return'](); } finally { if (_d) throw _e; } } return _arr; } else { throw new TypeError('Invalid attempt to destructure non-iterable instance'); } } - var RepulsionSolver = (function () { - function RepulsionSolver(body, physicsBody, options) { - _classCallCheck(this, RepulsionSolver); + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + + var util = __webpack_require__(1); + + var Label = (function () { + function Label(body, options) { + _classCallCheck(this, Label); this.body = body; - this.physicsBody = physicsBody; + + this.baseSize = undefined; this.setOptions(options); + this.size = { top: 0, left: 0, width: 0, height: 0, yLine: 0 }; // could be cached } - _createClass(RepulsionSolver, [{ - key: "setOptions", + _createClass(Label, [{ + key: 'setOptions', value: function setOptions(options) { + var allowDeletion = arguments[1] === undefined ? false : arguments[1]; + this.options = options; + + if (options.label !== undefined) { + this.labelDirty = true; + } + + if (options.font !== undefined) { + Label.parseOptions(this.options.font, options, allowDeletion); + if (typeof options.font === 'string') { + this.baseSize = this.options.font.size; + } else if (typeof options.font === 'object') { + if (options.font.size !== undefined) { + this.baseSize = options.font.size; + } + } + } } }, { - key: "solve", + key: 'draw', /** - * Calculate the forces the nodes apply on each other based on a repulsion field. - * This field is linearly approximated. - * - * @private + * Main function. This is called from anything that wants to draw a label. + * @param ctx + * @param x + * @param y + * @param selected + * @param baseline */ - value: function solve() { - var dx, dy, distance, fx, fy, repulsingForce, node1, node2; - - var nodes = this.body.nodes; - var nodeIndices = this.physicsBody.physicsNodeIndices; - var forces = this.physicsBody.forces; - - // repulsing forces between nodes - var nodeDistance = this.options.nodeDistance; + value: function draw(ctx, x, y, selected) { + var baseline = arguments[4] === undefined ? 'middle' : arguments[4]; - // approximation constants - var a = -2 / 3 / nodeDistance; - var b = 4 / 3; + // if no label, return + if (this.options.label === undefined) return; - // we loop from i over all but the last entree in the array - // j loops from i+1 to the last. This way we do not double count any of the indices, nor i === j - for (var i = 0; i < nodeIndices.length - 1; i++) { - node1 = nodes[nodeIndices[i]]; - for (var j = i + 1; j < nodeIndices.length; j++) { - node2 = nodes[nodeIndices[j]]; + // check if we have to render the label + var viewFontSize = this.options.font.size * this.body.view.scale; + if (this.options.label && viewFontSize < this.options.scaling.label.drawThreshold - 1) return; - dx = node2.x - node1.x; - dy = node2.y - node1.y; - distance = Math.sqrt(dx * dx + dy * dy); + // update the size cache if required + this.calculateLabelSize(ctx, selected, x, y, baseline); - // same condition as BarnesHutSolver, making sure nodes are never 100% overlapping. - if (distance === 0) { - distance = 0.1 * Math.random(); - dx = distance; - } + // create the fontfill background + this._drawBackground(ctx); + // draw text + this._drawText(ctx, selected, x, y, baseline); + } + }, { + key: '_drawBackground', - if (distance < 2 * nodeDistance) { - if (distance < 0.5 * nodeDistance) { - repulsingForce = 1; - } else { - repulsingForce = a * distance + b; // linear approx of 1 / (1 + Math.exp((distance / nodeDistance - 1) * steepness)) - } - repulsingForce = repulsingForce / distance; + /** + * Draws the label background + * @param {CanvasRenderingContext2D} ctx + * @private + */ + value: function _drawBackground(ctx) { + if (this.options.font.background !== undefined && this.options.font.background !== 'none') { + ctx.fillStyle = this.options.font.background; - fx = dx * repulsingForce; - fy = dy * repulsingForce; + var lineMargin = 2; - forces[node1.id].x -= fx; - forces[node1.id].y -= fy; - forces[node2.id].x += fx; - forces[node2.id].y += fy; - } + switch (this.options.font.align) { + case 'middle': + ctx.fillRect(-this.size.width * 0.5, -this.size.height * 0.5, this.size.width, this.size.height); + break; + case 'top': + ctx.fillRect(-this.size.width * 0.5, -(this.size.height + lineMargin), this.size.width, this.size.height); + break; + case 'bottom': + ctx.fillRect(-this.size.width * 0.5, lineMargin, this.size.width, this.size.height); + break; + default: + ctx.fillRect(this.size.left, this.size.top - 0.5 * lineMargin, this.size.width, this.size.height); + break; } } } - }]); - - return RepulsionSolver; - })(); - - exports["default"] = RepulsionSolver; - module.exports = exports["default"]; - -/***/ }, -/* 76 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - - Object.defineProperty(exports, "__esModule", { - value: true - }); - - var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); - - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - - var HierarchicalRepulsionSolver = (function () { - function HierarchicalRepulsionSolver(body, physicsBody, options) { - _classCallCheck(this, HierarchicalRepulsionSolver); - - this.body = body; - this.physicsBody = physicsBody; - this.setOptions(options); - } - - _createClass(HierarchicalRepulsionSolver, [{ - key: "setOptions", - value: function setOptions(options) { - this.options = options; - } }, { - key: "solve", + key: '_drawText', /** - * Calculate the forces the nodes apply on each other based on a repulsion field. - * This field is linearly approximated. * + * @param ctx + * @param x + * @param baseline * @private */ - value: function solve() { - var dx, dy, distance, fx, fy, repulsingForce, node1, node2, i, j; - - var nodes = this.body.nodes; - var nodeIndices = this.physicsBody.physicsNodeIndices; - var forces = this.physicsBody.forces; - - // repulsing forces between nodes - var nodeDistance = this.options.nodeDistance; - - // we loop from i over all but the last entree in the array - // j loops from i+1 to the last. This way we do not double count any of the indices, nor i === j - for (i = 0; i < nodeIndices.length - 1; i++) { - node1 = nodes[nodeIndices[i]]; - for (j = i + 1; j < nodeIndices.length; j++) { - node2 = nodes[nodeIndices[j]]; + value: function _drawText(ctx, selected, x, y) { + var baseline = arguments[4] === undefined ? 'middle' : arguments[4]; - // nodes only affect nodes on their level - if (node1.level === node2.level) { - dx = node2.x - node1.x; - dy = node2.y - node1.y; - distance = Math.sqrt(dx * dx + dy * dy); + var fontSize = this.options.font.size; + var viewFontSize = fontSize * this.body.view.scale; + // this ensures that there will not be HUGE letters on screen by setting an upper limit on the visible text size (regardless of zoomLevel) + if (viewFontSize >= this.options.scaling.label.maxVisible) { + fontSize = Number(this.options.scaling.label.maxVisible) / this.body.view.scale; + } - var steepness = 0.05; - if (distance < nodeDistance) { - repulsingForce = -Math.pow(steepness * distance, 2) + Math.pow(steepness * nodeDistance, 2); - } else { - repulsingForce = 0; - } - // normalize force with - if (distance === 0) { - distance = 0.01; - } else { - repulsingForce = repulsingForce / distance; - } - fx = dx * repulsingForce; - fy = dy * repulsingForce; + var yLine = this.size.yLine; - forces[node1.id].x -= fx; - forces[node1.id].y -= fy; - forces[node2.id].x += fx; - forces[node2.id].y += fy; - } - } - } - } - }]); + var _getColor = this._getColor(viewFontSize); - return HierarchicalRepulsionSolver; - })(); + var _getColor2 = _slicedToArray(_getColor, 2); - exports["default"] = HierarchicalRepulsionSolver; - module.exports = exports["default"]; + var fontColor = _getColor2[0]; + var strokeColor = _getColor2[1]; -/***/ }, -/* 77 */ -/***/ function(module, exports, __webpack_require__) { + var _setAlignment = this._setAlignment(ctx, x, yLine, baseline); - "use strict"; + var _setAlignment2 = _slicedToArray(_setAlignment, 2); - Object.defineProperty(exports, "__esModule", { - value: true - }); + x = _setAlignment2[0]; + yLine = _setAlignment2[1]; - var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + // configure context for drawing the text + ctx.font = (selected ? 'bold ' : '') + fontSize + 'px ' + this.options.font.face; + ctx.fillStyle = fontColor; + ctx.textAlign = 'center'; - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + // set the strokeWidth + if (this.options.font.strokeWidth > 0) { + ctx.lineWidth = this.options.font.strokeWidth; + ctx.strokeStyle = strokeColor; + ctx.lineJoin = 'round'; + } - var SpringSolver = (function () { - function SpringSolver(body, physicsBody, options) { - _classCallCheck(this, SpringSolver); + // draw the text + for (var i = 0; i < this.lineCount; i++) { + if (this.options.font.strokeWidth > 0) { + ctx.strokeText(this.lines[i], x, yLine); + } + ctx.fillText(this.lines[i], x, yLine); + yLine += fontSize; + } + } + }, { + key: '_setAlignment', + value: function _setAlignment(ctx, x, yLine, baseline) { + // check for label alignment (for edges) + // TODO: make alignment for nodes + if (this.options.font.align !== 'horizontal') { + x = 0; + yLine = 0; - this.body = body; - this.physicsBody = physicsBody; - this.setOptions(options); - } + var lineMargin = 2; + if (this.options.font.align === 'top') { + ctx.textBaseline = 'alphabetic'; + yLine -= 2 * lineMargin; // distance from edge, required because we use alphabetic. Alphabetic has less difference between browsers + } else if (this.options.font.align === 'bottom') { + ctx.textBaseline = 'hanging'; + yLine += 2 * lineMargin; // distance from edge, required because we use hanging. Hanging has less difference between browsers + } else { + ctx.textBaseline = 'middle'; + } + } else { + ctx.textBaseline = baseline; + } - _createClass(SpringSolver, [{ - key: "setOptions", - value: function setOptions(options) { - this.options = options; + return [x, yLine]; } }, { - key: "solve", + key: '_getColor', /** - * This function calculates the springforces on the nodes, accounting for the support nodes. + * fade in when relative scale is between threshold and threshold - 1. + * If the relative scale would be smaller than threshold -1 the draw function would have returned before coming here. * + * @param viewFontSize + * @returns {*[]} * @private */ - value: function solve() { - var edgeLength = undefined, - edge = undefined; - var edgeIndices = this.physicsBody.physicsEdgeIndices; - var edges = this.body.edges; - var node1 = undefined, - node2 = undefined, - node3 = undefined; + value: function _getColor(viewFontSize) { + var fontColor = this.options.font.color || '#000000'; + var strokeColor = this.options.font.strokeColor || '#ffffff'; + if (viewFontSize <= this.options.scaling.label.drawThreshold) { + var opacity = Math.max(0, Math.min(1, 1 - (this.options.scaling.label.drawThreshold - viewFontSize))); + fontColor = util.overrideOpacity(fontColor, opacity); + strokeColor = util.overrideOpacity(strokeColor, opacity); + } + return [fontColor, strokeColor]; + } + }, { + key: 'getTextSize', - // forces caused by the edges, modelled as springs - for (var i = 0; i < edgeIndices.length; i++) { - edge = edges[edgeIndices[i]]; - if (edge.connected === true && edge.toId !== edge.fromId) { - // only calculate forces if nodes are in the same sector - if (this.body.nodes[edge.toId] !== undefined && this.body.nodes[edge.fromId] !== undefined) { - if (edge.edgeType.via !== undefined) { - edgeLength = edge.options.length === undefined ? this.options.springLength : edge.options.length; - node1 = edge.to; - node2 = edge.edgeType.via; - node3 = edge.from; + /** + * + * @param ctx + * @param selected + * @returns {{width: number, height: number}} + */ + value: function getTextSize(ctx) { + var selected = arguments[1] === undefined ? false : arguments[1]; - this._calculateSpringForce(node1, node2, 0.5 * edgeLength); - this._calculateSpringForce(node2, node3, 0.5 * edgeLength); - } else { - // the * 1.5 is here so the edge looks as large as a smooth edge. It does not initially because the smooth edges use - // the support nodes which exert a repulsive force on the to and from nodes, making the edge appear larger. - edgeLength = edge.options.length === undefined ? this.options.springLength * 1.5 : edge.options.length; - this._calculateSpringForce(edge.from, edge.to, edgeLength); - } - } - } - } + var size = { + width: this._processLabel(ctx, selected), + height: this.options.font.size * this.lineCount, + lineCount: this.lineCount + }; + return size; } }, { - key: "_calculateSpringForce", + key: 'calculateLabelSize', /** - * This is the code actually performing the calculation for the function above. * - * @param node1 - * @param node2 - * @param edgeLength - * @private + * @param ctx + * @param selected + * @param x + * @param y + * @param baseline */ - value: function _calculateSpringForce(node1, node2, edgeLength) { - var dx = node1.x - node2.x; - var dy = node1.y - node2.y; - var distance = Math.max(Math.sqrt(dx * dx + dy * dy), 0.01); + value: function calculateLabelSize(ctx, selected) { + var x = arguments[2] === undefined ? 0 : arguments[2]; + var y = arguments[3] === undefined ? 0 : arguments[3]; + var baseline = arguments[4] === undefined ? 'middle' : arguments[4]; - // the 1/distance is so the fx and fy can be calculated without sine or cosine. - var springForce = this.options.springConstant * (edgeLength - distance) / distance; + if (this.labelDirty === true) { + this.size.width = this._processLabel(ctx, selected); + } + this.size.height = this.options.font.size * this.lineCount; + this.size.left = x - this.size.width * 0.5; + this.size.top = y - this.size.height * 0.5; + this.size.yLine = y + (1 - this.lineCount) * 0.5 * this.options.font.size; + if (baseline === 'hanging') { + this.size.top += 0.5 * this.options.font.size; + this.size.top += 4; // distance from node, required because we use hanging. Hanging has less difference between browsers + this.size.yLine += 4; // distance from node + } - var fx = dx * springForce; - var fy = dy * springForce; + this.labelDirty = false; + } + }, { + key: '_processLabel', - // handle the case where one node is not part of the physcis - if (this.physicsBody.forces[node1.id] !== undefined) { - this.physicsBody.forces[node1.id].x += fx; - this.physicsBody.forces[node1.id].y += fy; + /** + * This calculates the width as well as explodes the label string and calculates the amount of lines. + * @param ctx + * @param selected + * @returns {number} + * @private + */ + value: function _processLabel(ctx, selected) { + var width = 0; + var lines = ['']; + var lineCount = 0; + if (this.options.label !== undefined) { + lines = String(this.options.label).split('\n'); + lineCount = lines.length; + ctx.font = (selected ? 'bold ' : '') + this.options.font.size + 'px ' + this.options.font.face; + width = ctx.measureText(lines[0]).width; + for (var i = 1; i < lineCount; i++) { + var lineWidth = ctx.measureText(lines[i]).width; + width = lineWidth > width ? lineWidth : width; + } } + this.lines = lines; + this.lineCount = lineCount; - if (this.physicsBody.forces[node2.id] !== undefined) { - this.physicsBody.forces[node2.id].x -= fx; - this.physicsBody.forces[node2.id].y -= fy; + return width; + } + }], [{ + key: 'parseOptions', + value: function parseOptions(parentOptions, newOptions) { + var allowDeletion = arguments[2] === undefined ? false : arguments[2]; + + if (typeof newOptions.font === 'string') { + var newOptionsArray = newOptions.font.split(' '); + parentOptions.size = newOptionsArray[0].replace('px', ''); + parentOptions.face = newOptionsArray[1]; + parentOptions.color = newOptionsArray[2]; + } else if (typeof newOptions.font === 'object') { + util.fillIfDefined(parentOptions, newOptions.font, allowDeletion); } + parentOptions.size = Number(parentOptions.size); } }]); - return SpringSolver; + return Label; })(); - exports["default"] = SpringSolver; - module.exports = exports["default"]; + exports['default'] = Label; + module.exports = exports['default']; /***/ }, -/* 78 */ +/* 76 */ /***/ function(module, exports, __webpack_require__) { - "use strict"; + 'use strict'; - Object.defineProperty(exports, "__esModule", { + Object.defineProperty(exports, '__esModule', { value: true }); - var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); - - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); - var HierarchicalSpringSolver = (function () { - function HierarchicalSpringSolver(body, physicsBody, options) { - _classCallCheck(this, HierarchicalSpringSolver); + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - this.body = body; - this.physicsBody = physicsBody; - this.setOptions(options); - } + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - _createClass(HierarchicalSpringSolver, [{ - key: "setOptions", - value: function setOptions(options) { - this.options = options; - } - }, { - key: "solve", + var _sharedLabel = __webpack_require__(75); - /** - * This function calculates the springforces on the nodes, accounting for the support nodes. - * - * @private - */ - value: function solve() { - var edgeLength, edge; - var dx, dy, fx, fy, springForce, distance; - var edges = this.body.edges; - var factor = 0.5; + var _sharedLabel2 = _interopRequireDefault(_sharedLabel); - var edgeIndices = this.physicsBody.physicsEdgeIndices; - var nodeIndices = this.physicsBody.physicsNodeIndices; - var forces = this.physicsBody.forces; + var _edgesBezierEdgeDynamic = __webpack_require__(104); - // initialize the spring force counters - for (var i = 0; i < nodeIndices.length; i++) { - var nodeId = nodeIndices[i]; - forces[nodeId].springFx = 0; - forces[nodeId].springFy = 0; - } + var _edgesBezierEdgeDynamic2 = _interopRequireDefault(_edgesBezierEdgeDynamic); - // forces caused by the edges, modelled as springs - for (var i = 0; i < edgeIndices.length; i++) { - edge = edges[edgeIndices[i]]; - if (edge.connected === true) { - edgeLength = edge.options.length === undefined ? this.options.springLength : edge.options.length; + var _edgesBezierEdgeStatic = __webpack_require__(105); - dx = edge.from.x - edge.to.x; - dy = edge.from.y - edge.to.y; - distance = Math.sqrt(dx * dx + dy * dy); - distance = distance === 0 ? 0.01 : distance; + var _edgesBezierEdgeStatic2 = _interopRequireDefault(_edgesBezierEdgeStatic); - // the 1/distance is so the fx and fy can be calculated without sine or cosine. - springForce = this.options.springConstant * (edgeLength - distance) / distance; + var _edgesStraightEdge = __webpack_require__(106); - fx = dx * springForce; - fy = dy * springForce; + var _edgesStraightEdge2 = _interopRequireDefault(_edgesStraightEdge); - if (edge.to.level != edge.from.level) { - forces[edge.toId].springFx -= fx; - forces[edge.toId].springFy -= fy; - forces[edge.fromId].springFx += fx; - forces[edge.fromId].springFy += fy; - } else { - forces[edge.toId].x -= factor * fx; - forces[edge.toId].y -= factor * fy; - forces[edge.fromId].x += factor * fx; - forces[edge.fromId].y += factor * fy; - } - } - } + var util = __webpack_require__(1); - // normalize spring forces - var springForce = 1; - var springFx, springFy; - for (var i = 0; i < nodeIndices.length; i++) { - var nodeId = nodeIndices[i]; - springFx = Math.min(springForce, Math.max(-springForce, forces[nodeId].springFx)); - springFy = Math.min(springForce, Math.max(-springForce, forces[nodeId].springFy)); + /** + * @class Edge + * + * A edge connects two nodes + * @param {Object} properties Object with options. Must contain + * At least options from and to. + * Available options: from (number), + * to (number), label (string, color (string), + * width (number), style (string), + * length (number), title (string) + * @param {Network} network A Network object, used to find and edge to + * nodes. + * @param {Object} constants An object with default values for + * example for the color + */ - forces[nodeId].x += springFx; - forces[nodeId].y += springFy; - } + var Edge = (function () { + function Edge(options, body, globalOptions) { + _classCallCheck(this, Edge); - // retain energy balance - var totalFx = 0; - var totalFy = 0; - for (var i = 0; i < nodeIndices.length; i++) { - var nodeId = nodeIndices[i]; - totalFx += forces[nodeId].x; - totalFy += forces[nodeId].y; + if (body === undefined) { + throw 'No body provided'; + } + this.options = util.bridgeObject(globalOptions); + this.body = body; + + // initialize variables + this.id = undefined; + this.fromId = undefined; + this.toId = undefined; + this.selected = false; + this.hover = false; + this.labelDirty = true; + this.colorDirty = true; + + this.baseWidth = this.options.width; + this.baseFontSize = this.options.font.size; + + this.from = undefined; // a node + this.to = undefined; // a node + + this.edgeType = undefined; + + this.connected = false; + + this.labelModule = new _sharedLabel2['default'](this.body, this.options); + + this.setOptions(options); + } + + _createClass(Edge, [{ + key: 'setOptions', + + /** + * Set or overwrite options for the edge + * @param {Object} options an object with options + * @param doNotEmit + */ + value: function setOptions(options) { + if (!options) { + return; } - var correctionFx = totalFx / nodeIndices.length; - var correctionFy = totalFy / nodeIndices.length; + this.colorDirty = true; - for (var i = 0; i < nodeIndices.length; i++) { - var nodeId = nodeIndices[i]; - forces[nodeId].x -= correctionFx; - forces[nodeId].y -= correctionFy; + Edge.parseOptions(this.options, options, true); + + if (options.id !== undefined) { + this.id = options.id; } + if (options.from !== undefined) { + this.fromId = options.from; + } + if (options.to !== undefined) { + this.toId = options.to; + } + if (options.title !== undefined) { + this.title = options.title; + } + if (options.value !== undefined) { + options.value = parseInt(options.value); + } + + // A node is connected when it has a from and to node that both exist in the network.body.nodes. + this.connect(); + + // update label Module + this.updateLabelModule(); + + var dataChanged = this.updateEdgeType(); + + // if anything has been updates, reset the selection width and the hover width + this._setInteractionWidths(); + + return dataChanged; } - }]); + }, { + key: 'updateLabelModule', - return HierarchicalSpringSolver; - })(); + /** + * update the options in the label module + */ + value: function updateLabelModule() { + this.labelModule.setOptions(this.options, true); + if (this.labelModule.baseSize !== undefined) { + this.baseFontSize = this.labelModule.baseSize; + } + } + }, { + key: 'updateEdgeType', - exports["default"] = HierarchicalSpringSolver; - module.exports = exports["default"]; + /** + * update the edge type, set the options + * @returns {boolean} + */ + value: function updateEdgeType() { + var dataChanged = false; + var changeInType = true; + if (this.edgeType !== undefined) { + if (this.edgeType instanceof _edgesBezierEdgeDynamic2['default'] && this.options.smooth.enabled === true && this.options.smooth.type === 'dynamic') { + changeInType = false; + } + if (this.edgeType instanceof _edgesBezierEdgeStatic2['default'] && this.options.smooth.enabled === true && this.options.smooth.type !== 'dynamic') { + changeInType = false; + } + if (this.edgeType instanceof _edgesStraightEdge2['default'] && this.options.smooth.enabled === false) { + changeInType = false; + } -/***/ }, -/* 79 */ -/***/ function(module, exports, __webpack_require__) { + if (changeInType === true) { + dataChanged = this.edgeType.cleanup(); + } + } - "use strict"; + if (changeInType === true) { + if (this.options.smooth.enabled === true) { + if (this.options.smooth.type === 'dynamic') { + dataChanged = true; + this.edgeType = new _edgesBezierEdgeDynamic2['default'](this.options, this.body, this.labelModule); + } else { + this.edgeType = new _edgesBezierEdgeStatic2['default'](this.options, this.body, this.labelModule); + } + } else { + this.edgeType = new _edgesStraightEdge2['default'](this.options, this.body, this.labelModule); + } + } else { + // if nothing changes, we just set the options. + this.edgeType.setOptions(this.options); + } - Object.defineProperty(exports, "__esModule", { - value: true - }); + return dataChanged; + } + }, { + key: 'togglePhysics', - var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + /** + * Enable or disable the physics. + * @param status + */ + value: function togglePhysics(status) { + this.options.physics = status; + this.edgeType.togglePhysics(status); + } + }, { + key: 'connect', - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + /** + * Connect an edge to its nodes + */ + value: function connect() { + this.disconnect(); - var CentralGravitySolver = (function () { - function CentralGravitySolver(body, physicsBody, options) { - _classCallCheck(this, CentralGravitySolver); + this.from = this.body.nodes[this.fromId] || undefined; + this.to = this.body.nodes[this.toId] || undefined; + this.connected = this.from !== undefined && this.to !== undefined; - this.body = body; - this.physicsBody = physicsBody; - this.setOptions(options); - } + if (this.connected === true) { + this.from.attachEdge(this); + this.to.attachEdge(this); + } else { + if (this.from) { + this.from.detachEdge(this); + } + if (this.to) { + this.to.detachEdge(this); + } + } + } + }, { + key: 'disconnect', - _createClass(CentralGravitySolver, [{ - key: "setOptions", - value: function setOptions(options) { - this.options = options; + /** + * Disconnect an edge from its nodes + */ + value: function disconnect() { + if (this.from) { + this.from.detachEdge(this); + this.from = undefined; + } + if (this.to) { + this.to.detachEdge(this); + this.to = undefined; + } + + this.connected = false; } }, { - key: "solve", - value: function solve() { - var dx = undefined, - dy = undefined, - distance = undefined, - node = undefined; - var nodes = this.body.nodes; - var nodeIndices = this.physicsBody.physicsNodeIndices; - var forces = this.physicsBody.forces; + key: 'getTitle', - for (var i = 0; i < nodeIndices.length; i++) { - var nodeId = nodeIndices[i]; - node = nodes[nodeId]; - dx = -node.x; - dy = -node.y; - distance = Math.sqrt(dx * dx + dy * dy); + /** + * get the title of this edge. + * @return {string} title The title of the edge, or undefined when no title + * has been set. + */ + value: function getTitle() { + return this.title; + } + }, { + key: 'isSelected', - this._calculateForces(distance, dx, dy, forces, node); + /** + * check if this node is selecte + * @return {boolean} selected True if node is selected, else false + */ + value: function isSelected() { + return this.selected; + } + }, { + key: 'getValue', + + /** + * Retrieve the value of the edge. Can be undefined + * @return {Number} value + */ + value: function getValue() { + return this.options.value; + } + }, { + key: 'setValueRange', + + /** + * Adjust the value range of the edge. The edge will adjust it's width + * based on its value. + * @param {Number} min + * @param {Number} max + * @param total + */ + value: function setValueRange(min, max, total) { + if (this.options.value !== undefined) { + var scale = this.options.scaling.customScalingFunction(min, max, total, this.options.value); + var widthDiff = this.options.scaling.max - this.options.scaling.min; + if (this.options.scaling.label.enabled === true) { + var fontDiff = this.options.scaling.label.max - this.options.scaling.label.min; + this.options.font.size = this.options.scaling.label.min + scale * fontDiff; + } + this.options.width = this.options.scaling.min + scale * widthDiff; + } else { + this.options.width = this.baseWidth; + this.options.font.size = this.baseFontSize; } + + this._setInteractionWidths(); } }, { - key: "_calculateForces", + key: '_setInteractionWidths', + value: function _setInteractionWidths() { + if (typeof this.options.hoverWidth === 'function') { + this.edgeType.hoverWidth = this.options.hoverWidth(this.options.width); + } else { + this.edgeType.hoverWidth = this.options.hoverWidth + this.options.width; + } + + if (typeof this.options.selectionWidth === 'function') { + this.edgeType.selectionWidth = this.options.selectionWidth(this.options.width); + } else { + this.edgeType.selectionWidth = this.options.selectionWidth + this.options.width; + } + } + }, { + key: 'draw', /** - * Calculate the forces based on the distance. + * Redraw a edge + * Draw this edge in the given canvas + * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d"); + * @param {CanvasRenderingContext2D} ctx + */ + value: function draw(ctx) { + var via = this.edgeType.drawLine(ctx, this.selected, this.hover); + this.drawArrows(ctx, via); + this.drawLabel(ctx, via); + } + }, { + key: 'drawArrows', + value: function drawArrows(ctx, viaNode) { + if (this.options.arrows.from.enabled === true) { + this.edgeType.drawArrowHead(ctx, 'from', viaNode, this.selected, this.hover); + } + if (this.options.arrows.middle.enabled === true) { + this.edgeType.drawArrowHead(ctx, 'middle', viaNode, this.selected, this.hover); + } + if (this.options.arrows.to.enabled === true) { + this.edgeType.drawArrowHead(ctx, 'to', viaNode, this.selected, this.hover); + } + } + }, { + key: 'drawLabel', + value: function drawLabel(ctx, viaNode) { + if (this.options.label !== undefined) { + // set style + var node1 = this.from; + var node2 = this.to; + var selected = this.from.selected || this.to.selected || this.selected; + if (node1.id != node2.id) { + var point = this.edgeType.getPoint(0.5, viaNode); + ctx.save(); + + // if the label has to be rotated: + if (this.options.font.align !== 'horizontal') { + this.labelModule.calculateLabelSize(ctx, selected, point.x, point.y); + ctx.translate(point.x, this.labelModule.size.yLine); + this._rotateForLabelAlignment(ctx); + } + + // draw the label + this.labelModule.draw(ctx, point.x, point.y, selected); + ctx.restore(); + } else { + var x, y; + var radius = this.options.selfReferenceSize; + if (node1.shape.width > node1.shape.height) { + x = node1.x + node1.shape.width * 0.5; + y = node1.y - radius; + } else { + x = node1.x + radius; + y = node1.y - node1.shape.height * 0.5; + } + point = this._pointOnCircle(x, y, radius, 0.125); + this.labelModule.draw(ctx, point.x, point.y, selected); + } + } + } + }, { + key: 'isOverlappingWith', + + /** + * Check if this object is overlapping with the provided object + * @param {Object} obj an object with parameters left, top + * @return {boolean} True if location is located on the edge + */ + value: function isOverlappingWith(obj) { + if (this.connected) { + var distMax = 10; + var xFrom = this.from.x; + var yFrom = this.from.y; + var xTo = this.to.x; + var yTo = this.to.y; + var xObj = obj.left; + var yObj = obj.top; + + var dist = this.edgeType.getDistanceToEdge(xFrom, yFrom, xTo, yTo, xObj, yObj); + + return dist < distMax; + } else { + return false; + } + } + }, { + key: '_rotateForLabelAlignment', + + /** + * Rotates the canvas so the text is most readable + * @param {CanvasRenderingContext2D} ctx * @private */ - value: function _calculateForces(distance, dx, dy, forces, node) { - var gravityForce = distance === 0 ? 0 : this.options.centralGravity / distance; - forces[node.id].x = dx * gravityForce; - forces[node.id].y = dy * gravityForce; + value: function _rotateForLabelAlignment(ctx) { + var dy = this.from.y - this.to.y; + var dx = this.from.x - this.to.x; + var angleInDegrees = Math.atan2(dy, dx); + + // rotate so label it is readable + if (angleInDegrees < -1 && dx < 0 || angleInDegrees > 0 && dx < 0) { + angleInDegrees = angleInDegrees + Math.PI; + } + + ctx.rotate(angleInDegrees); + } + }, { + key: '_pointOnCircle', + + /** + * Get a point on a circle + * @param {Number} x + * @param {Number} y + * @param {Number} radius + * @param {Number} percentage. Value between 0 (line start) and 1 (line end) + * @return {Object} point + * @private + */ + value: function _pointOnCircle(x, y, radius, percentage) { + var angle = percentage * 2 * Math.PI; + return { + x: x + radius * Math.cos(angle), + y: y - radius * Math.sin(angle) + }; + } + }, { + key: 'select', + value: function select() { + this.selected = true; + } + }, { + key: 'unselect', + value: function unselect() { + this.selected = false; + } + }], [{ + key: 'parseOptions', + value: function parseOptions(parentOptions, newOptions) { + var allowDeletion = arguments[2] === undefined ? false : arguments[2]; + + var fields = ['id', 'from', 'hidden', 'hoverWidth', 'label', 'length', 'line', 'opacity', 'physics', 'selectionWidth', 'selfReferenceSize', 'to', 'title', 'value', 'width']; + + // only deep extend the items in the field array. These do not have shorthand. + util.selectiveDeepExtend(fields, parentOptions, newOptions, allowDeletion); + + util.mergeOptions(parentOptions, newOptions, 'smooth'); + util.mergeOptions(parentOptions, newOptions, 'shadow'); + + if (newOptions.dashes !== undefined && newOptions.dashes !== null) { + parentOptions.dashes = newOptions.dashes; + } else if (allowDeletion === true && newOptions.dashes === null) { + parentOptions.dashes = undefined; + delete parentOptions.dashes; + } + + // set the scaling newOptions + if (newOptions.scaling !== undefined && newOptions.scaling !== null) { + if (newOptions.scaling.min !== undefined) { + parentOptions.scaling.min = newOptions.scaling.min; + } + if (newOptions.scaling.max !== undefined) { + parentOptions.scaling.max = newOptions.scaling.max; + } + util.mergeOptions(parentOptions.scaling, newOptions.scaling, 'label'); + } else if (allowDeletion === true && newOptions.scaling === null) { + parentOptions.scaling = undefined; + delete parentOptions.scaling; + } + + // hanlde multiple input cases for arrows + if (newOptions.arrows !== undefined && newOptions.arrows !== null) { + if (typeof newOptions.arrows === 'string') { + var arrows = newOptions.arrows.toLowerCase(); + if (arrows.indexOf('to') != -1) { + parentOptions.arrows.to.enabled = true; + } + if (arrows.indexOf('middle') != -1) { + parentOptions.arrows.middle.enabled = true; + } + if (arrows.indexOf('from') != -1) { + parentOptions.arrows.from.enabled = true; + } + } else if (typeof newOptions.arrows === 'object') { + util.mergeOptions(parentOptions.arrows, newOptions.arrows, 'to'); + util.mergeOptions(parentOptions.arrows, newOptions.arrows, 'middle'); + util.mergeOptions(parentOptions.arrows, newOptions.arrows, 'from'); + } else { + throw new Error('The arrow newOptions can only be an object or a string. Refer to the documentation. You used:' + JSON.stringify(newOptions.arrows)); + } + } else if (allowDeletion === true && newOptions.arrows === null) { + parentOptions.arrows = undefined; + delete parentOptions.arrows; + } + + // hanlde multiple input cases for color + if (newOptions.color !== undefined && newOptions.color !== null) { + if (util.isString(newOptions.color)) { + parentOptions.color.color = newOptions.color; + parentOptions.color.highlight = newOptions.color; + parentOptions.color.hover = newOptions.color; + parentOptions.color.inherit = false; + } else { + var colorsDefined = false; + if (newOptions.color.color !== undefined) { + parentOptions.color.color = newOptions.color.color;colorsDefined = true; + } + if (newOptions.color.highlight !== undefined) { + parentOptions.color.highlight = newOptions.color.highlight;colorsDefined = true; + } + if (newOptions.color.hover !== undefined) { + parentOptions.color.hover = newOptions.color.hover;colorsDefined = true; + } + if (newOptions.color.inherit !== undefined) { + parentOptions.color.inherit = newOptions.color.inherit; + } + if (newOptions.color.opacity !== undefined) { + parentOptions.color.opacity = Math.min(1, Math.max(0, newOptions.color.opacity)); + } + + if (newOptions.color.inherit === undefined && colorsDefined === true) { + parentOptions.color.inherit = false; + } + } + } else if (allowDeletion === true && newOptions.color === null) { + parentOptions.color = undefined; + delete parentOptions.color; + } + + // handle the font settings + if (newOptions.font !== undefined) { + _sharedLabel2['default'].parseOptions(parentOptions.font, newOptions); + } } }]); - return CentralGravitySolver; + return Edge; })(); - exports["default"] = CentralGravitySolver; - module.exports = exports["default"]; + exports['default'] = Edge; + module.exports = exports['default']; /***/ }, -/* 80 */ +/* 77 */ /***/ function(module, exports, __webpack_require__) { "use strict"; @@ -35584,30 +36060,106 @@ return /******/ (function(modules) { // webpackBootstrap var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); - var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { desc = parent = getter = undefined; _again = false; var object = _x, - property = _x2, - receiver = _x3; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + var BarnesHutSolver = (function () { + function BarnesHutSolver(body, physicsBody, options) { + _classCallCheck(this, BarnesHutSolver); - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + this.body = body; + this.physicsBody = physicsBody; + this.barnesHutTree; + this.setOptions(options); + } - function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } + _createClass(BarnesHutSolver, [{ + key: "setOptions", + value: function setOptions(options) { + this.options = options; + this.thetaInversed = 1 / this.options.theta; + this.overlapAvoidanceFactor = 1 - Math.max(0, Math.min(1, this.options.avoidOverlap)); // if 1 then min distance = 0.5, if 0.5 then min distance = 0.5 + 0.5*node.shape.radius + } + }, { + key: "solve", - var _BarnesHutSolver2 = __webpack_require__(74); + /** + * This function calculates the forces the nodes apply on eachother based on a gravitational model. + * The Barnes Hut method is used to speed up this N-body simulation. + * + * @private + */ + value: function solve() { + if (this.options.gravitationalConstant !== 0 && this.physicsBody.physicsNodeIndices.length > 0) { + var node = undefined; + var nodes = this.body.nodes; + var nodeIndices = this.physicsBody.physicsNodeIndices; + var nodeCount = nodeIndices.length; - var _BarnesHutSolver3 = _interopRequireDefault(_BarnesHutSolver2); + // create the tree + var barnesHutTree = this._formBarnesHutTree(nodes, nodeIndices); - var ForceAtlas2BasedRepulsionSolver = (function (_BarnesHutSolver) { - function ForceAtlas2BasedRepulsionSolver(body, physicsBody, options) { - _classCallCheck(this, ForceAtlas2BasedRepulsionSolver); + // for debugging + this.barnesHutTree = barnesHutTree; - _get(Object.getPrototypeOf(ForceAtlas2BasedRepulsionSolver.prototype), "constructor", this).call(this, body, physicsBody, options); - } + // place the nodes one by one recursively + for (var i = 0; i < nodeCount; i++) { + node = nodes[nodeIndices[i]]; + if (node.options.mass > 0) { + // starting with root is irrelevant, it never passes the BarnesHutSolver condition + this._getForceContribution(barnesHutTree.root.children.NW, node); + this._getForceContribution(barnesHutTree.root.children.NE, node); + this._getForceContribution(barnesHutTree.root.children.SW, node); + this._getForceContribution(barnesHutTree.root.children.SE, node); + } + } + } + } + }, { + key: "_getForceContribution", - _inherits(ForceAtlas2BasedRepulsionSolver, _BarnesHutSolver); + /** + * This function traverses the barnesHutTree. It checks when it can approximate distant nodes with their center of mass. + * If a region contains a single node, we check if it is not itself, then we apply the force. + * + * @param parentBranch + * @param node + * @private + */ + value: function _getForceContribution(parentBranch, node) { + // we get no force contribution from an empty region + if (parentBranch.childrenCount > 0) { + var dx = undefined, + dy = undefined, + distance = undefined; - _createClass(ForceAtlas2BasedRepulsionSolver, [{ + // get the distance from the center of mass to the node. + dx = parentBranch.centerOfMass.x - node.x; + dy = parentBranch.centerOfMass.y - node.y; + distance = Math.sqrt(dx * dx + dy * dy); + + // BarnesHutSolver condition + // original condition : s/d < theta = passed === d/s > 1/theta = passed + // calcSize = 1/s --> d * 1/s > 1/theta = passed + if (distance * parentBranch.calcSize > this.thetaInversed) { + this._calculateForces(distance, dx, dy, node, parentBranch); + } else { + // Did not pass the condition, go into children if available + if (parentBranch.childrenCount === 4) { + this._getForceContribution(parentBranch.children.NW, node); + this._getForceContribution(parentBranch.children.NE, node); + this._getForceContribution(parentBranch.children.SW, node); + this._getForceContribution(parentBranch.children.SE, node); + } else { + // parentBranch must have only one node, if it was empty we wouldnt be here + if (parentBranch.children.data.id != node.id) { + // if it is not self + this._calculateForces(distance, dx, dy, node, parentBranch); + } + } + } + } + } + }, { key: "_calculateForces", /** @@ -35630,1039 +36182,986 @@ return /******/ (function(modules) { // webpackBootstrap distance = Math.max(0.1 + this.overlapAvoidanceFactor * node.shape.radius, distance - node.shape.radius); } - var degree = node.edges.length + 1; // the dividing by the distance cubed instead of squared allows us to get the fx and fy components without sines and cosines // it is shorthand for gravityforce with distance squared and fx = dx/distance * gravityForce - var gravityForce = this.options.gravitationalConstant * parentBranch.mass * node.options.mass * degree / Math.pow(distance, 2); + var gravityForce = this.options.gravitationalConstant * parentBranch.mass * node.options.mass / Math.pow(distance, 3); var fx = dx * gravityForce; var fy = dy * gravityForce; this.physicsBody.forces[node.id].x += fx; this.physicsBody.forces[node.id].y += fy; } - }]); + }, { + key: "_formBarnesHutTree", - return ForceAtlas2BasedRepulsionSolver; - })(_BarnesHutSolver3["default"]); + /** + * This function constructs the barnesHut tree recursively. It creates the root, splits it and starts placing the nodes. + * + * @param nodes + * @param nodeIndices + * @private + */ + value: function _formBarnesHutTree(nodes, nodeIndices) { + var node = undefined; + var nodeCount = nodeIndices.length; - exports["default"] = ForceAtlas2BasedRepulsionSolver; - module.exports = exports["default"]; + var minX = nodes[nodeIndices[0]].x; + var minY = nodes[nodeIndices[0]].y; + var maxX = nodes[nodeIndices[0]].x; + var maxY = nodes[nodeIndices[0]].y; -/***/ }, -/* 81 */ -/***/ function(module, exports, __webpack_require__) { + // get the range of the nodes + for (var i = 1; i < nodeCount; i++) { + var x = nodes[nodeIndices[i]].x; + var y = nodes[nodeIndices[i]].y; + if (nodes[nodeIndices[i]].options.mass > 0) { + if (x < minX) { + minX = x; + } + if (x > maxX) { + maxX = x; + } + if (y < minY) { + minY = y; + } + if (y > maxY) { + maxY = y; + } + } + } + // make the range a square + var sizeDiff = Math.abs(maxX - minX) - Math.abs(maxY - minY); // difference between X and Y + if (sizeDiff > 0) { + minY -= 0.5 * sizeDiff; + maxY += 0.5 * sizeDiff; + } // xSize > ySize + else { + minX += 0.5 * sizeDiff; + maxX -= 0.5 * sizeDiff; + } // xSize < ySize - "use strict"; + var minimumTreeSize = 0.00001; + var rootSize = Math.max(minimumTreeSize, Math.abs(maxX - minX)); + var halfRootSize = 0.5 * rootSize; + var centerX = 0.5 * (minX + maxX), + centerY = 0.5 * (minY + maxY); - Object.defineProperty(exports, "__esModule", { - value: true - }); + // construct the barnesHutTree + var barnesHutTree = { + root: { + centerOfMass: { x: 0, y: 0 }, + mass: 0, + range: { + minX: centerX - halfRootSize, maxX: centerX + halfRootSize, + minY: centerY - halfRootSize, maxY: centerY + halfRootSize + }, + size: rootSize, + calcSize: 1 / rootSize, + children: { data: null }, + maxWidth: 0, + level: 0, + childrenCount: 4 + } + }; + this._splitBranch(barnesHutTree.root); - var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + // place the nodes one by one recursively + for (var i = 0; i < nodeCount; i++) { + node = nodes[nodeIndices[i]]; + if (node.options.mass > 0) { + this._placeInTree(barnesHutTree.root, node); + } + } - var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { desc = parent = getter = undefined; _again = false; var object = _x, - property = _x2, - receiver = _x3; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; + // make global + return barnesHutTree; + } + }, { + key: "_updateBranchMass", - function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + /** + * this updates the mass of a branch. this is increased by adding a node. + * + * @param parentBranch + * @param node + * @private + */ + value: function _updateBranchMass(parentBranch, node) { + var totalMass = parentBranch.mass + node.options.mass; + var totalMassInv = 1 / totalMass; - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + parentBranch.centerOfMass.x = parentBranch.centerOfMass.x * parentBranch.mass + node.x * node.options.mass; + parentBranch.centerOfMass.x *= totalMassInv; - function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } + parentBranch.centerOfMass.y = parentBranch.centerOfMass.y * parentBranch.mass + node.y * node.options.mass; + parentBranch.centerOfMass.y *= totalMassInv; - var _CentralGravitySolver2 = __webpack_require__(79); + parentBranch.mass = totalMass; + var biggestSize = Math.max(Math.max(node.height, node.radius), node.width); + parentBranch.maxWidth = parentBranch.maxWidth < biggestSize ? biggestSize : parentBranch.maxWidth; + } + }, { + key: "_placeInTree", - var _CentralGravitySolver3 = _interopRequireDefault(_CentralGravitySolver2); + /** + * determine in which branch the node will be placed. + * + * @param parentBranch + * @param node + * @param skipMassUpdate + * @private + */ + value: function _placeInTree(parentBranch, node, skipMassUpdate) { + if (skipMassUpdate != true || skipMassUpdate === undefined) { + // update the mass of the branch. + this._updateBranchMass(parentBranch, node); + } - var ForceAtlas2BasedCentralGravitySolver = (function (_CentralGravitySolver) { - function ForceAtlas2BasedCentralGravitySolver(body, physicsBody, options) { - _classCallCheck(this, ForceAtlas2BasedCentralGravitySolver); + if (parentBranch.children.NW.range.maxX > node.x) { + // in NW or SW + if (parentBranch.children.NW.range.maxY > node.y) { + // in NW + this._placeInRegion(parentBranch, node, "NW"); + } else { + // in SW + this._placeInRegion(parentBranch, node, "SW"); + } + } else { + // in NE or SE + if (parentBranch.children.NW.range.maxY > node.y) { + // in NE + this._placeInRegion(parentBranch, node, "NE"); + } else { + // in SE + this._placeInRegion(parentBranch, node, "SE"); + } + } + } + }, { + key: "_placeInRegion", - _get(Object.getPrototypeOf(ForceAtlas2BasedCentralGravitySolver.prototype), "constructor", this).call(this, body, physicsBody, options); - } + /** + * actually place the node in a region (or branch) + * + * @param parentBranch + * @param node + * @param region + * @private + */ + value: function _placeInRegion(parentBranch, node, region) { + switch (parentBranch.children[region].childrenCount) { + case 0: + // place node here + parentBranch.children[region].children.data = node; + parentBranch.children[region].childrenCount = 1; + this._updateBranchMass(parentBranch.children[region], node); + break; + case 1: + // convert into children + // if there are two nodes exactly overlapping (on init, on opening of cluster etc.) + // we move one node a pixel and we do not put it in the tree. + if (parentBranch.children[region].children.data.x === node.x && parentBranch.children[region].children.data.y === node.y) { + node.x += Math.random(); + node.y += Math.random(); + } else { + this._splitBranch(parentBranch.children[region]); + this._placeInTree(parentBranch.children[region], node); + } + break; + case 4: + // place in branch + this._placeInTree(parentBranch.children[region], node); + break; + } + } + }, { + key: "_splitBranch", + + /** + * this function splits a branch into 4 sub branches. If the branch contained a node, we place it in the subbranch + * after the split is complete. + * + * @param parentBranch + * @private + */ + value: function _splitBranch(parentBranch) { + // if the branch is shaded with a node, replace the node in the new subset. + var containedNode = null; + if (parentBranch.childrenCount === 1) { + containedNode = parentBranch.children.data; + parentBranch.mass = 0; + parentBranch.centerOfMass.x = 0; + parentBranch.centerOfMass.y = 0; + } + parentBranch.childrenCount = 4; + parentBranch.children.data = null; + this._insertRegion(parentBranch, "NW"); + this._insertRegion(parentBranch, "NE"); + this._insertRegion(parentBranch, "SW"); + this._insertRegion(parentBranch, "SE"); + + if (containedNode != null) { + this._placeInTree(parentBranch, containedNode); + } + } + }, { + key: "_insertRegion", + + /** + * This function subdivides the region into four new segments. + * Specifically, this inserts a single new segment. + * It fills the children section of the parentBranch + * + * @param parentBranch + * @param region + * @param parentRange + * @private + */ + value: function _insertRegion(parentBranch, region) { + var minX = undefined, + maxX = undefined, + minY = undefined, + maxY = undefined; + var childSize = 0.5 * parentBranch.size; + switch (region) { + case "NW": + minX = parentBranch.range.minX; + maxX = parentBranch.range.minX + childSize; + minY = parentBranch.range.minY; + maxY = parentBranch.range.minY + childSize; + break; + case "NE": + minX = parentBranch.range.minX + childSize; + maxX = parentBranch.range.maxX; + minY = parentBranch.range.minY; + maxY = parentBranch.range.minY + childSize; + break; + case "SW": + minX = parentBranch.range.minX; + maxX = parentBranch.range.minX + childSize; + minY = parentBranch.range.minY + childSize; + maxY = parentBranch.range.maxY; + break; + case "SE": + minX = parentBranch.range.minX + childSize; + maxX = parentBranch.range.maxX; + minY = parentBranch.range.minY + childSize; + maxY = parentBranch.range.maxY; + break; + } + + parentBranch.children[region] = { + centerOfMass: { x: 0, y: 0 }, + mass: 0, + range: { minX: minX, maxX: maxX, minY: minY, maxY: maxY }, + size: 0.5 * parentBranch.size, + calcSize: 2 * parentBranch.calcSize, + children: { data: null }, + maxWidth: 0, + level: parentBranch.level + 1, + childrenCount: 0 + }; + } + }, { + key: "_debug", + + //--------------------------- DEBUGGING BELOW ---------------------------// + + /** + * This function is for debugging purposed, it draws the tree. + * + * @param ctx + * @param color + * @private + */ + value: function _debug(ctx, color) { + if (this.barnesHutTree !== undefined) { - _inherits(ForceAtlas2BasedCentralGravitySolver, _CentralGravitySolver); + ctx.lineWidth = 1; - _createClass(ForceAtlas2BasedCentralGravitySolver, [{ - key: "_calculateForces", + this._drawBranch(this.barnesHutTree.root, ctx, color); + } + } + }, { + key: "_drawBranch", /** - * Calculate the forces based on the distance. + * This function is for debugging purposes. It draws the branches recursively. + * + * @param branch + * @param ctx + * @param color * @private */ - value: function _calculateForces(distance, dx, dy, forces, node) { - if (distance > 0) { - var degree = node.edges.length + 1; - var gravityForce = this.options.centralGravity * degree * node.options.mass; - forces[node.id].x = dx * gravityForce; - forces[node.id].y = dy * gravityForce; + value: function _drawBranch(branch, ctx, color) { + if (color === undefined) { + color = "#FF0000"; } - } - }]); - - return ForceAtlas2BasedCentralGravitySolver; - })(_CentralGravitySolver3["default"]); - - exports["default"] = ForceAtlas2BasedCentralGravitySolver; - module.exports = exports["default"]; - -/***/ }, -/* 82 */ -/***/ function(module, exports, __webpack_require__) { - - 'use strict'; - - Object.defineProperty(exports, '__esModule', { - value: true - }); - - var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { desc = parent = getter = undefined; _again = false; var object = _x, - property = _x2, - receiver = _x3; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; - - function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - - function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } - - var _Node2 = __webpack_require__(85); - - var _Node3 = _interopRequireDefault(_Node2); - /** - * - */ + if (branch.childrenCount === 4) { + this._drawBranch(branch.children.NW, ctx); + this._drawBranch(branch.children.NE, ctx); + this._drawBranch(branch.children.SE, ctx); + this._drawBranch(branch.children.SW, ctx); + } + ctx.strokeStyle = color; + ctx.beginPath(); + ctx.moveTo(branch.range.minX, branch.range.minY); + ctx.lineTo(branch.range.maxX, branch.range.minY); + ctx.stroke(); - var Cluster = (function (_Node) { - function Cluster(options, body, imagelist, grouplist, globalOptions) { - _classCallCheck(this, Cluster); + ctx.beginPath(); + ctx.moveTo(branch.range.maxX, branch.range.minY); + ctx.lineTo(branch.range.maxX, branch.range.maxY); + ctx.stroke(); - _get(Object.getPrototypeOf(Cluster.prototype), 'constructor', this).call(this, options, body, imagelist, grouplist, globalOptions); + ctx.beginPath(); + ctx.moveTo(branch.range.maxX, branch.range.maxY); + ctx.lineTo(branch.range.minX, branch.range.maxY); + ctx.stroke(); - this.isCluster = true; - this.containedNodes = {}; - this.containedEdges = {}; - } + ctx.beginPath(); + ctx.moveTo(branch.range.minX, branch.range.maxY); + ctx.lineTo(branch.range.minX, branch.range.minY); + ctx.stroke(); - _inherits(Cluster, _Node); + /* + if (branch.mass > 0) { + ctx.circle(branch.centerOfMass.x, branch.centerOfMass.y, 3*branch.mass); + ctx.stroke(); + } + */ + } + }]); - return Cluster; - })(_Node3['default']); + return BarnesHutSolver; + })(); - exports['default'] = Cluster; - module.exports = exports['default']; + exports["default"] = BarnesHutSolver; + module.exports = exports["default"]; /***/ }, -/* 83 */ +/* 78 */ /***/ function(module, exports, __webpack_require__) { - 'use strict'; + "use strict"; - Object.defineProperty(exports, '__esModule', { + Object.defineProperty(exports, "__esModule", { value: true }); - var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); - - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - - var util = __webpack_require__(1); - var Hammer = __webpack_require__(41); - var hammerUtil = __webpack_require__(49); - var keycharm = __webpack_require__(87); + var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); - var NavigationHandler = (function () { - function NavigationHandler(body, canvas) { - var _this = this; + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - _classCallCheck(this, NavigationHandler); + var RepulsionSolver = (function () { + function RepulsionSolver(body, physicsBody, options) { + _classCallCheck(this, RepulsionSolver); this.body = body; - this.canvas = canvas; - - this.iconsCreated = false; - this.navigationHammers = []; - this.boundFunctions = {}; - this.touchTime = 0; - this.activated = false; - - this.body.emitter.on('release', function () { - _this._stopMovement(); - }); - this.body.emitter.on('activate', function () { - _this.activated = true;_this.configureKeyboardBindings(); - }); - this.body.emitter.on('deactivate', function () { - _this.activated = false;_this.configureKeyboardBindings(); - }); - this.body.emitter.on('destroy', function () { - if (_this.keycharm !== undefined) { - _this.keycharm.destroy(); - } - }); - - this.options = {}; + this.physicsBody = physicsBody; + this.setOptions(options); } - _createClass(NavigationHandler, [{ - key: 'setOptions', + _createClass(RepulsionSolver, [{ + key: "setOptions", value: function setOptions(options) { - if (options !== undefined) { - this.options = options; - this.create(); - } - } - }, { - key: 'create', - value: function create() { - if (this.options.navigationButtons === true) { - if (this.iconsCreated === false) { - this.loadNavigationElements(); - } - } else if (this.iconsCreated === true) { - this.cleanNavigation(); - } - - this.configureKeyboardBindings(); - } - }, { - key: 'cleanNavigation', - value: function cleanNavigation() { - // clean hammer bindings - if (this.navigationHammers.length != 0) { - for (var i = 0; i < this.navigationHammers.length; i++) { - this.navigationHammers[i].destroy(); - } - this.navigationHammers = []; - } - - this._navigationReleaseOverload = function () {}; - - // clean up previous navigation items - if (this.navigationDOM && this.navigationDOM['wrapper'] && this.navigationDOM['wrapper'].parentNode) { - this.navigationDOM['wrapper'].parentNode.removeChild(this.navigationDOM['wrapper']); - } - - this.iconsCreated = false; + this.options = options; } }, { - key: 'loadNavigationElements', + key: "solve", /** - * Creation of the navigation controls nodes. They are drawn over the rest of the nodes and are not affected by scale and translation - * they have a triggerFunction which is called on click. If the position of the navigation controls is dependent - * on this.frame.canvas.clientWidth or this.frame.canvas.clientHeight, we flag horizontalAlignLeft and verticalAlignTop false. - * This means that the location will be corrected by the _relocateNavigation function on a size change of the canvas. + * Calculate the forces the nodes apply on each other based on a repulsion field. + * This field is linearly approximated. * * @private */ - value: function loadNavigationElements() { - this.cleanNavigation(); - - this.navigationDOM = {}; - var navigationDivs = ['up', 'down', 'left', 'right', 'zoomIn', 'zoomOut', 'zoomExtends']; - var navigationDivActions = ['_moveUp', '_moveDown', '_moveLeft', '_moveRight', '_zoomIn', '_zoomOut', '_fit']; - - this.navigationDOM['wrapper'] = document.createElement('div'); - this.navigationDOM['wrapper'].className = 'vis-navigation'; - this.canvas.frame.appendChild(this.navigationDOM['wrapper']); - - for (var i = 0; i < navigationDivs.length; i++) { - this.navigationDOM[navigationDivs[i]] = document.createElement('div'); - this.navigationDOM[navigationDivs[i]].className = 'vis-button vis-' + navigationDivs[i]; - this.navigationDOM['wrapper'].appendChild(this.navigationDOM[navigationDivs[i]]); - - var hammer = new Hammer(this.navigationDOM[navigationDivs[i]]); - if (navigationDivActions[i] === '_fit') { - hammerUtil.onTouch(hammer, this._fit.bind(this)); - } else { - hammerUtil.onTouch(hammer, this.bindToRedraw.bind(this, navigationDivActions[i])); - } - - this.navigationHammers.push(hammer); - } + value: function solve() { + var dx, dy, distance, fx, fy, repulsingForce, node1, node2; - this.iconsCreated = true; - } - }, { - key: 'bindToRedraw', - value: function bindToRedraw(action) { - if (this.boundFunctions[action] === undefined) { - this.boundFunctions[action] = this[action].bind(this); - this.body.emitter.on('initRedraw', this.boundFunctions[action]); - this.body.emitter.emit('_startRendering'); - } - } - }, { - key: 'unbindFromRedraw', - value: function unbindFromRedraw(action) { - if (this.boundFunctions[action] !== undefined) { - this.body.emitter.off('initRedraw', this.boundFunctions[action]); - this.body.emitter.emit('_stopRendering'); - delete this.boundFunctions[action]; - } - } - }, { - key: '_fit', + var nodes = this.body.nodes; + var nodeIndices = this.physicsBody.physicsNodeIndices; + var forces = this.physicsBody.forces; - /** - * this stops all movement induced by the navigation buttons - * - * @private - */ - value: function _fit() { - if (new Date().valueOf() - this.touchTime > 700) { - // TODO: fix ugly hack to avoid hammer's double fireing of event (because we use release?) - this.body.emitter.emit('fit', { duration: 700 }); - this.touchTime = new Date().valueOf(); - } - } - }, { - key: '_stopMovement', + // repulsing forces between nodes + var nodeDistance = this.options.nodeDistance; - /** - * this stops all movement induced by the navigation buttons - * - * @private - */ - value: function _stopMovement() { - for (var boundAction in this.boundFunctions) { - if (this.boundFunctions.hasOwnProperty(boundAction)) { - this.body.emitter.off('initRedraw', this.boundFunctions[boundAction]); - this.body.emitter.emit('_stopRendering'); - } - } - this.boundFunctions = {}; - } - }, { - key: '_moveUp', - value: function _moveUp() { - this.body.view.translation.y += this.options.keyboard.speed.y; - } - }, { - key: '_moveDown', - value: function _moveDown() { - this.body.view.translation.y -= this.options.keyboard.speed.y; - } - }, { - key: '_moveLeft', - value: function _moveLeft() { - this.body.view.translation.x += this.options.keyboard.speed.x; - } - }, { - key: '_moveRight', - value: function _moveRight() { - this.body.view.translation.x -= this.options.keyboard.speed.x; - } - }, { - key: '_zoomIn', - value: function _zoomIn() { - this.body.view.scale *= 1 + this.options.keyboard.speed.zoom; - } - }, { - key: '_zoomOut', - value: function _zoomOut() { - this.body.view.scale /= 1 + this.options.keyboard.speed.zoom; - } - }, { - key: 'configureKeyboardBindings', + // approximation constants + var a = -2 / 3 / nodeDistance; + var b = 4 / 3; - /** - * bind all keys using keycharm. - */ - value: function configureKeyboardBindings() { - if (this.keycharm !== undefined) { - this.keycharm.destroy(); - } + // we loop from i over all but the last entree in the array + // j loops from i+1 to the last. This way we do not double count any of the indices, nor i === j + for (var i = 0; i < nodeIndices.length - 1; i++) { + node1 = nodes[nodeIndices[i]]; + for (var j = i + 1; j < nodeIndices.length; j++) { + node2 = nodes[nodeIndices[j]]; - if (this.options.keyboard.enabled === true) { + dx = node2.x - node1.x; + dy = node2.y - node1.y; + distance = Math.sqrt(dx * dx + dy * dy); - if (this.options.keyboard.bindToWindow === true) { - this.keycharm = keycharm({ container: window, preventDefault: true }); - } else { - this.keycharm = keycharm({ container: this.canvas.frame, preventDefault: true }); - } + // same condition as BarnesHutSolver, making sure nodes are never 100% overlapping. + if (distance === 0) { + distance = 0.1 * Math.random(); + dx = distance; + } - this.keycharm.reset(); + if (distance < 2 * nodeDistance) { + if (distance < 0.5 * nodeDistance) { + repulsingForce = 1; + } else { + repulsingForce = a * distance + b; // linear approx of 1 / (1 + Math.exp((distance / nodeDistance - 1) * steepness)) + } + repulsingForce = repulsingForce / distance; - if (this.activated === true) { - this.keycharm.bind('up', this.bindToRedraw.bind(this, '_moveUp'), 'keydown'); - this.keycharm.bind('down', this.bindToRedraw.bind(this, '_moveDown'), 'keydown'); - this.keycharm.bind('left', this.bindToRedraw.bind(this, '_moveLeft'), 'keydown'); - this.keycharm.bind('right', this.bindToRedraw.bind(this, '_moveRight'), 'keydown'); - this.keycharm.bind('=', this.bindToRedraw.bind(this, '_zoomIn'), 'keydown'); - this.keycharm.bind('num+', this.bindToRedraw.bind(this, '_zoomIn'), 'keydown'); - this.keycharm.bind('num-', this.bindToRedraw.bind(this, '_zoomOut'), 'keydown'); - this.keycharm.bind('-', this.bindToRedraw.bind(this, '_zoomOut'), 'keydown'); - this.keycharm.bind('[', this.bindToRedraw.bind(this, '_zoomOut'), 'keydown'); - this.keycharm.bind(']', this.bindToRedraw.bind(this, '_zoomIn'), 'keydown'); - this.keycharm.bind('pageup', this.bindToRedraw.bind(this, '_zoomIn'), 'keydown'); - this.keycharm.bind('pagedown', this.bindToRedraw.bind(this, '_zoomOut'), 'keydown'); + fx = dx * repulsingForce; + fy = dy * repulsingForce; - this.keycharm.bind('up', this.unbindFromRedraw.bind(this, '_moveUp'), 'keyup'); - this.keycharm.bind('down', this.unbindFromRedraw.bind(this, '_moveDown'), 'keyup'); - this.keycharm.bind('left', this.unbindFromRedraw.bind(this, '_moveLeft'), 'keyup'); - this.keycharm.bind('right', this.unbindFromRedraw.bind(this, '_moveRight'), 'keyup'); - this.keycharm.bind('=', this.unbindFromRedraw.bind(this, '_zoomIn'), 'keyup'); - this.keycharm.bind('num+', this.unbindFromRedraw.bind(this, '_zoomIn'), 'keyup'); - this.keycharm.bind('num-', this.unbindFromRedraw.bind(this, '_zoomOut'), 'keyup'); - this.keycharm.bind('-', this.unbindFromRedraw.bind(this, '_zoomOut'), 'keyup'); - this.keycharm.bind('[', this.unbindFromRedraw.bind(this, '_zoomOut'), 'keyup'); - this.keycharm.bind(']', this.unbindFromRedraw.bind(this, '_zoomIn'), 'keyup'); - this.keycharm.bind('pageup', this.unbindFromRedraw.bind(this, '_zoomIn'), 'keyup'); - this.keycharm.bind('pagedown', this.unbindFromRedraw.bind(this, '_zoomOut'), 'keyup'); + forces[node1.id].x -= fx; + forces[node1.id].y -= fy; + forces[node2.id].x += fx; + forces[node2.id].y += fy; + } } } } }]); - return NavigationHandler; + return RepulsionSolver; })(); - exports['default'] = NavigationHandler; - module.exports = exports['default']; + exports["default"] = RepulsionSolver; + module.exports = exports["default"]; /***/ }, -/* 84 */ +/* 79 */ /***/ function(module, exports, __webpack_require__) { - 'use strict'; + "use strict"; - Object.defineProperty(exports, '__esModule', { + Object.defineProperty(exports, "__esModule", { value: true }); - var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); - - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - - /** - * Popup is a class to create a popup window with some text - * @param {Element} container The container object. - * @param {Number} [x] - * @param {Number} [y] - * @param {String} [text] - * @param {Object} [style] An object containing borderColor, - * backgroundColor, etc. - */ - - var Popup = (function () { - function Popup(container) { - _classCallCheck(this, Popup); + var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); - this.container = container; + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - this.x = 0; - this.y = 0; - this.padding = 5; - this.hidden = false; + var HierarchicalRepulsionSolver = (function () { + function HierarchicalRepulsionSolver(body, physicsBody, options) { + _classCallCheck(this, HierarchicalRepulsionSolver); - // create the frame - this.frame = document.createElement('div'); - this.frame.className = 'vis-network-tooltip'; - this.container.appendChild(this.frame); + this.body = body; + this.physicsBody = physicsBody; + this.setOptions(options); } - _createClass(Popup, [{ - key: 'setPosition', - - /** - * @param {number} x Horizontal position of the popup window - * @param {number} y Vertical position of the popup window - */ - value: function setPosition(x, y) { - this.x = parseInt(x); - this.y = parseInt(y); + _createClass(HierarchicalRepulsionSolver, [{ + key: "setOptions", + value: function setOptions(options) { + this.options = options; } }, { - key: 'setText', + key: "solve", /** - * Set the content for the popup window. This can be HTML code or text. - * @param {string | Element} content + * Calculate the forces the nodes apply on each other based on a repulsion field. + * This field is linearly approximated. + * + * @private */ - value: function setText(content) { - if (content instanceof Element) { - this.frame.innerHTML = ''; - this.frame.appendChild(content); - } else { - this.frame.innerHTML = content; // string containing text or HTML - } - } - }, { - key: 'show', + value: function solve() { + var dx, dy, distance, fx, fy, repulsingForce, node1, node2, i, j; - /** - * Show the popup window - * @param {boolean} [doShow] Show or hide the window - */ - value: function show(doShow) { - if (doShow === undefined) { - doShow = true; - } + var nodes = this.body.nodes; + var nodeIndices = this.physicsBody.physicsNodeIndices; + var forces = this.physicsBody.forces; - if (doShow === true) { - var height = this.frame.clientHeight; - var width = this.frame.clientWidth; - var maxHeight = this.frame.parentNode.clientHeight; - var maxWidth = this.frame.parentNode.clientWidth; + // repulsing forces between nodes + var nodeDistance = this.options.nodeDistance; - var top = this.y - height; - if (top + height + this.padding > maxHeight) { - top = maxHeight - height - this.padding; - } - if (top < this.padding) { - top = this.padding; - } + // we loop from i over all but the last entree in the array + // j loops from i+1 to the last. This way we do not double count any of the indices, nor i === j + for (i = 0; i < nodeIndices.length - 1; i++) { + node1 = nodes[nodeIndices[i]]; + for (j = i + 1; j < nodeIndices.length; j++) { + node2 = nodes[nodeIndices[j]]; + + // nodes only affect nodes on their level + if (node1.level === node2.level) { + dx = node2.x - node1.x; + dy = node2.y - node1.y; + distance = Math.sqrt(dx * dx + dy * dy); + + var steepness = 0.05; + if (distance < nodeDistance) { + repulsingForce = -Math.pow(steepness * distance, 2) + Math.pow(steepness * nodeDistance, 2); + } else { + repulsingForce = 0; + } + // normalize force with + if (distance === 0) { + distance = 0.01; + } else { + repulsingForce = repulsingForce / distance; + } + fx = dx * repulsingForce; + fy = dy * repulsingForce; - var left = this.x; - if (left + width + this.padding > maxWidth) { - left = maxWidth - width - this.padding; - } - if (left < this.padding) { - left = this.padding; + forces[node1.id].x -= fx; + forces[node1.id].y -= fy; + forces[node2.id].x += fx; + forces[node2.id].y += fy; + } } - - this.frame.style.left = left + 'px'; - this.frame.style.top = top + 'px'; - this.frame.style.visibility = 'visible'; - this.hidden = false; - } else { - this.hide(); } } - }, { - key: 'hide', - - /** - * Hide the popup window - */ - value: function hide() { - this.hidden = true; - this.frame.style.visibility = 'hidden'; - } }]); - return Popup; + return HierarchicalRepulsionSolver; })(); - exports['default'] = Popup; - module.exports = exports['default']; + exports["default"] = HierarchicalRepulsionSolver; + module.exports = exports["default"]; /***/ }, -/* 85 */ +/* 80 */ /***/ function(module, exports, __webpack_require__) { - 'use strict'; + "use strict"; - Object.defineProperty(exports, '__esModule', { + Object.defineProperty(exports, "__esModule", { value: true }); - var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); - - function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - - var _sharedLabel = __webpack_require__(91); - - var _sharedLabel2 = _interopRequireDefault(_sharedLabel); - - var _nodesShapesBox = __webpack_require__(92); - - var _nodesShapesBox2 = _interopRequireDefault(_nodesShapesBox); - - var _nodesShapesCircle = __webpack_require__(93); - - var _nodesShapesCircle2 = _interopRequireDefault(_nodesShapesCircle); - - var _nodesShapesCircularImage = __webpack_require__(94); - - var _nodesShapesCircularImage2 = _interopRequireDefault(_nodesShapesCircularImage); - - var _nodesShapesDatabase = __webpack_require__(95); - - var _nodesShapesDatabase2 = _interopRequireDefault(_nodesShapesDatabase); - - var _nodesShapesDiamond = __webpack_require__(96); - - var _nodesShapesDiamond2 = _interopRequireDefault(_nodesShapesDiamond); - - var _nodesShapesDot = __webpack_require__(97); - - var _nodesShapesDot2 = _interopRequireDefault(_nodesShapesDot); - - var _nodesShapesEllipse = __webpack_require__(98); + var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); - var _nodesShapesEllipse2 = _interopRequireDefault(_nodesShapesEllipse); + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - var _nodesShapesIcon = __webpack_require__(99); + var SpringSolver = (function () { + function SpringSolver(body, physicsBody, options) { + _classCallCheck(this, SpringSolver); - var _nodesShapesIcon2 = _interopRequireDefault(_nodesShapesIcon); + this.body = body; + this.physicsBody = physicsBody; + this.setOptions(options); + } - var _nodesShapesImage = __webpack_require__(100); + _createClass(SpringSolver, [{ + key: "setOptions", + value: function setOptions(options) { + this.options = options; + } + }, { + key: "solve", - var _nodesShapesImage2 = _interopRequireDefault(_nodesShapesImage); + /** + * This function calculates the springforces on the nodes, accounting for the support nodes. + * + * @private + */ + value: function solve() { + var edgeLength = undefined, + edge = undefined; + var edgeIndices = this.physicsBody.physicsEdgeIndices; + var edges = this.body.edges; + var node1 = undefined, + node2 = undefined, + node3 = undefined; - var _nodesShapesSquare = __webpack_require__(101); + // forces caused by the edges, modelled as springs + for (var i = 0; i < edgeIndices.length; i++) { + edge = edges[edgeIndices[i]]; + if (edge.connected === true && edge.toId !== edge.fromId) { + // only calculate forces if nodes are in the same sector + if (this.body.nodes[edge.toId] !== undefined && this.body.nodes[edge.fromId] !== undefined) { + if (edge.edgeType.via !== undefined) { + edgeLength = edge.options.length === undefined ? this.options.springLength : edge.options.length; + node1 = edge.to; + node2 = edge.edgeType.via; + node3 = edge.from; - var _nodesShapesSquare2 = _interopRequireDefault(_nodesShapesSquare); + this._calculateSpringForce(node1, node2, 0.5 * edgeLength); + this._calculateSpringForce(node2, node3, 0.5 * edgeLength); + } else { + // the * 1.5 is here so the edge looks as large as a smooth edge. It does not initially because the smooth edges use + // the support nodes which exert a repulsive force on the to and from nodes, making the edge appear larger. + edgeLength = edge.options.length === undefined ? this.options.springLength * 1.5 : edge.options.length; + this._calculateSpringForce(edge.from, edge.to, edgeLength); + } + } + } + } + } + }, { + key: "_calculateSpringForce", - var _nodesShapesStar = __webpack_require__(102); + /** + * This is the code actually performing the calculation for the function above. + * + * @param node1 + * @param node2 + * @param edgeLength + * @private + */ + value: function _calculateSpringForce(node1, node2, edgeLength) { + var dx = node1.x - node2.x; + var dy = node1.y - node2.y; + var distance = Math.max(Math.sqrt(dx * dx + dy * dy), 0.01); - var _nodesShapesStar2 = _interopRequireDefault(_nodesShapesStar); + // the 1/distance is so the fx and fy can be calculated without sine or cosine. + var springForce = this.options.springConstant * (edgeLength - distance) / distance; - var _nodesShapesText = __webpack_require__(103); + var fx = dx * springForce; + var fy = dy * springForce; - var _nodesShapesText2 = _interopRequireDefault(_nodesShapesText); + // handle the case where one node is not part of the physcis + if (this.physicsBody.forces[node1.id] !== undefined) { + this.physicsBody.forces[node1.id].x += fx; + this.physicsBody.forces[node1.id].y += fy; + } - var _nodesShapesTriangle = __webpack_require__(104); + if (this.physicsBody.forces[node2.id] !== undefined) { + this.physicsBody.forces[node2.id].x -= fx; + this.physicsBody.forces[node2.id].y -= fy; + } + } + }]); - var _nodesShapesTriangle2 = _interopRequireDefault(_nodesShapesTriangle); + return SpringSolver; + })(); - var _nodesShapesTriangleDown = __webpack_require__(105); + exports["default"] = SpringSolver; + module.exports = exports["default"]; - var _nodesShapesTriangleDown2 = _interopRequireDefault(_nodesShapesTriangleDown); +/***/ }, +/* 81 */ +/***/ function(module, exports, __webpack_require__) { - var _sharedValidator = __webpack_require__(46); + "use strict"; - var _sharedValidator2 = _interopRequireDefault(_sharedValidator); + Object.defineProperty(exports, "__esModule", { + value: true + }); - var util = __webpack_require__(1); + var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); - /** - * @class Node - * A node. A node can be connected to other nodes via one or multiple edges. - * @param {object} options An object containing options for the node. All - * options are optional, except for the id. - * {number} id Id of the node. Required - * {string} label Text label for the node - * {number} x Horizontal position of the node - * {number} y Vertical position of the node - * {string} shape Node shape, available: - * "database", "circle", "ellipse", - * "box", "image", "text", "dot", - * "star", "triangle", "triangleDown", - * "square", "icon" - * {string} image An image url - * {string} title An title text, can be HTML - * {anytype} group A group name or number - * @param {Network.Images} imagelist A list with images. Only needed - * when the node has an image - * @param {Network.Groups} grouplist A list with groups. Needed for - * retrieving group options - * @param {Object} constants An object with default values for - * example for the color - * - */ + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - var Node = (function () { - function Node(options, body, imagelist, grouplist, globalOptions) { - _classCallCheck(this, Node); + var HierarchicalSpringSolver = (function () { + function HierarchicalSpringSolver(body, physicsBody, options) { + _classCallCheck(this, HierarchicalSpringSolver); - this.options = util.bridgeObject(globalOptions); this.body = body; - - this.edges = []; // all edges connected to this node - - // set defaults for the options - this.id = undefined; - this.imagelist = imagelist; - this.grouplist = grouplist; - - // state options - this.x = undefined; - this.y = undefined; - this.baseSize = this.options.size; - this.baseFontSize = this.options.font.size; - this.predefinedPosition = false; // used to check if initial fit should just take the range or approximate - this.selected = false; - this.hover = false; - - this.labelModule = new _sharedLabel2['default'](this.body, this.options); + this.physicsBody = physicsBody; this.setOptions(options); } - _createClass(Node, [{ - key: 'attachEdge', - - /** - * Attach a edge to the node - * @param {Edge} edge - */ - value: function attachEdge(edge) { - if (this.edges.indexOf(edge) === -1) { - this.edges.push(edge); - } + _createClass(HierarchicalSpringSolver, [{ + key: "setOptions", + value: function setOptions(options) { + this.options = options; } }, { - key: 'detachEdge', + key: "solve", /** - * Detach a edge from the node - * @param {Edge} edge + * This function calculates the springforces on the nodes, accounting for the support nodes. + * + * @private */ - value: function detachEdge(edge) { - var index = this.edges.indexOf(edge); - if (index != -1) { - this.edges.splice(index, 1); - } - } - }, { - key: 'togglePhysics', + value: function solve() { + var edgeLength, edge; + var dx, dy, fx, fy, springForce, distance; + var edges = this.body.edges; + var factor = 0.5; - /** - * Enable or disable the physics. - * @param status - */ - value: function togglePhysics(status) { - this.options.physics = status; - } - }, { - key: 'setOptions', + var edgeIndices = this.physicsBody.physicsEdgeIndices; + var nodeIndices = this.physicsBody.physicsNodeIndices; + var forces = this.physicsBody.forces; - /** - * Set or overwrite options for the node - * @param {Object} options an object with options - * @param {Object} constants and object with default, global options - */ - value: function setOptions(options) { - if (!options) { - return; - } - // basic options - if (options.id !== undefined) { - this.id = options.id; + // initialize the spring force counters + for (var i = 0; i < nodeIndices.length; i++) { + var nodeId = nodeIndices[i]; + forces[nodeId].springFx = 0; + forces[nodeId].springFy = 0; } - if (this.id === undefined) { - throw 'Node must have an id'; - } + // forces caused by the edges, modelled as springs + for (var i = 0; i < edgeIndices.length; i++) { + edge = edges[edgeIndices[i]]; + if (edge.connected === true) { + edgeLength = edge.options.length === undefined ? this.options.springLength : edge.options.length; - if (options.x !== undefined) { - this.x = parseInt(options.x);this.predefinedPosition = true; - } - if (options.y !== undefined) { - this.y = parseInt(options.y);this.predefinedPosition = true; - } - if (options.size !== undefined) { - this.baseSize = options.size; - } - if (options.value !== undefined) { - options.value = parseInt(options.value); - } + dx = edge.from.x - edge.to.x; + dy = edge.from.y - edge.to.y; + distance = Math.sqrt(dx * dx + dy * dy); + distance = distance === 0 ? 0.01 : distance; - // this transforms all shorthands into fully defined options - Node.parseOptions(this.options, options, true); + // the 1/distance is so the fx and fy can be calculated without sine or cosine. + springForce = this.options.springConstant * (edgeLength - distance) / distance; - // copy group options - if (typeof options.group === 'number' || typeof options.group === 'string' && options.group != '') { - var groupObj = this.grouplist.get(options.group); - util.deepExtend(this.options, groupObj); - // the color object needs to be completely defined. Since groups can partially overwrite the colors, we parse it again, just in case. - this.options.color = util.parseColor(this.options.color); - } + fx = dx * springForce; + fy = dy * springForce; - // load the images - if (this.options.image !== undefined && this.options.image != '') { - if (this.imagelist) { - this.imageObj = this.imagelist.load(this.options.image, this.options.brokenImage); - } else { - throw 'No imagelist provided'; + if (edge.to.level != edge.from.level) { + forces[edge.toId].springFx -= fx; + forces[edge.toId].springFy -= fy; + forces[edge.fromId].springFx += fx; + forces[edge.fromId].springFy += fy; + } else { + forces[edge.toId].x -= factor * fx; + forces[edge.toId].y -= factor * fy; + forces[edge.fromId].x += factor * fx; + forces[edge.fromId].y += factor * fy; + } } } - this.updateShape(); - this.updateLabelModule(); + // normalize spring forces + var springForce = 1; + var springFx, springFy; + for (var i = 0; i < nodeIndices.length; i++) { + var nodeId = nodeIndices[i]; + springFx = Math.min(springForce, Math.max(-springForce, forces[nodeId].springFx)); + springFy = Math.min(springForce, Math.max(-springForce, forces[nodeId].springFy)); - // reset the size of the node, this can be changed - this._reset(); - } - }, { - key: 'updateLabelModule', - value: function updateLabelModule() { - if (this.options.label === undefined || this.options.label === null) { - this.options.label = ''; + forces[nodeId].x += springFx; + forces[nodeId].y += springFy; } - this.labelModule.setOptions(this.options, true); - if (this.labelModule.baseSize !== undefined) { - this.baseFontSize = this.labelModule.baseSize; + + // retain energy balance + var totalFx = 0; + var totalFy = 0; + for (var i = 0; i < nodeIndices.length; i++) { + var nodeId = nodeIndices[i]; + totalFx += forces[nodeId].x; + totalFy += forces[nodeId].y; } - } - }, { - key: 'updateShape', - value: function updateShape() { - // choose draw method depending on the shape - switch (this.options.shape) { - case 'box': - this.shape = new _nodesShapesBox2['default'](this.options, this.body, this.labelModule); - break; - case 'circle': - this.shape = new _nodesShapesCircle2['default'](this.options, this.body, this.labelModule); - break; - case 'circularImage': - this.shape = new _nodesShapesCircularImage2['default'](this.options, this.body, this.labelModule, this.imageObj); - break; - case 'database': - this.shape = new _nodesShapesDatabase2['default'](this.options, this.body, this.labelModule); - break; - case 'diamond': - this.shape = new _nodesShapesDiamond2['default'](this.options, this.body, this.labelModule); - break; - case 'dot': - this.shape = new _nodesShapesDot2['default'](this.options, this.body, this.labelModule); - break; - case 'ellipse': - this.shape = new _nodesShapesEllipse2['default'](this.options, this.body, this.labelModule); - break; - case 'icon': - this.shape = new _nodesShapesIcon2['default'](this.options, this.body, this.labelModule); - break; - case 'image': - this.shape = new _nodesShapesImage2['default'](this.options, this.body, this.labelModule, this.imageObj); - break; - case 'square': - this.shape = new _nodesShapesSquare2['default'](this.options, this.body, this.labelModule); - break; - case 'star': - this.shape = new _nodesShapesStar2['default'](this.options, this.body, this.labelModule); - break; - case 'text': - this.shape = new _nodesShapesText2['default'](this.options, this.body, this.labelModule); - break; - case 'triangle': - this.shape = new _nodesShapesTriangle2['default'](this.options, this.body, this.labelModule); - break; - case 'triangleDown': - this.shape = new _nodesShapesTriangleDown2['default'](this.options, this.body, this.labelModule); - break; - default: - this.shape = new _nodesShapesEllipse2['default'](this.options, this.body, this.labelModule); - break; + var correctionFx = totalFx / nodeIndices.length; + var correctionFy = totalFy / nodeIndices.length; + + for (var i = 0; i < nodeIndices.length; i++) { + var nodeId = nodeIndices[i]; + forces[nodeId].x -= correctionFx; + forces[nodeId].y -= correctionFy; } - this._reset(); } - }, { - key: 'select', + }]); - /** - * select this node - */ - value: function select() { - this.selected = true; - this._reset(); - } - }, { - key: 'unselect', + return HierarchicalSpringSolver; + })(); - /** - * unselect this node - */ - value: function unselect() { - this.selected = false; - this._reset(); - } - }, { - key: '_reset', + exports["default"] = HierarchicalSpringSolver; + module.exports = exports["default"]; - /** - * Reset the calculated size of the node, forces it to recalculate its size - * @private - */ - value: function _reset() { - this.shape.width = undefined; - this.shape.height = undefined; - } - }, { - key: 'getTitle', +/***/ }, +/* 82 */ +/***/ function(module, exports, __webpack_require__) { - /** - * get the title of this node. - * @return {string} title The title of the node, or undefined when no title - * has been set. - */ - value: function getTitle() { - return this.options.title; - } - }, { - key: 'distanceToBorder', + "use strict"; - /** - * Calculate the distance to the border of the Node - * @param {CanvasRenderingContext2D} ctx - * @param {Number} angle Angle in radians - * @returns {number} distance Distance to the border in pixels - */ - value: function distanceToBorder(ctx, angle) { - return this.shape.distanceToBorder(ctx, angle); - } - }, { - key: 'isFixed', + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + var CentralGravitySolver = (function () { + function CentralGravitySolver(body, physicsBody, options) { + _classCallCheck(this, CentralGravitySolver); + + this.body = body; + this.physicsBody = physicsBody; + this.setOptions(options); + } - /** - * Check if this node has a fixed x and y position - * @return {boolean} true if fixed, false if not - */ - value: function isFixed() { - return this.options.fixed.x && this.options.fixed.y; + _createClass(CentralGravitySolver, [{ + key: "setOptions", + value: function setOptions(options) { + this.options = options; } }, { - key: 'isSelected', + key: "solve", + value: function solve() { + var dx = undefined, + dy = undefined, + distance = undefined, + node = undefined; + var nodes = this.body.nodes; + var nodeIndices = this.physicsBody.physicsNodeIndices; + var forces = this.physicsBody.forces; - /** - * check if this node is selecte - * @return {boolean} selected True if node is selected, else false - */ - value: function isSelected() { - return this.selected; + for (var i = 0; i < nodeIndices.length; i++) { + var nodeId = nodeIndices[i]; + node = nodes[nodeId]; + dx = -node.x; + dy = -node.y; + distance = Math.sqrt(dx * dx + dy * dy); + + this._calculateForces(distance, dx, dy, forces, node); + } } }, { - key: 'getValue', + key: "_calculateForces", /** - * Retrieve the value of the node. Can be undefined - * @return {Number} value + * Calculate the forces based on the distance. + * @private */ - value: function getValue() { - return this.options.value; + value: function _calculateForces(distance, dx, dy, forces, node) { + var gravityForce = distance === 0 ? 0 : this.options.centralGravity / distance; + forces[node.id].x = dx * gravityForce; + forces[node.id].y = dy * gravityForce; } - }, { - key: 'setValueRange', + }]); + + return CentralGravitySolver; + })(); + + exports["default"] = CentralGravitySolver; + module.exports = exports["default"]; + +/***/ }, +/* 83 */ +/***/ function(module, exports, __webpack_require__) { + + "use strict"; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + + var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { desc = parent = getter = undefined; _again = false; var object = _x, + property = _x2, + receiver = _x3; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } + + var _BarnesHutSolver2 = __webpack_require__(77); + + var _BarnesHutSolver3 = _interopRequireDefault(_BarnesHutSolver2); + + var ForceAtlas2BasedRepulsionSolver = (function (_BarnesHutSolver) { + function ForceAtlas2BasedRepulsionSolver(body, physicsBody, options) { + _classCallCheck(this, ForceAtlas2BasedRepulsionSolver); + + _get(Object.getPrototypeOf(ForceAtlas2BasedRepulsionSolver.prototype), "constructor", this).call(this, body, physicsBody, options); + } + + _inherits(ForceAtlas2BasedRepulsionSolver, _BarnesHutSolver); + + _createClass(ForceAtlas2BasedRepulsionSolver, [{ + key: "_calculateForces", /** - * Adjust the value range of the node. The node will adjust it's size - * based on its value. - * @param {Number} min - * @param {Number} max + * Calculate the forces based on the distance. + * + * @param distance + * @param dx + * @param dy + * @param node + * @param parentBranch + * @private */ - value: function setValueRange(min, max, total) { - if (this.options.value !== undefined) { - var scale = this.options.scaling.customScalingFunction(min, max, total, this.options.value); - var sizeDiff = this.options.scaling.max - this.options.scaling.min; - if (this.options.scaling.label.enabled === true) { - var fontDiff = this.options.scaling.label.max - this.options.scaling.label.min; - this.options.font.size = this.options.scaling.label.min + scale * fontDiff; - } - this.options.size = this.options.scaling.min + scale * sizeDiff; - } else { - this.options.size = this.baseSize; - this.options.font.size = this.baseFontSize; + value: function _calculateForces(distance, dx, dy, node, parentBranch) { + if (distance === 0) { + distance = 0.1 * Math.random(); + dx = distance; } - } - }, { - key: 'draw', - /** - * Draw this node in the given canvas - * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d"); - * @param {CanvasRenderingContext2D} ctx - */ - value: function draw(ctx) { - this.shape.draw(ctx, this.x, this.y, this.selected, this.hover); - } - }, { - key: 'updateBoundingBox', + if (this.overlapAvoidanceFactor < 1) { + distance = Math.max(0.1 + this.overlapAvoidanceFactor * node.shape.radius, distance - node.shape.radius); + } - /** - * Update the bounding box of the shape - */ - value: function updateBoundingBox() { - this.shape.updateBoundingBox(this.x, this.y); - } - }, { - key: 'resize', + var degree = node.edges.length + 1; + // the dividing by the distance cubed instead of squared allows us to get the fx and fy components without sines and cosines + // it is shorthand for gravityforce with distance squared and fx = dx/distance * gravityForce + var gravityForce = this.options.gravitationalConstant * parentBranch.mass * node.options.mass * degree / Math.pow(distance, 2); + var fx = dx * gravityForce; + var fy = dy * gravityForce; - /** - * Recalculate the size of this node in the given canvas - * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d"); - * @param {CanvasRenderingContext2D} ctx - */ - value: function resize(ctx) { - this.shape.resize(ctx); + this.physicsBody.forces[node.id].x += fx; + this.physicsBody.forces[node.id].y += fy; } - }, { - key: 'isOverlappingWith', + }]); - /** - * Check if this object is overlapping with the provided object - * @param {Object} obj an object with parameters left, top, right, bottom - * @return {boolean} True if location is located on node - */ - value: function isOverlappingWith(obj) { - return this.shape.left < obj.right && this.shape.left + this.shape.width > obj.left && this.shape.top < obj.bottom && this.shape.top + this.shape.height > obj.top; - } - }, { - key: 'isBoundingBoxOverlappingWith', + return ForceAtlas2BasedRepulsionSolver; + })(_BarnesHutSolver3["default"]); - /** - * Check if this object is overlapping with the provided object - * @param {Object} obj an object with parameters left, top, right, bottom - * @return {boolean} True if location is located on node - */ - value: function isBoundingBoxOverlappingWith(obj) { - return this.shape.boundingBox.left < obj.right && this.shape.boundingBox.right > obj.left && this.shape.boundingBox.top < obj.bottom && this.shape.boundingBox.bottom > obj.top; - } - }], [{ - key: 'parseOptions', + exports["default"] = ForceAtlas2BasedRepulsionSolver; + module.exports = exports["default"]; - /** - * This process all possible shorthands in the new options and makes sure that the parentOptions are fully defined. - * Static so it can also be used by the handler. - * @param parentOptions - * @param newOptions - */ - value: function parseOptions(parentOptions, newOptions) { - var allowDeletion = arguments[2] === undefined ? false : arguments[2]; +/***/ }, +/* 84 */ +/***/ function(module, exports, __webpack_require__) { - var fields = ['color', 'font', 'fixed', 'shadow']; - util.selectiveNotDeepExtend(fields, parentOptions, newOptions, allowDeletion); + "use strict"; - // merge the shadow options into the parent. - util.mergeOptions(parentOptions, newOptions, 'shadow'); + Object.defineProperty(exports, "__esModule", { + value: true + }); - // individual shape newOptions - if (newOptions.color !== undefined && newOptions.color !== null) { - var parsedColor = util.parseColor(newOptions.color); - util.fillIfDefined(parentOptions.color, parsedColor); - } else if (allowDeletion === true && newOptions.color === null) { - parentOptions.color = undefined; - delete parentOptions.color; - } + var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); - if (newOptions.fixed !== undefined && newOptions.fixed !== null) { - if (typeof newOptions.fixed === 'boolean') { - parentOptions.fixed.x = newOptions.fixed; - parentOptions.fixed.y = newOptions.fixed; - } else { - if (newOptions.fixed.x !== undefined && typeof newOptions.fixed.x === 'boolean') { - parentOptions.fixed.x = newOptions.fixed.x; - } - if (newOptions.fixed.y !== undefined && typeof newOptions.fixed.y === 'boolean') { - parentOptions.fixed.y = newOptions.fixed.y; - } - } - } + var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { desc = parent = getter = undefined; _again = false; var object = _x, + property = _x2, + receiver = _x3; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; - if (newOptions.font !== undefined) { - _sharedLabel2['default'].parseOptions(parentOptions.font, newOptions); - } + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } - if (newOptions.scaling !== undefined) { - util.mergeOptions(parentOptions.scaling, newOptions.scaling, 'label'); + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } + + var _CentralGravitySolver2 = __webpack_require__(82); + + var _CentralGravitySolver3 = _interopRequireDefault(_CentralGravitySolver2); + + var ForceAtlas2BasedCentralGravitySolver = (function (_CentralGravitySolver) { + function ForceAtlas2BasedCentralGravitySolver(body, physicsBody, options) { + _classCallCheck(this, ForceAtlas2BasedCentralGravitySolver); + + _get(Object.getPrototypeOf(ForceAtlas2BasedCentralGravitySolver.prototype), "constructor", this).call(this, body, physicsBody, options); + } + + _inherits(ForceAtlas2BasedCentralGravitySolver, _CentralGravitySolver); + + _createClass(ForceAtlas2BasedCentralGravitySolver, [{ + key: "_calculateForces", + + /** + * Calculate the forces based on the distance. + * @private + */ + value: function _calculateForces(distance, dx, dy, forces, node) { + if (distance > 0) { + var degree = node.edges.length + 1; + var gravityForce = this.options.centralGravity * degree * node.options.mass; + forces[node.id].x = dx * gravityForce; + forces[node.id].y = dy * gravityForce; } } }]); - return Node; - })(); + return ForceAtlas2BasedCentralGravitySolver; + })(_CentralGravitySolver3["default"]); - exports['default'] = Node; - module.exports = exports['default']; + exports["default"] = ForceAtlas2BasedCentralGravitySolver; + module.exports = exports["default"]; /***/ }, -/* 86 */ +/* 85 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -36671,548 +37170,442 @@ return /******/ (function(modules) { // webpackBootstrap value: true }); - var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { desc = parent = getter = undefined; _again = false; var object = _x, + property = _x2, + receiver = _x3; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - var _sharedLabel = __webpack_require__(91); + function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } - var _sharedLabel2 = _interopRequireDefault(_sharedLabel); + var _Node2 = __webpack_require__(74); - var _edgesBezierEdgeDynamic = __webpack_require__(106); + var _Node3 = _interopRequireDefault(_Node2); - var _edgesBezierEdgeDynamic2 = _interopRequireDefault(_edgesBezierEdgeDynamic); + /** + * + */ - var _edgesBezierEdgeStatic = __webpack_require__(107); + var Cluster = (function (_Node) { + function Cluster(options, body, imagelist, grouplist, globalOptions) { + _classCallCheck(this, Cluster); - var _edgesBezierEdgeStatic2 = _interopRequireDefault(_edgesBezierEdgeStatic); + _get(Object.getPrototypeOf(Cluster.prototype), 'constructor', this).call(this, options, body, imagelist, grouplist, globalOptions); - var _edgesStraightEdge = __webpack_require__(108); + this.isCluster = true; + this.containedNodes = {}; + this.containedEdges = {}; + } - var _edgesStraightEdge2 = _interopRequireDefault(_edgesStraightEdge); + _inherits(Cluster, _Node); - var util = __webpack_require__(1); + return Cluster; + })(_Node3['default']); - /** - * @class Edge - * - * A edge connects two nodes - * @param {Object} properties Object with options. Must contain - * At least options from and to. - * Available options: from (number), - * to (number), label (string, color (string), - * width (number), style (string), - * length (number), title (string) - * @param {Network} network A Network object, used to find and edge to - * nodes. - * @param {Object} constants An object with default values for - * example for the color - */ + exports['default'] = Cluster; + module.exports = exports['default']; - var Edge = (function () { - function Edge(options, body, globalOptions) { - _classCallCheck(this, Edge); +/***/ }, +/* 86 */ +/***/ function(module, exports, __webpack_require__) { - if (body === undefined) { - throw 'No body provided'; - } - this.options = util.bridgeObject(globalOptions); - this.body = body; + 'use strict'; - // initialize variables - this.id = undefined; - this.fromId = undefined; - this.toId = undefined; - this.selected = false; - this.hover = false; - this.labelDirty = true; - this.colorDirty = true; + Object.defineProperty(exports, '__esModule', { + value: true + }); - this.baseWidth = this.options.width; - this.baseFontSize = this.options.font.size; + var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); - this.from = undefined; // a node - this.to = undefined; // a node + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - this.edgeType = undefined; + var util = __webpack_require__(1); + var Hammer = __webpack_require__(41); + var hammerUtil = __webpack_require__(48); + var keycharm = __webpack_require__(88); - this.connected = false; + var NavigationHandler = (function () { + function NavigationHandler(body, canvas) { + var _this = this; - this.labelModule = new _sharedLabel2['default'](this.body, this.options); + _classCallCheck(this, NavigationHandler); - this.setOptions(options); - } + this.body = body; + this.canvas = canvas; - _createClass(Edge, [{ - key: 'setOptions', + this.iconsCreated = false; + this.navigationHammers = []; + this.boundFunctions = {}; + this.touchTime = 0; + this.activated = false; - /** - * Set or overwrite options for the edge - * @param {Object} options an object with options - * @param doNotEmit - */ - value: function setOptions(options) { - if (!options) { - return; + this.body.emitter.on('release', function () { + _this._stopMovement(); + }); + this.body.emitter.on('activate', function () { + _this.activated = true;_this.configureKeyboardBindings(); + }); + this.body.emitter.on('deactivate', function () { + _this.activated = false;_this.configureKeyboardBindings(); + }); + this.body.emitter.on('destroy', function () { + if (_this.keycharm !== undefined) { + _this.keycharm.destroy(); } - this.colorDirty = true; + }); - Edge.parseOptions(this.options, options, true); + this.options = {}; + } - if (options.id !== undefined) { - this.id = options.id; - } - if (options.from !== undefined) { - this.fromId = options.from; - } - if (options.to !== undefined) { - this.toId = options.to; - } - if (options.title !== undefined) { - this.title = options.title; + _createClass(NavigationHandler, [{ + key: 'setOptions', + value: function setOptions(options) { + if (options !== undefined) { + this.options = options; + this.create(); } - if (options.value !== undefined) { - options.value = parseInt(options.value); + } + }, { + key: 'create', + value: function create() { + if (this.options.navigationButtons === true) { + if (this.iconsCreated === false) { + this.loadNavigationElements(); + } + } else if (this.iconsCreated === true) { + this.cleanNavigation(); } - // A node is connected when it has a from and to node that both exist in the network.body.nodes. - this.connect(); - - // update label Module - this.updateLabelModule(); - - var dataChanged = this.updateEdgeType(); - - // if anything has been updates, reset the selection width and the hover width - this._setInteractionWidths(); - - return dataChanged; + this.configureKeyboardBindings(); } }, { - key: 'updateLabelModule', + key: 'cleanNavigation', + value: function cleanNavigation() { + // clean hammer bindings + if (this.navigationHammers.length != 0) { + for (var i = 0; i < this.navigationHammers.length; i++) { + this.navigationHammers[i].destroy(); + } + this.navigationHammers = []; + } - /** - * update the options in the label module - */ - value: function updateLabelModule() { - this.labelModule.setOptions(this.options, true); - if (this.labelModule.baseSize !== undefined) { - this.baseFontSize = this.labelModule.baseSize; + this._navigationReleaseOverload = function () {}; + + // clean up previous navigation items + if (this.navigationDOM && this.navigationDOM['wrapper'] && this.navigationDOM['wrapper'].parentNode) { + this.navigationDOM['wrapper'].parentNode.removeChild(this.navigationDOM['wrapper']); } + + this.iconsCreated = false; } }, { - key: 'updateEdgeType', + key: 'loadNavigationElements', /** - * update the edge type, set the options - * @returns {boolean} + * Creation of the navigation controls nodes. They are drawn over the rest of the nodes and are not affected by scale and translation + * they have a triggerFunction which is called on click. If the position of the navigation controls is dependent + * on this.frame.canvas.clientWidth or this.frame.canvas.clientHeight, we flag horizontalAlignLeft and verticalAlignTop false. + * This means that the location will be corrected by the _relocateNavigation function on a size change of the canvas. + * + * @private */ - value: function updateEdgeType() { - var dataChanged = false; - var changeInType = true; - if (this.edgeType !== undefined) { - if (this.edgeType instanceof _edgesBezierEdgeDynamic2['default'] && this.options.smooth.enabled === true && this.options.smooth.type === 'dynamic') { - changeInType = false; - } - if (this.edgeType instanceof _edgesBezierEdgeStatic2['default'] && this.options.smooth.enabled === true && this.options.smooth.type !== 'dynamic') { - changeInType = false; - } - if (this.edgeType instanceof _edgesStraightEdge2['default'] && this.options.smooth.enabled === false) { - changeInType = false; - } + value: function loadNavigationElements() { + this.cleanNavigation(); - if (changeInType === true) { - dataChanged = this.edgeType.cleanup(); - } - } + this.navigationDOM = {}; + var navigationDivs = ['up', 'down', 'left', 'right', 'zoomIn', 'zoomOut', 'zoomExtends']; + var navigationDivActions = ['_moveUp', '_moveDown', '_moveLeft', '_moveRight', '_zoomIn', '_zoomOut', '_fit']; - if (changeInType === true) { - if (this.options.smooth.enabled === true) { - if (this.options.smooth.type === 'dynamic') { - dataChanged = true; - this.edgeType = new _edgesBezierEdgeDynamic2['default'](this.options, this.body, this.labelModule); - } else { - this.edgeType = new _edgesBezierEdgeStatic2['default'](this.options, this.body, this.labelModule); - } + this.navigationDOM['wrapper'] = document.createElement('div'); + this.navigationDOM['wrapper'].className = 'vis-navigation'; + this.canvas.frame.appendChild(this.navigationDOM['wrapper']); + + for (var i = 0; i < navigationDivs.length; i++) { + this.navigationDOM[navigationDivs[i]] = document.createElement('div'); + this.navigationDOM[navigationDivs[i]].className = 'vis-button vis-' + navigationDivs[i]; + this.navigationDOM['wrapper'].appendChild(this.navigationDOM[navigationDivs[i]]); + + var hammer = new Hammer(this.navigationDOM[navigationDivs[i]]); + if (navigationDivActions[i] === '_fit') { + hammerUtil.onTouch(hammer, this._fit.bind(this)); } else { - this.edgeType = new _edgesStraightEdge2['default'](this.options, this.body, this.labelModule); + hammerUtil.onTouch(hammer, this.bindToRedraw.bind(this, navigationDivActions[i])); } - } else { - // if nothing changes, we just set the options. - this.edgeType.setOptions(this.options); + + this.navigationHammers.push(hammer); } - return dataChanged; + this.iconsCreated = true; } }, { - key: 'togglePhysics', - - /** - * Enable or disable the physics. - * @param status - */ - value: function togglePhysics(status) { - this.options.physics = status; - this.edgeType.togglePhysics(status); + key: 'bindToRedraw', + value: function bindToRedraw(action) { + if (this.boundFunctions[action] === undefined) { + this.boundFunctions[action] = this[action].bind(this); + this.body.emitter.on('initRedraw', this.boundFunctions[action]); + this.body.emitter.emit('_startRendering'); + } } }, { - key: 'connect', - - /** - * Connect an edge to its nodes - */ - value: function connect() { - this.disconnect(); - - this.from = this.body.nodes[this.fromId] || undefined; - this.to = this.body.nodes[this.toId] || undefined; - this.connected = this.from !== undefined && this.to !== undefined; - - if (this.connected === true) { - this.from.attachEdge(this); - this.to.attachEdge(this); - } else { - if (this.from) { - this.from.detachEdge(this); - } - if (this.to) { - this.to.detachEdge(this); - } + key: 'unbindFromRedraw', + value: function unbindFromRedraw(action) { + if (this.boundFunctions[action] !== undefined) { + this.body.emitter.off('initRedraw', this.boundFunctions[action]); + this.body.emitter.emit('_stopRendering'); + delete this.boundFunctions[action]; } } }, { - key: 'disconnect', + key: '_fit', /** - * Disconnect an edge from its nodes + * this stops all movement induced by the navigation buttons + * + * @private */ - value: function disconnect() { - if (this.from) { - this.from.detachEdge(this); - this.from = undefined; - } - if (this.to) { - this.to.detachEdge(this); - this.to = undefined; + value: function _fit() { + if (new Date().valueOf() - this.touchTime > 700) { + // TODO: fix ugly hack to avoid hammer's double fireing of event (because we use release?) + this.body.emitter.emit('fit', { duration: 700 }); + this.touchTime = new Date().valueOf(); } - - this.connected = false; } }, { - key: 'getTitle', + key: '_stopMovement', /** - * get the title of this edge. - * @return {string} title The title of the edge, or undefined when no title - * has been set. + * this stops all movement induced by the navigation buttons + * + * @private */ - value: function getTitle() { - return this.title; + value: function _stopMovement() { + for (var boundAction in this.boundFunctions) { + if (this.boundFunctions.hasOwnProperty(boundAction)) { + this.body.emitter.off('initRedraw', this.boundFunctions[boundAction]); + this.body.emitter.emit('_stopRendering'); + } + } + this.boundFunctions = {}; } }, { - key: 'isSelected', - - /** - * check if this node is selecte - * @return {boolean} selected True if node is selected, else false - */ - value: function isSelected() { - return this.selected; + key: '_moveUp', + value: function _moveUp() { + this.body.view.translation.y += this.options.keyboard.speed.y; } }, { - key: 'getValue', - - /** - * Retrieve the value of the edge. Can be undefined - * @return {Number} value - */ - value: function getValue() { - return this.options.value; + key: '_moveDown', + value: function _moveDown() { + this.body.view.translation.y -= this.options.keyboard.speed.y; } }, { - key: 'setValueRange', - - /** - * Adjust the value range of the edge. The edge will adjust it's width - * based on its value. - * @param {Number} min - * @param {Number} max - * @param total - */ - value: function setValueRange(min, max, total) { - if (this.options.value !== undefined) { - var scale = this.options.scaling.customScalingFunction(min, max, total, this.options.value); - var widthDiff = this.options.scaling.max - this.options.scaling.min; - if (this.options.scaling.label.enabled === true) { - var fontDiff = this.options.scaling.label.max - this.options.scaling.label.min; - this.options.font.size = this.options.scaling.label.min + scale * fontDiff; - } - this.options.width = this.options.scaling.min + scale * widthDiff; - } else { - this.options.width = this.baseWidth; - this.options.font.size = this.baseFontSize; - } - - this._setInteractionWidths(); + key: '_moveLeft', + value: function _moveLeft() { + this.body.view.translation.x += this.options.keyboard.speed.x; } }, { - key: '_setInteractionWidths', - value: function _setInteractionWidths() { - if (typeof this.options.hoverWidth === 'function') { - this.edgeType.hoverWidth = this.options.hoverWidth(this.options.width); - } else { - this.edgeType.hoverWidth = this.options.hoverWidth + this.options.width; - } - - if (typeof this.options.selectionWidth === 'function') { - this.edgeType.selectionWidth = this.options.selectionWidth(this.options.width); - } else { - this.edgeType.selectionWidth = this.options.selectionWidth + this.options.width; - } + key: '_moveRight', + value: function _moveRight() { + this.body.view.translation.x -= this.options.keyboard.speed.x; } }, { - key: 'draw', - - /** - * Redraw a edge - * Draw this edge in the given canvas - * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d"); - * @param {CanvasRenderingContext2D} ctx - */ - value: function draw(ctx) { - var via = this.edgeType.drawLine(ctx, this.selected, this.hover); - this.drawArrows(ctx, via); - this.drawLabel(ctx, via); + key: '_zoomIn', + value: function _zoomIn() { + this.body.view.scale *= 1 + this.options.keyboard.speed.zoom; } }, { - key: 'drawArrows', - value: function drawArrows(ctx, viaNode) { - if (this.options.arrows.from.enabled === true) { - this.edgeType.drawArrowHead(ctx, 'from', viaNode, this.selected, this.hover); - } - if (this.options.arrows.middle.enabled === true) { - this.edgeType.drawArrowHead(ctx, 'middle', viaNode, this.selected, this.hover); - } - if (this.options.arrows.to.enabled === true) { - this.edgeType.drawArrowHead(ctx, 'to', viaNode, this.selected, this.hover); - } + key: '_zoomOut', + value: function _zoomOut() { + this.body.view.scale /= 1 + this.options.keyboard.speed.zoom; } }, { - key: 'drawLabel', - value: function drawLabel(ctx, viaNode) { - if (this.options.label !== undefined) { - // set style - var node1 = this.from; - var node2 = this.to; - var selected = this.from.selected || this.to.selected || this.selected; - if (node1.id != node2.id) { - var point = this.edgeType.getPoint(0.5, viaNode); - ctx.save(); + key: 'configureKeyboardBindings', - // if the label has to be rotated: - if (this.options.font.align !== 'horizontal') { - this.labelModule.calculateLabelSize(ctx, selected, point.x, point.y); - ctx.translate(point.x, this.labelModule.size.yLine); - this._rotateForLabelAlignment(ctx); - } + /** + * bind all keys using keycharm. + */ + value: function configureKeyboardBindings() { + if (this.keycharm !== undefined) { + this.keycharm.destroy(); + } - // draw the label - this.labelModule.draw(ctx, point.x, point.y, selected); - ctx.restore(); + if (this.options.keyboard.enabled === true) { + + if (this.options.keyboard.bindToWindow === true) { + this.keycharm = keycharm({ container: window, preventDefault: true }); } else { - var x, y; - var radius = this.options.selfReferenceSize; - if (node1.shape.width > node1.shape.height) { - x = node1.x + node1.shape.width * 0.5; - y = node1.y - radius; - } else { - x = node1.x + radius; - y = node1.y - node1.shape.height * 0.5; - } - point = this._pointOnCircle(x, y, radius, 0.125); - this.labelModule.draw(ctx, point.x, point.y, selected); + this.keycharm = keycharm({ container: this.canvas.frame, preventDefault: true }); } - } - } - }, { - key: 'isOverlappingWith', - /** - * Check if this object is overlapping with the provided object - * @param {Object} obj an object with parameters left, top - * @return {boolean} True if location is located on the edge - */ - value: function isOverlappingWith(obj) { - if (this.connected) { - var distMax = 10; - var xFrom = this.from.x; - var yFrom = this.from.y; - var xTo = this.to.x; - var yTo = this.to.y; - var xObj = obj.left; - var yObj = obj.top; + this.keycharm.reset(); - var dist = this.edgeType.getDistanceToEdge(xFrom, yFrom, xTo, yTo, xObj, yObj); + if (this.activated === true) { + this.keycharm.bind('up', this.bindToRedraw.bind(this, '_moveUp'), 'keydown'); + this.keycharm.bind('down', this.bindToRedraw.bind(this, '_moveDown'), 'keydown'); + this.keycharm.bind('left', this.bindToRedraw.bind(this, '_moveLeft'), 'keydown'); + this.keycharm.bind('right', this.bindToRedraw.bind(this, '_moveRight'), 'keydown'); + this.keycharm.bind('=', this.bindToRedraw.bind(this, '_zoomIn'), 'keydown'); + this.keycharm.bind('num+', this.bindToRedraw.bind(this, '_zoomIn'), 'keydown'); + this.keycharm.bind('num-', this.bindToRedraw.bind(this, '_zoomOut'), 'keydown'); + this.keycharm.bind('-', this.bindToRedraw.bind(this, '_zoomOut'), 'keydown'); + this.keycharm.bind('[', this.bindToRedraw.bind(this, '_zoomOut'), 'keydown'); + this.keycharm.bind(']', this.bindToRedraw.bind(this, '_zoomIn'), 'keydown'); + this.keycharm.bind('pageup', this.bindToRedraw.bind(this, '_zoomIn'), 'keydown'); + this.keycharm.bind('pagedown', this.bindToRedraw.bind(this, '_zoomOut'), 'keydown'); - return dist < distMax; - } else { - return false; + this.keycharm.bind('up', this.unbindFromRedraw.bind(this, '_moveUp'), 'keyup'); + this.keycharm.bind('down', this.unbindFromRedraw.bind(this, '_moveDown'), 'keyup'); + this.keycharm.bind('left', this.unbindFromRedraw.bind(this, '_moveLeft'), 'keyup'); + this.keycharm.bind('right', this.unbindFromRedraw.bind(this, '_moveRight'), 'keyup'); + this.keycharm.bind('=', this.unbindFromRedraw.bind(this, '_zoomIn'), 'keyup'); + this.keycharm.bind('num+', this.unbindFromRedraw.bind(this, '_zoomIn'), 'keyup'); + this.keycharm.bind('num-', this.unbindFromRedraw.bind(this, '_zoomOut'), 'keyup'); + this.keycharm.bind('-', this.unbindFromRedraw.bind(this, '_zoomOut'), 'keyup'); + this.keycharm.bind('[', this.unbindFromRedraw.bind(this, '_zoomOut'), 'keyup'); + this.keycharm.bind(']', this.unbindFromRedraw.bind(this, '_zoomIn'), 'keyup'); + this.keycharm.bind('pageup', this.unbindFromRedraw.bind(this, '_zoomIn'), 'keyup'); + this.keycharm.bind('pagedown', this.unbindFromRedraw.bind(this, '_zoomOut'), 'keyup'); + } } } - }, { - key: '_rotateForLabelAlignment', + }]); - /** - * Rotates the canvas so the text is most readable - * @param {CanvasRenderingContext2D} ctx - * @private - */ - value: function _rotateForLabelAlignment(ctx) { - var dy = this.from.y - this.to.y; - var dx = this.from.x - this.to.x; - var angleInDegrees = Math.atan2(dy, dx); + return NavigationHandler; + })(); - // rotate so label it is readable - if (angleInDegrees < -1 && dx < 0 || angleInDegrees > 0 && dx < 0) { - angleInDegrees = angleInDegrees + Math.PI; - } + exports['default'] = NavigationHandler; + module.exports = exports['default']; - ctx.rotate(angleInDegrees); - } - }, { - key: '_pointOnCircle', +/***/ }, +/* 87 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, '__esModule', { + value: true + }); + + var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + + /** + * Popup is a class to create a popup window with some text + * @param {Element} container The container object. + * @param {Number} [x] + * @param {Number} [y] + * @param {String} [text] + * @param {Object} [style] An object containing borderColor, + * backgroundColor, etc. + */ + + var Popup = (function () { + function Popup(container) { + _classCallCheck(this, Popup); + + this.container = container; + + this.x = 0; + this.y = 0; + this.padding = 5; + this.hidden = false; + + // create the frame + this.frame = document.createElement('div'); + this.frame.className = 'vis-network-tooltip'; + this.container.appendChild(this.frame); + } + + _createClass(Popup, [{ + key: 'setPosition', /** - * Get a point on a circle - * @param {Number} x - * @param {Number} y - * @param {Number} radius - * @param {Number} percentage. Value between 0 (line start) and 1 (line end) - * @return {Object} point - * @private + * @param {number} x Horizontal position of the popup window + * @param {number} y Vertical position of the popup window */ - value: function _pointOnCircle(x, y, radius, percentage) { - var angle = percentage * 2 * Math.PI; - return { - x: x + radius * Math.cos(angle), - y: y - radius * Math.sin(angle) - }; + value: function setPosition(x, y) { + this.x = parseInt(x); + this.y = parseInt(y); } }, { - key: 'select', - value: function select() { - this.selected = true; + key: 'setText', + + /** + * Set the content for the popup window. This can be HTML code or text. + * @param {string | Element} content + */ + value: function setText(content) { + if (content instanceof Element) { + this.frame.innerHTML = ''; + this.frame.appendChild(content); + } else { + this.frame.innerHTML = content; // string containing text or HTML + } } }, { - key: 'unselect', - value: function unselect() { - this.selected = false; - } - }], [{ - key: 'parseOptions', - value: function parseOptions(parentOptions, newOptions) { - var allowDeletion = arguments[2] === undefined ? false : arguments[2]; - - var fields = ['id', 'from', 'hidden', 'hoverWidth', 'label', 'length', 'line', 'opacity', 'physics', 'selectionWidth', 'selfReferenceSize', 'to', 'title', 'value', 'width']; - - // only deep extend the items in the field array. These do not have shorthand. - util.selectiveDeepExtend(fields, parentOptions, newOptions, allowDeletion); - - util.mergeOptions(parentOptions, newOptions, 'smooth'); - util.mergeOptions(parentOptions, newOptions, 'shadow'); + key: 'show', - if (newOptions.dashes !== undefined && newOptions.dashes !== null) { - parentOptions.dashes = newOptions.dashes; - } else if (allowDeletion === true && newOptions.dashes === null) { - parentOptions.dashes = undefined; - delete parentOptions.dashes; + /** + * Show the popup window + * @param {boolean} [doShow] Show or hide the window + */ + value: function show(doShow) { + if (doShow === undefined) { + doShow = true; } - // set the scaling newOptions - if (newOptions.scaling !== undefined && newOptions.scaling !== null) { - if (newOptions.scaling.min !== undefined) { - parentOptions.scaling.min = newOptions.scaling.min; + if (doShow === true) { + var height = this.frame.clientHeight; + var width = this.frame.clientWidth; + var maxHeight = this.frame.parentNode.clientHeight; + var maxWidth = this.frame.parentNode.clientWidth; + + var top = this.y - height; + if (top + height + this.padding > maxHeight) { + top = maxHeight - height - this.padding; } - if (newOptions.scaling.max !== undefined) { - parentOptions.scaling.max = newOptions.scaling.max; + if (top < this.padding) { + top = this.padding; } - util.mergeOptions(parentOptions.scaling, newOptions.scaling, 'label'); - } else if (allowDeletion === true && newOptions.scaling === null) { - parentOptions.scaling = undefined; - delete parentOptions.scaling; - } - // hanlde multiple input cases for arrows - if (newOptions.arrows !== undefined && newOptions.arrows !== null) { - if (typeof newOptions.arrows === 'string') { - var arrows = newOptions.arrows.toLowerCase(); - if (arrows.indexOf('to') != -1) { - parentOptions.arrows.to.enabled = true; - } - if (arrows.indexOf('middle') != -1) { - parentOptions.arrows.middle.enabled = true; - } - if (arrows.indexOf('from') != -1) { - parentOptions.arrows.from.enabled = true; - } - } else if (typeof newOptions.arrows === 'object') { - util.mergeOptions(parentOptions.arrows, newOptions.arrows, 'to'); - util.mergeOptions(parentOptions.arrows, newOptions.arrows, 'middle'); - util.mergeOptions(parentOptions.arrows, newOptions.arrows, 'from'); - } else { - throw new Error('The arrow newOptions can only be an object or a string. Refer to the documentation. You used:' + JSON.stringify(newOptions.arrows)); + var left = this.x; + if (left + width + this.padding > maxWidth) { + left = maxWidth - width - this.padding; } - } else if (allowDeletion === true && newOptions.arrows === null) { - parentOptions.arrows = undefined; - delete parentOptions.arrows; - } - - // hanlde multiple input cases for color - if (newOptions.color !== undefined && newOptions.color !== null) { - if (util.isString(newOptions.color)) { - parentOptions.color.color = newOptions.color; - parentOptions.color.highlight = newOptions.color; - parentOptions.color.hover = newOptions.color; - parentOptions.color.inherit = false; - } else { - var colorsDefined = false; - if (newOptions.color.color !== undefined) { - parentOptions.color.color = newOptions.color.color;colorsDefined = true; - } - if (newOptions.color.highlight !== undefined) { - parentOptions.color.highlight = newOptions.color.highlight;colorsDefined = true; - } - if (newOptions.color.hover !== undefined) { - parentOptions.color.hover = newOptions.color.hover;colorsDefined = true; - } - if (newOptions.color.inherit !== undefined) { - parentOptions.color.inherit = newOptions.color.inherit; - } - if (newOptions.color.opacity !== undefined) { - parentOptions.color.opacity = Math.min(1, Math.max(0, newOptions.color.opacity)); - } - - if (newOptions.color.inherit === undefined && colorsDefined === true) { - parentOptions.color.inherit = false; - } + if (left < this.padding) { + left = this.padding; } - } else if (allowDeletion === true && newOptions.color === null) { - parentOptions.color = undefined; - delete parentOptions.color; + + this.frame.style.left = left + 'px'; + this.frame.style.top = top + 'px'; + this.frame.style.visibility = 'visible'; + this.hidden = false; + } else { + this.hide(); } } + }, { + key: 'hide', + + /** + * Hide the popup window + */ + value: function hide() { + this.hidden = true; + this.frame.style.visibility = 'hidden'; + } }]); - return Edge; + return Popup; })(); - exports['default'] = Edge; + exports['default'] = Popup; module.exports = exports['default']; /***/ }, -/* 87 */ +/* 88 */ /***/ function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;"use strict"; @@ -37410,7 +37803,7 @@ return /******/ (function(modules) { // webpackBootstrap /***/ }, -/* 88 */ +/* 89 */ /***/ function(module, exports, __webpack_require__) { function webpackContext(req) { @@ -37419,350 +37812,11 @@ return /******/ (function(modules) { // webpackBootstrap webpackContext.keys = function() { return []; }; webpackContext.resolve = webpackContext; module.exports = webpackContext; - webpackContext.id = 88; - - -/***/ }, -/* 89 */ -/***/ function(module, exports, __webpack_require__) { - - module.exports = function(module) { - if(!module.webpackPolyfill) { - module.deprecate = function() {}; - module.paths = []; - // module.parent = undefined by default - module.children = []; - module.webpackPolyfill = 1; - } - return module; - } + webpackContext.id = 89; /***/ }, /* 90 */ -/***/ function(module, exports, __webpack_require__) { - - /* WEBPACK VAR INJECTION */(function(__webpack_amd_options__) {module.exports = __webpack_amd_options__; - - /* WEBPACK VAR INJECTION */}.call(exports, {})) - -/***/ }, -/* 91 */ -/***/ function(module, exports, __webpack_require__) { - - 'use strict'; - - Object.defineProperty(exports, '__esModule', { - value: true - }); - - var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); - - function _slicedToArray(arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i['return']) _i['return'](); } finally { if (_d) throw _e; } } return _arr; } else { throw new TypeError('Invalid attempt to destructure non-iterable instance'); } } - - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - - var util = __webpack_require__(1); - - var Label = (function () { - function Label(body, options) { - _classCallCheck(this, Label); - - this.body = body; - - this.baseSize = undefined; - this.setOptions(options); - this.size = { top: 0, left: 0, width: 0, height: 0, yLine: 0 }; // could be cached - } - - _createClass(Label, [{ - key: 'setOptions', - value: function setOptions(options) { - var allowDeletion = arguments[1] === undefined ? false : arguments[1]; - - this.options = options; - - if (options.label !== undefined) { - this.labelDirty = true; - } - - if (options.font !== undefined) { - Label.parseOptions(this.options.font, options, allowDeletion); - if (typeof options.font === 'string') { - this.baseSize = this.options.font.size; - } else if (typeof options.font === 'object') { - if (options.font.size !== undefined) { - this.baseSize = options.font.size; - } - } - } - } - }, { - key: 'draw', - - /** - * Main function. This is called from anything that wants to draw a label. - * @param ctx - * @param x - * @param y - * @param selected - * @param baseline - */ - value: function draw(ctx, x, y, selected) { - var baseline = arguments[4] === undefined ? 'middle' : arguments[4]; - - // if no label, return - if (this.options.label === undefined) return; - - // check if we have to render the label - var viewFontSize = this.options.font.size * this.body.view.scale; - if (this.options.label && viewFontSize < this.options.scaling.label.drawThreshold - 1) return; - - // update the size cache if required - this.calculateLabelSize(ctx, selected, x, y, baseline); - - // create the fontfill background - this._drawBackground(ctx); - // draw text - this._drawText(ctx, selected, x, y, baseline); - } - }, { - key: '_drawBackground', - - /** - * Draws the label background - * @param {CanvasRenderingContext2D} ctx - * @private - */ - value: function _drawBackground(ctx) { - if (this.options.font.background !== undefined && this.options.font.background !== 'none') { - ctx.fillStyle = this.options.font.background; - - var lineMargin = 2; - - switch (this.options.font.align) { - case 'middle': - ctx.fillRect(-this.size.width * 0.5, -this.size.height * 0.5, this.size.width, this.size.height); - break; - case 'top': - ctx.fillRect(-this.size.width * 0.5, -(this.size.height + lineMargin), this.size.width, this.size.height); - break; - case 'bottom': - ctx.fillRect(-this.size.width * 0.5, lineMargin, this.size.width, this.size.height); - break; - default: - ctx.fillRect(this.size.left, this.size.top - 0.5 * lineMargin, this.size.width, this.size.height); - break; - } - } - } - }, { - key: '_drawText', - - /** - * - * @param ctx - * @param x - * @param baseline - * @private - */ - value: function _drawText(ctx, selected, x, y) { - var baseline = arguments[4] === undefined ? 'middle' : arguments[4]; - - var fontSize = this.options.font.size; - var viewFontSize = fontSize * this.body.view.scale; - // this ensures that there will not be HUGE letters on screen by setting an upper limit on the visible text size (regardless of zoomLevel) - if (viewFontSize >= this.options.scaling.label.maxVisible) { - fontSize = Number(this.options.scaling.label.maxVisible) / this.body.view.scale; - } - - var yLine = this.size.yLine; - - var _getColor = this._getColor(viewFontSize); - - var _getColor2 = _slicedToArray(_getColor, 2); - - var fontColor = _getColor2[0]; - var strokeColor = _getColor2[1]; - - var _setAlignment = this._setAlignment(ctx, x, yLine, baseline); - - var _setAlignment2 = _slicedToArray(_setAlignment, 2); - - x = _setAlignment2[0]; - yLine = _setAlignment2[1]; - - // configure context for drawing the text - ctx.font = (selected ? 'bold ' : '') + fontSize + 'px ' + this.options.font.face; - ctx.fillStyle = fontColor; - ctx.textAlign = 'center'; - - // set the strokeWidth - if (this.options.font.strokeWidth > 0) { - ctx.lineWidth = this.options.font.strokeWidth; - ctx.strokeStyle = strokeColor; - ctx.lineJoin = 'round'; - } - - // draw the text - for (var i = 0; i < this.lineCount; i++) { - if (this.options.font.strokeWidth > 0) { - ctx.strokeText(this.lines[i], x, yLine); - } - ctx.fillText(this.lines[i], x, yLine); - yLine += fontSize; - } - } - }, { - key: '_setAlignment', - value: function _setAlignment(ctx, x, yLine, baseline) { - // check for label alignment (for edges) - // TODO: make alignment for nodes - if (this.options.font.align !== 'horizontal') { - x = 0; - yLine = 0; - - var lineMargin = 2; - if (this.options.font.align === 'top') { - ctx.textBaseline = 'alphabetic'; - yLine -= 2 * lineMargin; // distance from edge, required because we use alphabetic. Alphabetic has less difference between browsers - } else if (this.options.font.align === 'bottom') { - ctx.textBaseline = 'hanging'; - yLine += 2 * lineMargin; // distance from edge, required because we use hanging. Hanging has less difference between browsers - } else { - ctx.textBaseline = 'middle'; - } - } else { - ctx.textBaseline = baseline; - } - - return [x, yLine]; - } - }, { - key: '_getColor', - - /** - * fade in when relative scale is between threshold and threshold - 1. - * If the relative scale would be smaller than threshold -1 the draw function would have returned before coming here. - * - * @param viewFontSize - * @returns {*[]} - * @private - */ - value: function _getColor(viewFontSize) { - var fontColor = this.options.font.color || '#000000'; - var strokeColor = this.options.font.strokeColor || '#ffffff'; - if (viewFontSize <= this.options.scaling.label.drawThreshold) { - var opacity = Math.max(0, Math.min(1, 1 - (this.options.scaling.label.drawThreshold - viewFontSize))); - fontColor = util.overrideOpacity(fontColor, opacity); - strokeColor = util.overrideOpacity(strokeColor, opacity); - } - return [fontColor, strokeColor]; - } - }, { - key: 'getTextSize', - - /** - * - * @param ctx - * @param selected - * @returns {{width: number, height: number}} - */ - value: function getTextSize(ctx) { - var selected = arguments[1] === undefined ? false : arguments[1]; - - var size = { - width: this._processLabel(ctx, selected), - height: this.options.font.size * this.lineCount, - lineCount: this.lineCount - }; - return size; - } - }, { - key: 'calculateLabelSize', - - /** - * - * @param ctx - * @param selected - * @param x - * @param y - * @param baseline - */ - value: function calculateLabelSize(ctx, selected) { - var x = arguments[2] === undefined ? 0 : arguments[2]; - var y = arguments[3] === undefined ? 0 : arguments[3]; - var baseline = arguments[4] === undefined ? 'middle' : arguments[4]; - - if (this.labelDirty === true) { - this.size.width = this._processLabel(ctx, selected); - } - this.size.height = this.options.font.size * this.lineCount; - this.size.left = x - this.size.width * 0.5; - this.size.top = y - this.size.height * 0.5; - this.size.yLine = y + (1 - this.lineCount) * 0.5 * this.options.font.size; - if (baseline === 'hanging') { - this.size.top += 0.5 * this.options.font.size; - this.size.top += 4; // distance from node, required because we use hanging. Hanging has less difference between browsers - this.size.yLine += 4; // distance from node - } - - this.labelDirty = false; - } - }, { - key: '_processLabel', - - /** - * This calculates the width as well as explodes the label string and calculates the amount of lines. - * @param ctx - * @param selected - * @returns {number} - * @private - */ - value: function _processLabel(ctx, selected) { - var width = 0; - var lines = ['']; - var lineCount = 0; - if (this.options.label !== undefined) { - lines = String(this.options.label).split('\n'); - lineCount = lines.length; - ctx.font = (selected ? 'bold ' : '') + this.options.font.size + 'px ' + this.options.font.face; - width = ctx.measureText(lines[0]).width; - for (var i = 1; i < lineCount; i++) { - var lineWidth = ctx.measureText(lines[i]).width; - width = lineWidth > width ? lineWidth : width; - } - } - this.lines = lines; - this.lineCount = lineCount; - - return width; - } - }], [{ - key: 'parseOptions', - value: function parseOptions(parentOptions, newOptions) { - var allowDeletion = arguments[2] === undefined ? false : arguments[2]; - - if (typeof newOptions.font === 'string') { - var newOptionsArray = newOptions.font.split(' '); - parentOptions.size = newOptionsArray[0].replace('px', ''); - parentOptions.face = newOptionsArray[1]; - parentOptions.color = newOptionsArray[2]; - } else if (typeof newOptions.font === 'object') { - util.fillIfDefined(parentOptions, newOptions.font, allowDeletion); - } - parentOptions.size = Number(parentOptions.size); - } - }]); - - return Label; - })(); - - exports['default'] = Label; - module.exports = exports['default']; - -/***/ }, -/* 92 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -37871,7 +37925,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 93 */ +/* 91 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -37965,7 +38019,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 94 */ +/* 92 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -38073,7 +38127,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 95 */ +/* 93 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -38182,7 +38236,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 96 */ +/* 94 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -38242,7 +38296,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 97 */ +/* 95 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -38302,7 +38356,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 98 */ +/* 96 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -38413,7 +38467,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 99 */ +/* 97 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -38533,7 +38587,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 100 */ +/* 98 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -38624,7 +38678,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 101 */ +/* 99 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -38685,7 +38739,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 102 */ +/* 100 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -38745,7 +38799,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 103 */ +/* 101 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -38834,7 +38888,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 104 */ +/* 102 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -38894,7 +38948,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 105 */ +/* 103 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -38954,7 +39008,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 106 */ +/* 104 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -39112,7 +39166,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 107 */ +/* 105 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -39378,7 +39432,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 108 */ +/* 106 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -39489,6 +39543,30 @@ return /******/ (function(modules) { // webpackBootstrap exports['default'] = StraightEdge; module.exports = exports['default']; +/***/ }, +/* 107 */ +/***/ function(module, exports, __webpack_require__) { + + module.exports = function(module) { + if(!module.webpackPolyfill) { + module.deprecate = function() {}; + module.paths = []; + // module.parent = undefined by default + module.children = []; + module.webpackPolyfill = 1; + } + return module; + } + + +/***/ }, +/* 108 */ +/***/ function(module, exports, __webpack_require__) { + + /* WEBPACK VAR INJECTION */(function(__webpack_amd_options__) {module.exports = __webpack_amd_options__; + + /* WEBPACK VAR INJECTION */}.call(exports, {})) + /***/ }, /* 109 */ /***/ function(module, exports, __webpack_require__) { diff --git a/docs/network/index.html b/docs/network/index.html index 80b8ac47..1ea95686 100644 --- a/docs/network/index.html +++ b/docs/network/index.html @@ -551,16 +551,18 @@ var locales = {
Number hubsize
,
+ [Number hubsize]
,
[Object options]
)
hubsize
qualify. Cluster by connection is performed on each of them.
- The
- options object is described for clusterByConnection
and does the same here.
+ specified with the hubsize
qualify. If a hubsize is not defined, the hubsize will be determined as the average
+ value plus two standard deviations. clusterByConnection
and does the same here.
nodeOptions: Object
clusterOptions: Object
,childNodesOptions: Array
,childEdgesOptions: Array
- This is the same example as 21_data_manipulation.html, except that there is a select box added which allows to switch locale. The localization is only relevant to the manipulation buttons. -
- -- - -
- -id | - |
label | - |
+ Create a simple network with some nodes and edges. +
+ + + + + + + + diff --git a/examples/network/categories/events/physicsEvents.html b/examples/network/categories/events/physicsEvents.html new file mode 100644 index 00000000..2dd1c3e5 --- /dev/null +++ b/examples/network/categories/events/physicsEvents.html @@ -0,0 +1,73 @@ + + + ++ Create a simple network with some nodes and edges. +
+ + + + + + + + diff --git a/examples/network/categories/events/renderEvents.html b/examples/network/categories/events/renderEvents.html new file mode 100644 index 00000000..a92bf81d --- /dev/null +++ b/examples/network/categories/events/renderEvents.html @@ -0,0 +1,83 @@ + + + ++ You can draw on the canvas using normal HTML5 canvas functions. The before drawing will be behind the network, the after drawing will be in front of the network. +
+ + + + + + + + diff --git a/examples/network/categories/10_multiline_text.html b/examples/network/categories/labels/multiline_text.html similarity index 62% rename from examples/network/categories/10_multiline_text.html rename to examples/network/categories/labels/multiline_text.html index 7d755b44..78c1baf2 100644 --- a/examples/network/categories/10_multiline_text.html +++ b/examples/network/categories/labels/multiline_text.html @@ -11,14 +11,14 @@ } - - + + - + diff --git a/examples/network/categories/25_physics_configuration.html b/examples/network/categories/physics/25_physics_configuration.html similarity index 100% rename from examples/network/categories/25_physics_configuration.html rename to examples/network/categories/physics/25_physics_configuration.html diff --git a/examples/network/categories/33_animation.html b/examples/network/categories/rest/animationShowcase.html similarity index 67% rename from examples/network/categories/33_animation.html rename to examples/network/categories/rest/animationShowcase.html index 39962d02..e703438d 100644 --- a/examples/network/categories/33_animation.html +++ b/examples/network/categories/rest/animationShowcase.html @@ -34,17 +34,20 @@ } - - + + + - ++Click any of the buttons below to cluster the network. On every push the network will be reinitialized first. You can click on a cluster to open it. +
+ ++ The localization is only relevant to the manipulation buttons. +
+ ++ + +
+ +id | + |
label | + |
Icons: | -![]() |
- ![]() |
- ![]() |
- ![]() |
- ![]() |
- ![]() |
- ![]() |
+ ![]() |
+ ![]() |
+ ![]() |
+ ![]() |
+ ![]() |
+ ![]() |
+ ![]() |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Keyboard shortcuts: | diff --git a/lib/network/Network.js b/lib/network/Network.js index 78f96759..00adb0e6 100644 --- a/lib/network/Network.js +++ b/lib/network/Network.js @@ -163,7 +163,7 @@ Network.prototype.setOptions = function (options) { this.nodesHandler.setOptions(options.nodes); this.edgesHandler.setOptions(options.edges); this.physics.setOptions(options.physics); - this.manipulation.setOptions(options.manipulation,options); // manipulation uses the locales in the globals + this.manipulation.setOptions(options.manipulation, options, this.options); // manipulation uses the locales in the globals this.interactionHandler.setOptions(options.interaction); this.renderer.setOptions(options.interaction); // options for rendering are in interaction @@ -428,46 +428,46 @@ Network.prototype.isActive = function () { }; -Network.prototype.setSize = function() {this.canvas.setSize.apply(this.canvas,arguments);}; -Network.prototype.canvasToDOM = function() {this.canvas.canvasToDOM.apply(this.canvas,arguments);}; -Network.prototype.DOMtoCanvas = function() {this.canvas.setSize.DOMtoCanvas(this.canvas,arguments);}; -Network.prototype.findNode = function() {this.clustering.findNode.apply(this.clustering,arguments);}; -Network.prototype.isCluster = function() {this.clustering.isCluster.apply(this.clustering,arguments);}; -Network.prototype.openCluster = function() {this.clustering.openCluster.apply(this.clustering,arguments);}; -Network.prototype.cluster = function() {this.clustering.cluster.apply(this.clustering,arguments);}; -Network.prototype.clusterByConnection = function() {this.clustering.clusterByConnection.apply(this.clustering,arguments);}; -Network.prototype.clusterByHubsize = function() {this.clustering.clusterByHubsize.apply(this.clustering,arguments);}; -Network.prototype.clusterOutliers = function() {this.clustering.clusterOutliers.apply(this.clustering,arguments);}; -Network.prototype.getSeed = function() {this.layoutEngine.getSeed.apply(this.layoutEngine,arguments);}; -Network.prototype.enableEditMode = function() {this.manipulation.enableEditMode.apply(this.manipulation,arguments);}; -Network.prototype.disableEditMode = function() {this.manipulation.disableEditMode.apply(this.manipulation,arguments);}; -Network.prototype.addNodeMode = function() {this.manipulation.addNodeMode.apply(this.manipulation,arguments);}; -Network.prototype.editNodeMode = function() {this.manipulation.editNodeMode.apply(this.manipulation,arguments);}; -Network.prototype.addEdgeMode = function() {this.manipulation.addEdgeMode.apply(this.manipulation,arguments);}; -Network.prototype.editEdgeMode = function() {this.manipulation.editEdgeMode.apply(this.manipulation,arguments);}; -Network.prototype.deleteSelected = function() {this.manipulation.deleteSelected.apply(this.manipulation,arguments);}; -Network.prototype.getPositions = function() {this.nodesHandler.getPositions.apply(this.nodesHandler,arguments);}; -Network.prototype.storePositions = function() {this.nodesHandler.storePositions.apply(this.nodesHandler,arguments);}; -Network.prototype.getBoundingBox = function() {this.nodesHandler.getBoundingBox.apply(this.nodesHandler,arguments);}; -Network.prototype.getConnectedNodes = function() {this.nodesHandler.getConnectedNodes.apply(this.nodesHandler,arguments);}; -Network.prototype.getEdges = function() {this.nodesHandler.getEdges.apply(this.nodesHandler,arguments);}; -Network.prototype.startSimulation = function() {this.physics.startSimulation.apply(this.physics,arguments);}; -Network.prototype.stopSimulation = function() {this.physics.stopSimulation.apply(this.physics,arguments);}; -Network.prototype.stabilize = function() {this.physics.stabilize.apply(this.physics,arguments);}; -Network.prototype.getSelection = function() {this.selectionHandler.getSelection.apply(this.selectionHandler,arguments);}; -Network.prototype.getSelectedNodes = function() {this.selectionHandler.getSelectedNodes.apply(this.selectionHandler,arguments);}; -Network.prototype.getSelectedEdges = function() {this.selectionHandler.getSelectedEdges.apply(this.selectionHandler,arguments);}; -Network.prototype.getNodeAt = function() {this.selectionHandler.getNodeAt.apply(this.selectionHandler,arguments);}; -Network.prototype.getEdgeAt = function() {this.selectionHandler.getEdgeAt.apply(this.selectionHandler,arguments);}; -Network.prototype.selectNodes = function() {this.selectionHandler.selectNodes.apply(this.selectionHandler,arguments);}; -Network.prototype.selectEdges = function() {this.selectionHandler.selectEdges.apply(this.selectionHandler,arguments);}; -Network.prototype.unselectAll = function() {this.selectionHandler.unselectAll.apply(this.selectionHandler,arguments);}; -Network.prototype.redraw = function() {this.renderer.redraw.apply(this.renderer,arguments);}; -Network.prototype.getScale = function() {this.view.getScale.apply(this.view,arguments);}; -Network.prototype.getPosition = function() {this.view.getPosition.apply(this.view,arguments);}; -Network.prototype.fit = function() {this.view.fit.apply(this.view,arguments);}; -Network.prototype.moveTo = function() {this.view.moveTo.apply(this.view,arguments);}; -Network.prototype.focus = function() {this.view.focus.apply(this.view,arguments);}; -Network.prototype.releaseNode = function() {this.view.releaseNode.apply(this.view,arguments);}; +Network.prototype.setSize = function() {return this.canvas.setSize.apply(this.canvas,arguments);}; +Network.prototype.canvasToDOM = function() {return this.canvas.canvasToDOM.apply(this.canvas,arguments);}; +Network.prototype.DOMtoCanvas = function() {return this.canvas.setSize.DOMtoCanvas(this.canvas,arguments);}; +Network.prototype.findNode = function() {return this.clustering.findNode.apply(this.clustering,arguments);}; +Network.prototype.isCluster = function() {return this.clustering.isCluster.apply(this.clustering,arguments);}; +Network.prototype.openCluster = function() {return this.clustering.openCluster.apply(this.clustering,arguments);}; +Network.prototype.cluster = function() {return this.clustering.cluster.apply(this.clustering,arguments);}; +Network.prototype.clusterByConnection = function() {return this.clustering.clusterByConnection.apply(this.clustering,arguments);}; +Network.prototype.clusterByHubsize = function() {return this.clustering.clusterByHubsize.apply(this.clustering,arguments);}; +Network.prototype.clusterOutliers = function() {return this.clustering.clusterOutliers.apply(this.clustering,arguments);}; +Network.prototype.getSeed = function() {return this.layoutEngine.getSeed.apply(this.layoutEngine,arguments);}; +Network.prototype.enableEditMode = function() {return this.manipulation.enableEditMode.apply(this.manipulation,arguments);}; +Network.prototype.disableEditMode = function() {return this.manipulation.disableEditMode.apply(this.manipulation,arguments);}; +Network.prototype.addNodeMode = function() {return this.manipulation.addNodeMode.apply(this.manipulation,arguments);}; +Network.prototype.editNodeMode = function() {return this.manipulation.editNodeMode.apply(this.manipulation,arguments);}; +Network.prototype.addEdgeMode = function() {return this.manipulation.addEdgeMode.apply(this.manipulation,arguments);}; +Network.prototype.editEdgeMode = function() {return this.manipulation.editEdgeMode.apply(this.manipulation,arguments);}; +Network.prototype.deleteSelected = function() {return this.manipulation.deleteSelected.apply(this.manipulation,arguments);}; +Network.prototype.getPositions = function() {return this.nodesHandler.getPositions.apply(this.nodesHandler,arguments);}; +Network.prototype.storePositions = function() {return this.nodesHandler.storePositions.apply(this.nodesHandler,arguments);}; +Network.prototype.getBoundingBox = function() {return this.nodesHandler.getBoundingBox.apply(this.nodesHandler,arguments);}; +Network.prototype.getConnectedNodes = function() {return this.nodesHandler.getConnectedNodes.apply(this.nodesHandler,arguments);}; +Network.prototype.getEdges = function() {return this.nodesHandler.getEdges.apply(this.nodesHandler,arguments);}; +Network.prototype.startSimulation = function() {return this.physics.startSimulation.apply(this.physics,arguments);}; +Network.prototype.stopSimulation = function() {return this.physics.stopSimulation.apply(this.physics,arguments);}; +Network.prototype.stabilize = function() {return this.physics.stabilize.apply(this.physics,arguments);}; +Network.prototype.getSelection = function() {return this.selectionHandler.getSelection.apply(this.selectionHandler,arguments);}; +Network.prototype.getSelectedNodes = function() {return this.selectionHandler.getSelectedNodes.apply(this.selectionHandler,arguments);}; +Network.prototype.getSelectedEdges = function() {return this.selectionHandler.getSelectedEdges.apply(this.selectionHandler,arguments);}; +Network.prototype.getNodeAt = function() {return this.selectionHandler.getNodeAt.apply(this.selectionHandler,arguments);}; +Network.prototype.getEdgeAt = function() {return this.selectionHandler.getEdgeAt.apply(this.selectionHandler,arguments);}; +Network.prototype.selectNodes = function() {return this.selectionHandler.selectNodes.apply(this.selectionHandler,arguments);}; +Network.prototype.selectEdges = function() {return this.selectionHandler.selectEdges.apply(this.selectionHandler,arguments);}; +Network.prototype.unselectAll = function() {return this.selectionHandler.unselectAll.apply(this.selectionHandler,arguments);}; +Network.prototype.redraw = function() {return this.renderer.redraw.apply(this.renderer,arguments);}; +Network.prototype.getScale = function() {return this.view.getScale.apply(this.view,arguments);}; +Network.prototype.getPosition = function() {return this.view.getPosition.apply(this.view,arguments);}; +Network.prototype.fit = function() {return this.view.fit.apply(this.view,arguments);}; +Network.prototype.moveTo = function() {return this.view.moveTo.apply(this.view,arguments);}; +Network.prototype.focus = function() {return this.view.focus.apply(this.view,arguments);}; +Network.prototype.releaseNode = function() {return this.view.releaseNode.apply(this.view,arguments);}; module.exports = Network; \ No newline at end of file diff --git a/lib/network/modules/CanvasRenderer.js b/lib/network/modules/CanvasRenderer.js index ac2b8ca4..a2ec76f4 100644 --- a/lib/network/modules/CanvasRenderer.js +++ b/lib/network/modules/CanvasRenderer.js @@ -160,14 +160,15 @@ class CanvasRenderer { let h = this.canvas.frame.canvas.clientHeight; ctx.clearRect(0, 0, w, h); - - this.body.emitter.emit("beforeDrawing", ctx); - // set scaling and translation ctx.save(); ctx.translate(this.body.view.translation.x, this.body.view.translation.y); ctx.scale(this.body.view.scale, this.body.view.scale); + ctx.beginPath(); + this.body.emitter.emit("beforeDrawing", ctx); + ctx.closePath(); + if (hidden === false) { if (this.dragging === false || (this.dragging === true && this.options.hideEdgesOnDrag === false)) { this._drawEdges(ctx); @@ -182,10 +183,10 @@ class CanvasRenderer { this._drawControlNodes(ctx); } + ctx.beginPath(); //this.physics.nodesSolver._debug(ctx,"#F00F0F"); - this.body.emitter.emit("afterDrawing", ctx); - + ctx.closePath(); // restore original scaling and translation ctx.restore(); diff --git a/lib/network/modules/Clustering.js b/lib/network/modules/Clustering.js index fa6ef7cb..bc52d8ef 100644 --- a/lib/network/modules/Clustering.js +++ b/lib/network/modules/Clustering.js @@ -40,8 +40,7 @@ class ClusterEngine { } for (let i = 0; i < nodesToCluster.length; i++) { - let node = this.body.nodes[nodesToCluster[i]]; - this.clusterByConnection(node,options,false); + this.clusterByConnection(nodesToCluster[i],options,false); } this.body.emitter.emit('_dataChanged'); } @@ -64,9 +63,16 @@ class ClusterEngine { // collect the nodes that will be in the cluster for (let i = 0; i < this.body.nodeIndices.length; i++) { let nodeId = this.body.nodeIndices[i]; - let clonedOptions = this._cloneOptions(nodeId); + let node = this.body.nodes[nodeId]; + let clonedOptions = this._cloneOptions(node); if (options.joinCondition(clonedOptions) === true) { childNodesObj[nodeId] = this.body.nodes[nodeId]; + + // collect the nodes that will be in the cluster + for (let i = 0; i < node.edges.length; i++) { + let edge = node.edges[i]; + childEdgesObj[edge.id] = edge; + } } } @@ -89,20 +95,24 @@ class ClusterEngine { let childEdgesObj = {}; let nodeId = this.body.nodeIndices[i]; if (this.body.nodes[nodeId].edges.length === 1) { + // this is an outlier let edge = this.body.nodes[nodeId].edges[0]; let childNodeId = this._getConnectedId(edge, nodeId); - if (childNodeId != nodeId) { + if (childNodeId !== nodeId) { if (options.joinCondition === undefined) { + childEdgesObj[edge.id] = edge; childNodesObj[nodeId] = this.body.nodes[nodeId]; childNodesObj[childNodeId] = this.body.nodes[childNodeId]; } else { - let clonedOptions = this._cloneOptions(nodeId); + let clonedOptions = this._cloneOptions(this.body.nodes[nodeId]); if (options.joinCondition(clonedOptions) === true) { + childEdgesObj[edge.id] = edge; childNodesObj[nodeId] = this.body.nodes[nodeId]; } - clonedOptions = this._cloneOptions(childNodeId); + clonedOptions = this._cloneOptions(this.body.nodes[childNodeId]); if (options.joinCondition(clonedOptions) === true) { + childEdgesObj[edge.id] = edge; childNodesObj[childNodeId] = this.body.nodes[childNodeId]; } } @@ -145,7 +155,7 @@ class ClusterEngine { let childNodesObj = {}; let childEdgesObj = {}; let parentNodeId = node.id; - let parentClonedOptions = this._cloneOptions(parentNodeId); + let parentClonedOptions = this._cloneOptions(node); childNodesObj[parentNodeId] = node; // collect the nodes that will be in the cluster @@ -160,7 +170,7 @@ class ClusterEngine { } else { // clone the options and insert some additional parameters that could be interesting. - let childClonedOptions = this._cloneOptions(childNodeId); + let childClonedOptions = this._cloneOptions(this.body.nodes[childNodeId]); if (options.joinCondition(parentClonedOptions, childClonedOptions) === true) { childEdgesObj[edge.id] = edge; childNodesObj[childNodeId] = this.body.nodes[childNodeId]; @@ -183,16 +193,16 @@ class ClusterEngine { * @returns {{}} * @private */ - _cloneOptions(objId, type) { + _cloneOptions(item, type) { let clonedOptions = {}; if (type === undefined || type === 'node') { - util.deepExtend(clonedOptions, this.body.nodes[objId].options, true); - clonedOptions.x = this.body.nodes[objId].x; - clonedOptions.y = this.body.nodes[objId].y; - clonedOptions.amountOfConnections = this.body.nodes[objId].edges.length; + util.deepExtend(clonedOptions, item.options, true); + clonedOptions.x = item.x; + clonedOptions.y = item.y; + clonedOptions.amountOfConnections = item.edges.length; } else { - util.deepExtend(clonedOptions, this.body.edges[objId].options, true); + util.deepExtend(clonedOptions, item.options, true); } return clonedOptions; } @@ -207,41 +217,37 @@ class ClusterEngine { * @param options * @private */ - _createClusterEdges (childNodesObj, childEdgesObj, newEdges, options) { - let edge, childNodeId, childNode; + _createClusterEdges (childNodesObj, childEdgesObj, newEdges, clusterNodeProperties, clusterEdgeProperties) { + let edge, childNodeId, childNode, toId, fromId, otherNodeId; let childKeys = Object.keys(childNodesObj); for (let i = 0; i < childKeys.length; i++) { childNodeId = childKeys[i]; childNode = childNodesObj[childNodeId]; - // mark all edges for removal from global and construct new edges from the cluster to others + // construct new edges from the cluster to others for (let j = 0; j < childNode.edges.length; j++) { edge = childNode.edges[j]; childEdgesObj[edge.id] = edge; - let otherNodeId = edge.toId; - let otherOnTo = true; - if (edge.toId != childNodeId) { - otherNodeId = edge.toId; - otherOnTo = true; + // childNodeId position will be replaced by the cluster. + if (edge.toId == childNodeId) { // this is a double equals because ints and strings can be interchanged here. + toId = clusterNodeProperties.id; + fromId = edge.fromId; + otherNodeId = fromId; } - else if (edge.fromId != childNodeId) { - otherNodeId = edge.fromId; - otherOnTo = false; + else { + toId = edge.toId; + fromId = clusterNodeProperties.id; + otherNodeId = toId; } + // if the node connected to the cluster is also in the cluster we do not need a new edge. if (childNodesObj[otherNodeId] === undefined) { - let clonedOptions = this._cloneOptions(edge.id, 'edge'); - util.deepExtend(clonedOptions, options.clusterEdgeProperties); - if (otherOnTo === true) { - clonedOptions.from = options.clusterNodeProperties.id; - clonedOptions.to = otherNodeId; - } - else { - clonedOptions.from = otherNodeId; - clonedOptions.to = options.clusterNodeProperties.id; - } + let clonedOptions = this._cloneOptions(edge, 'edge'); + util.deepExtend(clonedOptions, clusterEdgeProperties); + clonedOptions.from = fromId; + clonedOptions.to = toId; clonedOptions.id = 'clusterEdge:' + util.randomUUID(); newEdges.push(this.body.functions.createEdge(clonedOptions)) } @@ -249,7 +255,6 @@ class ClusterEngine { } } - /** * This function checks the options that can be supplied to the different cluster functions * for certain fields and inserts defaults if needed @@ -276,32 +281,34 @@ class ClusterEngine { // kill condition: no children so cant cluster if (Object.keys(childNodesObj).length === 0) {return;} + let clusterNodeProperties = util.deepExtend({},options.clusterNodeProperties); + // check if we have an unique id; - if (options.clusterNodeProperties.id === undefined) {options.clusterNodeProperties.id = 'cluster:' + util.randomUUID();} - let clusterId = options.clusterNodeProperties.id; + if (clusterNodeProperties.id === undefined) {clusterNodeProperties.id = 'cluster:' + util.randomUUID();} + let clusterId = clusterNodeProperties.id; // construct the clusterNodeProperties - let clusterNodeProperties = options.clusterNodeProperties; if (options.processProperties !== undefined) { // get the childNode options let childNodesOptions = []; for (let nodeId in childNodesObj) { - let clonedOptions = this._cloneOptions(nodeId); + let clonedOptions = this._cloneOptions(childNodesObj[nodeId]); childNodesOptions.push(clonedOptions); } // get clusterproperties based on childNodes let childEdgesOptions = []; for (let edgeId in childEdgesObj) { - let clonedOptions = this._cloneOptions(edgeId, 'edge'); + let clonedOptions = this._cloneOptions(childEdgesObj[edgeId], 'edge'); childEdgesOptions.push(clonedOptions); } clusterNodeProperties = options.processProperties(clusterNodeProperties, childNodesOptions, childEdgesOptions); if (!clusterNodeProperties) { - throw new Error("The processClusterProperties function does not return properties!"); + throw new Error("The processProperties function does not return properties!"); } } + if (clusterNodeProperties.label === undefined) { clusterNodeProperties.label = 'cluster'; } @@ -320,25 +327,23 @@ class ClusterEngine { clusterNodeProperties.y = pos.y; } - // force the ID to remain the same clusterNodeProperties.id = clusterId; - // create the clusterNode let clusterNode = this.body.functions.createNode(clusterNodeProperties, Cluster); clusterNode.isCluster = true; clusterNode.containedNodes = childNodesObj; clusterNode.containedEdges = childEdgesObj; - + // cache a copy from the cluster edge properties if we have to reconnect others later on + clusterNode.clusterEdgeProperties = options.clusterEdgeProperties; // finally put the cluster node into global this.body.nodes[clusterNodeProperties.id] = clusterNode; // create the new edges that will connect to the cluster let newEdges = []; - this._createClusterEdges(childNodesObj, childEdgesObj, newEdges, options); - + this._createClusterEdges(childNodesObj, childEdgesObj, newEdges, clusterNodeProperties, options.clusterEdgeProperties); // disable the childEdges for (let edgeId in childEdgesObj) { @@ -360,7 +365,6 @@ class ClusterEngine { } } - // push new edges to global for (let i = 0; i < newEdges.length; i++) { this.body.edges[newEdges[i].id] = newEdges[i]; @@ -370,7 +374,6 @@ class ClusterEngine { // set ID to undefined so no duplicates arise clusterNodeProperties.id = undefined; - // wrap up if (refreshData === true) { this.body.emitter.emit('_dataChanged'); @@ -406,17 +409,20 @@ class ClusterEngine { let minY = childNodesObj[childKeys[0]].y; let maxY = childNodesObj[childKeys[0]].y; let node; - for (let i = 0; i < childKeys.lenght; i++) { - node = childNodesObj[childKeys[0]]; + for (let i = 1; i < childKeys.length; i++) { + node = childNodesObj[childKeys[i]]; minX = node.x < minX ? node.x : minX; maxX = node.x > maxX ? node.x : maxX; minY = node.y < minY ? node.y : minY; maxY = node.y > maxY ? node.y : maxY; } + + return {x: 0.5*(minX + maxX), y: 0.5*(minY + maxY)}; } + /** * Open a cluster by calling this function. * @param {String} clusterNodeId | the ID of the cluster node @@ -457,9 +463,46 @@ class ClusterEngine { // release edges for (let edgeId in containedEdges) { if (containedEdges.hasOwnProperty(edgeId)) { - let edge = this.body.edges[edgeId]; - edge.options.hidden = false; - edge.togglePhysics(true); + let edge = containedEdges[edgeId]; + // if this edge was a temporary edge and it's connected nodes do not exist anymore, we remove it from the data + if (this.body.nodes[edge.fromId] === undefined || this.body.nodes[edge.toId] === undefined) { + edge.edgeType.cleanup(); + // this removes the edge from node.edges, which is why edgeIds is formed + edge.disconnect(); + delete this.body.edges[edgeId]; + } + else { + // one of the nodes connected to this edge is in a cluster. We give the edge to that cluster and make a new temporary edge. + if (this.clusteredNodes[edge.fromId] !== undefined || this.clusteredNodes[edge.toId] !== undefined) { + let fromId, toId; + let clusteredNode = this.clusteredNodes[edge.fromId] || this.clusteredNodes[edge.toId]; + let clusterId = clusteredNode.clusterId; + let clusterNode = this.body.nodes[clusterId]; + clusterNode.containedEdges[edgeId] = edge; + + if (this.clusteredNodes[edge.fromId] !== undefined) { + fromId = clusterId; + toId = edge.toId; + } + else { + fromId = edge.fromId; + toId = clusterId; + } + + let clonedOptions = this._cloneOptions(edge, 'edge'); + let id = 'clusterEdge:' + util.randomUUID(); + util.deepExtend(clonedOptions, clusterNode.clusterEdgeProperties); + util.deepExtend(clonedOptions, {from:fromId, to:toId, hidden:false, physics:true, id: id}); + let newEdge = this.body.functions.createEdge(clonedOptions); + + this.body.edges[id] = newEdge; + this.body.edges[id].connect(); + } + else { + edge.options.hidden = false; + edge.togglePhysics(true); + } + } } } diff --git a/lib/network/modules/LayoutEngine.js b/lib/network/modules/LayoutEngine.js index 8039c2e8..121215ac 100644 --- a/lib/network/modules/LayoutEngine.js +++ b/lib/network/modules/LayoutEngine.js @@ -40,7 +40,7 @@ class LayoutEngine { util.mergeOptions(this.options, options, 'hierarchical'); if (options.randomSeed !== undefined) { - this.randomSeed = options.randomSeed; + this.initialRandomSeed = options.randomSeed; } if (this.options.hierarchical.enabled === true) { @@ -129,6 +129,7 @@ class LayoutEngine { positionInitially(nodesArray) { if (this.options.hierarchical.enabled !== true) { + this.randomSeed = this.initialRandomSeed; for (let i = 0; i < nodesArray.length; i++) { let node = nodesArray[i]; if ((!node.isFixed()) && (node.x === undefined || node.y === undefined)) { diff --git a/lib/network/modules/ManipulationSystem.js b/lib/network/modules/ManipulationSystem.js index 0fea0be1..2a86ba8b 100644 --- a/lib/network/modules/ManipulationSystem.js +++ b/lib/network/modules/ManipulationSystem.js @@ -74,10 +74,10 @@ class ManipulationSystem { * Set the Options * @param options */ - setOptions(options, allOptions) { + setOptions(options, allOptions, globalOptions) { if (allOptions !== undefined) { - if (allOptions.locale !== undefined) {this.options.locale = allOptions.locale}; - if (allOptions.locales !== undefined) {this.options.locales = allOptions.locales}; + if (allOptions.locale !== undefined) {this.options.locale = allOptions.locale} else {this.options.locale = globalOptions.locale;} + if (allOptions.locales !== undefined) {this.options.locales = allOptions.locales} else {this.options.locales = globalOptions.locales;} } if (options !== undefined) { @@ -439,6 +439,7 @@ class ManipulationSystem { this.body.data.edges.remove(finalizedData.edges); this.body.data.nodes.remove(finalizedData.nodes); this.body.emitter.emit('startSimulation'); + this.showManipulatorToolbar(); } }); } @@ -450,6 +451,7 @@ class ManipulationSystem { this.body.data.edges.remove(selectedEdges); this.body.data.nodes.remove(selectedNodes); this.body.emitter.emit('startSimulation'); + this.showManipulatorToolbar(); } } @@ -559,6 +561,7 @@ class ManipulationSystem { // empty the editModeDiv util.recursiveDOMDelete(this.editModeDiv); + // create the contents for the editMode button let locale = this.options.locales[this.options.locale]; let button = this._createButton('editMode', 'vis-button vis-edit vis-edit-mode', locale['edit'] || this.options.locales['en']['edit']); diff --git a/lib/network/modules/PhysicsEngine.js b/lib/network/modules/PhysicsEngine.js index b3f8f886..c3782497 100644 --- a/lib/network/modules/PhysicsEngine.js +++ b/lib/network/modules/PhysicsEngine.js @@ -23,6 +23,7 @@ class PhysicsEngine { this.renderTimer = undefined; this.stabilized = false; + this.startedStabilization = false; this.stabilizationIterations = 0; this.ready = false; // will be set to true if the stabilize @@ -229,12 +230,8 @@ class PhysicsEngine { // The event is triggered on the next tick, to prevent the case that // it is fired while initializing the Network, in which case you would not // be able to catch it - this.stabilizationIterations = 0; this.startedStabilization = false; - this._emitStabilized(); - } - else { - this.stabilizationIterations = 0; + //this._emitStabilized(); } this.stopSimulation(); } @@ -244,6 +241,7 @@ class PhysicsEngine { if (this.stabilizationIterations > 1) { setTimeout(() => { this.body.emitter.emit('stabilized', {iterations: this.stabilizationIterations}); + this.stabilizationIterations = 0; }, 0); } } @@ -495,8 +493,6 @@ class PhysicsEngine { // block redraw requests this.body.emitter.emit('_blockRedrawRequests'); - this.body.emitter.emit('startStabilizing'); - this.startedStabilization = true; this.targetIterations = iterations; // start the stabilization @@ -505,7 +501,7 @@ class PhysicsEngine { } this.stabilizationIterations = 0; - setTimeout(this._stabilizationBatch.bind(this),0); + setTimeout(() => this._stabilizationBatch(),0); } _stabilizationBatch() { @@ -538,6 +534,13 @@ class PhysicsEngine { this.body.emitter.emit('stabilizationIterationsDone'); this.body.emitter.emit('_requestRedraw'); + if (this.stabilized === true) { + this._emitStabilized(); + } + else { + this.startSimulation(); + } + this.ready = true; } diff --git a/lib/network/modules/components/Edge.js b/lib/network/modules/components/Edge.js index 76a0e760..9431e2ad 100644 --- a/lib/network/modules/components/Edge.js +++ b/lib/network/modules/components/Edge.js @@ -178,6 +178,11 @@ class Edge { parentOptions.color = undefined; delete parentOptions.color; } + + // handle the font settings + if (newOptions.font !== undefined) { + Label.parseOptions(parentOptions.font, newOptions); + } } diff --git a/lib/network/modules/components/Node.js b/lib/network/modules/components/Node.js index 2debfeea..6d7fd840 100644 --- a/lib/network/modules/components/Node.js +++ b/lib/network/modules/components/Node.js @@ -180,6 +180,7 @@ class Node { delete parentOptions.color; } + // handle the fixed options if (newOptions.fixed !== undefined && newOptions.fixed !== null) { if (typeof newOptions.fixed === 'boolean') { parentOptions.fixed.x = newOptions.fixed; @@ -195,10 +196,12 @@ class Node { } } + // handle the font options if (newOptions.font !== undefined) { Label.parseOptions(parentOptions.font, newOptions); } + // handle the scaling options, specifically the label part if (newOptions.scaling !== undefined) { util.mergeOptions(parentOptions.scaling, newOptions.scaling, 'label'); } diff --git a/lib/network/options.js b/lib/network/options.js index f9276c70..e1ae5bc1 100644 --- a/lib/network/options.js +++ b/lib/network/options.js @@ -165,7 +165,7 @@ let allOptions = { size: {number}, // px face: {string}, background: {string}, - stroke: {number}, // px + strokeWidth: {number}, // px strokeColor: {string}, __type__: {object,string} }, diff --git a/lib/network/shapes.js b/lib/network/shapes.js index fe27ced4..f2232bfc 100644 --- a/lib/network/shapes.js +++ b/lib/network/shapes.js @@ -9,6 +9,7 @@ if (typeof CanvasRenderingContext2D !== 'undefined') { CanvasRenderingContext2D.prototype.circle = function (x, y, r) { this.beginPath(); this.arc(x, y, r, 0, 2 * Math.PI, false); + this.closePath(); }; /** @@ -20,6 +21,7 @@ if (typeof CanvasRenderingContext2D !== 'undefined') { CanvasRenderingContext2D.prototype.square = function (x, y, r) { this.beginPath(); this.rect(x - r, y - r, r * 2, r * 2); + this.closePath(); }; /** @@ -142,6 +144,7 @@ if (typeof CanvasRenderingContext2D !== 'undefined') { this.arc(x + r, y + h - r, r, r2d * 90, r2d * 180, false); this.lineTo(x, y + r); this.arc(x + r, y + r, r, r2d * 180, r2d * 270, false); + this.closePath(); }; /** @@ -162,6 +165,7 @@ if (typeof CanvasRenderingContext2D !== 'undefined') { this.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); this.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); this.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + this.closePath(); }; @@ -210,7 +214,6 @@ if (typeof CanvasRenderingContext2D !== 'undefined') { var yt = y - length * Math.sin(angle); // inner tail - // TODO: allow to customize different shapes var xi = x - length * 0.9 * Math.cos(angle); var yi = y - length * 0.9 * Math.sin(angle); diff --git a/lib/shared/Validator.js b/lib/shared/Validator.js index 5bbb2d5e..cd9d8339 100644 --- a/lib/shared/Validator.js +++ b/lib/shared/Validator.js @@ -195,16 +195,14 @@ class Validator { * @returns {{closestMatch: string, path: Array, distance: number}} */ static findInOptions(option, options, path, recursive = false) { - //console.log(option, options, path) let min = 1e9; let closestMatch = ''; let closestMatchPath = []; let lowerCaseOption = option.toLowerCase(); let indexMatch = undefined; for (let op in options) { - let type = Validator.getType(options[op]); let distance; - if (type === 'object' && recursive === true) { + if (options[op].__type__ !== undefined && recursive === true) { let result = Validator.findInOptions(option, options[op], util.copyAndExtendArray(path,op)); if (min > result.distance) { closestMatch = result.closestMatch; @@ -225,7 +223,7 @@ class Validator { } } } - return {closestMatch:closestMatch, path:closestMatchPath, distance:min, indexMatch: indexMatch} + return {closestMatch:closestMatch, path:closestMatchPath, distance:min, indexMatch: indexMatch}; } static printLocation(path, option, prefix = 'Problem value found at: \n') { diff --git a/lib/util.js b/lib/util.js index c8142dd9..7787d844 100644 --- a/lib/util.js +++ b/lib/util.js @@ -1218,11 +1218,13 @@ exports.mergeOptions = function (mergeTarget, options, option, allowDeletion = f } else { if (options[option] !== undefined) { - if (typeof options[option] == 'boolean') { + if (typeof options[option] === 'boolean') { mergeTarget[option].enabled = options[option]; } else { - mergeTarget[option].enabled = true; + if (options[option].enabled === undefined) { + mergeTarget[option].enabled = true; + } for (var prop in options[option]) { if (options[option].hasOwnProperty(prop)) { mergeTarget[option][prop] = options[option][prop];