From ad2d70ed8b2362b1e3e6ed008f87a56bc1c9a174 Mon Sep 17 00:00:00 2001 From: Alex de Mulder Date: Thu, 21 May 2015 16:22:15 +0200 Subject: [PATCH] fixed numerous clustering bugs, added more examples, numerous other bug fixes --- dist/vis.js | 5614 +++++++++-------- docs/network/index.html | 18 +- .../31_manipulation_and_localization.html | 246 - .../network/categories/39_newClustering.html | 105 - .../categories/events/clickEvents.html | 75 + .../categories/events/physicsEvents.html | 73 + .../categories/events/renderEvents.html | 83 + .../multiline_text.html} | 16 +- .../25_physics_configuration.html | 0 .../animationShowcase.html} | 123 +- .../network/categories/rest/clustering.html | 141 + .../network/categories/rest/manipulation.html | 167 + .../navigation.html} | 22 +- lib/network/Network.js | 84 +- lib/network/modules/CanvasRenderer.js | 11 +- lib/network/modules/Clustering.js | 151 +- lib/network/modules/LayoutEngine.js | 3 +- lib/network/modules/ManipulationSystem.js | 9 +- lib/network/modules/PhysicsEngine.js | 19 +- lib/network/modules/components/Edge.js | 5 + lib/network/modules/components/Node.js | 3 + lib/network/options.js | 2 +- lib/network/shapes.js | 5 +- lib/shared/Validator.js | 6 +- lib/util.js | 6 +- 25 files changed, 3634 insertions(+), 3353 deletions(-) delete mode 100644 examples/network/categories/31_manipulation_and_localization.html delete mode 100644 examples/network/categories/39_newClustering.html create mode 100644 examples/network/categories/events/clickEvents.html create mode 100644 examples/network/categories/events/physicsEvents.html create mode 100644 examples/network/categories/events/renderEvents.html rename examples/network/categories/{10_multiline_text.html => labels/multiline_text.html} (62%) rename examples/network/categories/{ => physics}/25_physics_configuration.html (100%) rename examples/network/categories/{33_animation.html => rest/animationShowcase.html} (67%) create mode 100644 examples/network/categories/rest/clustering.html create mode 100644 examples/network/categories/rest/manipulation.html rename examples/network/categories/{20_navigation.html => rest/navigation.html} (79%) 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 = { clusterByHubsize( - Number hubsize, + [Number hubsize], [Object options]) Returns: none This method checks all nodes in the network and those with a equal or higher amount of edges than - specified with the 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.

+ + For all qualifying nodes, clusterByConnection is performed on each of them. + The options object is described for clusterByConnection and does the same here. @@ -569,7 +571,9 @@ var locales = { Returns: none - This method will cluster all nodes with 1 edge with their respective connected node. + This method will cluster all nodes with 1 edge with their respective connected node. + The options object is explained in full below. + findNode( @@ -1055,7 +1059,9 @@ network.clustering.cluster(options); - processProperties(
  nodeOptions: Object
) + processProperties(
  clusterOptions: Object,
+   childNodesOptions: Array,
+   childEdgesOptions: Array
) Function Optional. Before creating the new cluster node, this (optional) function will be called with the diff --git a/examples/network/categories/31_manipulation_and_localization.html b/examples/network/categories/31_manipulation_and_localization.html deleted file mode 100644 index 655b306d..00000000 --- a/examples/network/categories/31_manipulation_and_localization.html +++ /dev/null @@ -1,246 +0,0 @@ - - - - Network | Localization - - - - - - - - - - -

Editing the dataset (localized)

-

- 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. -

- -

- - -

- -
- node
- - - - - -
id
label
- - -
-
-
- -

- - - diff --git a/examples/network/categories/39_newClustering.html b/examples/network/categories/39_newClustering.html deleted file mode 100644 index 5c0278c7..00000000 --- a/examples/network/categories/39_newClustering.html +++ /dev/null @@ -1,105 +0,0 @@ - - - - Network | Clustering - - - - - - - - - - -
- - - - - diff --git a/examples/network/categories/events/clickEvents.html b/examples/network/categories/events/clickEvents.html new file mode 100644 index 00000000..3b276059 --- /dev/null +++ b/examples/network/categories/events/clickEvents.html @@ -0,0 +1,75 @@ + + + + Network | Basic usage + + + + + + + + +

+ 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 @@
+
+
+
+  Network | Basic usage
+
+  
+  
+
+  
+
+
+
+

+ 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 @@
+
+
+
+  Network | Basic usage
+
+  
+  
+
+  
+
+
+
+

+ 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 @@
     }
   
 
-  
-  
+  
+  
+  
 
   
-  
+  
 
 
 
 

Camera animations

You can move the view around programmatically using the .moveTo(options) function. The options supplied to this function can - also be (partially) supplied to the .zoomExtent() and .focusOnNode() methods. These are explained in the docs. + also be (partially) supplied to the .fit() and .focusOnNode() methods. These are explained in the docs.

The buttons below take the fields from the table when they can. For instance, the "Animate with default settings." takes the position, scale and offset while using - the default animation values for duration and easing function. The focusOnNode takes everything except the position and the zoomExtent takes only the duration and easing function. + the default animation values for duration and easing function. The focusOnNode takes everything except the position and the fit takes only the duration and easing function.

Here you can see a full description of the options you can supply to moveTo:
@@ -290,7 +237,7 @@ var moveToOptions = { offset y px - duration ms + duration ms easingFunction @@ -317,7 +264,7 @@ var moveToOptions = { Examples:

-
+


diff --git a/examples/network/categories/rest/clustering.html b/examples/network/categories/rest/clustering.html new file mode 100644 index 00000000..0e304245 --- /dev/null +++ b/examples/network/categories/rest/clustering.html @@ -0,0 +1,141 @@ + + + + Network | Clustering + + + + + + + + + + + +

+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. +

+ +
+
+
+
+
+ +
+ + + + + diff --git a/examples/network/categories/rest/manipulation.html b/examples/network/categories/rest/manipulation.html new file mode 100644 index 00000000..e3b2c619 --- /dev/null +++ b/examples/network/categories/rest/manipulation.html @@ -0,0 +1,167 @@ + + + + Network | Localization + + + + + + + + + + + +

Editing the nodes and edges (localized)

+

+ The localization is only relevant to the manipulation buttons. +

+ +

+ + +

+ +
+ node
+ + + + + +
id
label
+ + +
+
+
+ + + + diff --git a/examples/network/categories/20_navigation.html b/examples/network/categories/rest/navigation.html similarity index 79% rename from examples/network/categories/20_navigation.html rename to examples/network/categories/rest/navigation.html index 8244a0b0..f73d8a3d 100644 --- a/examples/network/categories/20_navigation.html +++ b/examples/network/categories/rest/navigation.html @@ -10,7 +10,6 @@ #mynetwork { width: 600px; height: 600px; - margin:100px; border: 1px solid lightgray; } table.legend_table { @@ -28,8 +27,9 @@ } - - + + + - + @@ -94,13 +94,13 @@ - - - - - - - + + + + + + + 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];
Icons:
Keyboard shortcuts: