diff --git a/dist/vis.js b/dist/vis.js index 0a5654a8..37d64779 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-19 + * @date 2015-05-20 * * @license * Copyright (C) 2011-2014 Almende B.V, http://almende.com @@ -377,7 +377,7 @@ return /******/ (function(modules) { // webpackBootstrap if (a[prop].constructor === Object) { exports.deepExtend(a[prop], b[prop], false, allowDeletion); } else { - if ((b[prop] === undefined || b[prop] === null) && a[prop] !== undefined && allowDeletion === true) { + if (b[prop] === null && a[prop] !== undefined && allowDeletion === true) { delete a[prop]; } else { a[prop] = b[prop]; @@ -419,7 +419,7 @@ return /******/ (function(modules) { // webpackBootstrap if (a[prop].constructor === Object) { exports.deepExtend(a[prop], b[prop]); } else { - if ((b[prop] === undefined || b[prop] === null) && a[prop] !== undefined && allowDeletion === true) { + if (b[prop] === null && a[prop] !== undefined && allowDeletion === true) { delete a[prop]; } else { a[prop] = b[prop]; @@ -442,7 +442,7 @@ return /******/ (function(modules) { // webpackBootstrap * @param {Object} b * @param [Boolean] protoExtend --> optional parameter. If true, the prototype values will also be extended. * (ie. the options objects that inherit from others will also get the inherited options) - * @param [Boolean] global --> optional parameter. If true, the values of fields that are undefined or null will not deleted + * @param [Boolean] global --> optional parameter. If true, the values of fields that are null will not deleted * @returns {Object} */ exports.deepExtend = function (a, b, protoExtend, allowDeletion) { @@ -455,7 +455,7 @@ return /******/ (function(modules) { // webpackBootstrap if (a[prop].constructor === Object) { exports.deepExtend(a[prop], b[prop], protoExtend); } else { - if ((b[prop] === undefined || b[prop] === null) && a[prop] !== undefined && allowDeletion === true) { + if (b[prop] === null && a[prop] !== undefined && allowDeletion === true) { delete a[prop]; } else { a[prop] = b[prop]; @@ -1324,14 +1324,21 @@ return /******/ (function(modules) { // webpackBootstrap * @private */ exports.mergeOptions = function (mergeTarget, options, option) { - if (options[option] !== undefined) { - if (typeof options[option] == 'boolean') { - mergeTarget[option].enabled = options[option]; - } else { - mergeTarget[option].enabled = true; - for (var prop in options[option]) { - if (options[option].hasOwnProperty(prop)) { - mergeTarget[option][prop] = options[option][prop]; + var allowDeletion = arguments[3] === undefined ? false : arguments[3]; + + if (options[option] === null) { + mergeTarget[option] = undefined; + delete mergeTarget[option]; + } else { + if (options[option] !== undefined) { + if (typeof options[option] == 'boolean') { + mergeTarget[option].enabled = options[option]; + } else { + mergeTarget[option].enabled = true; + for (var prop in options[option]) { + if (options[option].hasOwnProperty(prop)) { + mergeTarget[option][prop] = options[option][prop]; + } } } } @@ -1841,8 +1848,10 @@ return /******/ (function(modules) { // webpackBootstrap }); }; - // TODO: make this function deprecated (replaced with `on` since version 0.5) - DataSet.prototype.subscribe = DataSet.prototype.on; + // TODO: remove this deprecated function some day (replaced with `on` since version 0.5, deprecated since v4.0) + DataSet.prototype.subscribe = function () { + throw new Error('DataSet.subscribe is deprecated. Use DataSet.on instead.'); + }; /** * Unsubscribe from an event, remove an event listener @@ -1858,8 +1867,10 @@ return /******/ (function(modules) { // webpackBootstrap } }; - // TODO: make this function deprecated (replaced with `on` since version 0.5) - DataSet.prototype.unsubscribe = DataSet.prototype.off; + // TODO: remove this deprecated function some day (replaced with `on` since version 0.5, deprecated since v4.0) + DataSet.prototype.unsubscribe = function () { + throw new Error('DataSet.unsubscribe is deprecated. Use DataSet.off instead.'); + }; /** * Trigger an event @@ -6318,7 +6329,7 @@ return /******/ (function(modules) { // webpackBootstrap var CustomTime = __webpack_require__(27); var ItemSet = __webpack_require__(32); - var ConfigurationSystem = __webpack_require__(45); + var Configurator = __webpack_require__(45); var Validator = __webpack_require__(46)['default']; var printStyle = __webpack_require__(46).printStyle; var allOptions = __webpack_require__(47).allOptions; @@ -6426,7 +6437,7 @@ return /******/ (function(modules) { // webpackBootstrap }; // setup configuration system - this.configurationSystem = new ConfigurationSystem(this, container, configureOptions); + this.configurator = new Configurator(this, container, configureOptions); // apply options if (options) { @@ -6475,8 +6486,10 @@ return /******/ (function(modules) { // webpackBootstrap // force recreation of all items var itemsData = this.itemsData; if (itemsData) { + var selection = this.getSelection(); this.setItems(null); // remove all this.setItems(itemsData); // add all + this.setSelection(selection); // restore selection } } } @@ -6757,7 +6770,7 @@ return /******/ (function(modules) { // webpackBootstrap var CustomTime = __webpack_require__(27); var LineGraph = __webpack_require__(34); - var ConfigurationSystem = __webpack_require__(45); + var Configurator = __webpack_require__(45); var Validator = __webpack_require__(46)['default']; var printStyle = __webpack_require__(46).printStyle; var allOptions = __webpack_require__(48).allOptions; @@ -6852,13 +6865,11 @@ return /******/ (function(modules) { // webpackBootstrap me.emit('contextmenu', me.getEventProperties(event)); }; + // setup configuration system + this.configurator = new Configurator(this, container, configureOptions); + // apply options if (options) { - var errorFound = Validator.validate(options, allOptions); - if (errorFound === true) { - console.log('%cErrors have been found in the supplied options object.', printStyle); - } - this.setOptions(options); } @@ -6878,6 +6889,16 @@ return /******/ (function(modules) { // webpackBootstrap // Extend the functionality from Core Graph2d.prototype = new Core(); + Graph2d.prototype.setOptions = function (options) { + // validate options + var errorFound = Validator.validate(options, allOptions); + if (errorFound === true) { + console.log('%cErrors have been found in the supplied options object.', printStyle); + } + + Core.prototype.setOptions.call(this, options); + }; + /** * Set items * @param {vis.DataSet | Array | null} items @@ -15652,9 +15673,9 @@ return /******/ (function(modules) { // webpackBootstrap var _modulesManipulationSystem2 = _interopRequireDefault(_modulesManipulationSystem); - var _sharedConfigurationSystem = __webpack_require__(45); + var _sharedConfigurator = __webpack_require__(45); - var _sharedConfigurationSystem2 = _interopRequireDefault(_sharedConfigurationSystem); + var _sharedConfigurator2 = _interopRequireDefault(_sharedConfigurator); var _sharedValidator = __webpack_require__(46); @@ -15770,7 +15791,7 @@ return /******/ (function(modules) { // webpackBootstrap this.canvas._create(); // setup configuration system - this.configurationSystem = new _sharedConfigurationSystem2['default'](this, this.body.container, _optionsJs.configureOptions, this.canvas.pixelRatio); + this.configurator = new _sharedConfigurator2['default'](this, this.body.container, _optionsJs.configureOptions, this.canvas.pixelRatio); // apply options this.setOptions(options); @@ -15814,14 +15835,18 @@ return /******/ (function(modules) { // webpackBootstrap this.renderer.setOptions(options.interaction); // options for rendering are in interaction this.selectionHandler.setOptions(options.interaction); // options for selection are in interaction + // reload the settings of the nodes to apply changes in groups that are not referenced by pointer. + if (options.groups !== undefined) { + this.body.emitter.emit('refreshNodes'); + } // these two do not have options at the moment, here for completeness //this.view.setOptions(options.view); //this.clustering.setOptions(options.clustering); - this.configurationSystem.setOptions(options.configure); + this.configurator.setOptions(options.configure); // if the configuration system is enabled, copy all options and put them into the config system - if (this.configurationSystem.options.enabled === true) { + if (this.configurator.options.enabled === true) { var networkOptions = { nodes: {}, edges: {}, layout: {}, interaction: {}, manipulation: {}, physics: {}, global: {} }; util.deepExtend(networkOptions.nodes, this.nodesHandler.options); util.deepExtend(networkOptions.edges, this.edgesHandler.options); @@ -15838,7 +15863,7 @@ return /******/ (function(modules) { // webpackBootstrap util.deepExtend(networkOptions.global, this.canvas.options); util.deepExtend(networkOptions.global, this.options); - this.configurationSystem.setModuleOptions(networkOptions); + this.configurator.setModuleOptions(networkOptions); } // handle network global options @@ -15993,7 +16018,7 @@ return /******/ (function(modules) { // webpackBootstrap delete this.manipulation; delete this.nodesHandler; delete this.edgesHandler; - delete this.configurationSystem; + delete this.configurator; delete this.images; // delete emitter bindings @@ -17853,16 +17878,15 @@ return /******/ (function(modules) { // webpackBootstrap }); // enable/disable configure - if (this.configurationSystem) { - this.configurationSystem.setOptions(options.configure); + if (this.configurator) { + this.configurator.setOptions(options.configure); // collect the settings of all components, and pass them to the configuration system var appliedOptions = util.deepExtend({}, this.options); this.components.forEach(function (component) { util.deepExtend(appliedOptions, component.options); }); - this.configurationSystem.setModuleOptions({ global: appliedOptions }); - console.log(appliedOptions); + this.configurator.setModuleOptions({ global: appliedOptions }); } // redraw everything @@ -18551,9 +18575,9 @@ return /******/ (function(modules) { // webpackBootstrap function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - var _networkModulesComponentsColorPicker = __webpack_require__(73); + var _ColorPicker = __webpack_require__(73); - var _networkModulesComponentsColorPicker2 = _interopRequireDefault(_networkModulesComponentsColorPicker); + var _ColorPicker2 = _interopRequireDefault(_ColorPicker); var util = __webpack_require__(1); @@ -18572,11 +18596,11 @@ return /******/ (function(modules) { // webpackBootstrap * @param pixelRatio | canvas pixel ratio */ - var ConfigurationSystem = (function () { - function ConfigurationSystem(parentModule, defaultContainer, configureOptions) { + var Configurator = (function () { + function Configurator(parentModule, defaultContainer, configureOptions) { var pixelRatio = arguments[3] === undefined ? 1 : arguments[3]; - _classCallCheck(this, ConfigurationSystem); + _classCallCheck(this, Configurator); this.parent = parentModule; this.changedOptions = []; @@ -18594,11 +18618,11 @@ return /******/ (function(modules) { // webpackBootstrap this.configureOptions = configureOptions; this.moduleOptions = {}; this.domElements = []; - this.colorPicker = new _networkModulesComponentsColorPicker2['default'](pixelRatio); - this.wrapper; + this.colorPicker = new _ColorPicker2['default'](pixelRatio); + this.wrapper = undefined; } - _createClass(ConfigurationSystem, [{ + _createClass(Configurator, [{ key: 'setOptions', /** @@ -18652,7 +18676,6 @@ return /******/ (function(modules) { // webpackBootstrap /** * Create all DOM elements - * @param {Boolean | String} config * @private */ value: function _create() { @@ -18661,17 +18684,17 @@ return /******/ (function(modules) { // webpackBootstrap this._clean(); this.changedOptions = []; - var config = this.options.filter; + var filter = this.options.filter; var counter = 0; var show = false; for (var option in this.configureOptions) { if (this.configureOptions.hasOwnProperty(option)) { this.allowCreation = false; show = false; - if (typeof config === 'function') { - show = config(option, []); + if (typeof filter === 'function') { + show = filter(option, []); show = show || this._handleObject(this.configureOptions[option], [option]); - } else if (config === true || config.indexOf(option) !== -1) { + } else if (filter === true || filter.indexOf(option) !== -1) { show = true; } @@ -19052,13 +19075,15 @@ return /******/ (function(modules) { // webpackBootstrap var path = arguments[1] === undefined ? [] : arguments[1]; var show = false; - var config = this.options.filter; + var filter = this.options.filter; var visibleInSet = false; for (var subObj in obj) { if (obj.hasOwnProperty(subObj)) { show = false; - if (typeof config === 'function') { - show = config(subObj, path); + if (typeof filter === 'function') { + show = filter(subObj, path); + } else if (filter === true) { + show = true; } if (show !== false) { @@ -19186,10 +19211,10 @@ return /******/ (function(modules) { // webpackBootstrap } }]); - return ConfigurationSystem; + return Configurator; })(); - exports['default'] = ConfigurationSystem; + exports['default'] = Configurator; module.exports = exports['default']; /***/ }, @@ -19537,9 +19562,9 @@ return /******/ (function(modules) { // webpackBootstrap var allOptions = { configure: { enabled: { boolean: boolean }, - filter: { boolean: boolean, string: string, array: array }, + filter: { boolean: boolean, fn: fn }, container: { dom: dom }, - __type__: { object: object, boolean: boolean, string: string, array: array } + __type__: { object: object, boolean: boolean, fn: fn } }, //globals : @@ -19754,9 +19779,9 @@ return /******/ (function(modules) { // webpackBootstrap var allOptions = { configure: { enabled: { boolean: boolean }, - filter: { boolean: boolean, string: string, array: array }, + filter: { boolean: boolean, fn: fn }, container: { dom: dom }, - __type__: { object: object, boolean: boolean, string: string, array: array } + __type__: { object: object, boolean: boolean, fn: fn } }, //globals : @@ -19912,7 +19937,7 @@ return /******/ (function(modules) { // webpackBootstrap drawPoints: { enabled: true, size: [6, 2, 30, 1], - style: 'square' // square, circle + style: ['square', 'circle'] // square, circle }, dataAxis: { showMinorLabels: true, @@ -19977,7 +20002,7 @@ return /******/ (function(modules) { // webpackBootstrap maxHeight: '', min: '', minHeight: '', - movable: true, + moveable: true, orientation: ['both', 'bottom', 'top'], showCurrentTime: false, showMajorLabels: true, @@ -20835,11 +20860,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__(74); + var _componentsNode = __webpack_require__(85); var _componentsNode2 = _interopRequireDefault(_componentsNode); - var _componentsSharedLabel = __webpack_require__(75); + var _componentsSharedLabel = __webpack_require__(91); var _componentsSharedLabel2 = _interopRequireDefault(_componentsSharedLabel); @@ -20924,7 +20949,7 @@ return /******/ (function(modules) { // webpackBootstrap min: 14, max: 30, maxVisible: 30, - drawThreshold: 3 + drawThreshold: 6 }, customScalingFunction: function customScalingFunction(min, max, total, value) { if (max === min) { @@ -21108,7 +21133,7 @@ return /******/ (function(modules) { // webpackBootstrap var data = changedData[i]; if (node !== undefined) { // update node - node.setOptions(data, this.constants); + node.setOptions(data); } else { dataChanged = true; // create node @@ -21308,11 +21333,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__(76); + var _componentsEdge = __webpack_require__(86); var _componentsEdge2 = _interopRequireDefault(_componentsEdge); - var _componentsSharedLabel = __webpack_require__(75); + var _componentsSharedLabel = __webpack_require__(91); var _componentsSharedLabel2 = _interopRequireDefault(_componentsSharedLabel); @@ -21382,7 +21407,7 @@ return /******/ (function(modules) { // webpackBootstrap min: 14, max: 30, maxVisible: 30, - drawThreshold: 3 + drawThreshold: 6 }, customScalingFunction: function customScalingFunction(min, max, total, value) { if (max === min) { @@ -21727,35 +21752,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__(77); + var _componentsPhysicsBarnesHutSolver = __webpack_require__(74); var _componentsPhysicsBarnesHutSolver2 = _interopRequireDefault(_componentsPhysicsBarnesHutSolver); - var _componentsPhysicsRepulsionSolver = __webpack_require__(78); + var _componentsPhysicsRepulsionSolver = __webpack_require__(75); var _componentsPhysicsRepulsionSolver2 = _interopRequireDefault(_componentsPhysicsRepulsionSolver); - var _componentsPhysicsHierarchicalRepulsionSolver = __webpack_require__(79); + var _componentsPhysicsHierarchicalRepulsionSolver = __webpack_require__(76); var _componentsPhysicsHierarchicalRepulsionSolver2 = _interopRequireDefault(_componentsPhysicsHierarchicalRepulsionSolver); - var _componentsPhysicsSpringSolver = __webpack_require__(80); + var _componentsPhysicsSpringSolver = __webpack_require__(77); var _componentsPhysicsSpringSolver2 = _interopRequireDefault(_componentsPhysicsSpringSolver); - var _componentsPhysicsHierarchicalSpringSolver = __webpack_require__(81); + var _componentsPhysicsHierarchicalSpringSolver = __webpack_require__(78); var _componentsPhysicsHierarchicalSpringSolver2 = _interopRequireDefault(_componentsPhysicsHierarchicalSpringSolver); - var _componentsPhysicsCentralGravitySolver = __webpack_require__(82); + var _componentsPhysicsCentralGravitySolver = __webpack_require__(79); var _componentsPhysicsCentralGravitySolver2 = _interopRequireDefault(_componentsPhysicsCentralGravitySolver); - var _componentsPhysicsFA2BasedRepulsionSolver = __webpack_require__(83); + var _componentsPhysicsFA2BasedRepulsionSolver = __webpack_require__(80); var _componentsPhysicsFA2BasedRepulsionSolver2 = _interopRequireDefault(_componentsPhysicsFA2BasedRepulsionSolver); - var _componentsPhysicsFA2BasedCentralGravitySolver = __webpack_require__(84); + var _componentsPhysicsFA2BasedCentralGravitySolver = __webpack_require__(81); var _componentsPhysicsFA2BasedCentralGravitySolver2 = _interopRequireDefault(_componentsPhysicsFA2BasedCentralGravitySolver); @@ -22343,7 +22368,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__(85); + var _componentsNodesCluster = __webpack_require__(82); var _componentsNodesCluster2 = _interopRequireDefault(_componentsNodesCluster); @@ -24143,11 +24168,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__(86); + var _componentsNavigationHandler = __webpack_require__(83); var _componentsNavigationHandler2 = _interopRequireDefault(_componentsNavigationHandler); - var _componentsPopup = __webpack_require__(87); + var _componentsPopup = __webpack_require__(84); var _componentsPopup2 = _interopRequireDefault(_componentsPopup); @@ -24847,8 +24872,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__(74); - var Edge = __webpack_require__(76); + var Node = __webpack_require__(85); + var Edge = __webpack_require__(86); var util = __webpack_require__(1); var SelectionHandler = (function () { @@ -27486,7 +27511,6 @@ return /******/ (function(modules) { // webpackBootstrap springConstant: { number: number }, nodeDistance: { number: number }, damping: { number: number }, - avoidOverlap: { number: number }, __type__: { object: object } }, hierarchicalRepulsion: { @@ -27693,8 +27717,7 @@ return /******/ (function(modules) { // webpackBootstrap springLength: [200, 0, 500, 5], springConstant: [0.05, 0, 5, 0.005], nodeDistance: [100, 0, 500, 5], - damping: [0.09, 0, 1, 0.01], - avoidOverlap: [0, 0, 1, 0.01] + damping: [0.09, 0, 1, 0.01] }, hierarchicalRepulsion: { centralGravity: [0.2, 0, 10, 0.05], @@ -28006,7 +28029,7 @@ return /******/ (function(modules) { // webpackBootstrap 'use strict'; - var keycharm = __webpack_require__(88); + var keycharm = __webpack_require__(87); var Emitter = __webpack_require__(43); var Hammer = __webpack_require__(41); var util = __webpack_require__(1); @@ -31284,7 +31307,7 @@ return /******/ (function(modules) { // webpackBootstrap return _moment; })); - /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(90)(module))) + /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(89)(module))) /***/ }, /* 71 */ @@ -33970,7 +33993,7 @@ return /******/ (function(modules) { // webpackBootstrap prefixed: prefixed }); - if ("function" == TYPE_FUNCTION && __webpack_require__(91)) { + if ("function" == TYPE_FUNCTION && __webpack_require__(90)) { !(__WEBPACK_AMD_DEFINE_RESULT__ = function() { return Hammer; }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); @@ -34567,2504 +34590,2071 @@ 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; }; })(); - - 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__(75); - - 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 _nodesShapesEllipse2 = _interopRequireDefault(_nodesShapesEllipse); - - var _nodesShapesIcon = __webpack_require__(99); - - var _nodesShapesIcon2 = _interopRequireDefault(_nodesShapesIcon); - - var _nodesShapesImage = __webpack_require__(100); - - var _nodesShapesImage2 = _interopRequireDefault(_nodesShapesImage); - - var _nodesShapesSquare = __webpack_require__(101); - - var _nodesShapesSquare2 = _interopRequireDefault(_nodesShapesSquare); - - var _nodesShapesStar = __webpack_require__(102); - - var _nodesShapesStar2 = _interopRequireDefault(_nodesShapesStar); - - var _nodesShapesText = __webpack_require__(103); - - var _nodesShapesText2 = _interopRequireDefault(_nodesShapesText); - - var _nodesShapesTriangle = __webpack_require__(104); - - var _nodesShapesTriangle2 = _interopRequireDefault(_nodesShapesTriangle); - - var _nodesShapesTriangleDown = __webpack_require__(105); - - var _nodesShapesTriangleDown2 = _interopRequireDefault(_nodesShapesTriangleDown); - - var _sharedValidator = __webpack_require__(46); - - var _sharedValidator2 = _interopRequireDefault(_sharedValidator); - - 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 BarnesHutSolver = (function () { + function BarnesHutSolver(body, physicsBody, options) { + _classCallCheck(this, BarnesHutSolver); - 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.barnesHutTree; 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(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: 'detachEdge', + key: "solve", /** - * Detach a edge from the node - * @param {Edge} edge + * 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 detachEdge(edge) { - var index = this.edges.indexOf(edge); - if (index != -1) { - this.edges.splice(index, 1); + 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); + } + } } } }, { - key: 'togglePhysics', + key: "_getForceContribution", /** - * Enable or disable the physics. - * @param status + * 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 togglePhysics(status) { - this.options.physics = 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); + } + } + } + } } }, { - key: 'setOptions', + key: "_calculateForces", /** - * Set or overwrite options for the node - * @param {Object} options an object with options - * @param {Object} constants and object with default, global options + * Calculate the forces based on the distance. + * + * @param distance + * @param dx + * @param dy + * @param node + * @param parentBranch + * @private */ - value: function setOptions(options) { - if (!options) { - return; + value: function _calculateForces(distance, dx, dy, node, parentBranch) { + if (distance === 0) { + distance = 0.1 * Math.random(); + dx = distance; } - // basic options - if (options.id !== undefined) { - this.id = options.id; + if (this.overlapAvoidanceFactor < 1) { + distance = Math.max(0.1 + this.overlapAvoidanceFactor * node.shape.radius, distance - node.shape.radius); } - if (this.id === undefined) { - throw 'Node must have an 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 (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.physicsBody.forces[node.id].x += fx; + this.physicsBody.forces[node.id].y += fy; + } + }, { + key: "_formBarnesHutTree", - // this transforms all shorthands into fully defined options - Node.parseOptions(this.options, options, true); + /** + * 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; - // 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); - } + var minX = nodes[nodeIndices[0]].x; + var minY = nodes[nodeIndices[0]].y; + var maxX = nodes[nodeIndices[0]].x; + var maxY = nodes[nodeIndices[0]].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'; + // 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 - this.updateShape(); - this.updateLabelModule(); + 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); - // 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; + // 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); + } } - this._reset(); - } - }, { - key: 'select', - /** - * select this node - */ - value: function select() { - this.selected = true; - this._reset(); + // make global + return barnesHutTree; } }, { - key: 'unselect', + key: "_updateBranchMass", /** - * unselect this node + * this updates the mass of a branch. this is increased by adding a node. + * + * @param parentBranch + * @param node + * @private */ - value: function unselect() { - this.selected = false; - this._reset(); + value: function _updateBranchMass(parentBranch, node) { + var totalMass = parentBranch.mass + node.options.mass; + var totalMassInv = 1 / totalMass; + + parentBranch.centerOfMass.x = parentBranch.centerOfMass.x * parentBranch.mass + node.x * node.options.mass; + parentBranch.centerOfMass.x *= totalMassInv; + + parentBranch.centerOfMass.y = parentBranch.centerOfMass.y * parentBranch.mass + node.y * node.options.mass; + parentBranch.centerOfMass.y *= totalMassInv; + + parentBranch.mass = totalMass; + var biggestSize = Math.max(Math.max(node.height, node.radius), node.width); + parentBranch.maxWidth = parentBranch.maxWidth < biggestSize ? biggestSize : parentBranch.maxWidth; } }, { - key: '_reset', + key: "_placeInTree", /** - * Reset the calculated size of the node, forces it to recalculate its size + * determine in which branch the node will be placed. + * + * @param parentBranch + * @param node + * @param skipMassUpdate * @private */ - value: function _reset() { - this.shape.width = undefined; - this.shape.height = undefined; - } - }, { - key: 'getTitle', - - /** - * 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', + value: function _placeInTree(parentBranch, node, skipMassUpdate) { + if (skipMassUpdate != true || skipMassUpdate === undefined) { + // update the mass of the branch. + this._updateBranchMass(parentBranch, node); + } - /** - * 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); + 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: 'isFixed', + key: "_placeInRegion", /** - * Check if this node has a fixed x and y position - * @return {boolean} true if fixed, false if not + * actually place the node in a region (or branch) + * + * @param parentBranch + * @param node + * @param region + * @private */ - value: function isFixed() { - return this.options.fixed.x && this.options.fixed.y; + 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: 'isSelected', + key: "_splitBranch", /** - * check if this node is selecte - * @return {boolean} selected True if node is selected, else false + * 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 isSelected() { - return this.selected; - } - }, { - key: 'getValue', + 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"); - /** - * Retrieve the value of the node. Can be undefined - * @return {Number} value - */ - value: function getValue() { - return this.options.value; + if (containedNode != null) { + this._placeInTree(parentBranch, containedNode); + } } }, { - key: 'setValueRange', + key: "_insertRegion", /** - * Adjust the value range of the node. The node will adjust it's size - * based on its value. - * @param {Number} min - * @param {Number} max + * 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 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 _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; } - } - }, { - 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); + 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: 'updateBoundingBox', + key: "_debug", - /** - * Update the bounding box of the shape - */ - value: function updateBoundingBox() { - this.shape.updateBoundingBox(this.x, this.y); - } - }, { - key: 'resize', + //--------------------------- DEBUGGING BELOW ---------------------------// /** - * 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 + * This function is for debugging purposed, it draws the tree. + * + * @param ctx + * @param color + * @private */ - value: function resize(ctx) { - this.shape.resize(ctx); - } - }, { - key: 'isOverlappingWith', + value: function _debug(ctx, color) { + if (this.barnesHutTree !== undefined) { - /** - * 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', + ctx.lineWidth = 1; - /** - * 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; + this._drawBranch(this.barnesHutTree.root, ctx, color); + } } - }], [{ - key: 'parseOptions', + }, { + key: "_drawBranch", /** - * 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 + * This function is for debugging purposes. It draws the branches recursively. + * + * @param branch + * @param ctx + * @param color + * @private */ - value: function parseOptions(parentOptions, newOptions) { - var allowDeletion = arguments[2] === undefined ? false : arguments[2]; - - var fields = ['color', 'font', 'fixed', 'shadow']; - util.selectiveNotDeepExtend(fields, parentOptions, newOptions); - - // merge the shadow options into the parent. - util.mergeOptions(parentOptions, newOptions, 'shadow'); - - // 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) { - parentOptions.color = undefined; - delete parentOptions.color; + value: function _drawBranch(branch, ctx, color) { + if (color === undefined) { + color = "#FF0000"; } - 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; - } - } + 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(); - if (newOptions.font !== undefined) { - _sharedLabel2['default'].parseOptions(parentOptions.font, newOptions); - } + ctx.beginPath(); + ctx.moveTo(branch.range.maxX, branch.range.minY); + ctx.lineTo(branch.range.maxX, branch.range.maxY); + ctx.stroke(); - if (newOptions.scaling !== undefined) { - util.mergeOptions(parentOptions.scaling, newOptions.scaling, 'label'); - } + ctx.beginPath(); + ctx.moveTo(branch.range.maxX, branch.range.maxY); + ctx.lineTo(branch.range.minX, branch.range.maxY); + ctx.stroke(); + + ctx.beginPath(); + ctx.moveTo(branch.range.minX, branch.range.maxY); + ctx.lineTo(branch.range.minX, branch.range.minY); + ctx.stroke(); + + /* + if (branch.mass > 0) { + ctx.circle(branch.centerOfMass.x, branch.centerOfMass.y, 3*branch.mass); + ctx.stroke(); + } + */ } }]); - return Node; + return BarnesHutSolver; })(); - exports['default'] = Node; - module.exports = exports['default']; + exports["default"] = BarnesHutSolver; + 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; }; })(); - - 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 _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 util = __webpack_require__(1); + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - var Label = (function () { - function Label(body, options) { - _classCallCheck(this, Label); + var RepulsionSolver = (function () { + function RepulsionSolver(body, physicsBody, options) { + _classCallCheck(this, RepulsionSolver); this.body = body; - - this.baseSize = undefined; + this.physicsBody = physicsBody; this.setOptions(options); - this.size = { top: 0, left: 0, width: 0, height: 0, yLine: 0 }; // could be cached } - _createClass(Label, [{ - key: 'setOptions', + _createClass(RepulsionSolver, [{ + 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', + key: "solve", /** - * Main function. This is called from anything that wants to draw a label. - * @param ctx - * @param x - * @param y - * @param selected - * @param baseline + * Calculate the forces the nodes apply on each other based on a repulsion field. + * This field is linearly approximated. + * + * @private */ - value: function draw(ctx, x, y, selected) { - var baseline = arguments[4] === undefined ? 'middle' : arguments[4]; + value: function solve() { + var dx, dy, distance, fx, fy, repulsingForce, node1, node2; - // if no label, return - if (this.options.label === undefined) return; + var nodes = this.body.nodes; + var nodeIndices = this.physicsBody.physicsNodeIndices; + var forces = this.physicsBody.forces; - // 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; + // repulsing forces between nodes + var nodeDistance = this.options.nodeDistance; - // update the size cache if required - this.calculateLabelSize(ctx, selected, x, y, baseline); + // approximation constants + var a = -2 / 3 / nodeDistance; + var b = 4 / 3; - // create the fontfill background - this._drawBackground(ctx); - // draw text - this._drawText(ctx, selected, x, y, baseline); - } - }, { - key: '_drawBackground', + // 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]]; - /** - * 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; + dx = node2.x - node1.x; + dy = node2.y - node1.y; + distance = Math.sqrt(dx * dx + dy * dy); - var lineMargin = 2; + // same condition as BarnesHutSolver, making sure nodes are never 100% overlapping. + if (distance === 0) { + distance = 0.1 * Math.random(); + dx = distance; + } - 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; + 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; + + fx = dx * repulsingForce; + fy = dy * repulsingForce; + + forces[node1.id].x -= fx; + forces[node1.id].y -= fy; + forces[node2.id].x += fx; + forces[node2.id].y += fy; + } } } } - }, { - 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; - } + return RepulsionSolver; + })(); - var yLine = this.size.yLine; + exports["default"] = RepulsionSolver; + module.exports = exports["default"]; - var _getColor = this._getColor(viewFontSize); +/***/ }, +/* 76 */ +/***/ function(module, exports, __webpack_require__) { - var _getColor2 = _slicedToArray(_getColor, 2); + "use strict"; - var fontColor = _getColor2[0]; - var strokeColor = _getColor2[1]; + Object.defineProperty(exports, "__esModule", { + value: true + }); - var _setAlignment = this._setAlignment(ctx, x, yLine, baseline); + 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 _setAlignment2 = _slicedToArray(_setAlignment, 2); + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - x = _setAlignment2[0]; - yLine = _setAlignment2[1]; + var HierarchicalRepulsionSolver = (function () { + function HierarchicalRepulsionSolver(body, physicsBody, options) { + _classCallCheck(this, HierarchicalRepulsionSolver); - // configure context for drawing the text - ctx.font = (selected ? 'bold ' : '') + fontSize + 'px ' + this.options.font.face; - ctx.fillStyle = fontColor; - ctx.textAlign = 'center'; + this.body = body; + this.physicsBody = physicsBody; + this.setOptions(options); + } - // 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]; + _createClass(HierarchicalRepulsionSolver, [{ + key: "setOptions", + value: function setOptions(options) { + this.options = options; } }, { - key: '_getColor', + key: "solve", /** - * 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. + * Calculate the forces the nodes apply on each other based on a repulsion field. + * This field is linearly approximated. * - * @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); + 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]]; + + // 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; + + forces[node1.id].x -= fx; + forces[node1.id].y -= fy; + forces[node2.id].x += fx; + forces[node2.id].y += fy; + } + } } - 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]; + return HierarchicalRepulsionSolver; + })(); - var size = { - width: this._processLabel(ctx, selected), - height: this.options.font.size * this.lineCount, - lineCount: this.lineCount - }; - return size; + exports["default"] = HierarchicalRepulsionSolver; + module.exports = exports["default"]; + +/***/ }, +/* 77 */ +/***/ 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 SpringSolver = (function () { + function SpringSolver(body, physicsBody, options) { + _classCallCheck(this, SpringSolver); + + this.body = body; + this.physicsBody = physicsBody; + this.setOptions(options); + } + + _createClass(SpringSolver, [{ + key: "setOptions", + value: function setOptions(options) { + this.options = options; } }, { - key: 'calculateLabelSize', + key: "solve", /** + * This function calculates the springforces on the nodes, accounting for the support nodes. * - * @param ctx - * @param selected - * @param x - * @param y - * @param baseline + * @private */ - 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]; + 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; - 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 - } + // 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; - this.labelDirty = false; + 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: '_processLabel', + key: "_calculateSpringForce", /** - * This calculates the width as well as explodes the label string and calculates the amount of lines. - * @param ctx - * @param selected - * @returns {number} + * This is the code actually performing the calculation for the function above. + * + * @param node1 + * @param node2 + * @param edgeLength * @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; + 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); - return width; - } - }], [{ - key: 'parseOptions', - value: function parseOptions(parentOptions, newOptions) { - var allowDeletion = arguments[2] === undefined ? false : arguments[2]; + // 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 (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); + var fx = dx * springForce; + var fy = dy * springForce; + + // 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; + } + + if (this.physicsBody.forces[node2.id] !== undefined) { + this.physicsBody.forces[node2.id].x -= fx; + this.physicsBody.forces[node2.id].y -= fy; } - parentOptions.size = Number(parentOptions.size); } }]); - return Label; + return SpringSolver; })(); - exports['default'] = Label; - module.exports = exports['default']; + exports["default"] = SpringSolver; + module.exports = exports["default"]; /***/ }, -/* 76 */ +/* 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; }; })(); + 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"); } } - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + var HierarchicalSpringSolver = (function () { + function HierarchicalSpringSolver(body, physicsBody, options) { + _classCallCheck(this, HierarchicalSpringSolver); - var _sharedLabel = __webpack_require__(75); + this.body = body; + this.physicsBody = physicsBody; + this.setOptions(options); + } - var _sharedLabel2 = _interopRequireDefault(_sharedLabel); + _createClass(HierarchicalSpringSolver, [{ + key: "setOptions", + value: function setOptions(options) { + this.options = options; + } + }, { + key: "solve", - var _edgesBezierEdgeDynamic = __webpack_require__(106); + /** + * 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 _edgesBezierEdgeDynamic2 = _interopRequireDefault(_edgesBezierEdgeDynamic); + var edgeIndices = this.physicsBody.physicsEdgeIndices; + var nodeIndices = this.physicsBody.physicsNodeIndices; + var forces = this.physicsBody.forces; - var _edgesBezierEdgeStatic = __webpack_require__(107); + // 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 _edgesBezierEdgeStatic2 = _interopRequireDefault(_edgesBezierEdgeStatic); + // 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 _edgesStraightEdge = __webpack_require__(108); + 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 _edgesStraightEdge2 = _interopRequireDefault(_edgesStraightEdge); + // the 1/distance is so the fx and fy can be calculated without sine or cosine. + springForce = this.options.springConstant * (edgeLength - distance) / distance; - var util = __webpack_require__(1); + fx = dx * springForce; + fy = dy * springForce; - /** - * @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 - */ + 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 Edge = (function () { - function Edge(options, body, globalOptions) { - _classCallCheck(this, Edge); + // 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)); - if (body === undefined) { - throw 'No body provided'; + forces[nodeId].x += springFx; + forces[nodeId].y += springFy; + } + + // 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; + } + 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.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; + return HierarchicalSpringSolver; + })(); - this.baseWidth = this.options.width; - this.baseFontSize = this.options.font.size; + exports["default"] = HierarchicalSpringSolver; + module.exports = exports["default"]; - this.from = undefined; // a node - this.to = undefined; // a node +/***/ }, +/* 79 */ +/***/ function(module, exports, __webpack_require__) { - this.edgeType = undefined; + "use strict"; - this.connected = false; + Object.defineProperty(exports, "__esModule", { + value: true + }); - this.labelModule = new _sharedLabel2['default'](this.body, this.options); + 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); } - _createClass(Edge, [{ - key: 'setOptions', - - /** - * Set or overwrite options for the edge - * @param {Object} options an object with options - * @param doNotEmit - */ + _createClass(CentralGravitySolver, [{ + key: "setOptions", value: function setOptions(options) { - if (!options) { - return; - } - this.colorDirty = true; + this.options = options; + } + }, { + 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; - Edge.parseOptions(this.options, options, true); + 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); - 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); + this._calculateForces(distance, dx, dy, forces, node); } - - // 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', + key: "_calculateForces", /** - * update the options in the label module + * Calculate the forces based on the distance. + * @private */ - value: function updateLabelModule() { - this.labelModule.setOptions(this.options, true); - if (this.labelModule.baseSize !== undefined) { - this.baseFontSize = this.labelModule.baseSize; - } + 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: 'updateEdgeType', + }]); - /** - * 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; - } + return CentralGravitySolver; + })(); - if (changeInType === true) { - dataChanged = this.edgeType.cleanup(); - } - } + exports["default"] = CentralGravitySolver; + module.exports = exports["default"]; - 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); - } +/***/ }, +/* 80 */ +/***/ function(module, exports, __webpack_require__) { - return dataChanged; - } - }, { - key: 'togglePhysics', + "use strict"; - /** - * Enable or disable the physics. - * @param status - */ - value: function togglePhysics(status) { - this.options.physics = status; - this.edgeType.togglePhysics(status); - } - }, { - key: 'connect', + Object.defineProperty(exports, "__esModule", { + value: true + }); - /** - * Connect an edge to its nodes - */ - value: function connect() { - this.disconnect(); + 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 = this.body.nodes[this.fromId] || undefined; - this.to = this.body.nodes[this.toId] || undefined; - this.connected = this.from !== undefined && this.to !== undefined; + 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 (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', + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } - /** - * 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; - } + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - this.connected = false; - } - }, { - key: 'getTitle', + 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; } - /** - * 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', + var _BarnesHutSolver2 = __webpack_require__(74); - /** - * check if this node is selecte - * @return {boolean} selected True if node is selected, else false - */ - value: function isSelected() { - return this.selected; - } - }, { - key: 'getValue', + var _BarnesHutSolver3 = _interopRequireDefault(_BarnesHutSolver2); - /** - * Retrieve the value of the edge. Can be undefined - * @return {Number} value - */ - value: function getValue() { - return this.options.value; - } - }, { - key: 'setValueRange', + var ForceAtlas2BasedRepulsionSolver = (function (_BarnesHutSolver) { + function ForceAtlas2BasedRepulsionSolver(body, physicsBody, options) { + _classCallCheck(this, ForceAtlas2BasedRepulsionSolver); - /** - * 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; - } + _get(Object.getPrototypeOf(ForceAtlas2BasedRepulsionSolver.prototype), "constructor", this).call(this, body, physicsBody, options); + } - this._setInteractionWidths(); - } - }, { - 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; - } + _inherits(ForceAtlas2BasedRepulsionSolver, _BarnesHutSolver); - 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', + _createClass(ForceAtlas2BasedRepulsionSolver, [{ + key: "_calculateForces", /** - * 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 + * Calculate the forces based on the distance. + * + * @param distance + * @param dx + * @param dy + * @param node + * @param parentBranch + * @private */ - 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); + value: function _calculateForces(distance, dx, dy, node, parentBranch) { + if (distance === 0) { + distance = 0.1 * Math.random(); + dx = distance; } - if (this.options.arrows.to.enabled === true) { - this.edgeType.drawArrowHead(ctx, 'to', viaNode, this.selected, this.hover); + + if (this.overlapAvoidanceFactor < 1) { + distance = Math.max(0.1 + this.overlapAvoidanceFactor * node.shape.radius, distance - node.shape.radius); } - } - }, { - 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); - } + 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; - // 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); - } - } + 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 - * @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; + return ForceAtlas2BasedRepulsionSolver; + })(_BarnesHutSolver3["default"]); - var dist = this.edgeType.getDistanceToEdge(xFrom, yFrom, xTo, yTo, xObj, yObj); + exports["default"] = ForceAtlas2BasedRepulsionSolver; + module.exports = exports["default"]; - return dist < distMax; - } else { - return false; - } - } - }, { - key: '_rotateForLabelAlignment', +/***/ }, +/* 81 */ +/***/ function(module, exports, __webpack_require__) { - /** - * 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); + "use strict"; - // rotate so label it is readable - if (angleInDegrees < -1 && dx < 0 || angleInDegrees > 0 && dx < 0) { - angleInDegrees = angleInDegrees + Math.PI; - } + Object.defineProperty(exports, "__esModule", { + value: true + }); - ctx.rotate(angleInDegrees); - } - }, { - key: '_pointOnCircle', + 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; }; })(); - /** - * 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 _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); } } }; - var fields = ['id', 'from', 'hidden', 'hoverWidth', 'label', 'length', 'line', 'opacity', 'physics', 'selectionWidth', 'selfReferenceSize', 'to', 'title', 'value', 'width']; + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } - // only deep extend the items in the field array. These do not have shorthand. - util.selectiveDeepExtend(fields, parentOptions, newOptions, allowDeletion); + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - util.mergeOptions(parentOptions, newOptions, 'smooth'); - util.mergeOptions(parentOptions, newOptions, 'shadow'); + 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; } - if (newOptions.dashes !== undefined && newOptions.dashes !== null) { - parentOptions.dashes = newOptions.dashes; - } else if (allowDeletion === true) { - parentOptions.dashes = undefined; - delete parentOptions.dashes; - } + var _CentralGravitySolver2 = __webpack_require__(79); - // 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) { - parentOptions.scaling = undefined; - delete parentOptions.scaling; - } + var _CentralGravitySolver3 = _interopRequireDefault(_CentralGravitySolver2); - // 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) { - parentOptions.arrows = undefined; - delete parentOptions.arrows; - } + var ForceAtlas2BasedCentralGravitySolver = (function (_CentralGravitySolver) { + function ForceAtlas2BasedCentralGravitySolver(body, physicsBody, options) { + _classCallCheck(this, ForceAtlas2BasedCentralGravitySolver); - // 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)); - } + _get(Object.getPrototypeOf(ForceAtlas2BasedCentralGravitySolver.prototype), "constructor", this).call(this, body, physicsBody, options); + } - if (newOptions.color.inherit === undefined && colorsDefined === true) { - parentOptions.color.inherit = false; - } - } - } else if (allowDeletion === true) { - parentOptions.color = undefined; - delete parentOptions.color; + _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 Edge; - })(); + return ForceAtlas2BasedCentralGravitySolver; + })(_CentralGravitySolver3["default"]); - exports['default'] = Edge; - module.exports = exports['default']; + exports["default"] = ForceAtlas2BasedCentralGravitySolver; + module.exports = exports["default"]; /***/ }, -/* 77 */ +/* 82 */ /***/ 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 _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 _Node2 = __webpack_require__(85); - /** - * 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 _Node3 = _interopRequireDefault(_Node2); - // create the tree - var barnesHutTree = this._formBarnesHutTree(nodes, nodeIndices); + /** + * + */ - // for debugging - this.barnesHutTree = barnesHutTree; + var Cluster = (function (_Node) { + function Cluster(options, body, imagelist, grouplist, globalOptions) { + _classCallCheck(this, Cluster); - // 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", + _get(Object.getPrototypeOf(Cluster.prototype), 'constructor', this).call(this, options, body, imagelist, grouplist, globalOptions); - /** - * 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; + this.isCluster = true; + this.containedNodes = {}; + this.containedEdges = {}; + } - // 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); + _inherits(Cluster, _Node); - // 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", + return Cluster; + })(_Node3['default']); - /** - * Calculate the forces based on the distance. - * - * @param distance - * @param dx - * @param dy - * @param node - * @param parentBranch - * @private - */ - value: function _calculateForces(distance, dx, dy, node, parentBranch) { - if (distance === 0) { - distance = 0.1 * Math.random(); - dx = distance; - } + exports['default'] = Cluster; + module.exports = exports['default']; - if (this.overlapAvoidanceFactor < 1) { - distance = Math.max(0.1 + this.overlapAvoidanceFactor * node.shape.radius, distance - node.shape.radius); - } +/***/ }, +/* 83 */ +/***/ function(module, exports, __webpack_require__) { - // 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; + 'use strict'; - this.physicsBody.forces[node.id].x += fx; - this.physicsBody.forces[node.id].y += fy; - } - }, { - key: "_formBarnesHutTree", + Object.defineProperty(exports, '__esModule', { + value: true + }); - /** - * 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; + 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 minX = nodes[nodeIndices[0]].x; - var minY = nodes[nodeIndices[0]].y; - var maxX = nodes[nodeIndices[0]].x; - var maxY = nodes[nodeIndices[0]].y; + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - // 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 + var util = __webpack_require__(1); + var Hammer = __webpack_require__(41); + var hammerUtil = __webpack_require__(49); + var keycharm = __webpack_require__(87); - 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); + var NavigationHandler = (function () { + function NavigationHandler(body, canvas) { + var _this = this; - // 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); + _classCallCheck(this, NavigationHandler); - // 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); - } + 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(); } + }); - // make global - return barnesHutTree; + this.options = {}; + } + + _createClass(NavigationHandler, [{ + key: 'setOptions', + value: function setOptions(options) { + if (options !== undefined) { + this.options = options; + this.create(); + } } }, { - key: "_updateBranchMass", + key: 'create', + value: function create() { + if (this.options.navigationButtons === true) { + if (this.iconsCreated === false) { + this.loadNavigationElements(); + } + } else if (this.iconsCreated === true) { + this.cleanNavigation(); + } - /** - * 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; + 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 = []; + } - parentBranch.centerOfMass.x = parentBranch.centerOfMass.x * parentBranch.mass + node.x * node.options.mass; - parentBranch.centerOfMass.x *= totalMassInv; + this._navigationReleaseOverload = function () {}; - parentBranch.centerOfMass.y = parentBranch.centerOfMass.y * parentBranch.mass + node.y * node.options.mass; - parentBranch.centerOfMass.y *= totalMassInv; + // clean up previous navigation items + if (this.navigationDOM && this.navigationDOM['wrapper'] && this.navigationDOM['wrapper'].parentNode) { + this.navigationDOM['wrapper'].parentNode.removeChild(this.navigationDOM['wrapper']); + } - parentBranch.mass = totalMass; - var biggestSize = Math.max(Math.max(node.height, node.radius), node.width); - parentBranch.maxWidth = parentBranch.maxWidth < biggestSize ? biggestSize : parentBranch.maxWidth; + this.iconsCreated = false; } }, { - key: "_placeInTree", + key: 'loadNavigationElements', /** - * determine in which branch the node will be placed. + * 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. * - * @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); - } + value: function loadNavigationElements() { + this.cleanNavigation(); - 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"); + 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 { - // in SE - this._placeInRegion(parentBranch, node, "SE"); + hammerUtil.onTouch(hammer, this.bindToRedraw.bind(this, navigationDivActions[i])); } + + this.navigationHammers.push(hammer); } + + this.iconsCreated = true; } }, { - key: "_placeInRegion", - - /** - * 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: '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: "_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: '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: "_insertRegion", + key: '_fit', /** - * This function subdivides the region into four new segments. - * Specifically, this inserts a single new segment. - * It fills the children section of the parentBranch + * this stops all movement induced by the navigation buttons * - * @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; + 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(); } - - 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 ---------------------------// + key: '_stopMovement', /** - * This function is for debugging purposed, it draws the tree. + * this stops all movement induced by the navigation buttons * - * @param ctx - * @param color * @private */ - value: function _debug(ctx, color) { - if (this.barnesHutTree !== undefined) { - - ctx.lineWidth = 1; - - this._drawBranch(this.barnesHutTree.root, ctx, color); + 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: "_drawBranch", - - /** - * This function is for debugging purposes. It draws the branches recursively. - * - * @param branch - * @param ctx - * @param color - * @private - */ - value: function _drawBranch(branch, ctx, color) { - if (color === undefined) { - color = "#FF0000"; - } - - 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(); - - ctx.beginPath(); - ctx.moveTo(branch.range.maxX, branch.range.minY); - ctx.lineTo(branch.range.maxX, branch.range.maxY); - ctx.stroke(); - - ctx.beginPath(); - ctx.moveTo(branch.range.maxX, branch.range.maxY); - ctx.lineTo(branch.range.minX, branch.range.maxY); - ctx.stroke(); - - ctx.beginPath(); - ctx.moveTo(branch.range.minX, branch.range.maxY); - ctx.lineTo(branch.range.minX, branch.range.minY); - ctx.stroke(); - - /* - if (branch.mass > 0) { - ctx.circle(branch.centerOfMass.x, branch.centerOfMass.y, 3*branch.mass); - ctx.stroke(); - } - */ + key: '_moveUp', + value: function _moveUp() { + this.body.view.translation.y += this.options.keyboard.speed.y; } - }]); - - return BarnesHutSolver; - })(); - - exports["default"] = BarnesHutSolver; - module.exports = exports["default"]; - -/***/ }, -/* 78 */ -/***/ 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 RepulsionSolver = (function () { - function RepulsionSolver(body, physicsBody, options) { - _classCallCheck(this, RepulsionSolver); - - this.body = body; - this.physicsBody = physicsBody; - this.setOptions(options); - } - - _createClass(RepulsionSolver, [{ - key: "setOptions", - value: function setOptions(options) { - this.options = options; + }, { + key: '_moveDown', + value: function _moveDown() { + this.body.view.translation.y -= this.options.keyboard.speed.y; } }, { - key: "solve", - - /** - * Calculate the forces the nodes apply on each other based on a repulsion field. - * This field is linearly approximated. - * - * @private - */ - 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; - - // approximation constants - var a = -2 / 3 / nodeDistance; - var b = 4 / 3; + 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', - // 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]]; + /** + * bind all keys using keycharm. + */ + value: function configureKeyboardBindings() { + if (this.keycharm !== undefined) { + this.keycharm.destroy(); + } - dx = node2.x - node1.x; - dy = node2.y - node1.y; - distance = Math.sqrt(dx * dx + dy * dy); + if (this.options.keyboard.enabled === true) { - // same condition as BarnesHutSolver, making sure nodes are never 100% overlapping. - if (distance === 0) { - distance = 0.1 * Math.random(); - dx = distance; - } + if (this.options.keyboard.bindToWindow === true) { + this.keycharm = keycharm({ container: window, preventDefault: true }); + } else { + this.keycharm = keycharm({ container: this.canvas.frame, preventDefault: true }); + } - 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; + this.keycharm.reset(); - fx = dx * repulsingForce; - fy = dy * repulsingForce; + 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'); - forces[node1.id].x -= fx; - forces[node1.id].y -= fy; - forces[node2.id].x += fx; - forces[node2.id].y += fy; - } + 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'); } } } }]); - return RepulsionSolver; + return NavigationHandler; })(); - exports["default"] = RepulsionSolver; - module.exports = exports["default"]; + exports['default'] = NavigationHandler; + module.exports = exports['default']; /***/ }, -/* 79 */ +/* 84 */ /***/ 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 _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); + /** + * 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. + */ - this.body = body; - this.physicsBody = physicsBody; - this.setOptions(options); + 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(HierarchicalRepulsionSolver, [{ - key: "setOptions", - value: function setOptions(options) { - this.options = 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); } }, { - key: "solve", + key: 'setText', /** - * Calculate the forces the nodes apply on each other based on a repulsion field. - * This field is linearly approximated. - * - * @private + * Set the content for the popup window. This can be HTML code or text. + * @param {string | Element} content */ - 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; + 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', - // 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]]; + /** + * Show the popup window + * @param {boolean} [doShow] Show or hide the window + */ + value: function show(doShow) { + if (doShow === undefined) { + doShow = true; + } - // 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); + 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 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 top = this.y - height; + if (top + height + this.padding > maxHeight) { + top = maxHeight - height - this.padding; + } + if (top < this.padding) { + top = this.padding; + } - forces[node1.id].x -= fx; - forces[node1.id].y -= fy; - forces[node2.id].x += fx; - forces[node2.id].y += fy; - } + var left = this.x; + if (left + width + this.padding > maxWidth) { + left = maxWidth - width - this.padding; + } + if (left < this.padding) { + left = this.padding; } + + 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 HierarchicalRepulsionSolver; + return Popup; })(); - exports["default"] = HierarchicalRepulsionSolver; - module.exports = exports["default"]; + exports['default'] = Popup; + module.exports = exports['default']; /***/ }, -/* 80 */ +/* 85 */ /***/ 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 SpringSolver = (function () { - function SpringSolver(body, physicsBody, options) { - _classCallCheck(this, SpringSolver); - - this.body = body; - this.physicsBody = physicsBody; - this.setOptions(options); - } + 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; }; })(); - _createClass(SpringSolver, [{ - key: "setOptions", - value: function setOptions(options) { - this.options = options; - 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", + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - /** - * 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; + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - // 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 _sharedLabel = __webpack_require__(91); - 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 _sharedLabel2 = _interopRequireDefault(_sharedLabel); - /** - * 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 = undefined, - dy = undefined; - if (this.overlapAvoidanceFactor < 1) { - var nodesWidth = this.overlapAvoidanceFactor * (node1.shape.radius + node2.shape.radius); - dx = node1.x - node2.x - nodesWidth; - dy = node1.y - node2.y - nodesWidth; - } else { - dx = node1.x - node2.x; - dy = node1.y - node2.y; - } + var _nodesShapesBox = __webpack_require__(92); - var distance = Math.sqrt(dx * dx + dy * dy); - distance = distance === 0 ? 0.01 : distance; + var _nodesShapesBox2 = _interopRequireDefault(_nodesShapesBox); - // 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 _nodesShapesCircle = __webpack_require__(93); - var fx = dx * springForce; - var fy = dy * springForce; + var _nodesShapesCircle2 = _interopRequireDefault(_nodesShapesCircle); - // 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 _nodesShapesCircularImage = __webpack_require__(94); - if (this.physicsBody.forces[node2.id] !== undefined) { - this.physicsBody.forces[node2.id].x -= fx; - this.physicsBody.forces[node2.id].y -= fy; - } - } - }]); + var _nodesShapesCircularImage2 = _interopRequireDefault(_nodesShapesCircularImage); - return SpringSolver; - })(); + var _nodesShapesDatabase = __webpack_require__(95); - exports["default"] = SpringSolver; - module.exports = exports["default"]; + var _nodesShapesDatabase2 = _interopRequireDefault(_nodesShapesDatabase); -/***/ }, -/* 81 */ -/***/ function(module, exports, __webpack_require__) { + var _nodesShapesDiamond = __webpack_require__(96); - "use strict"; + var _nodesShapesDiamond2 = _interopRequireDefault(_nodesShapesDiamond); - Object.defineProperty(exports, "__esModule", { - value: true - }); + var _nodesShapesDot = __webpack_require__(97); - 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 _nodesShapesDot2 = _interopRequireDefault(_nodesShapesDot); - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + var _nodesShapesEllipse = __webpack_require__(98); - var HierarchicalSpringSolver = (function () { - function HierarchicalSpringSolver(body, physicsBody, options) { - _classCallCheck(this, HierarchicalSpringSolver); + var _nodesShapesEllipse2 = _interopRequireDefault(_nodesShapesEllipse); - this.body = body; - this.physicsBody = physicsBody; - this.setOptions(options); - } + var _nodesShapesIcon = __webpack_require__(99); - _createClass(HierarchicalSpringSolver, [{ - key: "setOptions", - value: function setOptions(options) { - this.options = options; - } - }, { - key: "solve", + var _nodesShapesIcon2 = _interopRequireDefault(_nodesShapesIcon); - /** - * 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 _nodesShapesImage = __webpack_require__(100); - var edgeIndices = this.physicsBody.physicsEdgeIndices; - var nodeIndices = this.physicsBody.physicsNodeIndices; - var forces = this.physicsBody.forces; + var _nodesShapesImage2 = _interopRequireDefault(_nodesShapesImage); - // 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 _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) { - edgeLength = edge.options.length === undefined ? this.options.springLength : edge.options.length; + var _nodesShapesSquare2 = _interopRequireDefault(_nodesShapesSquare); - 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 _nodesShapesStar = __webpack_require__(102); - // the 1/distance is so the fx and fy can be calculated without sine or cosine. - springForce = this.options.springConstant * (edgeLength - distance) / distance; + var _nodesShapesStar2 = _interopRequireDefault(_nodesShapesStar); - fx = dx * springForce; - fy = dy * springForce; + var _nodesShapesText = __webpack_require__(103); - 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 _nodesShapesText2 = _interopRequireDefault(_nodesShapesText); - // 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)); + var _nodesShapesTriangle = __webpack_require__(104); - forces[nodeId].x += springFx; - forces[nodeId].y += springFy; - } + var _nodesShapesTriangle2 = _interopRequireDefault(_nodesShapesTriangle); - // 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; - } - var correctionFx = totalFx / nodeIndices.length; - var correctionFy = totalFy / nodeIndices.length; + var _nodesShapesTriangleDown = __webpack_require__(105); - for (var i = 0; i < nodeIndices.length; i++) { - var nodeId = nodeIndices[i]; - forces[nodeId].x -= correctionFx; - forces[nodeId].y -= correctionFy; - } - } - }]); + var _nodesShapesTriangleDown2 = _interopRequireDefault(_nodesShapesTriangleDown); - return HierarchicalSpringSolver; - })(); + var _sharedValidator = __webpack_require__(46); - exports["default"] = HierarchicalSpringSolver; - module.exports = exports["default"]; + var _sharedValidator2 = _interopRequireDefault(_sharedValidator); -/***/ }, -/* 82 */ -/***/ function(module, exports, __webpack_require__) { + var util = __webpack_require__(1); - "use strict"; + /** + * @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 + * + */ - Object.defineProperty(exports, "__esModule", { - value: true - }); + var Node = (function () { + function Node(options, body, imagelist, grouplist, globalOptions) { + _classCallCheck(this, Node); - 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.options = util.bridgeObject(globalOptions); + this.body = body; - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + this.edges = []; // all edges connected to this node - var CentralGravitySolver = (function () { - function CentralGravitySolver(body, physicsBody, options) { - _classCallCheck(this, CentralGravitySolver); + // set defaults for the options + this.id = undefined; + this.imagelist = imagelist; + this.grouplist = grouplist; - this.body = body; - this.physicsBody = physicsBody; + // 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(CentralGravitySolver, [{ - key: "setOptions", - value: function setOptions(options) { - this.options = 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); + } } }, { - 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; - - 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); + key: 'detachEdge', - this._calculateForces(distance, dx, dy, forces, node); + /** + * Detach a edge from the node + * @param {Edge} edge + */ + value: function detachEdge(edge) { + var index = this.edges.indexOf(edge); + if (index != -1) { + this.edges.splice(index, 1); } } }, { - key: "_calculateForces", + key: 'togglePhysics', /** - * Calculate the forces based on the distance. - * @private + * Enable or disable the physics. + * @param status */ - 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 togglePhysics(status) { + this.options.physics = status; } - }]); - - 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; }; })(); + }, { + key: 'setOptions', - 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); } } }; + /** + * 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; + } - function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + if (this.id === undefined) { + throw 'Node must have an id'; + } - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + 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); + } - 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; } + // this transforms all shorthands into fully defined options + Node.parseOptions(this.options, options, true); - var _BarnesHutSolver2 = __webpack_require__(77); + // 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); + } - var _BarnesHutSolver3 = _interopRequireDefault(_BarnesHutSolver2); + // 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'; + } + } - var ForceAtlas2BasedRepulsionSolver = (function (_BarnesHutSolver) { - function ForceAtlas2BasedRepulsionSolver(body, physicsBody, options) { - _classCallCheck(this, ForceAtlas2BasedRepulsionSolver); + this.updateShape(); + this.updateLabelModule(); - _get(Object.getPrototypeOf(ForceAtlas2BasedRepulsionSolver.prototype), "constructor", this).call(this, body, physicsBody, options); - } + // 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', - _inherits(ForceAtlas2BasedRepulsionSolver, _BarnesHutSolver); + /** + * select this node + */ + value: function select() { + this.selected = true; + this._reset(); + } + }, { + key: 'unselect', - _createClass(ForceAtlas2BasedRepulsionSolver, [{ - key: "_calculateForces", + /** + * unselect this node + */ + value: function unselect() { + this.selected = false; + this._reset(); + } + }, { + key: '_reset', /** - * Calculate the forces based on the distance. - * - * @param distance - * @param dx - * @param dy - * @param node - * @param parentBranch + * Reset the calculated size of the node, forces it to recalculate its size * @private */ - value: function _calculateForces(distance, dx, dy, node, parentBranch) { - if (distance === 0) { - distance = 0.1 * Math.random(); - dx = distance; - } - - if (this.overlapAvoidanceFactor < 1) { - 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 fx = dx * gravityForce; - var fy = dy * gravityForce; + value: function _reset() { + this.shape.width = undefined; + this.shape.height = undefined; + } + }, { + key: 'getTitle', - this.physicsBody.forces[node.id].x += fx; - this.physicsBody.forces[node.id].y += fy; + /** + * 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', - return ForceAtlas2BasedRepulsionSolver; - })(_BarnesHutSolver3["default"]); + /** + * 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', - exports["default"] = ForceAtlas2BasedRepulsionSolver; - module.exports = exports["default"]; + /** + * 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: 'isSelected', -/***/ }, -/* 84 */ -/***/ function(module, exports, __webpack_require__) { + /** + * check if this node is selecte + * @return {boolean} selected True if node is selected, else false + */ + value: function isSelected() { + return this.selected; + } + }, { + key: 'getValue', - "use strict"; + /** + * Retrieve the value of the node. Can be undefined + * @return {Number} value + */ + value: function getValue() { + return this.options.value; + } + }, { + key: 'setValueRange', - Object.defineProperty(exports, "__esModule", { - value: true - }); + /** + * 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 { + this.options.size = this.baseSize; + this.options.font.size = this.baseFontSize; + } + } + }, { + key: 'draw', - 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; }; })(); + /** + * 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', - 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); } } }; + /** + * Update the bounding box of the shape + */ + value: function updateBoundingBox() { + this.shape.updateBoundingBox(this.x, this.y); + } + }, { + key: 'resize', - function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + /** + * 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); + } + }, { + key: 'isOverlappingWith', - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + /** + * 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', - 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; } + /** + * 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', - var _CentralGravitySolver2 = __webpack_require__(82); + /** + * 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]; - var _CentralGravitySolver3 = _interopRequireDefault(_CentralGravitySolver2); + var fields = ['color', 'font', 'fixed', 'shadow']; + util.selectiveNotDeepExtend(fields, parentOptions, newOptions, allowDeletion); - var ForceAtlas2BasedCentralGravitySolver = (function (_CentralGravitySolver) { - function ForceAtlas2BasedCentralGravitySolver(body, physicsBody, options) { - _classCallCheck(this, ForceAtlas2BasedCentralGravitySolver); + // merge the shadow options into the parent. + util.mergeOptions(parentOptions, newOptions, 'shadow'); - _get(Object.getPrototypeOf(ForceAtlas2BasedCentralGravitySolver.prototype), "constructor", this).call(this, body, physicsBody, options); - } + // 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; + } - _inherits(ForceAtlas2BasedCentralGravitySolver, _CentralGravitySolver); + 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; + } + } + } - _createClass(ForceAtlas2BasedCentralGravitySolver, [{ - key: "_calculateForces", + if (newOptions.font !== undefined) { + _sharedLabel2['default'].parseOptions(parentOptions.font, newOptions); + } - /** - * 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; + if (newOptions.scaling !== undefined) { + util.mergeOptions(parentOptions.scaling, newOptions.scaling, 'label'); } } }]); - return ForceAtlas2BasedCentralGravitySolver; - })(_CentralGravitySolver3["default"]); + return Node; + })(); - exports["default"] = ForceAtlas2BasedCentralGravitySolver; - module.exports = exports["default"]; + exports['default'] = Node; + module.exports = exports['default']; /***/ }, -/* 85 */ +/* 86 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -37073,442 +36663,548 @@ return /******/ (function(modules) { // webpackBootstrap 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); } } }; + 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'); } } - 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 _sharedLabel = __webpack_require__(91); - var _Node2 = __webpack_require__(74); + var _sharedLabel2 = _interopRequireDefault(_sharedLabel); - var _Node3 = _interopRequireDefault(_Node2); + var _edgesBezierEdgeDynamic = __webpack_require__(106); + + var _edgesBezierEdgeDynamic2 = _interopRequireDefault(_edgesBezierEdgeDynamic); + + var _edgesBezierEdgeStatic = __webpack_require__(107); + + var _edgesBezierEdgeStatic2 = _interopRequireDefault(_edgesBezierEdgeStatic); + + var _edgesStraightEdge = __webpack_require__(108); + + var _edgesStraightEdge2 = _interopRequireDefault(_edgesStraightEdge); + + var util = __webpack_require__(1); /** + * @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 */ - var Cluster = (function (_Node) { - function Cluster(options, body, imagelist, grouplist, globalOptions) { - _classCallCheck(this, Cluster); + var Edge = (function () { + function Edge(options, body, globalOptions) { + _classCallCheck(this, Edge); - _get(Object.getPrototypeOf(Cluster.prototype), 'constructor', this).call(this, options, body, imagelist, grouplist, globalOptions); + if (body === undefined) { + throw 'No body provided'; + } + this.options = util.bridgeObject(globalOptions); + this.body = body; - this.isCluster = true; - this.containedNodes = {}; - this.containedEdges = {}; - } + // initialize variables + this.id = undefined; + this.fromId = undefined; + this.toId = undefined; + this.selected = false; + this.hover = false; + this.labelDirty = true; + this.colorDirty = true; - _inherits(Cluster, _Node); + this.baseWidth = this.options.width; + this.baseFontSize = this.options.font.size; - return Cluster; - })(_Node3['default']); + this.from = undefined; // a node + this.to = undefined; // a node - exports['default'] = Cluster; - module.exports = exports['default']; + this.edgeType = undefined; -/***/ }, -/* 86 */ -/***/ function(module, exports, __webpack_require__) { + this.connected = false; - 'use strict'; + this.labelModule = new _sharedLabel2['default'](this.body, this.options); - Object.defineProperty(exports, '__esModule', { - value: true - }); + this.setOptions(options); + } - 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; }; })(); + _createClass(Edge, [{ + key: 'setOptions', - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + /** + * Set or overwrite options for the edge + * @param {Object} options an object with options + * @param doNotEmit + */ + value: function setOptions(options) { + if (!options) { + return; + } + this.colorDirty = true; - var util = __webpack_require__(1); - var Hammer = __webpack_require__(41); - var hammerUtil = __webpack_require__(49); - var keycharm = __webpack_require__(88); + Edge.parseOptions(this.options, options, true); - var NavigationHandler = (function () { - function NavigationHandler(body, canvas) { - var _this = this; + 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); + } - _classCallCheck(this, NavigationHandler); + // A node is connected when it has a from and to node that both exist in the network.body.nodes. + this.connect(); - this.body = body; - this.canvas = canvas; + // update label Module + this.updateLabelModule(); - this.iconsCreated = false; - this.navigationHammers = []; - this.boundFunctions = {}; - this.touchTime = 0; - this.activated = false; + var dataChanged = this.updateEdgeType(); - 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(); - } - }); + // if anything has been updates, reset the selection width and the hover width + this._setInteractionWidths(); - this.options = {}; - } + return dataChanged; + } + }, { + key: 'updateLabelModule', - _createClass(NavigationHandler, [{ - key: 'setOptions', - value: function setOptions(options) { - if (options !== undefined) { - this.options = options; - this.create(); + /** + * 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: 'create', - value: function create() { - if (this.options.navigationButtons === true) { - if (this.iconsCreated === false) { - this.loadNavigationElements(); + key: 'updateEdgeType', + + /** + * 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; } - } 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(); + if (changeInType === true) { + dataChanged = this.edgeType.cleanup(); } - 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']); + 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); } - this.iconsCreated = false; + return dataChanged; } }, { - key: 'loadNavigationElements', + key: 'togglePhysics', /** - * 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 + * Enable or disable the physics. + * @param status */ - 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']; + value: function togglePhysics(status) { + this.options.physics = status; + this.edgeType.togglePhysics(status); + } + }, { + key: 'connect', - this.navigationDOM['wrapper'] = document.createElement('div'); - this.navigationDOM['wrapper'].className = 'vis-navigation'; - this.canvas.frame.appendChild(this.navigationDOM['wrapper']); + /** + * Connect an edge to its nodes + */ + value: function connect() { + this.disconnect(); - 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]]); + this.from = this.body.nodes[this.fromId] || undefined; + this.to = this.body.nodes[this.toId] || undefined; + this.connected = this.from !== undefined && this.to !== undefined; - 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])); + 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); } - - this.navigationHammers.push(hammer); } - - 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: 'disconnect', + + /** + * Disconnect an edge from its nodes + */ + value: function disconnect() { + if (this.from) { + this.from.detachEdge(this); + this.from = undefined; } - } - }, { - 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]; + if (this.to) { + this.to.detachEdge(this); + this.to = undefined; } + + this.connected = false; } }, { - key: '_fit', + key: 'getTitle', /** - * this stops all movement induced by the navigation buttons - * - * @private + * get the title of this edge. + * @return {string} title The title of the edge, or undefined when no title + * has been set. */ - 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(); - } + value: function getTitle() { + return this.title; } }, { - key: '_stopMovement', + key: 'isSelected', /** - * this stops all movement induced by the navigation buttons - * - * @private + * check if this node is selecte + * @return {boolean} selected True if node is selected, else false */ - 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; + value: function isSelected() { + return this.selected; } }, { - key: '_moveRight', - value: function _moveRight() { - this.body.view.translation.x -= this.options.keyboard.speed.x; + key: 'getValue', + + /** + * Retrieve the value of the edge. Can be undefined + * @return {Number} value + */ + value: function getValue() { + return this.options.value; } }, { - key: '_zoomIn', - value: function _zoomIn() { - this.body.view.scale *= 1 + this.options.keyboard.speed.zoom; + 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: '_zoomOut', - value: function _zoomOut() { - this.body.view.scale /= 1 + this.options.keyboard.speed.zoom; + 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: 'configureKeyboardBindings', + key: 'draw', /** - * bind all keys using keycharm. + * 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 configureKeyboardBindings() { - if (this.keycharm !== undefined) { - this.keycharm.destroy(); + 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 (this.options.keyboard.enabled === true) { + // 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); + } - if (this.options.keyboard.bindToWindow === true) { - this.keycharm = keycharm({ container: window, preventDefault: true }); + // draw the label + this.labelModule.draw(ctx, point.x, point.y, selected); + ctx.restore(); } else { - this.keycharm = keycharm({ container: this.canvas.frame, preventDefault: true }); - } - - this.keycharm.reset(); - - 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'); - - 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'); + 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); } } } - }]); - - return NavigationHandler; - })(); - - exports['default'] = NavigationHandler; - module.exports = exports['default']; - -/***/ }, -/* 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; + }, { + key: 'isOverlappingWith', - // create the frame - this.frame = document.createElement('div'); - this.frame.className = 'vis-network-tooltip'; - this.container.appendChild(this.frame); - } + /** + * 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; - _createClass(Popup, [{ - key: 'setPosition', + var dist = this.edgeType.getDistanceToEdge(xFrom, yFrom, xTo, yTo, xObj, yObj); - /** - * @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); + return dist < distMax; + } else { + return false; + } } }, { - key: 'setText', + key: '_rotateForLabelAlignment', /** - * Set the content for the popup window. This can be HTML code or text. - * @param {string | Element} content + * Rotates the canvas so the text is most readable + * @param {CanvasRenderingContext2D} ctx + * @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 + 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: 'show', + key: '_pointOnCircle', /** - * Show the popup window - * @param {boolean} [doShow] Show or hide the window + * 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 show(doShow) { - if (doShow === undefined) { - doShow = true; - } + 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]; - 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 fields = ['id', 'from', 'hidden', 'hoverWidth', 'label', 'length', 'line', 'opacity', 'physics', 'selectionWidth', 'selfReferenceSize', 'to', 'title', 'value', 'width']; - var top = this.y - height; - if (top + height + this.padding > maxHeight) { - top = maxHeight - height - this.padding; - } - if (top < this.padding) { - top = this.padding; - } + // only deep extend the items in the field array. These do not have shorthand. + util.selectiveDeepExtend(fields, parentOptions, newOptions, allowDeletion); - var left = this.x; - if (left + width + this.padding > maxWidth) { - left = maxWidth - width - this.padding; + 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 (left < this.padding) { - left = this.padding; + 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; + } - this.frame.style.left = left + 'px'; - this.frame.style.top = top + 'px'; - this.frame.style.visibility = 'visible'; - this.hidden = false; - } else { - this.hide(); + // 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; } - } - }, { - key: 'hide', - /** - * Hide the popup window - */ - value: function hide() { - this.hidden = true; - this.frame.style.visibility = 'hidden'; + // 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; + } } }]); - return Popup; + return Edge; })(); - exports['default'] = Popup; + exports['default'] = Edge; module.exports = exports['default']; /***/ }, -/* 88 */ +/* 87 */ /***/ function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;"use strict"; @@ -37706,7 +37402,7 @@ return /******/ (function(modules) { // webpackBootstrap /***/ }, -/* 89 */ +/* 88 */ /***/ function(module, exports, __webpack_require__) { function webpackContext(req) { @@ -37715,11 +37411,11 @@ return /******/ (function(modules) { // webpackBootstrap webpackContext.keys = function() { return []; }; webpackContext.resolve = webpackContext; module.exports = webpackContext; - webpackContext.id = 89; + webpackContext.id = 88; /***/ }, -/* 90 */ +/* 89 */ /***/ function(module, exports, __webpack_require__) { module.exports = function(module) { @@ -37735,13 +37431,328 @@ return /******/ (function(modules) { // webpackBootstrap /***/ }, -/* 91 */ +/* 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__) { diff --git a/docs/network/edges.html b/docs/network/edges.html index f9961fcc..18fcf9f3 100644 --- a/docs/network/edges.html +++ b/docs/network/edges.html @@ -89,7 +89,7 @@

The options for the edges have to be contained in an object titled 'edges'. All of these options can be supplied per edge as well. Obviously, 'id' should not be defined globally but per edge. Options defined in the global edges object, are applied to all edges. If an edge has options of its own, those will be used instead of the global options.

-

When you have given an edge an option, you will override the global option for that property. If you then set that option to undefined or null, +

When you have given an edge an option, you will override the global option for that property. If you then set that option to null, it will revert back to the default value.

Click on the full options or shorthand options to show how these options are supposed to be used.

@@ -138,7 +138,7 @@ var options = { min: 14, max: 30, maxVisible: 30, - drawThreshold: 3 + drawThreshold: 6 }, customScalingFunction: function (min,max,total,value) { if (max === min) { @@ -480,7 +480,7 @@ var options: { scaling.label.drawThreshold Number - 3 + 6 When zooming out, the font will be drawn smaller. This defines a lower limit for when the font is drawn. When using font scaling, you can use this together with the maxVisible to first show labels of important edges when zoomed out and only show the rest when zooming in. diff --git a/docs/network/nodes.html b/docs/network/nodes.html index 98d95d74..4ae5ecba 100644 --- a/docs/network/nodes.html +++ b/docs/network/nodes.html @@ -80,7 +80,7 @@

Options

The options for the nodes have to be contained in an object titled 'nodes'. All of these options can be supplied per node as well. Obviously, 'id' should not be defined globally but per node. Options defined in the global nodes object, are applied to all nodes. If a node has options of its own, those will be used instead of the global options.

-

When you have given a node an option, you will override the global option for that property. If you then set that option to undefined or null, +

When you have given a node an option, you will override the global option for that property. If you then set that option to null, it will revert back to the default value.

Click on the full options or shorthand options to show how these options are supposed to be used.

@@ -143,7 +143,7 @@ var options = { min: 14, max: 30, maxVisible: 30, - drawThreshold: 3 + drawThreshold: 6 }, customScalingFunction: function (min,max,total,value) { if (max === min) { @@ -507,7 +507,7 @@ network.setOptions(options); scaling.label.drawThreshold Number - 3 + 6 When zooming out, the font will be drawn smaller. This defines a lower limit for when the font is drawn. When using font scaling, you can use this together with the maxVisible to first show labels of important nodes when zoomed out and only show the rest when zooming in. diff --git a/examples/network/categories/02_random_nodes.html b/examples/network/categories/02_random_nodes.html index 550b988a..b6ebe89b 100644 --- a/examples/network/categories/02_random_nodes.html +++ b/examples/network/categories/02_random_nodes.html @@ -32,52 +32,7 @@ function draw() { destroy(); - nodes = []; - edges = []; - var connectionCount = []; - // randomly create some nodes and edges - var nodeCount = document.getElementById('nodeCount').value; - for (var i = 0; i < nodeCount; i++) { - nodes.push({ - id: i, - label: String(i) - }); - - connectionCount[i] = 0; - - // create edges in a scale-free-network way - if (i == 1) { - var from = i; - var to = 0; - edges.push({ - from: from, - to: to - }); - connectionCount[from]++; - connectionCount[to]++; - } - else if (i > 1) { - var conn = edges.length * 2; - var rand = Math.floor(Math.random() * conn); - var cum = 0; - var j = 0; - while (j < connectionCount.length && cum < rand) { - cum += connectionCount[j]; - j++; - } - - - var from = i; - var to = j; - edges.push({ - from: from, - to: to - }); - connectionCount[from]++; - connectionCount[to]++; - } - } // create a network var container = document.getElementById('mynetwork'); var data = { diff --git a/examples/network/categories/data/datasets.html b/examples/network/categories/data/datasets.html new file mode 100644 index 00000000..53575c6e --- /dev/null +++ b/examples/network/categories/data/datasets.html @@ -0,0 +1,140 @@ + + + + Network | Dynamic Data + + + + + + + +

+ You can change any settings you want while the network is initialized using the vis Dataset, setOptions and setData. Finally you can destroy the network and completely reinitialize it. +

+ +

DataSet (change the data while it's loaded and initialzed):

+
+
+
+
+
+ +

setOptions (change the global options):

+
+ +

setData (reinitialize the data):

+
+ +

Cleanly destroy the network and restart it:

+
+
+ + + + + + diff --git a/examples/network/categories/data/dot_language.html b/examples/network/categories/data/dot_language.html index 5cb44e80..4f3df920 100644 --- a/examples/network/categories/data/dot_language.html +++ b/examples/network/categories/data/dot_language.html @@ -2,9 +2,9 @@ Network | DOT Language - - - + + +

diff --git a/examples/network/categories/data/dynamic_data.html b/examples/network/categories/data/dynamic_data.html index 305c4780..b6c43036 100644 --- a/examples/network/categories/data/dynamic_data.html +++ b/examples/network/categories/data/dynamic_data.html @@ -53,8 +53,8 @@ - - + + - + diff --git a/examples/network/categories/data/importing_from_gephi.html b/examples/network/categories/data/importing_from_gephi.html index c9bfec24..6b46bd14 100644 --- a/examples/network/categories/data/importing_from_gephi.html +++ b/examples/network/categories/data/importing_from_gephi.html @@ -4,6 +4,8 @@ Dynamic Data - Importing from Gephi (JSON) + + @@ -92,7 +94,7 @@ var nodeContent = document.getElementById('nodeContent'); - loadJSON('../../data/WorldCup2014.json', redrawAll); + loadJSON('../../data/WorldCup2014.json', redrawAll, function(err) {console.log('error')}); var container = document.getElementById('mynetwork'); var data = { @@ -127,6 +129,12 @@ }; network = new vis.Network(container, data, options); + network.on('click', function (params) { + if (params.nodes.length > 0) { + var data = nodes.get(params.nodes[0]); // get the data from selected node + nodeContent.innerHTML = JSON.stringify(data, undefined, 3); // show the data in the div + } + }) /** * This function fills the DataSets. These DataSets will update the network. @@ -154,28 +162,11 @@ nodes.add(parsed.nodes); edges.add(parsed.edges); - var data = nodes.get(2); // get the data from node 2 + var data = nodes.get(2); // get the data from node 2 as example nodeContent.innerHTML = JSON.stringify(data,undefined,3); // show the data in the div network.fit(); // zoom to fit } - - function loadJSON(path, success, error) { - var xhr = new XMLHttpRequest(); - xhr.onreadystatechange = function () { - if (xhr.readyState === 4) { - if (xhr.status === 200) { - success(JSON.parse(xhr.responseText)); - } - else { - error(xhr); - } - } - }; - xhr.open('GET', path, true); - xhr.send(); - } - diff --git a/examples/network/categories/data/scaling_nodes_edges.html b/examples/network/categories/data/scaling_nodes_edges.html index fa01ec29..a5bd3eee 100644 --- a/examples/network/categories/data/scaling_nodes_edges.html +++ b/examples/network/categories/data/scaling_nodes_edges.html @@ -72,7 +72,7 @@

- Scale nodes and edges depending on their value. Hover over nodes and edges to get more information. + Scale nodes and edges depending on their value. Hover over the edges to get a popup with more information.

diff --git a/examples/network/exampleUtil.js b/examples/network/exampleUtil.js new file mode 100644 index 00000000..a5554ced --- /dev/null +++ b/examples/network/exampleUtil.js @@ -0,0 +1,70 @@ +/** + * Created by Alex on 5/20/2015. + */ + +function loadJSON(path, success, error) { + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function () { + if (xhr.readyState === 4) { + if (xhr.status === 200) { + success(JSON.parse(xhr.responseText)); + } + else { + error(xhr); + } + } + }; + xhr.open('GET', path, true); + xhr.send(); +} + + +function getScaleFreeNetwork(nodeCount) { + var nodes = []; + var edges = []; + var connectionCount = []; + + // randomly create some nodes and edges + for (var i = 0; i < nodeCount; i++) { + nodes.push({ + id: i, + label: String(i) + }); + + connectionCount[i] = 0; + + // create edges in a scale-free-network way + if (i == 1) { + var from = i; + var to = 0; + edges.push({ + from: from, + to: to + }); + connectionCount[from]++; + connectionCount[to]++; + } + else if (i > 1) { + var conn = edges.length * 2; + var rand = Math.floor(Math.random() * conn); + var cum = 0; + var j = 0; + while (j < connectionCount.length && cum < rand) { + cum += connectionCount[j]; + j++; + } + + + var from = i; + var to = j; + edges.push({ + from: from, + to: to + }); + connectionCount[from]++; + connectionCount[to]++; + } + } + + return {nodes:nodes, edges:edges}; +} \ No newline at end of file diff --git a/lib/network/Network.js b/lib/network/Network.js index fdc83e17..78f96759 100644 --- a/lib/network/Network.js +++ b/lib/network/Network.js @@ -169,6 +169,10 @@ Network.prototype.setOptions = function (options) { this.renderer.setOptions(options.interaction); // options for rendering are in interaction this.selectionHandler.setOptions(options.interaction); // options for selection are in interaction + // reload the settings of the nodes to apply changes in groups that are not referenced by pointer. + if (options.groups !== undefined) { + this.body.emitter.emit("refreshNodes"); + } // these two do not have options at the moment, here for completeness //this.view.setOptions(options.view); //this.clustering.setOptions(options.clustering); diff --git a/lib/network/modules/EdgesHandler.js b/lib/network/modules/EdgesHandler.js index c261a483..0ecb2587 100644 --- a/lib/network/modules/EdgesHandler.js +++ b/lib/network/modules/EdgesHandler.js @@ -57,7 +57,7 @@ class EdgesHandler { min: 14, max: 30, maxVisible: 30, - drawThreshold: 3 + drawThreshold: 6 }, customScalingFunction: function (min,max,total,value) { if (max === min) { diff --git a/lib/network/modules/NodesHandler.js b/lib/network/modules/NodesHandler.js index df40f59d..211c6413 100644 --- a/lib/network/modules/NodesHandler.js +++ b/lib/network/modules/NodesHandler.js @@ -72,7 +72,7 @@ class NodesHandler { min: 14, max: 30, maxVisible: 30, - drawThreshold: 3 + drawThreshold: 6 }, customScalingFunction: function (min,max,total,value) { if (max === min) { @@ -106,7 +106,7 @@ class NodesHandler { // refresh the nodes. Used when reverting from hierarchical layout this.body.emitter.on('refreshNodes', this.refresh.bind(this)); this.body.emitter.on('refresh', this.refresh.bind(this)); - this.body.emitter.on("destroy", () => { + this.body.emitter.on('destroy', () => { delete this.body.functions.createNode; delete this.nodesListeners.add; delete this.nodesListeners.update; @@ -242,7 +242,7 @@ class NodesHandler { let data = changedData[i]; if (node !== undefined) { // update node - node.setOptions(data, this.constants); + node.setOptions(data); } else { dataChanged = true; diff --git a/lib/network/modules/components/Edge.js b/lib/network/modules/components/Edge.js index 9e542539..76a0e760 100644 --- a/lib/network/modules/components/Edge.js +++ b/lib/network/modules/components/Edge.js @@ -115,7 +115,7 @@ class Edge { if (newOptions.dashes !== undefined && newOptions.dashes !== null) { parentOptions.dashes = newOptions.dashes; } - else if (allowDeletion === true) { + else if (allowDeletion === true && newOptions.dashes === null) { parentOptions.dashes = undefined; delete parentOptions.dashes; } @@ -126,7 +126,7 @@ class Edge { if (newOptions.scaling.max !== undefined) {parentOptions.scaling.max = newOptions.scaling.max;} util.mergeOptions(parentOptions.scaling, newOptions.scaling, 'label'); } - else if (allowDeletion === true) { + else if (allowDeletion === true && newOptions.scaling === null) { parentOptions.scaling = undefined; delete parentOptions.scaling; } @@ -148,7 +148,7 @@ class Edge { 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) { + else if (allowDeletion === true && newOptions.arrows === null) { parentOptions.arrows = undefined; delete parentOptions.arrows; } @@ -174,7 +174,7 @@ class Edge { } } } - else if (allowDeletion === true) { + else if (allowDeletion === true && newOptions.color === null) { parentOptions.color = undefined; delete parentOptions.color; } diff --git a/lib/network/modules/components/Node.js b/lib/network/modules/components/Node.js index 5ff7efa4..2debfeea 100644 --- a/lib/network/modules/components/Node.js +++ b/lib/network/modules/components/Node.js @@ -111,7 +111,6 @@ class Node { if (!options) { return; } - // basic options if (options.id !== undefined) {this.id = options.id;} @@ -166,7 +165,7 @@ class Node { 'fixed', 'shadow' ]; - util.selectiveNotDeepExtend(fields, parentOptions, newOptions); + util.selectiveNotDeepExtend(fields, parentOptions, newOptions, allowDeletion); // merge the shadow options into the parent. util.mergeOptions(parentOptions, newOptions, 'shadow'); @@ -176,7 +175,7 @@ class Node { let parsedColor = util.parseColor(newOptions.color); util.fillIfDefined(parentOptions.color, parsedColor); } - else if (allowDeletion === true) { + else if (allowDeletion === true && newOptions.color === null) { parentOptions.color = undefined; delete parentOptions.color; } diff --git a/lib/network/modules/components/physics/SpringSolver.js b/lib/network/modules/components/physics/SpringSolver.js index 5998823a..194631f3 100644 --- a/lib/network/modules/components/physics/SpringSolver.js +++ b/lib/network/modules/components/physics/SpringSolver.js @@ -7,8 +7,6 @@ class SpringSolver { setOptions(options) { this.options = options; - 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 - } /** @@ -58,19 +56,9 @@ class SpringSolver { * @private */ _calculateSpringForce(node1, node2, edgeLength) { - let dx,dy; - if (this.overlapAvoidanceFactor < 1) { - let nodesWidth = this.overlapAvoidanceFactor * (node1.shape.radius + node2.shape.radius); - dx = (node1.x - node2.x) - nodesWidth; - dy = (node1.y - node2.y) - nodesWidth; - } - else { - dx = (node1.x - node2.x); - dy = (node1.y - node2.y); - } - - let distance = Math.sqrt(dx * dx + dy * dy); - distance = distance === 0 ? 0.01 : distance; + let dx = (node1.x - node2.x); + let dy = (node1.y - node2.y); + let distance = Math.max(Math.sqrt(dx * dx + dy * dy),0.01); // the 1/distance is so the fx and fy can be calculated without sine or cosine. let springForce = this.options.springConstant * (edgeLength - distance) / distance; diff --git a/lib/network/options.js b/lib/network/options.js index ddbb37d3..f9276c70 100644 --- a/lib/network/options.js +++ b/lib/network/options.js @@ -238,7 +238,6 @@ let allOptions = { springConstant: {number}, nodeDistance: {number}, damping: {number}, - avoidOverlap: {number}, __type__: {object} }, hierarchicalRepulsion: { @@ -446,8 +445,7 @@ let configureOptions = { springLength: [200, 0, 500, 5], springConstant: [0.05, 0, 5, 0.005], nodeDistance: [100, 0, 500, 5], - damping: [0.09, 0, 1, 0.01], - avoidOverlap: [0, 0, 1, 0.01] + damping: [0.09, 0, 1, 0.01] }, hierarchicalRepulsion: { centralGravity: [0.2, 0, 10, 0.05], diff --git a/lib/util.js b/lib/util.js index 01b03d4e..c8142dd9 100644 --- a/lib/util.js +++ b/lib/util.js @@ -126,6 +126,8 @@ exports.fillIfDefined = function (a, b, allowDeletion = false) { } } + + /** * Extend object a with the properties of object b or a series of objects * Only properties with defined values are copied @@ -214,7 +216,7 @@ exports.selectiveDeepExtend = function (props, a, b, allowDeletion = false) { exports.deepExtend(a[prop], b[prop], false, allowDeletion); } else { - if ((b[prop] === undefined || b[prop] === null) && a[prop] !== undefined && allowDeletion === true) { + if ((b[prop] === null) && a[prop] !== undefined && allowDeletion === true) { delete a[prop]; } else { @@ -257,7 +259,7 @@ exports.selectiveNotDeepExtend = function (props, a, b, allowDeletion = false) { exports.deepExtend(a[prop], b[prop]); } else { - if ((b[prop] === undefined || b[prop] === null) && a[prop] !== undefined && allowDeletion === true) { + if ((b[prop] === null) && a[prop] !== undefined && allowDeletion === true) { delete a[prop]; } else { @@ -281,7 +283,7 @@ exports.selectiveNotDeepExtend = function (props, a, b, allowDeletion = false) { * @param {Object} b * @param [Boolean] protoExtend --> optional parameter. If true, the prototype values will also be extended. * (ie. the options objects that inherit from others will also get the inherited options) - * @param [Boolean] global --> optional parameter. If true, the values of fields that are undefined or null will not deleted + * @param [Boolean] global --> optional parameter. If true, the values of fields that are null will not deleted * @returns {Object} */ exports.deepExtend = function(a, b, protoExtend, allowDeletion) { @@ -295,7 +297,7 @@ exports.deepExtend = function(a, b, protoExtend, allowDeletion) { exports.deepExtend(a[prop], b[prop], protoExtend); } else { - if ((b[prop] === undefined || b[prop] === null) && a[prop] !== undefined && allowDeletion === true) { + if ((b[prop] === null) && a[prop] !== undefined && allowDeletion === true) { delete a[prop]; } else { @@ -1209,16 +1211,22 @@ exports.bridgeObject = function(referenceObject) { * @param [String] option | this is the option key in the options argument * @private */ -exports.mergeOptions = function (mergeTarget, options, option) { - if (options[option] !== undefined) { - if (typeof options[option] == 'boolean') { - mergeTarget[option].enabled = options[option]; - } - else { - mergeTarget[option].enabled = true; - for (var prop in options[option]) { - if (options[option].hasOwnProperty(prop)) { - mergeTarget[option][prop] = options[option][prop]; +exports.mergeOptions = function (mergeTarget, options, option, allowDeletion = false) { + if (options[option] === null) { + mergeTarget[option] = undefined; + delete mergeTarget[option]; + } + else { + if (options[option] !== undefined) { + if (typeof options[option] == 'boolean') { + mergeTarget[option].enabled = options[option]; + } + else { + mergeTarget[option].enabled = true; + for (var prop in options[option]) { + if (options[option].hasOwnProperty(prop)) { + mergeTarget[option][prop] = options[option][prop]; + } } } }