diff --git a/HISTORY.md b/HISTORY.md index 9378384f..8dd044ac 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -29,6 +29,7 @@ http://visjs.org - Second click on node is no longer unselect. - Added releaseFunction to openCluster. - Fixed bug where the network could flicker when the pixelRatio is not integer. +- Added enabled property to physics. ## 2015-05-28, version 4.1.0 diff --git a/dist/vis.js b/dist/vis.js index 51790ec2..e21e0ecf 100644 --- a/dist/vis.js +++ b/dist/vis.js @@ -5,7 +5,7 @@ * A dynamic, browser-based visualization library. * * @version 4.1.1-SNAPSHOT - * @date 2015-06-03 + * @date 2015-06-04 * * @license * Copyright (C) 2011-2014 Almende B.V, http://almende.com @@ -84,65 +84,65 @@ return /******/ (function(modules) { // webpackBootstrap // utils 'use strict'; - exports.util = __webpack_require__(2); - exports.DOMutil = __webpack_require__(9); + exports.util = __webpack_require__(4); + exports.DOMutil = __webpack_require__(10); // data - exports.DataSet = __webpack_require__(10); - exports.DataView = __webpack_require__(12); - exports.Queue = __webpack_require__(11); + exports.DataSet = __webpack_require__(11); + exports.DataView = __webpack_require__(13); + exports.Queue = __webpack_require__(12); // Graph3d - exports.Graph3d = __webpack_require__(13); + exports.Graph3d = __webpack_require__(14); exports.graph3d = { - Camera: __webpack_require__(17), - Filter: __webpack_require__(18), - Point2d: __webpack_require__(14), - Point3d: __webpack_require__(16), - Slider: __webpack_require__(19), - StepNumber: __webpack_require__(20) + Camera: __webpack_require__(18), + Filter: __webpack_require__(19), + Point2d: __webpack_require__(15), + Point3d: __webpack_require__(17), + Slider: __webpack_require__(20), + StepNumber: __webpack_require__(21) }; // Timeline - exports.Timeline = __webpack_require__(21); - exports.Graph2d = __webpack_require__(51); + exports.Timeline = __webpack_require__(22); + exports.Graph2d = __webpack_require__(52); exports.timeline = { - DateUtil: __webpack_require__(31), - DataStep: __webpack_require__(54), - Range: __webpack_require__(29), - stack: __webpack_require__(35), - TimeStep: __webpack_require__(38), + DateUtil: __webpack_require__(32), + DataStep: __webpack_require__(55), + Range: __webpack_require__(30), + stack: __webpack_require__(36), + TimeStep: __webpack_require__(39), components: { items: { - Item: __webpack_require__(37), - BackgroundItem: __webpack_require__(42), - BoxItem: __webpack_require__(40), - PointItem: __webpack_require__(41), - RangeItem: __webpack_require__(36) + Item: __webpack_require__(38), + BackgroundItem: __webpack_require__(43), + BoxItem: __webpack_require__(41), + PointItem: __webpack_require__(42), + RangeItem: __webpack_require__(37) }, - Component: __webpack_require__(23), - CurrentTime: __webpack_require__(22), - CustomTime: __webpack_require__(46), - DataAxis: __webpack_require__(53), - GraphGroup: __webpack_require__(55), - Group: __webpack_require__(34), - BackgroundGroup: __webpack_require__(39), - ItemSet: __webpack_require__(33), - Legend: __webpack_require__(59), - LineGraph: __webpack_require__(52), - TimeAxis: __webpack_require__(43) + Component: __webpack_require__(24), + CurrentTime: __webpack_require__(23), + CustomTime: __webpack_require__(47), + DataAxis: __webpack_require__(54), + GraphGroup: __webpack_require__(56), + Group: __webpack_require__(35), + BackgroundGroup: __webpack_require__(40), + ItemSet: __webpack_require__(34), + Legend: __webpack_require__(60), + LineGraph: __webpack_require__(53), + TimeAxis: __webpack_require__(44) } }; // Network - exports.Network = __webpack_require__(61); + exports.Network = __webpack_require__(62); exports.network = { Images: __webpack_require__(112), dotparser: __webpack_require__(110), gephiParser: __webpack_require__(111), - allOptions: __webpack_require__(108) + allOptions: __webpack_require__(5) }; exports.network.convertDot = function (input) { return exports.network.dotparser.DOTToGraph(input); @@ -157,9 +157,9 @@ return /******/ (function(modules) { // webpackBootstrap }; // bundled external libraries - exports.moment = __webpack_require__(5); - exports.hammer = __webpack_require__(25); // TODO: deprecate exports.hammer some day - exports.Hammer = __webpack_require__(25); + exports.moment = __webpack_require__(6); + exports.hammer = __webpack_require__(26); // TODO: deprecate exports.hammer some day + exports.Hammer = __webpack_require__(26); /***/ }, /* 1 */ @@ -178,1347 +178,1212 @@ return /******/ (function(modules) { // webpackBootstrap /* 2 */ /***/ function(module, exports, __webpack_require__) { - // utility functions - - // first check if moment.js is already loaded in the browser window, if so, - // use this instance. Else, load via commonjs. - 'use strict'; - var moment = __webpack_require__(5); - var uuid = __webpack_require__(8); + Object.defineProperty(exports, '__esModule', { + value: true + }); - /** - * Test whether given object is a number - * @param {*} object - * @return {Boolean} isNumber - */ - exports.isNumber = function (object) { - return object instanceof Number || typeof object == 'number'; - }; + 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; }; })(); - /** - * Remove everything in the DOM object - * @param DOMobject - */ - exports.recursiveDOMDelete = function (DOMobject) { - if (DOMobject) { - while (DOMobject.hasChildNodes() === true) { - exports.recursiveDOMDelete(DOMobject.firstChild); - DOMobject.removeChild(DOMobject.firstChild); - } - } - }; + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + + var util = __webpack_require__(4); + var Hammer = __webpack_require__(26); + var hammerUtil = __webpack_require__(31); /** - * this function gives you a range between 0 and 1 based on the min and max values in the set, the total sum of all values and the current value. + * clears the toolbar div element of children * - * @param min - * @param max - * @param total - * @param value - * @returns {number} + * @private */ - exports.giveRange = function (min, max, total, value) { - if (max == min) { - return 0.5; - } else { - var scale = 1 / (max - min); - return Math.max(0, (value - min) * scale); - } - }; - /** - * Test whether given object is a string - * @param {*} object - * @return {Boolean} isString - */ - exports.isString = function (object) { - return object instanceof String || typeof object == 'string'; - }; + var ManipulationSystem = (function () { + function ManipulationSystem(body, canvas, selectionHandler) { + var _this = this; - /** - * Test whether given object is a Date, or a String containing a Date - * @param {Date | String} object - * @return {Boolean} isDate - */ - exports.isDate = function (object) { - if (object instanceof Date) { - return true; - } else if (exports.isString(object)) { - // test whether this string contains a date - var match = ASPDateRegex.exec(object); - if (match) { - return true; - } else if (!isNaN(Date.parse(object))) { - return true; - } - } + _classCallCheck(this, ManipulationSystem); - return false; - }; + this.body = body; + this.canvas = canvas; + this.selectionHandler = selectionHandler; - /** - * Create a semi UUID - * source: http://stackoverflow.com/a/105074/1262753 - * @return {String} uuid - */ - exports.randomUUID = function () { - return uuid.v4(); - }; + this.editMode = false; + this.manipulationDiv = undefined; + this.editModeDiv = undefined; + this.closeDiv = undefined; - /** - * assign all keys of an object that are not nested objects to a certain value (used for color objects). - * @param obj - * @param value - */ - exports.assignAllKeys = function (obj, value) { - for (var prop in obj) { - if (obj.hasOwnProperty(prop)) { - if (typeof obj[prop] !== 'object') { - obj[prop] = value; + this.manipulationHammers = []; + this.temporaryUIFunctions = {}; + this.temporaryEventFunctions = []; + + this.touchTime = 0; + this.temporaryIds = { nodes: [], edges: [] }; + this.guiEnabled = false; + this.inMode = false; + this.selectedControlNode = undefined; + + this.options = {}; + this.defaultOptions = { + enabled: false, + initiallyActive: false, + addNode: true, + addEdge: true, + editNode: undefined, + editEdge: true, + deleteNode: true, + deleteEdge: true, + controlNodeStyle: { + shape: 'dot', + size: 6, + color: { background: '#ff0000', border: '#3c3c3c', highlight: { background: '#07f968', border: '#3c3c3c' } }, + borderWidth: 2, + borderWidthSelected: 2 } - } + }; + util.extend(this.options, this.defaultOptions); + + this.body.emitter.on('destroy', function () { + _this._clean(); + }); + this.body.emitter.on('_dataChanged', this._restore.bind(this)); + this.body.emitter.on('_resetData', this._restore.bind(this)); } - }; - /** - * Fill an object with a possibly partially defined other object. Only copies values if the a object has an object requiring values. - * That means an object is not created on a property if only the b object has it. - * @param obj - * @param value - */ - exports.fillIfDefined = function (a, b) { - var allowDeletion = arguments[2] === undefined ? false : arguments[2]; + _createClass(ManipulationSystem, [{ + key: '_restore', - for (var prop in a) { - if (b[prop] !== undefined) { - if (typeof b[prop] !== 'object') { - if ((b[prop] === undefined || b[prop] === null) && a[prop] !== undefined && allowDeletion === true) { - delete a[prop]; + /** + * If something changes in the data during editing, switch back to the initial datamanipulation state and close all edit modes. + * @private + */ + value: function _restore() { + if (this.inMode !== false) { + if (this.options.initiallyActive === true) { + this.enableEditMode(); } else { - a[prop] = b[prop]; - } - } else { - if (typeof a[prop] === 'object') { - exports.fillIfDefined(a[prop], b[prop], allowDeletion); + this.disableEditMode(); } } } - } - }; + }, { + key: 'setOptions', - /** - * Extend object a with the properties of object b or a series of objects - * Only properties with defined values are copied - * @param {Object} a - * @param {... Object} b - * @return {Object} a - */ - exports.protoExtend = function (a, b) { - for (var i = 1; i < arguments.length; i++) { - var other = arguments[i]; - for (var prop in other) { - a[prop] = other[prop]; - } - } - return a; - }; + /** + * Set the Options + * @param options + */ + value: function setOptions(options, allOptions, globalOptions) { + if (allOptions !== undefined) { + if (allOptions.locale !== undefined) { + this.options.locale = allOptions.locale; + } else { + this.options.locale = globalOptions.locale; + } + if (allOptions.locales !== undefined) { + this.options.locales = allOptions.locales; + } else { + this.options.locales = globalOptions.locales; + } + } - /** - * Extend object a with the properties of object b or a series of objects - * Only properties with defined values are copied - * @param {Object} a - * @param {... Object} b - * @return {Object} a - */ - exports.extend = function (a, b) { - for (var i = 1; i < arguments.length; i++) { - var other = arguments[i]; - for (var prop in other) { - if (other.hasOwnProperty(prop)) { - a[prop] = other[prop]; + if (options !== undefined) { + if (typeof options === 'boolean') { + this.options.enabled = options; + } else { + this.options.enabled = true; + util.deepExtend(this.options, options); + } + if (this.options.initiallyActive === true) { + this.editMode = true; + } + this._setup(); } } - } - return a; - }; + }, { + key: 'toggleEditMode', - /** - * Extend object a with selected properties of object b or a series of objects - * Only properties with defined values are copied - * @param {Array.} props - * @param {Object} a - * @param {Object} b - * @return {Object} a - */ - exports.selectiveExtend = function (props, a, b) { - if (!Array.isArray(props)) { - throw new Error('Array with property names expected as first argument'); - } + /** + * Enable or disable edit-mode. Draws the DOM required and cleans up after itself. + * + * @private + */ + value: function toggleEditMode() { + if (this.editMode === true) { + this.disableEditMode(); + } else { + this.enableEditMode(); + } + } + }, { + key: 'enableEditMode', + value: function enableEditMode() { + this.editMode = true; - for (var i = 2; i < arguments.length; i++) { - var other = arguments[i]; + this._clean(); + if (this.guiEnabled === true) { + this.manipulationDiv.style.display = 'block'; + this.closeDiv.style.display = 'block'; + this.editModeDiv.style.display = 'none'; + this.showManipulatorToolbar(); + } + } + }, { + key: 'disableEditMode', + value: function disableEditMode() { + this.editMode = false; - for (var p = 0; p < props.length; p++) { - var prop = props[p]; - if (other.hasOwnProperty(prop)) { - a[prop] = other[prop]; + this._clean(); + if (this.guiEnabled === true) { + this.manipulationDiv.style.display = 'none'; + this.closeDiv.style.display = 'none'; + this.editModeDiv.style.display = 'block'; + this._createEditButton(); } } - } - return a; - }; + }, { + key: 'showManipulatorToolbar', - /** - * Extend object a with selected properties of object b or a series of objects - * Only properties with defined values are copied - * @param {Array.} props - * @param {Object} a - * @param {Object} b - * @return {Object} a - */ - exports.selectiveDeepExtend = function (props, a, b) { - var allowDeletion = arguments[3] === undefined ? false : arguments[3]; + /** + * Creates the main toolbar. Removes functions bound to the select event. Binds all the buttons of the toolbar. + * + * @private + */ + value: function showManipulatorToolbar() { + // restore the state of any bound functions or events, remove control nodes, restore physics + this._clean(); - // TODO: add support for Arrays to deepExtend - if (Array.isArray(b)) { - throw new TypeError('Arrays are not supported by deepExtend'); - } - for (var i = 2; i < arguments.length; i++) { - var other = arguments[i]; - for (var p = 0; p < props.length; p++) { - var prop = props[p]; - if (other.hasOwnProperty(prop)) { - if (b[prop] && b[prop].constructor === Object) { - if (a[prop] === undefined) { - a[prop] = {}; - } - if (a[prop].constructor === Object) { - exports.deepExtend(a[prop], b[prop], false, allowDeletion); + // reset global letiables + this.manipulationDOM = {}; + + // if the gui is enabled, draw all elements. + if (this.guiEnabled === true) { + // a _restore will hide these menus + this.editMode = true; + this.manipulationDiv.style.display = 'block'; + this.closeDiv.style.display = 'block'; + + var selectedNodeCount = this.selectionHandler._getSelectedNodeCount(); + var selectedEdgeCount = this.selectionHandler._getSelectedEdgeCount(); + var selectedTotalCount = selectedNodeCount + selectedEdgeCount; + var locale = this.options.locales[this.options.locale]; + var needSeperator = false; + + if (this.options.addNode !== false) { + this._createAddNodeButton(locale); + needSeperator = true; + } + if (this.options.addEdge !== false) { + if (needSeperator === true) { + this._createSeperator(1); } else { - if (b[prop] === null && a[prop] !== undefined && allowDeletion === true) { - delete a[prop]; - } else { - a[prop] = b[prop]; - } + needSeperator = true; } - } else if (Array.isArray(b[prop])) { - throw new TypeError('Arrays are not supported by deepExtend'); - } else { - a[prop] = b[prop]; + this._createAddEdgeButton(locale); } - } - } - } - return a; - }; - - /** - * Extend object a with selected properties of object b or a series of objects - * Only properties with defined values are copied - * @param {Array.} props - * @param {Object} a - * @param {Object} b - * @return {Object} a - */ - exports.selectiveNotDeepExtend = function (props, a, b) { - var allowDeletion = arguments[3] === undefined ? false : arguments[3]; - // TODO: add support for Arrays to deepExtend - if (Array.isArray(b)) { - throw new TypeError('Arrays are not supported by deepExtend'); - } - for (var prop in b) { - if (b.hasOwnProperty(prop)) { - if (props.indexOf(prop) == -1) { - if (b[prop] && b[prop].constructor === Object) { - if (a[prop] === undefined) { - a[prop] = {}; + if (selectedNodeCount === 1 && typeof this.options.editNode === 'function') { + if (needSeperator === true) { + this._createSeperator(2); + } else { + needSeperator = true; } - if (a[prop].constructor === Object) { - exports.deepExtend(a[prop], b[prop]); + this._createEditNodeButton(locale); + } else if (selectedEdgeCount === 1 && selectedNodeCount === 0 && this.options.editEdge !== false) { + if (needSeperator === true) { + this._createSeperator(3); } else { - if (b[prop] === null && a[prop] !== undefined && allowDeletion === true) { - delete a[prop]; - } else { - a[prop] = b[prop]; - } + needSeperator = true; } - } else if (Array.isArray(b[prop])) { - throw new TypeError('Arrays are not supported by deepExtend'); - } else { - a[prop] = b[prop]; + this._createEditEdgeButton(locale); } - } - } - } - return a; - }; - /** - * Deep extend an object a with the properties of object b - * @param {Object} a - * @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 null will not deleted - * @returns {Object} - */ - exports.deepExtend = function (a, b, protoExtend, allowDeletion) { - for (var prop in b) { - if (b.hasOwnProperty(prop) || protoExtend === true) { - if (b[prop] && b[prop].constructor === Object) { - if (a[prop] === undefined) { - a[prop] = {}; - } - if (a[prop].constructor === Object) { - exports.deepExtend(a[prop], b[prop], protoExtend); - } else { - if (b[prop] === null && a[prop] !== undefined && allowDeletion === true) { - delete a[prop]; - } else { - a[prop] = b[prop]; + // remove buttons + if (selectedTotalCount !== 0) { + if (selectedNodeCount === 1 && this.options.deleteNode !== false) { + if (needSeperator === true) { + this._createSeperator(4); + } + this._createDeleteButton(locale); + } else if (selectedNodeCount === 0 && this.options.deleteEdge !== false) { + if (needSeperator === true) { + this._createSeperator(4); + } + this._createDeleteButton(locale); } } - } else if (Array.isArray(b[prop])) { - a[prop] = []; - for (var i = 0; i < b[prop].length; i++) { - a[prop].push(b[prop][i]); - } - } else { - a[prop] = b[prop]; - } - } - } - return a; - }; - /** - * Test whether all elements in two arrays are equal. - * @param {Array} a - * @param {Array} b - * @return {boolean} Returns true if both arrays have the same length and same - * elements. - */ - exports.equalArray = function (a, b) { - if (a.length != b.length) return false; + // bind the close button + this._bindHammerToDiv(this.closeDiv, this.toggleEditMode.bind(this)); - for (var i = 0, len = a.length; i < len; i++) { - if (a[i] != b[i]) return false; - } + // refresh this bar based on what has been selected + this._temporaryBindEvent('select', this.showManipulatorToolbar.bind(this)); + } - return true; - }; + // redraw to show any possible changes + this.body.emitter.emit('_redraw'); + } + }, { + key: 'addNodeMode', - /** - * Convert an object to another type - * @param {Boolean | Number | String | Date | Moment | Null | undefined} object - * @param {String | undefined} type Name of the type. Available types: - * 'Boolean', 'Number', 'String', - * 'Date', 'Moment', ISODate', 'ASPDate'. - * @return {*} object - * @throws Error - */ - exports.convert = function (object, type) { - var match; + /** + * Create the toolbar for adding Nodes + * + * @private + */ + value: function addNodeMode() { + // when using the gui, enable edit mode if it wasnt already. + if (this.editMode !== true) { + this.enableEditMode(); + } - if (object === undefined) { - return undefined; - } - if (object === null) { - return null; - } + // restore the state of any bound functions or events, remove control nodes, restore physics + this._clean(); - if (!type) { - return object; - } - if (!(typeof type === 'string') && !(type instanceof String)) { - throw new Error('Type must be a string'); - } + this.inMode = 'addNode'; + if (this.guiEnabled === true) { + var locale = this.options.locales[this.options.locale]; + this.manipulationDOM = {}; + this._createBackButton(locale); + this._createSeperator(); + this._createDescription(locale['addDescription'] || this.options.locales['en']['addDescription']); - //noinspection FallthroughInSwitchStatementJS - switch (type) { - case 'boolean': - case 'Boolean': - return Boolean(object); + // bind the close button + this._bindHammerToDiv(this.closeDiv, this.toggleEditMode.bind(this)); + } - case 'number': - case 'Number': - return Number(object.valueOf()); + this._temporaryBindEvent('click', this._performAddNode.bind(this)); + } + }, { + key: 'editNode', - case 'string': - case 'String': - return String(object); + /** + * call the bound function to handle the editing of the node. The node has to be selected. + * + * @private + */ + value: function editNode() { + var _this2 = this; - case 'Date': - if (exports.isNumber(object)) { - return new Date(object); - } - if (object instanceof Date) { - return new Date(object.valueOf()); - } else if (moment.isMoment(object)) { - return new Date(object.valueOf()); - } - if (exports.isString(object)) { - match = ASPDateRegex.exec(object); - if (match) { - // object is an ASP date - return new Date(Number(match[1])); // parse number - } else { - return moment(object).toDate(); // parse string - } - } else { - throw new Error('Cannot convert object of type ' + exports.getType(object) + ' to type Date'); + // when using the gui, enable edit mode if it wasnt already. + if (this.editMode !== true) { + this.enableEditMode(); } - case 'Moment': - if (exports.isNumber(object)) { - return moment(object); - } - if (object instanceof Date) { - return moment(object.valueOf()); - } else if (moment.isMoment(object)) { - return moment(object); - } - if (exports.isString(object)) { - match = ASPDateRegex.exec(object); - if (match) { - // object is an ASP date - return moment(Number(match[1])); // parse number - } else { - return moment(object); // parse string - } - } else { - throw new Error('Cannot convert object of type ' + exports.getType(object) + ' to type Date'); - } + // restore the state of any bound functions or events, remove control nodes, restore physics + this._clean(); + var node = this.selectionHandler._getSelectedNode(); + if (node !== undefined) { + this.inMode = 'editNode'; + if (typeof this.options.editNode === 'function') { + if (node.isCluster !== true) { + var data = util.deepExtend({}, node.options, true); + data.x = node.x; + data.y = node.y; - case 'ISODate': - if (exports.isNumber(object)) { - return new Date(object); - } else if (object instanceof Date) { - return object.toISOString(); - } else if (moment.isMoment(object)) { - return object.toDate().toISOString(); - } else if (exports.isString(object)) { - match = ASPDateRegex.exec(object); - if (match) { - // object is an ASP date - return new Date(Number(match[1])).toISOString(); // parse number + if (this.options.editNode.length === 2) { + this.options.editNode(data, function (finalizedData) { + if (finalizedData !== null && finalizedData !== undefined && _this2.inMode === 'editNode') { + // if for whatever reason the mode has changes (due to dataset change) disregard the callback) { + _this2.body.data.nodes.getDataSet().update(finalizedData); + } + _this2.showManipulatorToolbar(); + }); + } else { + throw new Error('The function for edit does not support two arguments (data, callback)'); + } + } else { + alert(this.options.locales[this.options.locale]['editClusterError'] || this.options.locales['en']['editClusterError']); + } } else { - return new Date(object).toISOString(); // parse string + throw new Error('No function has been configured to handle the editing of nodes.'); } } else { - throw new Error('Cannot convert object of type ' + exports.getType(object) + ' to type ISODate'); + this.showManipulatorToolbar(); } + } + }, { + key: 'addEdgeMode', - case 'ASPDate': - if (exports.isNumber(object)) { - return '/Date(' + object + ')/'; - } else if (object instanceof Date) { - return '/Date(' + object.valueOf() + ')/'; - } else if (exports.isString(object)) { - match = ASPDateRegex.exec(object); - var value; - if (match) { - // object is an ASP date - value = new Date(Number(match[1])).valueOf(); // parse number - } else { - value = new Date(object).valueOf(); // parse string - } - return '/Date(' + value + ')/'; - } else { - throw new Error('Cannot convert object of type ' + exports.getType(object) + ' to type ASPDate'); + /** + * create the toolbar to connect nodes + * + * @private + */ + value: function addEdgeMode() { + // when using the gui, enable edit mode if it wasnt already. + if (this.editMode !== true) { + this.enableEditMode(); } - default: - throw new Error('Unknown type "' + type + '"'); - } - }; + // restore the state of any bound functions or events, remove control nodes, restore physics + this._clean(); - // parse ASP.Net Date pattern, - // for example '/Date(1198908717056)/' or '/Date(1198908717056-0700)/' - // code from http://momentjs.com/ - var ASPDateRegex = /^\/?Date\((\-?\d+)/i; + this.inMode = 'addEdge'; + if (this.guiEnabled === true) { + var locale = this.options.locales[this.options.locale]; + this.manipulationDOM = {}; + this._createBackButton(locale); + this._createSeperator(); + this._createDescription(locale['edgeDescription'] || this.options.locales['en']['edgeDescription']); - /** - * Get the type of an object, for example exports.getType([]) returns 'Array' - * @param {*} object - * @return {String} type - */ - exports.getType = function (object) { - var type = typeof object; + // bind the close button + this._bindHammerToDiv(this.closeDiv, this.toggleEditMode.bind(this)); + } - if (type == 'object') { - if (object === null) { - return 'null'; - } - if (object instanceof Boolean) { - return 'Boolean'; - } - if (object instanceof Number) { - return 'Number'; - } - if (object instanceof String) { - return 'String'; - } - if (Array.isArray(object)) { - return 'Array'; - } - if (object instanceof Date) { - return 'Date'; + // temporarily overload functions + this._temporaryBindUI('onTouch', this._handleConnect.bind(this)); + this._temporaryBindUI('onDragEnd', this._finishConnect.bind(this)); + this._temporaryBindUI('onDrag', this._dragControlNode.bind(this)); + this._temporaryBindUI('onRelease', this._finishConnect.bind(this)); + + this._temporaryBindUI('onDragStart', function () {}); + this._temporaryBindUI('onHold', function () {}); } - return 'Object'; - } else if (type == 'number') { - return 'Number'; - } else if (type == 'boolean') { - return 'Boolean'; - } else if (type == 'string') { - return 'String'; - } else if (type === undefined) { - return 'undefined'; - } + }, { + key: 'editEdgeMode', - return type; - }; + /** + * create the toolbar to edit edges + * + * @private + */ + value: function editEdgeMode() { + var _this3 = this; - /** - * Used to extend an array and copy it. This is used to propagate paths recursively. - * - * @param arr - * @param newValue - * @returns {Array} - */ - exports.copyAndExtendArray = function (arr, newValue) { - var newArr = []; - for (var i = 0; i < arr.length; i++) { - newArr.push(arr[i]); - } - newArr.push(newValue); - return newArr; - }; + // when using the gui, enable edit mode if it wasnt already. + if (this.editMode !== true) { + this.enableEditMode(); + } - /** - * Used to extend an array and copy it. This is used to propagate paths recursively. - * - * @param arr - * @param newValue - * @returns {Array} - */ - exports.copyArray = function (arr) { - var newArr = []; - for (var i = 0; i < arr.length; i++) { - newArr.push(arr[i]); - } - return newArr; - }; + // restore the state of any bound functions or events, remove control nodes, restore physics + this._clean(); - /** - * Retrieve the absolute left value of a DOM element - * @param {Element} elem A dom element, for example a div - * @return {number} left The absolute left position of this element - * in the browser page. - */ - exports.getAbsoluteLeft = function (elem) { - return elem.getBoundingClientRect().left; - }; + this.inMode = 'editEdge'; + if (this.guiEnabled === true) { + var locale = this.options.locales[this.options.locale]; + this.manipulationDOM = {}; + this._createBackButton(locale); + this._createSeperator(); + this._createDescription(locale['editEdgeDescription'] || this.options.locales['en']['editEdgeDescription']); - /** - * Retrieve the absolute top value of a DOM element - * @param {Element} elem A dom element, for example a div - * @return {number} top The absolute top position of this element - * in the browser page. - */ - exports.getAbsoluteTop = function (elem) { - return elem.getBoundingClientRect().top; - }; + // bind the close button + this._bindHammerToDiv(this.closeDiv, this.toggleEditMode.bind(this)); + } - /** - * add a className to the given elements style - * @param {Element} elem - * @param {String} className - */ - exports.addClassName = function (elem, className) { - var classes = elem.className.split(' '); - if (classes.indexOf(className) == -1) { - classes.push(className); // add the class to the array - elem.className = classes.join(' '); - } - }; + this.edgeBeingEditedId = this.selectionHandler.getSelectedEdges()[0]; + if (this.edgeBeingEditedId !== undefined) { + (function () { + var edge = _this3.body.edges[_this3.edgeBeingEditedId]; - /** - * add a className to the given elements style - * @param {Element} elem - * @param {String} className - */ - exports.removeClassName = function (elem, className) { - var classes = elem.className.split(' '); - var index = classes.indexOf(className); - if (index != -1) { - classes.splice(index, 1); // remove the class from the array - elem.className = classes.join(' '); - } - }; + // create control nodes + var controlNodeFrom = _this3._getNewTargetNode(edge.from.x, edge.from.y); + var controlNodeTo = _this3._getNewTargetNode(edge.to.x, edge.to.y); - /** - * For each method for both arrays and objects. - * In case of an array, the built-in Array.forEach() is applied. - * In case of an Object, the method loops over all properties of the object. - * @param {Object | Array} object An Object or Array - * @param {function} callback Callback method, called for each item in - * the object or array with three parameters: - * callback(value, index, object) - */ - exports.forEach = function (object, callback) { - var i, len; - if (Array.isArray(object)) { - // array - for (i = 0, len = object.length; i < len; i++) { - callback(object[i], i, object); - } - } else { - // object - for (i in object) { - if (object.hasOwnProperty(i)) { - callback(object[i], i, object); + _this3.temporaryIds.nodes.push(controlNodeFrom.id); + _this3.temporaryIds.nodes.push(controlNodeTo.id); + + _this3.body.nodes[controlNodeFrom.id] = controlNodeFrom; + _this3.body.nodeIndices.push(controlNodeFrom.id); + _this3.body.nodes[controlNodeTo.id] = controlNodeTo; + _this3.body.nodeIndices.push(controlNodeTo.id); + + // temporarily overload UI functions, cleaned up automatically because of _temporaryBindUI + _this3._temporaryBindUI('onTouch', _this3._controlNodeTouch.bind(_this3)); // used to get the position + _this3._temporaryBindUI('onTap', function () {}); // disabled + _this3._temporaryBindUI('onHold', function () {}); // disabled + _this3._temporaryBindUI('onDragStart', _this3._controlNodeDragStart.bind(_this3)); // used to select control node + _this3._temporaryBindUI('onDrag', _this3._controlNodeDrag.bind(_this3)); // used to drag control node + _this3._temporaryBindUI('onDragEnd', _this3._controlNodeDragEnd.bind(_this3)); // used to connect or revert control nodes + _this3._temporaryBindUI('onMouseMove', function () {}); // disabled + + // create function to position control nodes correctly on movement + // automatically cleaned up because we use the temporary bind + _this3._temporaryBindEvent('beforeDrawing', function (ctx) { + var positions = edge.edgeType.findBorderPositions(ctx); + if (controlNodeFrom.selected === false) { + controlNodeFrom.x = positions.from.x; + controlNodeFrom.y = positions.from.y; + } + if (controlNodeTo.selected === false) { + controlNodeTo.x = positions.to.x; + controlNodeTo.y = positions.to.y; + } + }); + + _this3.body.emitter.emit('_redraw'); + })(); + } else { + this.showManipulatorToolbar(); } } - } - }; + }, { + key: 'deleteSelected', - /** - * Convert an object into an array: all objects properties are put into the - * array. The resulting array is unordered. - * @param {Object} object - * @param {Array} array - */ - exports.toArray = function (object) { - var array = []; + /** + * delete everything in the selection + * + * @private + */ + value: function deleteSelected() { + var _this4 = this; - for (var prop in object) { - if (object.hasOwnProperty(prop)) array.push(object[prop]); - } + // when using the gui, enable edit mode if it wasnt already. + if (this.editMode !== true) { + this.enableEditMode(); + } - return array; - }; + // restore the state of any bound functions or events, remove control nodes, restore physics + this._clean(); - /** - * Update a property in an object - * @param {Object} object - * @param {String} key - * @param {*} value - * @return {Boolean} changed - */ - exports.updateProperty = function (object, key, value) { - if (object[key] !== value) { - object[key] = value; - return true; - } else { - return false; - } - }; + this.inMode = 'delete'; + var selectedNodes = this.selectionHandler.getSelectedNodes(); + var selectedEdges = this.selectionHandler.getSelectedEdges(); + var deleteFunction = undefined; + if (selectedNodes.length > 0) { + for (var i = 0; i < selectedNodes.length; i++) { + if (this.body.nodes[selectedNodes[i]].isCluster === true) { + alert(this.options.locales[this.options.locale]['deleteClusterError'] || this.options.locales['en']['deleteClusterError']); + return; + } + } - /** - * Add and event listener. Works for all browsers - * @param {Element} element An html element - * @param {string} action The action, for example "click", - * without the prefix "on" - * @param {function} listener The callback function to be executed - * @param {boolean} [useCapture] - */ - exports.addEventListener = function (element, action, listener, useCapture) { - if (element.addEventListener) { - if (useCapture === undefined) useCapture = false; + if (typeof this.options.deleteNode === 'function') { + deleteFunction = this.options.deleteNode; + } + } else if (selectedEdges.length > 0) { + if (typeof this.options.deleteEdge === 'function') { + deleteFunction = this.options.deleteEdge; + } + } - if (action === 'mousewheel' && navigator.userAgent.indexOf('Firefox') >= 0) { - action = 'DOMMouseScroll'; // For Firefox + if (typeof deleteFunction === 'function') { + var data = { nodes: selectedNodes, edges: selectedEdges }; + if (deleteFunction.length === 2) { + deleteFunction(data, function (finalizedData) { + if (finalizedData !== null && finalizedData !== undefined && _this4.inMode === 'delete') { + // if for whatever reason the mode has changes (due to dataset change) disregard the callback) { + _this4.body.data.edges.getDataSet().remove(finalizedData.edges); + _this4.body.data.nodes.getDataSet().remove(finalizedData.nodes); + _this4.body.emitter.emit('startSimulation'); + _this4.showManipulatorToolbar(); + } + }); + } else { + throw new Error('The function for delete does not support two arguments (data, callback)'); + } + } else { + this.body.data.edges.getDataSet().remove(selectedEdges); + this.body.data.nodes.getDataSet().remove(selectedNodes); + this.body.emitter.emit('startSimulation'); + this.showManipulatorToolbar(); + } } + }, { + key: '_setup', - element.addEventListener(action, listener, useCapture); - } else { - element.attachEvent('on' + action, listener); // IE browsers - } - }; + //********************************************** PRIVATE ***************************************// - /** - * Remove an event listener from an element - * @param {Element} element An html dom element - * @param {string} action The name of the event, for example "mousedown" - * @param {function} listener The listener function - * @param {boolean} [useCapture] - */ - exports.removeEventListener = function (element, action, listener, useCapture) { - if (element.removeEventListener) { - // non-IE browsers - if (useCapture === undefined) useCapture = false; + /** + * draw or remove the DOM + * @private + */ + value: function _setup() { + if (this.options.enabled === true) { + // Enable the GUI + this.guiEnabled = true; - if (action === 'mousewheel' && navigator.userAgent.indexOf('Firefox') >= 0) { - action = 'DOMMouseScroll'; // For Firefox - } + this._createWrappers(); + if (this.editMode === false) { + this._createEditButton(); + } else { + this.showManipulatorToolbar(); + } + } else { + this._removeManipulationDOM(); - element.removeEventListener(action, listener, useCapture); - } else { - // IE browsers - element.detachEvent('on' + action, listener); - } - }; - - /** - * Cancels the event if it is cancelable, without stopping further propagation of the event. - */ - exports.preventDefault = function (event) { - if (!event) event = window.event; + // disable the gui + this.guiEnabled = false; + } + } + }, { + key: '_createWrappers', - if (event.preventDefault) { - event.preventDefault(); // non-IE browsers - } else { - event.returnValue = false; // IE browsers - } - }; + /** + * create the div overlays that contain the DOM + * @private + */ + value: function _createWrappers() { + // load the manipulator HTML elements. All styling done in css. + if (this.manipulationDiv === undefined) { + this.manipulationDiv = document.createElement('div'); + this.manipulationDiv.className = 'vis-manipulation'; + if (this.editMode === true) { + this.manipulationDiv.style.display = 'block'; + } else { + this.manipulationDiv.style.display = 'none'; + } + this.canvas.frame.appendChild(this.manipulationDiv); + } - /** - * Get HTML element which is the target of the event - * @param {Event} event - * @return {Element} target element - */ - exports.getTarget = function (event) { - // code from http://www.quirksmode.org/js/events_properties.html - if (!event) { - event = window.event; - } + // container for the edit button. + if (this.editModeDiv === undefined) { + this.editModeDiv = document.createElement('div'); + this.editModeDiv.className = 'vis-edit-mode'; + if (this.editMode === true) { + this.editModeDiv.style.display = 'none'; + } else { + this.editModeDiv.style.display = 'block'; + } + this.canvas.frame.appendChild(this.editModeDiv); + } - var target; + // container for the close div button + if (this.closeDiv === undefined) { + this.closeDiv = document.createElement('div'); + this.closeDiv.className = 'vis-close'; + this.closeDiv.style.display = this.manipulationDiv.style.display; + this.canvas.frame.appendChild(this.closeDiv); + } + } + }, { + key: '_getNewTargetNode', - if (event.target) { - target = event.target; - } else if (event.srcElement) { - target = event.srcElement; - } + /** + * generate a new target node. Used for creating new edges and editing edges + * @param x + * @param y + * @returns {*} + * @private + */ + value: function _getNewTargetNode(x, y) { + var controlNodeStyle = util.deepExtend({}, this.options.controlNodeStyle); - if (target.nodeType != undefined && target.nodeType == 3) { - // defeat Safari bug - target = target.parentNode; - } + controlNodeStyle.id = 'targetNode' + util.randomUUID(); + controlNodeStyle.hidden = false; + controlNodeStyle.physics = false; + controlNodeStyle.x = x; + controlNodeStyle.y = y; - return target; - }; + return this.body.functions.createNode(controlNodeStyle); + } + }, { + key: '_createEditButton', - /** - * Check if given element contains given parent somewhere in the DOM tree - * @param {Element} element - * @param {Element} parent - */ - exports.hasParent = function (element, parent) { - var e = element; + /** + * Create the edit button + */ + value: function _createEditButton() { + // restore everything to it's original state (if applicable) + this._clean(); - while (e) { - if (e === parent) { - return true; - } - e = e.parentNode; - } + // reset the manipulationDOM + this.manipulationDOM = {}; - return false; - }; + // empty the editModeDiv + util.recursiveDOMDelete(this.editModeDiv); - exports.option = {}; + // create the contents for the editMode button + var locale = this.options.locales[this.options.locale]; + var button = this._createButton('editMode', 'vis-button vis-edit vis-edit-mode', locale['edit'] || this.options.locales['en']['edit']); + this.editModeDiv.appendChild(button); - /** - * Convert a value into a boolean - * @param {Boolean | function | undefined} value - * @param {Boolean} [defaultValue] - * @returns {Boolean} bool - */ - exports.option.asBoolean = function (value, defaultValue) { - if (typeof value == 'function') { - value = value(); - } + // bind a hammer listener to the button, calling the function toggleEditMode. + this._bindHammerToDiv(button, this.toggleEditMode.bind(this)); + } + }, { + key: '_clean', - if (value != null) { - return value != false; - } + /** + * this function cleans up after everything this module does. Temporary elements, functions and events are removed, physics restored, hammers removed. + * @private + */ + value: function _clean() { + // not in mode + this.inMode = false; - return defaultValue || null; - }; + // _clean the divs + if (this.guiEnabled === true) { + util.recursiveDOMDelete(this.editModeDiv); + util.recursiveDOMDelete(this.manipulationDiv); - /** - * Convert a value into a number - * @param {Boolean | function | undefined} value - * @param {Number} [defaultValue] - * @returns {Number} number - */ - exports.option.asNumber = function (value, defaultValue) { - if (typeof value == 'function') { - value = value(); - } + // removes all the bindings and overloads + this._cleanManipulatorHammers(); + } - if (value != null) { - return Number(value) || defaultValue || null; - } + // remove temporary nodes and edges + this._cleanupTemporaryNodesAndEdges(); - return defaultValue || null; - }; + // restore overloaded UI functions + this._unbindTemporaryUIs(); - /** - * Convert a value into a string - * @param {String | function | undefined} value - * @param {String} [defaultValue] - * @returns {String} str - */ - exports.option.asString = function (value, defaultValue) { - if (typeof value == 'function') { - value = value(); - } + // remove the temporaryEventFunctions + this._unbindTemporaryEvents(); - if (value != null) { - return String(value); - } + // restore the physics if required + this.body.emitter.emit('restorePhysics'); + } + }, { + key: '_cleanManipulatorHammers', - return defaultValue || null; - }; + /** + * Each dom element has it's own hammer. They are stored in this.manipulationHammers. This cleans them up. + * @private + */ + value: function _cleanManipulatorHammers() { + // _clean hammer bindings + if (this.manipulationHammers.length != 0) { + for (var i = 0; i < this.manipulationHammers.length; i++) { + this.manipulationHammers[i].destroy(); + } + this.manipulationHammers = []; + } + } + }, { + key: '_removeManipulationDOM', - /** - * Convert a size or location into a string with pixels or a percentage - * @param {String | Number | function | undefined} value - * @param {String} [defaultValue] - * @returns {String} size - */ - exports.option.asSize = function (value, defaultValue) { - if (typeof value == 'function') { - value = value(); - } + /** + * Remove all DOM elements created by this module. + * @private + */ + value: function _removeManipulationDOM() { + // removes all the bindings and overloads + this._clean(); - if (exports.isString(value)) { - return value; - } else if (exports.isNumber(value)) { - return value + 'px'; - } else { - return defaultValue || null; - } - }; + // empty the manipulation divs + util.recursiveDOMDelete(this.manipulationDiv); + util.recursiveDOMDelete(this.editModeDiv); + util.recursiveDOMDelete(this.closeDiv); - /** - * Convert a value into a DOM element - * @param {HTMLElement | function | undefined} value - * @param {HTMLElement} [defaultValue] - * @returns {HTMLElement | null} dom - */ - exports.option.asElement = function (value, defaultValue) { - if (typeof value == 'function') { - value = value(); - } + // remove the manipulation divs + if (this.manipulationDiv) { + this.canvas.frame.removeChild(this.manipulationDiv); + } + if (this.editModeDiv) { + this.canvas.frame.removeChild(this.editModeDiv); + } + if (this.closeDiv) { + this.canvas.frame.removeChild(this.manipulationDiv); + } - return value || defaultValue || null; - }; + // set the references to undefined + this.manipulationDiv = undefined; + this.editModeDiv = undefined; + this.closeDiv = undefined; + } + }, { + key: '_createSeperator', - /** - * http://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb - * - * @param {String} hex - * @returns {{r: *, g: *, b: *}} | 255 range - */ - exports.hexToRGB = function (hex) { - // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF") - var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; - hex = hex.replace(shorthandRegex, function (m, r, g, b) { - return r + r + g + g + b + b; - }); - var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); - return result ? { - r: parseInt(result[1], 16), - g: parseInt(result[2], 16), - b: parseInt(result[3], 16) - } : null; - }; + /** + * create a seperator line. the index is to differentiate in the manipulation dom + * @param index + * @private + */ + value: function _createSeperator() { + var index = arguments[0] === undefined ? 1 : arguments[0]; - /** - * This function takes color in hex format or rgb() or rgba() format and overrides the opacity. Returns rgba() string. - * @param color - * @param opacity - * @returns {*} - */ - exports.overrideOpacity = function (color, opacity) { - if (color.indexOf('rgba') != -1) { - return color; - } else if (color.indexOf('rgb') != -1) { - var rgb = color.substr(color.indexOf('(') + 1).replace(')', '').split(','); - return 'rgba(' + rgb[0] + ',' + rgb[1] + ',' + rgb[2] + ',' + opacity + ')'; - } else { - var rgb = exports.hexToRGB(color); - if (rgb == null) { - return color; - } else { - return 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + opacity + ')'; + this.manipulationDOM['seperatorLineDiv' + index] = document.createElement('div'); + this.manipulationDOM['seperatorLineDiv' + index].className = 'vis-separator-line'; + this.manipulationDiv.appendChild(this.manipulationDOM['seperatorLineDiv' + index]); } - } - }; + }, { + key: '_createAddNodeButton', - /** - * - * @param red 0 -- 255 - * @param green 0 -- 255 - * @param blue 0 -- 255 - * @returns {string} - * @constructor - */ - exports.RGBToHex = function (red, green, blue) { - return '#' + ((1 << 24) + (red << 16) + (green << 8) + blue).toString(16).slice(1); - }; + // ---------------------- DOM functions for buttons --------------------------// - /** - * Parse a color property into an object with border, background, and - * highlight colors - * @param {Object | String} color - * @return {Object} colorObject - */ - exports.parseColor = function (color) { - var c; - if (exports.isString(color) === true) { - if (exports.isValidRGB(color) === true) { - var rgb = color.substr(4).substr(0, color.length - 5).split(',').map(function (value) { - return parseInt(value); - }); - color = exports.RGBToHex(rgb[0], rgb[1], rgb[2]); + value: function _createAddNodeButton(locale) { + var button = this._createButton('addNode', 'vis-button vis-add', locale['addNode'] || this.options.locales['en']['addNode']); + this.manipulationDiv.appendChild(button); + this._bindHammerToDiv(button, this.addNodeMode.bind(this)); } - if (exports.isValidHex(color) === true) { - var hsv = exports.hexToHSV(color); - var lighterColorHSV = { h: hsv.h, s: hsv.s * 0.8, v: Math.min(1, hsv.v * 1.02) }; - var darkerColorHSV = { h: hsv.h, s: Math.min(1, hsv.s * 1.25), v: hsv.v * 0.8 }; - var darkerColorHex = exports.HSVToHex(darkerColorHSV.h, darkerColorHSV.s, darkerColorHSV.v); - var lighterColorHex = exports.HSVToHex(lighterColorHSV.h, lighterColorHSV.s, lighterColorHSV.v); - c = { - background: color, - border: darkerColorHex, - highlight: { - background: lighterColorHex, - border: darkerColorHex - }, - hover: { - background: lighterColorHex, - border: darkerColorHex - } - }; - } else { - c = { - background: color, - border: color, - highlight: { - background: color, - border: color - }, - hover: { - background: color, - border: color - } - }; + }, { + key: '_createAddEdgeButton', + value: function _createAddEdgeButton(locale) { + var button = this._createButton('addEdge', 'vis-button vis-connect', locale['addEdge'] || this.options.locales['en']['addEdge']); + this.manipulationDiv.appendChild(button); + this._bindHammerToDiv(button, this.addEdgeMode.bind(this)); } - } else { - c = {}; - c.background = color.background || undefined; - c.border = color.border || undefined; - - if (exports.isString(color.highlight)) { - c.highlight = { - border: color.highlight, - background: color.highlight - }; - } else { - c.highlight = {}; - c.highlight.background = color.highlight && color.highlight.background || undefined; - c.highlight.border = color.highlight && color.highlight.border || undefined; + }, { + key: '_createEditNodeButton', + value: function _createEditNodeButton(locale) { + var button = this._createButton('editNode', 'vis-button vis-edit', locale['editNode'] || this.options.locales['en']['editNode']); + this.manipulationDiv.appendChild(button); + this._bindHammerToDiv(button, this.editNode.bind(this)); + } + }, { + key: '_createEditEdgeButton', + value: function _createEditEdgeButton(locale) { + var button = this._createButton('editEdge', 'vis-button vis-edit', locale['editEdge'] || this.options.locales['en']['editEdge']); + this.manipulationDiv.appendChild(button); + this._bindHammerToDiv(button, this.editEdgeMode.bind(this)); + } + }, { + key: '_createDeleteButton', + value: function _createDeleteButton(locale) { + var button = this._createButton('delete', 'vis-button vis-delete', locale['del'] || this.options.locales['en']['del']); + this.manipulationDiv.appendChild(button); + this._bindHammerToDiv(button, this.deleteSelected.bind(this)); + } + }, { + key: '_createBackButton', + value: function _createBackButton(locale) { + var button = this._createButton('back', 'vis-button vis-back', locale['back'] || this.options.locales['en']['back']); + this.manipulationDiv.appendChild(button); + this._bindHammerToDiv(button, this.showManipulatorToolbar.bind(this)); } + }, { + key: '_createButton', + value: function _createButton(id, className, label) { + var labelClassName = arguments[3] === undefined ? 'vis-label' : arguments[3]; - if (exports.isString(color.hover)) { - c.hover = { - border: color.hover, - background: color.hover - }; - } else { - c.hover = {}; - c.hover.background = color.hover && color.hover.background || undefined; - c.hover.border = color.hover && color.hover.border || undefined; + this.manipulationDOM[id + 'Div'] = document.createElement('div'); + this.manipulationDOM[id + 'Div'].className = className; + this.manipulationDOM[id + 'Label'] = document.createElement('div'); + this.manipulationDOM[id + 'Label'].className = labelClassName; + this.manipulationDOM[id + 'Label'].innerHTML = label; + this.manipulationDOM[id + 'Div'].appendChild(this.manipulationDOM[id + 'Label']); + return this.manipulationDOM[id + 'Div']; } - } + }, { + key: '_createDescription', + value: function _createDescription(label) { + this.manipulationDiv.appendChild(this._createButton('description', 'vis-button vis-none', label)); + } + }, { + key: '_temporaryBindEvent', - return c; - }; + // -------------------------- End of DOM functions for buttons ------------------------------// - /** - * http://www.javascripter.net/faq/rgb2hsv.htm - * - * @param red - * @param green - * @param blue - * @returns {*} - * @constructor - */ - exports.RGBToHSV = function (red, green, blue) { - red = red / 255;green = green / 255;blue = blue / 255; - var minRGB = Math.min(red, Math.min(green, blue)); - var maxRGB = Math.max(red, Math.max(green, blue)); + /** + * this binds an event until cleanup by the clean functions. + * @param event + * @param newFunction + * @private + */ + value: function _temporaryBindEvent(event, newFunction) { + this.temporaryEventFunctions.push({ event: event, boundFunction: newFunction }); + this.body.emitter.on(event, newFunction); + } + }, { + key: '_temporaryBindUI', - // Black-gray-white - if (minRGB == maxRGB) { - return { h: 0, s: 0, v: minRGB }; - } + /** + * this overrides an UI function until cleanup by the clean function + * @param UIfunctionName + * @param newFunction + * @private + */ + value: function _temporaryBindUI(UIfunctionName, newFunction) { + if (this.body.eventListeners[UIfunctionName] !== undefined) { + this.temporaryUIFunctions[UIfunctionName] = this.body.eventListeners[UIfunctionName]; + this.body.eventListeners[UIfunctionName] = newFunction; + } else { + throw new Error('This UI function does not exist. Typo? You tried: ' + UIfunctionName + ' possible are: ' + JSON.stringify(Object.keys(this.body.eventListeners))); + } + } + }, { + key: '_unbindTemporaryUIs', - // Colors other than black-gray-white: - var d = red == minRGB ? green - blue : blue == minRGB ? red - green : blue - red; - var h = red == minRGB ? 3 : blue == minRGB ? 1 : 5; - var hue = 60 * (h - d / (maxRGB - minRGB)) / 360; - var saturation = (maxRGB - minRGB) / maxRGB; - var value = maxRGB; - return { h: hue, s: saturation, v: value }; - }; - - var cssUtil = { - // split a string with css styles into an object with key/values - split: function split(cssText) { - var styles = {}; - - cssText.split(';').forEach(function (style) { - if (style.trim() != '') { - var parts = style.split(':'); - var key = parts[0].trim(); - var value = parts[1].trim(); - styles[key] = value; + /** + * Restore the overridden UI functions to their original state. + * + * @private + */ + value: function _unbindTemporaryUIs() { + for (var functionName in this.temporaryUIFunctions) { + if (this.temporaryUIFunctions.hasOwnProperty(functionName)) { + this.body.eventListeners[functionName] = this.temporaryUIFunctions[functionName]; + delete this.temporaryUIFunctions[functionName]; + } } - }); - - return styles; - }, + this.temporaryUIFunctions = {}; + } + }, { + key: '_unbindTemporaryEvents', - // build a css text string from an object with key/values - join: function join(styles) { - return Object.keys(styles).map(function (key) { - return key + ': ' + styles[key]; - }).join('; '); - } - }; + /** + * Unbind the events created by _temporaryBindEvent + * @private + */ + value: function _unbindTemporaryEvents() { + for (var i = 0; i < this.temporaryEventFunctions.length; i++) { + var eventName = this.temporaryEventFunctions[i].event; + var boundFunction = this.temporaryEventFunctions[i].boundFunction; + this.body.emitter.off(eventName, boundFunction); + } + this.temporaryEventFunctions = []; + } + }, { + key: '_bindHammerToDiv', - /** - * Append a string with css styles to an element - * @param {Element} element - * @param {String} cssText - */ - exports.addCssText = function (element, cssText) { - var currentStyles = cssUtil.split(element.style.cssText); - var newStyles = cssUtil.split(cssText); - var styles = exports.extend(currentStyles, newStyles); + /** + * Bind an hammer instance to a DOM element. + * @param domElement + * @param funct + */ + value: function _bindHammerToDiv(domElement, boundFunction) { + var hammer = new Hammer(domElement, {}); + hammerUtil.onTouch(hammer, boundFunction); + this.manipulationHammers.push(hammer); + } + }, { + key: '_cleanupTemporaryNodesAndEdges', - element.style.cssText = cssUtil.join(styles); - }; + /** + * Neatly clean up temporary edges and nodes + * @private + */ + value: function _cleanupTemporaryNodesAndEdges() { + // _clean temporary edges + for (var i = 0; i < this.temporaryIds.edges.length; i++) { + this.body.edges[this.temporaryIds.edges[i]].disconnect(); + delete this.body.edges[this.temporaryIds.edges[i]]; + var indexTempEdge = this.body.edgeIndices.indexOf(this.temporaryIds.edges[i]); + if (indexTempEdge !== -1) { + this.body.edgeIndices.splice(indexTempEdge, 1); + } + } - /** - * Remove a string with css styles from an element - * @param {Element} element - * @param {String} cssText - */ - exports.removeCssText = function (element, cssText) { - var styles = cssUtil.split(element.style.cssText); - var removeStyles = cssUtil.split(cssText); + // _clean temporary nodes + for (var i = 0; i < this.temporaryIds.nodes.length; i++) { + delete this.body.nodes[this.temporaryIds.nodes[i]]; + var indexTempNode = this.body.nodeIndices.indexOf(this.temporaryIds.nodes[i]); + if (indexTempNode !== -1) { + this.body.nodeIndices.splice(indexTempNode, 1); + } + } - for (var key in removeStyles) { - if (removeStyles.hasOwnProperty(key)) { - delete styles[key]; + this.temporaryIds = { nodes: [], edges: [] }; } - } + }, { + key: '_controlNodeTouch', - element.style.cssText = cssUtil.join(styles); - }; + // ------------------------------------------ EDIT EDGE FUNCTIONS -----------------------------------------// - /** - * https://gist.github.com/mjijackson/5311256 - * @param h - * @param s - * @param v - * @returns {{r: number, g: number, b: number}} - * @constructor - */ - exports.HSVToRGB = function (h, s, v) { - var r, g, b; + /** + * the touch is used to get the position of the initial click + * @param event + * @private + */ + value: function _controlNodeTouch(event) { + this.selectionHandler.unselectAll(); + this.lastTouch = this.body.functions.getPointer(event.center); + this.lastTouch.translation = util.extend({}, this.body.view.translation); // copy the object + } + }, { + key: '_controlNodeDragStart', - var i = Math.floor(h * 6); - var f = h * 6 - i; - var p = v * (1 - s); - var q = v * (1 - f * s); - var t = v * (1 - (1 - f) * s); + /** + * the drag start is used to mark one of the control nodes as selected. + * @param event + * @private + */ + value: function _controlNodeDragStart(event) { + var pointer = this.lastTouch; + var pointerObj = this.selectionHandler._pointerToPositionObject(pointer); + var from = this.body.nodes[this.temporaryIds.nodes[0]]; + var to = this.body.nodes[this.temporaryIds.nodes[1]]; + var edge = this.body.edges[this.edgeBeingEditedId]; + this.selectedControlNode = undefined; - switch (i % 6) { - case 0: - r = v, g = t, b = p;break; - case 1: - r = q, g = v, b = p;break; - case 2: - r = p, g = v, b = t;break; - case 3: - r = p, g = q, b = v;break; - case 4: - r = t, g = p, b = v;break; - case 5: - r = v, g = p, b = q;break; - } + var fromSelect = from.isOverlappingWith(pointerObj); + var toSelect = to.isOverlappingWith(pointerObj); - return { r: Math.floor(r * 255), g: Math.floor(g * 255), b: Math.floor(b * 255) }; - }; + if (fromSelect === true) { + this.selectedControlNode = from; + edge.edgeType.from = from; + } else if (toSelect === true) { + this.selectedControlNode = to; + edge.edgeType.to = to; + } - exports.HSVToHex = function (h, s, v) { - var rgb = exports.HSVToRGB(h, s, v); - return exports.RGBToHex(rgb.r, rgb.g, rgb.b); - }; + this.body.emitter.emit('_redraw'); + } + }, { + key: '_controlNodeDrag', - exports.hexToHSV = function (hex) { - var rgb = exports.hexToRGB(hex); - return exports.RGBToHSV(rgb.r, rgb.g, rgb.b); - }; + /** + * dragging the control nodes or the canvas + * @param event + * @private + */ + value: function _controlNodeDrag(event) { + this.body.emitter.emit('disablePhysics'); + var pointer = this.body.functions.getPointer(event.center); + var pos = this.canvas.DOMtoCanvas(pointer); - exports.isValidHex = function (hex) { - var isOk = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(hex); - return isOk; - }; + if (this.selectedControlNode !== undefined) { + this.selectedControlNode.x = pos.x; + this.selectedControlNode.y = pos.y; + } else { + // if the drag was not started properly because the click started outside the network div, start it now. + var diffX = pointer.x - this.lastTouch.x; + var diffY = pointer.y - this.lastTouch.y; + this.body.view.translation = { x: this.lastTouch.translation.x + diffX, y: this.lastTouch.translation.y + diffY }; + } + this.body.emitter.emit('_redraw'); + } + }, { + key: '_controlNodeDragEnd', - exports.isValidRGB = function (rgb) { - rgb = rgb.replace(' ', ''); - var isOk = /rgb\((\d{1,3}),(\d{1,3}),(\d{1,3})\)/i.test(rgb); - return isOk; - }; - exports.isValidRGBA = function (rgba) { - rgba = rgba.replace(' ', ''); - var isOk = /rgba\((\d{1,3}),(\d{1,3}),(\d{1,3}),(.{1,3})\)/i.test(rgba); - return isOk; - }; + /** + * connecting or restoring the control nodes. + * @param event + * @private + */ + value: function _controlNodeDragEnd(event) { + var pointer = this.body.functions.getPointer(event.center); + var pointerObj = this.selectionHandler._pointerToPositionObject(pointer); + var edge = this.body.edges[this.edgeBeingEditedId]; - /** - * This recursively redirects the prototype of JSON objects to the referenceObject - * This is used for default options. - * - * @param referenceObject - * @returns {*} - */ - exports.selectiveBridgeObject = function (fields, referenceObject) { - if (typeof referenceObject == 'object') { - var objectTo = Object.create(referenceObject); - for (var i = 0; i < fields.length; i++) { - if (referenceObject.hasOwnProperty(fields[i])) { - if (typeof referenceObject[fields[i]] == 'object') { - objectTo[fields[i]] = exports.bridgeObject(referenceObject[fields[i]]); + var overlappingNodeIds = this.selectionHandler._getAllNodesOverlappingWith(pointerObj); + var node = undefined; + for (var i = overlappingNodeIds.length - 1; i >= 0; i--) { + if (overlappingNodeIds[i] !== this.selectedControlNode.id) { + node = this.body.nodes[overlappingNodeIds[i]]; + break; } } - } - return objectTo; - } else { - return null; - } - }; - /** - * This recursively redirects the prototype of JSON objects to the referenceObject - * This is used for default options. - * - * @param referenceObject - * @returns {*} - */ - exports.bridgeObject = function (referenceObject) { - if (typeof referenceObject == 'object') { - var objectTo = Object.create(referenceObject); - for (var i in referenceObject) { - if (referenceObject.hasOwnProperty(i)) { - if (typeof referenceObject[i] == 'object') { - objectTo[i] = exports.bridgeObject(referenceObject[i]); + // perform the connection + if (node !== undefined && this.selectedControlNode !== undefined) { + if (node.isCluster === true) { + alert(this.options.locales[this.options.locale]['createEdgeError'] || this.options.locales['en']['createEdgeError']); + } else { + var from = this.body.nodes[this.temporaryIds.nodes[0]]; + if (this.selectedControlNode.id === from.id) { + this._performEditEdge(node.id, edge.to.id); + } else { + this._performEditEdge(edge.from.id, node.id); + } } + } else { + edge.updateEdgeType(); + this.body.emitter.emit('restorePhysics'); } + this.body.emitter.emit('_redraw'); } - return objectTo; - } else { - return null; - } - }; + }, { + key: '_handleConnect', - /** - * this is used to set the options of subobjects in the options object. A requirement of these subobjects - * is that they have an 'enabled' element which is optional for the user but mandatory for the program. - * - * @param [object] mergeTarget | this is either this.options or the options used for the groups. - * @param [object] options | options - * @param [String] option | this is the option key in the options argument - * @private - */ - exports.mergeOptions = function (mergeTarget, options, option) { - var allowDeletion = arguments[3] === undefined ? false : arguments[3]; + // ------------------------------------ END OF EDIT EDGE FUNCTIONS -----------------------------------------// - 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 { - if (options[option].enabled === undefined) { - mergeTarget[option].enabled = true; - } - for (var prop in options[option]) { - if (options[option].hasOwnProperty(prop)) { - mergeTarget[option][prop] = options[option][prop]; + // ------------------------------------------- ADD EDGE FUNCTIONS -----------------------------------------// + /** + * the function bound to the selection event. It checks if you want to connect a cluster and changes the description + * to walk the user through the process. + * + * @private + */ + value: function _handleConnect(event) { + // check to avoid double fireing of this function. + if (new Date().valueOf() - this.touchTime > 100) { + this.lastTouch = this.body.functions.getPointer(event.center); + this.lastTouch.translation = util.extend({}, this.body.view.translation); // copy the object + + var pointer = this.lastTouch; + var node = this.selectionHandler.getNodeAt(pointer); + + if (node !== undefined) { + if (node.isCluster === true) { + alert(this.options.locales[this.options.locale]['createEdgeError'] || this.options.locales['en']['createEdgeError']); + } else { + // create a node the temporary line can look at + var targetNode = this._getNewTargetNode(node.x, node.y); + this.body.nodes[targetNode.id] = targetNode; + this.body.nodeIndices.push(targetNode.id); + + // create a temporary edge + var connectionEdge = this.body.functions.createEdge({ + id: 'connectionEdge' + util.randomUUID(), + from: node.id, + to: targetNode.id, + physics: false, + smooth: { + enabled: true, + type: 'continuous', + roundness: 0.5 + } + }); + this.body.edges[connectionEdge.id] = connectionEdge; + this.body.edgeIndices.push(connectionEdge.id); + + this.temporaryIds.nodes.push(targetNode.id); + this.temporaryIds.edges.push(connectionEdge.id); } } + this.touchTime = new Date().valueOf(); } } - } - }; + }, { + key: '_dragControlNode', + value: function _dragControlNode(event) { + var pointer = this.body.functions.getPointer(event.center); + if (this.temporaryIds.nodes[0] !== undefined) { + var targetNode = this.body.nodes[this.temporaryIds.nodes[0]]; // there is only one temp node in the add edge mode. + targetNode.x = this.canvas._XconvertDOMtoCanvas(pointer.x); + targetNode.y = this.canvas._YconvertDOMtoCanvas(pointer.y); + this.body.emitter.emit('_redraw'); + } else { + var diffX = pointer.x - this.lastTouch.x; + var diffY = pointer.y - this.lastTouch.y; + this.body.view.translation = { x: this.lastTouch.translation.x + diffX, y: this.lastTouch.translation.y + diffY }; + } + } + }, { + key: '_finishConnect', - /** - * This function does a binary search for a visible item in a sorted list. If we find a visible item, the code that uses - * this function will then iterate in both directions over this sorted list to find all visible items. - * - * @param {Item[]} orderedItems | Items ordered by start - * @param {function} searchFunction | -1 is lower, 0 is found, 1 is higher - * @param {String} field - * @param {String} field2 - * @returns {number} - * @private - */ - exports.binarySearchCustom = function (orderedItems, searchFunction, field, field2) { - var maxIterations = 10000; - var iteration = 0; - var low = 0; - var high = orderedItems.length - 1; + /** + * Connect the new edge to the target if one exists, otherwise remove temp line + * @param event + * @private + */ + value: function _finishConnect(event) { + var pointer = this.body.functions.getPointer(event.center); + var pointerObj = this.selectionHandler._pointerToPositionObject(pointer); - while (low <= high && iteration < maxIterations) { - var middle = Math.floor((low + high) / 2); + // remember the edge id + var connectFromId = undefined; + if (this.temporaryIds.edges[0] !== undefined) { + connectFromId = this.body.edges[this.temporaryIds.edges[0]].fromId; + } - var item = orderedItems[middle]; - var value = field2 === undefined ? item[field] : item[field][field2]; + // get the overlapping node but NOT the temporary node; + var overlappingNodeIds = this.selectionHandler._getAllNodesOverlappingWith(pointerObj); + var node = undefined; + for (var i = overlappingNodeIds.length - 1; i >= 0; i--) { + // if the node id is NOT a temporary node, accept the node. + if (this.temporaryIds.nodes.indexOf(overlappingNodeIds[i]) === -1) { + node = this.body.nodes[overlappingNodeIds[i]]; + break; + } + } - var searchResult = searchFunction(value); - if (searchResult == 0) { - // jihaa, found a visible item! - return middle; - } else if (searchResult == -1) { - // it is too small --> increase low - low = middle + 1; - } else { - // it is too big --> decrease high - high = middle - 1; + // clean temporary nodes and edges. + this._cleanupTemporaryNodesAndEdges(); + + // perform the connection + if (node !== undefined) { + if (node.isCluster === true) { + alert(this.options.locales[this.options.locale]['createEdgeError'] || this.options.locales['en']['createEdgeError']); + } else { + if (this.body.nodes[connectFromId] !== undefined && this.body.nodes[node.id] !== undefined) { + this._performAddEdge(connectFromId, node.id); + } + } + } + this.body.emitter.emit('_redraw'); } + }, { + key: '_performAddNode', - iteration++; - } + // --------------------------------------- END OF ADD EDGE FUNCTIONS -------------------------------------// - return -1; - }; + // ------------------------------ Performing all the actual data manipulation ------------------------// - /** - * This function does a binary search for a specific value in a sorted array. If it does not exist but is in between of - * two values, we return either the one before or the one after, depending on user input - * If it is found, we return the index, else -1. - * - * @param {Array} orderedItems - * @param {{start: number, end: number}} target - * @param {String} field - * @param {String} sidePreference 'before' or 'after' - * @returns {number} - * @private - */ - exports.binarySearchValue = function (orderedItems, target, field, sidePreference) { - var maxIterations = 10000; - var iteration = 0; - var low = 0; - var high = orderedItems.length - 1; - var prevValue, value, nextValue, middle; + /** + * Adds a node on the specified location + */ + value: function _performAddNode(clickData) { + var _this5 = this; - while (low <= high && iteration < maxIterations) { - // get a new guess - middle = Math.floor(0.5 * (high + low)); - prevValue = orderedItems[Math.max(0, middle - 1)][field]; - value = orderedItems[middle][field]; - nextValue = orderedItems[Math.min(orderedItems.length - 1, middle + 1)][field]; + var defaultData = { + id: util.randomUUID(), + x: clickData.pointer.canvas.x, + y: clickData.pointer.canvas.y, + label: 'new' + }; - if (value == target) { - // we found the target - return middle; - } else if (prevValue < target && value > target) { - // target is in between of the previous and the current - return sidePreference == 'before' ? Math.max(0, middle - 1) : middle; - } else if (value < target && nextValue > target) { - // target is in between of the current and the next - return sidePreference == 'before' ? middle : Math.min(orderedItems.length - 1, middle + 1); - } else { - // didnt find the target, we need to change our boundaries. - if (value < target) { - // it is too small --> increase low - low = middle + 1; + if (typeof this.options.addNode === 'function') { + if (this.options.addNode.length === 2) { + this.options.addNode(defaultData, function (finalizedData) { + if (finalizedData !== null && finalizedData !== undefined && _this5.inMode === 'addNode') { + // if for whatever reason the mode has changes (due to dataset change) disregard the callback + _this5.body.data.nodes.getDataSet().add(finalizedData); + _this5.showManipulatorToolbar(); + } + }); + } else { + throw new Error('The function for add does not support two arguments (data,callback)'); + this.showManipulatorToolbar(); + } } else { - // it is too big --> decrease high - high = middle - 1; + this.body.data.nodes.getDataSet().add(defaultData); + this.showManipulatorToolbar(); } } - iteration++; - } + }, { + key: '_performAddEdge', - // didnt find anything. Return -1. - return -1; - }; + /** + * connect two nodes with a new edge. + * + * @private + */ + value: function _performAddEdge(sourceNodeId, targetNodeId) { + var _this6 = this; - /* - * Easing Functions - inspired from http://gizma.com/easing/ - * only considering the t value for the range [0, 1] => [0, 1] - * https://gist.github.com/gre/1650294 - */ - exports.easingFunctions = { - // no easing, no acceleration - linear: function linear(t) { - return t; - }, - // accelerating from zero velocity - easeInQuad: function easeInQuad(t) { - return t * t; - }, - // decelerating to zero velocity - easeOutQuad: function easeOutQuad(t) { - return t * (2 - t); - }, - // acceleration until halfway, then deceleration - easeInOutQuad: function easeInOutQuad(t) { - return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t; - }, - // accelerating from zero velocity - easeInCubic: function easeInCubic(t) { - return t * t * t; - }, - // decelerating to zero velocity - easeOutCubic: function easeOutCubic(t) { - return --t * t * t + 1; - }, - // acceleration until halfway, then deceleration - easeInOutCubic: function easeInOutCubic(t) { - return t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1; - }, - // accelerating from zero velocity - easeInQuart: function easeInQuart(t) { - return t * t * t * t; - }, - // decelerating to zero velocity - easeOutQuart: function easeOutQuart(t) { - return 1 - --t * t * t * t; - }, - // acceleration until halfway, then deceleration - easeInOutQuart: function easeInOutQuart(t) { - return t < 0.5 ? 8 * t * t * t * t : 1 - 8 * --t * t * t * t; - }, - // accelerating from zero velocity - easeInQuint: function easeInQuint(t) { - return t * t * t * t * t; - }, - // decelerating to zero velocity - easeOutQuint: function easeOutQuint(t) { - return 1 + --t * t * t * t * t; - }, - // acceleration until halfway, then deceleration - easeInOutQuint: function easeInOutQuint(t) { - return t < 0.5 ? 16 * t * t * t * t * t : 1 + 16 * --t * t * t * t * t; - } - }; + var defaultData = { from: sourceNodeId, to: targetNodeId }; + if (typeof this.options.addEdge === 'function') { + if (this.options.addEdge.length === 2) { + this.options.addEdge(defaultData, function (finalizedData) { + if (finalizedData !== null && finalizedData !== undefined && _this6.inMode === 'addEdge') { + // if for whatever reason the mode has changes (due to dataset change) disregard the callback + _this6.body.data.edges.add(finalizedData); + _this6.selectionHandler.unselectAll(); + _this6.showManipulatorToolbar(); + } + }); + } else { + throw new Error('The function for connect does not support two arguments (data,callback)'); + } + } else { + this.body.data.edges.add(defaultData); + this.selectionHandler.unselectAll(); + this.showManipulatorToolbar(); + } + } + }, { + key: '_performEditEdge', + + /** + * connect two nodes with a new edge. + * + * @private + */ + value: function _performEditEdge(sourceNodeId, targetNodeId) { + var _this7 = this; + + var defaultData = { id: this.edgeBeingEditedId, from: sourceNodeId, to: targetNodeId }; + if (typeof this.options.editEdge === 'function') { + if (this.options.editEdge.length === 2) { + this.options.editEdge(defaultData, function (finalizedData) { + if (finalizedData === null || finalizedData === undefined || _this7.inMode !== 'editEdge') { + // if for whatever reason the mode has changes (due to dataset change) disregard the callback) { + _this7.body.edges[defaultData.id].updateEdgeType(); + _this7.body.emitter.emit('_redraw'); + } else { + _this7.body.data.edges.update(finalizedData); + _this7.selectionHandler.unselectAll(); + _this7.showManipulatorToolbar(); + } + }); + } else { + throw new Error('The function for edit does not support two arguments (data, callback)'); + } + } else { + this.body.data.edges.update(defaultData); + this.selectionHandler.unselectAll(); + this.showManipulatorToolbar(); + } + } + }]); + + return ManipulationSystem; + })(); + + exports['default'] = ManipulationSystem; + module.exports = exports['default']; /***/ }, /* 3 */ @@ -1532,85 +1397,148 @@ return /******/ (function(modules) { // webpackBootstrap var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + 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'); } } - if (typeof window !== 'undefined') { - window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame; - } + var _componentsPhysicsBarnesHutSolver = __webpack_require__(91); - var util = __webpack_require__(2); + var _componentsPhysicsBarnesHutSolver2 = _interopRequireDefault(_componentsPhysicsBarnesHutSolver); - var CanvasRenderer = (function () { - function CanvasRenderer(body, canvas) { - _classCallCheck(this, CanvasRenderer); + var _componentsPhysicsRepulsionSolver = __webpack_require__(92); + + var _componentsPhysicsRepulsionSolver2 = _interopRequireDefault(_componentsPhysicsRepulsionSolver); + + var _componentsPhysicsHierarchicalRepulsionSolver = __webpack_require__(93); + + var _componentsPhysicsHierarchicalRepulsionSolver2 = _interopRequireDefault(_componentsPhysicsHierarchicalRepulsionSolver); + + var _componentsPhysicsSpringSolver = __webpack_require__(94); + + var _componentsPhysicsSpringSolver2 = _interopRequireDefault(_componentsPhysicsSpringSolver); + + var _componentsPhysicsHierarchicalSpringSolver = __webpack_require__(95); + + var _componentsPhysicsHierarchicalSpringSolver2 = _interopRequireDefault(_componentsPhysicsHierarchicalSpringSolver); + + var _componentsPhysicsCentralGravitySolver = __webpack_require__(96); + + var _componentsPhysicsCentralGravitySolver2 = _interopRequireDefault(_componentsPhysicsCentralGravitySolver); + + var _componentsPhysicsFA2BasedRepulsionSolver = __webpack_require__(97); + + var _componentsPhysicsFA2BasedRepulsionSolver2 = _interopRequireDefault(_componentsPhysicsFA2BasedRepulsionSolver); + + var _componentsPhysicsFA2BasedCentralGravitySolver = __webpack_require__(98); + + var _componentsPhysicsFA2BasedCentralGravitySolver2 = _interopRequireDefault(_componentsPhysicsFA2BasedCentralGravitySolver); + + var util = __webpack_require__(4); + + var PhysicsEngine = (function () { + function PhysicsEngine(body) { + _classCallCheck(this, PhysicsEngine); this.body = body; - this.canvas = canvas; + this.physicsBody = { physicsNodeIndices: [], physicsEdgeIndices: [], forces: {}, velocities: {} }; - this.redrawRequested = false; - this.renderTimer = undefined; + this.physicsEnabled = true; + this.simulationInterval = 1000 / 60; this.requiresTimeout = true; - this.renderingActive = false; - this.renderRequests = 0; - this.pixelRatio = undefined; - this.allowRedrawRequests = true; + this.previousStates = {}; + this.freezeCache = {}; + this.renderTimer = undefined; + this.initialStabilizationEmitted = false; - this.dragging = false; + this.stabilized = false; + this.startedStabilization = false; + this.stabilizationIterations = 0; + this.ready = false; // will be set to true if the stabilize + + // default options this.options = {}; this.defaultOptions = { - hideEdgesOnDrag: false, - hideNodesOnDrag: false + enabled: true, + barnesHut: { + theta: 0.5, + gravitationalConstant: -2000, + centralGravity: 0.3, + springLength: 95, + springConstant: 0.04, + damping: 0.09, + avoidOverlap: 0 + }, + forceAtlas2Based: { + theta: 0.5, + gravitationalConstant: -50, + centralGravity: 0.01, + springConstant: 0.08, + springLength: 100, + damping: 0.4, + avoidOverlap: 0 + }, + repulsion: { + centralGravity: 0.2, + springLength: 200, + springConstant: 0.05, + nodeDistance: 100, + damping: 0.09, + avoidOverlap: 0 + }, + hierarchicalRepulsion: { + centralGravity: 0, + springLength: 100, + springConstant: 0.01, + nodeDistance: 120, + damping: 0.09 + }, + maxVelocity: 50, + minVelocity: 0.1, // px/s + solver: 'barnesHut', + stabilization: { + enabled: true, + iterations: 1000, // maximum number of iteration to stabilize + updateInterval: 50, + onlyDynamicEdges: false, + fit: true + }, + timestep: 0.5 }; util.extend(this.options, this.defaultOptions); - this._determineBrowserMethod(); this.bindEventListeners(); } - _createClass(CanvasRenderer, [{ + _createClass(PhysicsEngine, [{ key: 'bindEventListeners', value: function bindEventListeners() { var _this = this; - this.body.emitter.on('dragStart', function () { - _this.dragging = true; + this.body.emitter.on('initPhysics', function () { + _this.initPhysics(); }); - this.body.emitter.on('dragEnd', function () { - return _this.dragging = false; + this.body.emitter.on('resetPhysics', function () { + _this.stopSimulation();_this.ready = false; }); - this.body.emitter.on('_resizeNodes', function () { - return _this._resizeNodes(); + this.body.emitter.on('disablePhysics', function () { + _this.physicsEnabled = false;_this.stopSimulation(); }); - this.body.emitter.on('_redraw', function () { - if (_this.renderingActive === false) { - _this._redraw(); + this.body.emitter.on('restorePhysics', function () { + _this.setOptions(_this.options); + if (_this.ready === true) { + _this.startSimulation(); } }); - this.body.emitter.on('_blockRedrawRequests', function () { - _this.allowRedrawRequests = false; - }); - this.body.emitter.on('_allowRedrawRequests', function () { - _this.allowRedrawRequests = true; - }); - this.body.emitter.on('_requestRedraw', this._requestRedraw.bind(this)); - this.body.emitter.on('_startRendering', function () { - _this.renderRequests += 1; - _this.renderingActive = true; - _this._startRendering(); + this.body.emitter.on('startSimulation', function () { + if (_this.ready === true) { + _this.startSimulation(); + } }); - this.body.emitter.on('_stopRendering', function () { - _this.renderRequests -= 1; - _this.renderingActive = _this.renderRequests > 0; - _this.renderTimer = undefined; + this.body.emitter.on('stopSimulation', function () { + _this.stopSimulation(); }); this.body.emitter.on('destroy', function () { - _this.renderRequests = 0; - _this.renderingActive = false; - if (_this.requiresTimeout === true) { - clearTimeout(_this.renderTimer); - } else { - cancelAnimationFrame(_this.renderTimer); - } + _this.stopSimulation(false); _this.body.emitter.off(); }); } @@ -1618,1577 +1546,3210 @@ return /******/ (function(modules) { // webpackBootstrap key: 'setOptions', value: function setOptions(options) { if (options !== undefined) { - var fields = ['hideEdgesOnDrag', 'hideNodesOnDrag']; - util.selectiveDeepExtend(fields, this.options, options); - } - } - }, { - key: '_startRendering', - value: function _startRendering() { - if (this.renderingActive === true) { - if (this.renderTimer === undefined) { - if (this.requiresTimeout === true) { - this.renderTimer = window.setTimeout(this._renderStep.bind(this), this.simulationInterval); // wait this.renderTimeStep milliseconds and perform the animation step function - } else { - this.renderTimer = window.requestAnimationFrame(this._renderStep.bind(this)); // wait this.renderTimeStep milliseconds and perform the animation step function + if (options === false) { + this.options.enabled = false; + this.physicsEnabled = false; + this.stopSimulation(); + } else { + this.physicsEnabled = true; + util.selectiveNotDeepExtend(['stabilization'], this.options, options); + util.mergeOptions(this.options, options, 'stabilization'); + + if (options.enabled === undefined) { + this.options.enabled = true; + } + + if (this.options.enabled === false) { + this.physicsEnabled = false; + this.stopSimulation(); } } } + this.init(); } }, { - key: '_renderStep', - value: function _renderStep() { - if (this.renderingActive === true) { - // reset the renderTimer so a new scheduled animation step can be set - this.renderTimer = undefined; - - if (this.requiresTimeout === true) { - // this schedules a new simulation step - this._startRendering(); - } - - this._redraw(); + key: 'init', + value: function init() { + var options; + if (this.options.solver === 'forceAtlas2Based') { + options = this.options.forceAtlas2Based; + this.nodesSolver = new _componentsPhysicsFA2BasedRepulsionSolver2['default'](this.body, this.physicsBody, options); + this.edgesSolver = new _componentsPhysicsSpringSolver2['default'](this.body, this.physicsBody, options); + this.gravitySolver = new _componentsPhysicsFA2BasedCentralGravitySolver2['default'](this.body, this.physicsBody, options); + } else if (this.options.solver === 'repulsion') { + options = this.options.repulsion; + this.nodesSolver = new _componentsPhysicsRepulsionSolver2['default'](this.body, this.physicsBody, options); + this.edgesSolver = new _componentsPhysicsSpringSolver2['default'](this.body, this.physicsBody, options); + this.gravitySolver = new _componentsPhysicsCentralGravitySolver2['default'](this.body, this.physicsBody, options); + } else if (this.options.solver === 'hierarchicalRepulsion') { + options = this.options.hierarchicalRepulsion; + this.nodesSolver = new _componentsPhysicsHierarchicalRepulsionSolver2['default'](this.body, this.physicsBody, options); + this.edgesSolver = new _componentsPhysicsHierarchicalSpringSolver2['default'](this.body, this.physicsBody, options); + this.gravitySolver = new _componentsPhysicsCentralGravitySolver2['default'](this.body, this.physicsBody, options); + } else { + // barnesHut + options = this.options.barnesHut; + this.nodesSolver = new _componentsPhysicsBarnesHutSolver2['default'](this.body, this.physicsBody, options); + this.edgesSolver = new _componentsPhysicsSpringSolver2['default'](this.body, this.physicsBody, options); + this.gravitySolver = new _componentsPhysicsCentralGravitySolver2['default'](this.body, this.physicsBody, options); + } - if (this.requiresTimeout === false) { - // this schedules a new simulation step - this._startRendering(); + this.modelOptions = options; + } + }, { + key: 'initPhysics', + value: function initPhysics() { + if (this.physicsEnabled === true && this.options.enabled === true) { + if (this.options.stabilization.enabled === true) { + this.stabilize(); + } else { + this.stabilized = false; + this.ready = true; + this.body.emitter.emit('fit', {}, true); + this.startSimulation(); } + } else { + this.ready = true; + this.body.emitter.emit('fit'); } } }, { - key: 'redraw', + key: 'startSimulation', /** - * Redraw the network with the current data - * chart will be resized too. + * Start the simulation */ - value: function redraw() { - this.body.emitter.emit('setSize'); - this._redraw(); + value: function startSimulation() { + if (this.physicsEnabled === true && this.options.enabled === true) { + this.stabilized = false; + + // this sets the width of all nodes initially which could be required for the avoidOverlap + this.body.emitter.emit('_resizeNodes'); + if (this.viewFunction === undefined) { + this.viewFunction = this.simulationStep.bind(this); + this.body.emitter.on('initRedraw', this.viewFunction); + this.body.emitter.emit('_startRendering'); + } + } else { + this.body.emitter.emit('_redraw'); + } } }, { - key: '_requestRedraw', + key: 'stopSimulation', /** - * Redraw the network with the current data - * @param hidden | used to get the first estimate of the node sizes. only the nodes are drawn after which they are quickly drawn over. - * @private + * Stop the simulation, force stabilization. */ - value: function _requestRedraw() { - var _this2 = this; + value: function stopSimulation() { + var emit = arguments[0] === undefined ? true : arguments[0]; - if (this.redrawRequested !== true && this.renderingActive === false && this.allowRedrawRequests === true) { - this.redrawRequested = true; - if (this.requiresTimeout === true) { - window.setTimeout(function () { - _this2._redraw(false); - }, 0); - } else { - window.requestAnimationFrame(function () { - _this2._redraw(false); - }); + this.stabilized = true; + if (emit === true) { + this._emitStabilized(); + } + if (this.viewFunction !== undefined) { + this.body.emitter.off('initRedraw', this.viewFunction); + this.viewFunction = undefined; + if (emit === true) { + this.body.emitter.emit('_stopRendering'); } } } }, { - key: '_redraw', - value: function _redraw() { - var hidden = arguments[0] === undefined ? false : arguments[0]; + key: 'simulationStep', - this.body.emitter.emit('initRedraw'); + /** + * The viewFunction inserts this step into each renderloop. It calls the physics tick and handles the cleanup at stabilized. + * + */ + value: function simulationStep() { + // check if the physics have settled + var startTime = Date.now(); + this.physicsTick(); + var physicsTime = Date.now() - startTime; - this.redrawRequested = false; - var ctx = this.canvas.frame.canvas.getContext('2d'); + // run double speed if it is a little graph + if ((physicsTime < 0.4 * this.simulationInterval || this.runDoubleSpeed === true) && this.stabilized === false) { + this.physicsTick(); - // when the container div was hidden, this fixes it back up! - if (this.canvas.frame.canvas.width === 0 || this.canvas.frame.canvas.height === 0) { - this.canvas.setSize(); + // this makes sure there is no jitter. The decision is taken once to run it at double speed. + this.runDoubleSpeed = true; } - if (this.pixelRatio === undefined) { - this.pixelRatio = (window.devicePixelRatio || 1) / (ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1); + if (this.stabilized === true) { + if (this.stabilizationIterations > 1) { + // trigger the 'stabilized' event. + // The event is triggered on the next tick, to prevent the case that + // it is fired while initializing the Network, in which case you would not + // be able to catch it + this.startedStabilization = false; + //this._emitStabilized(); + } + this.stopSimulation(); } + } + }, { + key: '_emitStabilized', + value: function _emitStabilized() { + var _this2 = this; - ctx.setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0); + if (this.stabilizationIterations > 1 || this.initialStabilizationEmitted === false) { + this.initialStabilizationEmitted = true; + setTimeout(function () { + _this2.body.emitter.emit('stabilized', { iterations: _this2.stabilizationIterations }); + _this2.stabilizationIterations = 0; + }, 0); + } + } + }, { + key: 'physicsTick', - // clear the canvas - var w = this.canvas.frame.canvas.clientWidth; - var h = this.canvas.frame.canvas.clientHeight; - ctx.clearRect(0, 0, w, h); + /** + * A single simulation step (or 'tick') in the physics simulation + * + * @private + */ + value: function physicsTick() { + if (this.stabilized === false) { + this.calculateForces(); + this.stabilized = this.moveNodes(); - // set scaling and translation - ctx.save(); - ctx.translate(this.body.view.translation.x, this.body.view.translation.y); - ctx.scale(this.body.view.scale, this.body.view.scale); + // determine if the network has stabilzied + if (this.stabilized === true) { + this.revert(); + } else { + // this is here to ensure that there is no start event when the network is already stable. + if (this.startedStabilization === false) { + this.body.emitter.emit('startStabilizing'); + this.startedStabilization = true; + } + } - ctx.beginPath(); - this.body.emitter.emit('beforeDrawing', ctx); - ctx.closePath(); + this.stabilizationIterations++; + } + } + }, { + key: 'updatePhysicsData', - if (hidden === false) { - if (this.dragging === false || this.dragging === true && this.options.hideEdgesOnDrag === false) { - this._drawEdges(ctx); + /** + * Nodes and edges can have the physics toggles on or off. A collection of indices is created here so we can skip the check all the time. + * + * @private + */ + value: function updatePhysicsData() { + this.physicsBody.forces = {}; + this.physicsBody.physicsNodeIndices = []; + this.physicsBody.physicsEdgeIndices = []; + var nodes = this.body.nodes; + var edges = this.body.edges; + + // get node indices for physics + for (var nodeId in nodes) { + if (nodes.hasOwnProperty(nodeId)) { + if (nodes[nodeId].options.physics === true) { + this.physicsBody.physicsNodeIndices.push(nodeId); + } } } - if (this.dragging === false || this.dragging === true && this.options.hideNodesOnDrag === false) { - this._drawNodes(ctx, hidden); + // get edge indices for physics + for (var edgeId in edges) { + if (edges.hasOwnProperty(edgeId)) { + if (edges[edgeId].options.physics === true) { + this.physicsBody.physicsEdgeIndices.push(edgeId); + } + } } - if (this.controlNodesActive === true) { - this._drawControlNodes(ctx); - } + // get the velocity and the forces vector + for (var i = 0; i < this.physicsBody.physicsNodeIndices.length; i++) { + var nodeId = this.physicsBody.physicsNodeIndices[i]; + this.physicsBody.forces[nodeId] = { x: 0, y: 0 }; - ctx.beginPath(); - //this.physics.nodesSolver._debug(ctx,"#F00F0F"); - this.body.emitter.emit('afterDrawing', ctx); - ctx.closePath(); - // restore original scaling and translation - ctx.restore(); + // forces can be reset because they are recalculated. Velocities have to persist. + if (this.physicsBody.velocities[nodeId] === undefined) { + this.physicsBody.velocities[nodeId] = { x: 0, y: 0 }; + } + } - if (hidden === true) { - ctx.clearRect(0, 0, w, h); + // clean deleted nodes from the velocity vector + for (var nodeId in this.physicsBody.velocities) { + if (nodes[nodeId] === undefined) { + delete this.physicsBody.velocities[nodeId]; + } } } }, { - key: '_resizeNodes', + key: 'revert', /** - * Redraw all nodes - * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d'); - * @param {CanvasRenderingContext2D} ctx - * @param {Boolean} [alwaysShow] - * @private + * Revert the simulation one step. This is done so after stabilization, every new start of the simulation will also say stabilized. */ - value: function _resizeNodes() { - var ctx = this.canvas.frame.canvas.getContext('2d'); - if (this.pixelRatio === undefined) { - this.pixelRatio = (window.devicePixelRatio || 1) / (ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1); - } - ctx.setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0); - ctx.save(); - ctx.translate(this.body.view.translation.x, this.body.view.translation.y); - ctx.scale(this.body.view.scale, this.body.view.scale); - + value: function revert() { + var nodeIds = Object.keys(this.previousStates); var nodes = this.body.nodes; - var node = undefined; + var velocities = this.physicsBody.velocities; - // resize all nodes - for (var nodeId in nodes) { - if (nodes.hasOwnProperty(nodeId)) { - node = nodes[nodeId]; - node.resize(ctx); - node.updateBoundingBox(ctx); + for (var i = 0; i < nodeIds.length; i++) { + var nodeId = nodeIds[i]; + if (nodes[nodeId] !== undefined) { + if (nodes[nodeId].options.physics === true) { + velocities[nodeId].x = this.previousStates[nodeId].vx; + velocities[nodeId].y = this.previousStates[nodeId].vy; + nodes[nodeId].x = this.previousStates[nodeId].x; + nodes[nodeId].y = this.previousStates[nodeId].y; + } + } else { + delete this.previousStates[nodeId]; } } - - // restore original scaling and translation - ctx.restore(); } }, { - key: '_drawNodes', + key: 'moveNodes', /** - * Redraw all nodes - * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d'); - * @param {CanvasRenderingContext2D} ctx - * @param {Boolean} [alwaysShow] - * @private + * move the nodes one timestap and check if they are stabilized + * @returns {boolean} */ - value: function _drawNodes(ctx) { - var alwaysShow = arguments[1] === undefined ? false : arguments[1]; - - var nodes = this.body.nodes; - var nodeIndices = this.body.nodeIndices; - var node = undefined; - var selected = []; - var margin = 20; - var topLeft = this.canvas.DOMtoCanvas({ x: -margin, y: -margin }); - var bottomRight = this.canvas.DOMtoCanvas({ - x: this.canvas.frame.canvas.clientWidth + margin, - y: this.canvas.frame.canvas.clientHeight + margin - }); - var viewableArea = { top: topLeft.y, left: topLeft.x, bottom: bottomRight.y, right: bottomRight.x }; + value: function moveNodes() { + var nodesPresent = false; + var nodeIndices = this.physicsBody.physicsNodeIndices; + var maxVelocity = this.options.maxVelocity ? this.options.maxVelocity : 1000000000; + var stabilized = true; + var vminCorrected = this.options.minVelocity / Math.max(this.body.view.scale, 0.05); - // draw unselected nodes; for (var i = 0; i < nodeIndices.length; i++) { - node = nodes[nodeIndices[i]]; - // set selected nodes aside - if (node.isSelected()) { - selected.push(nodeIndices[i]); - } else { - if (alwaysShow === true) { - node.draw(ctx); - } else if (node.isBoundingBoxOverlappingWith(viewableArea) === true) { - node.draw(ctx); - } else { - node.updateBoundingBox(ctx); - } - } + var nodeId = nodeIndices[i]; + var nodeVelocity = this._performStep(nodeId, maxVelocity); + // stabilized is true if stabilized is true and velocity is smaller than vmin --> all nodes must be stabilized + stabilized = nodeVelocity < vminCorrected && stabilized === true; + nodesPresent = true; } - // draw the selected nodes on top - for (var i = 0; i < selected.length; i++) { - node = nodes[selected[i]]; - node.draw(ctx); + if (nodesPresent === true) { + if (vminCorrected > 0.5 * this.options.maxVelocity) { + return false; + } else { + return stabilized; + } } + return true; } }, { - key: '_drawEdges', + key: '_performStep', /** - * Redraw all edges - * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d'); - * @param {CanvasRenderingContext2D} ctx + * Perform the actual step + * + * @param nodeId + * @param maxVelocity + * @returns {number} * @private */ - value: function _drawEdges(ctx) { - var edges = this.body.edges; - var edgeIndices = this.body.edgeIndices; - var edge = undefined; + value: function _performStep(nodeId, maxVelocity) { + var node = this.body.nodes[nodeId]; + var timestep = this.options.timestep; + var forces = this.physicsBody.forces; + var velocities = this.physicsBody.velocities; - for (var i = 0; i < edgeIndices.length; i++) { - edge = edges[edgeIndices[i]]; - if (edge.connected === true) { - edge.draw(ctx); - } + // store the state so we can revert + this.previousStates[nodeId] = { x: node.x, y: node.y, vx: velocities[nodeId].x, vy: velocities[nodeId].y }; + + if (node.options.fixed.x === false) { + var dx = this.modelOptions.damping * velocities[nodeId].x; // damping force + var ax = (forces[nodeId].x - dx) / node.options.mass; // acceleration + velocities[nodeId].x += ax * timestep; // velocity + velocities[nodeId].x = Math.abs(velocities[nodeId].x) > maxVelocity ? velocities[nodeId].x > 0 ? maxVelocity : -maxVelocity : velocities[nodeId].x; + node.x += velocities[nodeId].x * timestep; // position + } else { + forces[nodeId].x = 0; + velocities[nodeId].x = 0; + } + + if (node.options.fixed.y === false) { + var dy = this.modelOptions.damping * velocities[nodeId].y; // damping force + var ay = (forces[nodeId].y - dy) / node.options.mass; // acceleration + velocities[nodeId].y += ay * timestep; // velocity + velocities[nodeId].y = Math.abs(velocities[nodeId].y) > maxVelocity ? velocities[nodeId].y > 0 ? maxVelocity : -maxVelocity : velocities[nodeId].y; + node.y += velocities[nodeId].y * timestep; // position + } else { + forces[nodeId].y = 0; + velocities[nodeId].y = 0; } + + var totalVelocity = Math.sqrt(Math.pow(velocities[nodeId].x, 2) + Math.pow(velocities[nodeId].y, 2)); + return totalVelocity; } }, { - key: '_drawControlNodes', + key: 'calculateForces', /** - * Redraw all edges - * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d'); - * @param {CanvasRenderingContext2D} ctx - * @private + * calculate the forces for one physics iteration. */ - value: function _drawControlNodes(ctx) { - var edges = this.body.edges; - var edgeIndices = this.body.edgeIndices; - var edge = undefined; + value: function calculateForces() { + this.gravitySolver.solve(); + this.nodesSolver.solve(); + this.edgesSolver.solve(); + } + }, { + key: '_freezeNodes', - for (var i = 0; i < edgeIndices.length; i++) { - edge = edges[edgeIndices[i]]; - edge._drawControlNodes(ctx); + /** + * When initializing and stabilizing, we can freeze nodes with a predefined position. This greatly speeds up stabilization + * because only the supportnodes for the smoothCurves have to settle. + * + * @private + */ + value: function _freezeNodes() { + var nodes = this.body.nodes; + for (var id in nodes) { + if (nodes.hasOwnProperty(id)) { + if (nodes[id].x && nodes[id].y) { + this.freezeCache[id] = { x: nodes[id].options.fixed.x, y: nodes[id].options.fixed.y }; + nodes[id].options.fixed.x = true; + nodes[id].options.fixed.y = true; + } + } } } }, { - key: '_determineBrowserMethod', + key: '_restoreFrozenNodes', /** - * Determine if the browser requires a setTimeout or a requestAnimationFrame. This was required because - * some implementations (safari and IE9) did not support requestAnimationFrame + * Unfreezes the nodes that have been frozen by _freezeDefinedNodes. + * * @private */ - value: function _determineBrowserMethod() { - if (typeof window !== 'undefined') { - var browserType = navigator.userAgent.toLowerCase(); - this.requiresTimeout = false; - if (browserType.indexOf('msie 9.0') != -1) { - // IE 9 - this.requiresTimeout = true; - } else if (browserType.indexOf('safari') != -1) { - // safari - if (browserType.indexOf('chrome') <= -1) { - this.requiresTimeout = true; + value: function _restoreFrozenNodes() { + var nodes = this.body.nodes; + for (var id in nodes) { + if (nodes.hasOwnProperty(id)) { + if (this.freezeCache[id] !== undefined) { + nodes[id].options.fixed.x = this.freezeCache[id].x; + nodes[id].options.fixed.y = this.freezeCache[id].y; } } - } else { - this.requiresTimeout = true; } + this.freezeCache = {}; } - }]); - - return CanvasRenderer; - })(); - - exports['default'] = CanvasRenderer; - module.exports = exports['default']; - -/***/ }, -/* 4 */ -/***/ 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'); } } + }, { + key: 'stabilize', - var Hammer = __webpack_require__(25); - var hammerUtil = __webpack_require__(30); + /** + * Find a stable position for all nodes + * @private + */ + value: function stabilize() { + var _this3 = this; - var util = __webpack_require__(2); + var iterations = arguments[0] === undefined ? this.options.stabilization.iterations : arguments[0]; - /** - * Create the main frame for the Network. - * This function is executed once when a Network object is created. The frame - * contains a canvas, and this canvas contains all objects like the axis and - * nodes. - * @private - */ + if (typeof iterations !== 'number') { + console.log('The stabilize method needs a numeric amount of iterations. Switching to default: ', this.options.stabilization.iterations); + iterations = this.options.stabilization.iterations; + } - var Canvas = (function () { - function Canvas(body) { - _classCallCheck(this, Canvas); + // this sets the width of all nodes initially which could be required for the avoidOverlap + this.body.emitter.emit('_resizeNodes'); - this.body = body; - this.pixelRatio = 1; - this.resizeTimer = undefined; - this.resizeFunction = this._onResize.bind(this); + // stop the render loop + this.stopSimulation(); - this.options = {}; - this.defaultOptions = { - autoResize: true, - height: '100%', - width: '100%' - }; - util.extend(this.options, this.defaultOptions); + // set stabilze to false + this.stabilized = false; - this.bindEventListeners(); - } + // block redraw requests + this.body.emitter.emit('_blockRedrawRequests'); + this.targetIterations = iterations; - _createClass(Canvas, [{ - key: 'bindEventListeners', - value: function bindEventListeners() { - var _this = this; + // start the stabilization + if (this.options.stabilization.onlyDynamicEdges === true) { + this._freezeNodes(); + } + this.stabilizationIterations = 0; - // bind the events - this.body.emitter.once('resize', function (obj) { - if (obj.width !== 0) { - _this.body.view.translation.x = obj.width * 0.5; - } - if (obj.height !== 0) { - _this.body.view.translation.y = obj.height * 0.5; - } - }); - this.body.emitter.on('setSize', this.setSize.bind(this)); - this.body.emitter.on('destroy', function () { - _this.hammerFrame.destroy(); - _this.hammer.destroy(); - _this._cleanUp(); - }); + setTimeout(function () { + return _this3._stabilizationBatch(); + }, 0); } }, { - key: 'setOptions', - value: function setOptions(options) { - var _this2 = this; - - if (options !== undefined) { - var fields = ['width', 'height', 'autoResize']; - util.selectiveDeepExtend(fields, this.options, options); + key: '_stabilizationBatch', + value: function _stabilizationBatch() { + var count = 0; + while (this.stabilized === false && count < this.options.stabilization.updateInterval && this.stabilizationIterations < this.targetIterations) { + this.physicsTick(); + this.stabilizationIterations++; + count++; } - if (this.options.autoResize === true) { - // automatically adapt to a changing size of the browser. - this._cleanUp(); - this.resizeTimer = setInterval(function () { - var changed = _this2.setSize(); - if (changed === true) { - _this2.body.emitter.emit('_requestRedraw'); - } - }, 1000); - this.resizeFunction = this._onResize.bind(this); - util.addEventListener(window, 'resize', this.resizeFunction); + if (this.stabilized === false && this.stabilizationIterations < this.targetIterations) { + this.body.emitter.emit('stabilizationProgress', { iterations: this.stabilizationIterations, total: this.targetIterations }); + setTimeout(this._stabilizationBatch.bind(this), 0); + } else { + this._finalizeStabilization(); } } }, { - key: '_cleanUp', - value: function _cleanUp() { - // automatically adapt to a changing size of the browser. - if (this.resizeTimer !== undefined) { - clearInterval(this.resizeTimer); + key: '_finalizeStabilization', + value: function _finalizeStabilization() { + this.body.emitter.emit('_allowRedrawRequests'); + if (this.options.stabilization.fit === true) { + this.body.emitter.emit('fit'); } - util.removeEventListener(window, 'resize', this.resizeFunction); - this.resizeFunction = undefined; - } - }, { - key: '_onResize', - value: function _onResize() { - this.setSize(); - this.body.emitter.emit('_redraw'); - } - }, { - key: '_prepareValue', - value: function _prepareValue(value) { - if (typeof value === 'number') { - return value + 'px'; - } else if (typeof value === 'string') { - if (value.indexOf('%') !== -1 || value.indexOf('px') !== -1) { - return value; - } else if (value.indexOf('%') === -1) { - return value + 'px'; - } + + if (this.options.stabilization.onlyDynamicEdges === true) { + this._restoreFrozenNodes(); } - throw new Error('Could not use the value supplie for width or height:' + value); - } - }, { - key: '_create', - /** - * Create the HTML - */ - value: function _create() { - // remove all elements from the container element. - while (this.body.container.hasChildNodes()) { - this.body.container.removeChild(this.body.container.firstChild); + this.body.emitter.emit('stabilizationIterationsDone'); + this.body.emitter.emit('_requestRedraw'); + + if (this.stabilized === true) { + this._emitStabilized(); + } else { + this.startSimulation(); } - this.frame = document.createElement('div'); - this.frame.className = 'vis-network'; - this.frame.style.position = 'relative'; - this.frame.style.overflow = 'hidden'; - this.frame.tabIndex = 900; // tab index is required for keycharm to bind keystrokes to the div instead of the window + this.ready = true; + } + }]); - ////////////////////////////////////////////////////////////////// + return PhysicsEngine; + })(); - this.frame.canvas = document.createElement('canvas'); - this.frame.canvas.style.position = 'relative'; - this.frame.appendChild(this.frame.canvas); + exports['default'] = PhysicsEngine; + module.exports = exports['default']; - if (!this.frame.canvas.getContext) { - var noCanvas = document.createElement('DIV'); - noCanvas.style.color = 'red'; - noCanvas.style.fontWeight = 'bold'; - noCanvas.style.padding = '10px'; - noCanvas.innerHTML = 'Error: your browser does not support HTML canvas'; - this.frame.canvas.appendChild(noCanvas); - } else { - var ctx = this.frame.canvas.getContext('2d'); - this.pixelRatio = (window.devicePixelRatio || 1) / (ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1); +/***/ }, +/* 4 */ +/***/ function(module, exports, __webpack_require__) { - this.frame.canvas.getContext('2d').setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0); - } + // utility functions - // add the frame to the container element - this.body.container.appendChild(this.frame); + // first check if moment.js is already loaded in the browser window, if so, + // use this instance. Else, load via commonjs. - this.body.view.scale = 1; - this.body.view.translation = { x: 0.5 * this.frame.canvas.clientWidth, y: 0.5 * this.frame.canvas.clientHeight }; + 'use strict'; - this._bindHammer(); - } - }, { - key: '_bindHammer', + var moment = __webpack_require__(6); + var uuid = __webpack_require__(9); - /** - * This function binds hammer, it can be repeated over and over due to the uniqueness check. - * @private - */ - value: function _bindHammer() { - var _this3 = this; - - if (this.hammer !== undefined) { - this.hammer.destroy(); - } - this.drag = {}; - this.pinch = {}; - - // init hammer - this.hammer = new Hammer(this.frame.canvas); - this.hammer.get('pinch').set({ enable: true }); - // enable to get better response, todo: test on mobile. - //this.hammer.get('pan').set({threshold:2}); - - hammerUtil.onTouch(this.hammer, function (event) { - _this3.body.eventListeners.onTouch(event); - }); - this.hammer.on('tap', function (event) { - _this3.body.eventListeners.onTap(event); - }); - this.hammer.on('doubletap', function (event) { - _this3.body.eventListeners.onDoubleTap(event); - }); - this.hammer.on('press', function (event) { - _this3.body.eventListeners.onHold(event); - }); - this.hammer.on('panstart', function (event) { - _this3.body.eventListeners.onDragStart(event); - }); - this.hammer.on('panmove', function (event) { - _this3.body.eventListeners.onDrag(event); - }); - this.hammer.on('panend', function (event) { - _this3.body.eventListeners.onDragEnd(event); - }); - this.hammer.on('pinch', function (event) { - _this3.body.eventListeners.onPinch(event); - }); - - // TODO: neatly cleanup these handlers when re-creating the Canvas, IF these are done with hammer, event.stopPropagation will not work? - this.frame.canvas.addEventListener('mousewheel', function (event) { - _this3.body.eventListeners.onMouseWheel(event); - }); - this.frame.canvas.addEventListener('DOMMouseScroll', function (event) { - _this3.body.eventListeners.onMouseWheel(event); - }); - - this.frame.canvas.addEventListener('mousemove', function (event) { - _this3.body.eventListeners.onMouseMove(event); - }); - this.frame.canvas.addEventListener('contextmenu', function (event) { - _this3.body.eventListeners.onContext(event); - }); + /** + * Test whether given object is a number + * @param {*} object + * @return {Boolean} isNumber + */ + exports.isNumber = function (object) { + return object instanceof Number || typeof object == 'number'; + }; - this.hammerFrame = new Hammer(this.frame); - hammerUtil.onRelease(this.hammerFrame, function (event) { - _this3.body.eventListeners.onRelease(event); - }); + /** + * Remove everything in the DOM object + * @param DOMobject + */ + exports.recursiveDOMDelete = function (DOMobject) { + if (DOMobject) { + while (DOMobject.hasChildNodes() === true) { + exports.recursiveDOMDelete(DOMobject.firstChild); + DOMobject.removeChild(DOMobject.firstChild); } - }, { - key: 'setSize', - - /** - * Set a new size for the network - * @param {string} width Width in pixels or percentage (for example '800px' - * or '50%') - * @param {string} height Height in pixels or percentage (for example '400px' - * or '30%') - */ - value: function setSize() { - var width = arguments[0] === undefined ? this.options.width : arguments[0]; - var height = arguments[1] === undefined ? this.options.height : arguments[1]; + } + }; - width = this._prepareValue(width); - height = this._prepareValue(height); + /** + * this function gives you a range between 0 and 1 based on the min and max values in the set, the total sum of all values and the current value. + * + * @param min + * @param max + * @param total + * @param value + * @returns {number} + */ + exports.giveRange = function (min, max, total, value) { + if (max == min) { + return 0.5; + } else { + var scale = 1 / (max - min); + return Math.max(0, (value - min) * scale); + } + }; - var emitEvent = false; - var oldWidth = this.frame.canvas.width; - var oldHeight = this.frame.canvas.height; + /** + * Test whether given object is a string + * @param {*} object + * @return {Boolean} isString + */ + exports.isString = function (object) { + return object instanceof String || typeof object == 'string'; + }; - if (width != this.options.width || height != this.options.height || this.frame.style.width != width || this.frame.style.height != height) { - this.frame.style.width = width; - this.frame.style.height = height; + /** + * Test whether given object is a Date, or a String containing a Date + * @param {Date | String} object + * @return {Boolean} isDate + */ + exports.isDate = function (object) { + if (object instanceof Date) { + return true; + } else if (exports.isString(object)) { + // test whether this string contains a date + var match = ASPDateRegex.exec(object); + if (match) { + return true; + } else if (!isNaN(Date.parse(object))) { + return true; + } + } - this.frame.canvas.style.width = '100%'; - this.frame.canvas.style.height = '100%'; + return false; + }; - this.frame.canvas.width = Math.round(this.frame.canvas.clientWidth * this.pixelRatio); - this.frame.canvas.height = Math.round(this.frame.canvas.clientHeight * this.pixelRatio); + /** + * Create a semi UUID + * source: http://stackoverflow.com/a/105074/1262753 + * @return {String} uuid + */ + exports.randomUUID = function () { + return uuid.v4(); + }; - this.options.width = width; - this.options.height = height; + /** + * assign all keys of an object that are not nested objects to a certain value (used for color objects). + * @param obj + * @param value + */ + exports.assignAllKeys = function (obj, value) { + for (var prop in obj) { + if (obj.hasOwnProperty(prop)) { + if (typeof obj[prop] !== 'object') { + obj[prop] = value; + } + } + } + }; - emitEvent = true; - } else { - // this would adapt the width of the canvas to the width from 100% if and only if - // there is a change. + /** + * Fill an object with a possibly partially defined other object. Only copies values if the a object has an object requiring values. + * That means an object is not created on a property if only the b object has it. + * @param obj + * @param value + */ + exports.fillIfDefined = function (a, b) { + var allowDeletion = arguments[2] === undefined ? false : arguments[2]; - if (this.frame.canvas.width != Math.round(this.frame.canvas.clientWidth * this.pixelRatio)) { - this.frame.canvas.width = Math.round(this.frame.canvas.clientWidth * this.pixelRatio); - emitEvent = true; + for (var prop in a) { + if (b[prop] !== undefined) { + if (typeof b[prop] !== 'object') { + if ((b[prop] === undefined || b[prop] === null) && a[prop] !== undefined && allowDeletion === true) { + delete a[prop]; + } else { + a[prop] = b[prop]; } - if (this.frame.canvas.height != Math.round(this.frame.canvas.clientHeight * this.pixelRatio)) { - this.frame.canvas.height = Math.round(this.frame.canvas.clientHeight * this.pixelRatio); - emitEvent = true; + } else { + if (typeof a[prop] === 'object') { + exports.fillIfDefined(a[prop], b[prop], allowDeletion); } } - - if (emitEvent === true) { - this.body.emitter.emit('resize', { - width: Math.round(this.frame.canvas.width / this.pixelRatio), - height: Math.round(this.frame.canvas.height / this.pixelRatio), - oldWidth: Math.round(oldWidth / this.pixelRatio), - oldHeight: Math.round(oldHeight / this.pixelRatio) - }); - } - - return emitEvent; - } - }, { - key: '_XconvertDOMtoCanvas', - - /** - * Convert the X coordinate in DOM-space (coordinate point in browser relative to the container div) to - * the X coordinate in canvas-space (the simulation sandbox, which the camera looks upon) - * @param {number} x - * @returns {number} - * @private - */ - value: function _XconvertDOMtoCanvas(x) { - return (x - this.body.view.translation.x) / this.body.view.scale; - } - }, { - key: '_XconvertCanvasToDOM', - - /** - * Convert the X coordinate in canvas-space (the simulation sandbox, which the camera looks upon) to - * the X coordinate in DOM-space (coordinate point in browser relative to the container div) - * @param {number} x - * @returns {number} - * @private - */ - value: function _XconvertCanvasToDOM(x) { - return x * this.body.view.scale + this.body.view.translation.x; - } - }, { - key: '_YconvertDOMtoCanvas', - - /** - * Convert the Y coordinate in DOM-space (coordinate point in browser relative to the container div) to - * the Y coordinate in canvas-space (the simulation sandbox, which the camera looks upon) - * @param {number} y - * @returns {number} - * @private - */ - value: function _YconvertDOMtoCanvas(y) { - return (y - this.body.view.translation.y) / this.body.view.scale; - } - }, { - key: '_YconvertCanvasToDOM', - - /** - * Convert the Y coordinate in canvas-space (the simulation sandbox, which the camera looks upon) to - * the Y coordinate in DOM-space (coordinate point in browser relative to the container div) - * @param {number} y - * @returns {number} - * @private - */ - value: function _YconvertCanvasToDOM(y) { - return y * this.body.view.scale + this.body.view.translation.y; } - }, { - key: 'canvasToDOM', + } + }; - /** - * - * @param {object} pos = {x: number, y: number} - * @returns {{x: number, y: number}} - * @constructor - */ - value: function canvasToDOM(pos) { - return { x: this._XconvertCanvasToDOM(pos.x), y: this._YconvertCanvasToDOM(pos.y) }; + /** + * Extend object a with the properties of object b or a series of objects + * Only properties with defined values are copied + * @param {Object} a + * @param {... Object} b + * @return {Object} a + */ + exports.protoExtend = function (a, b) { + for (var i = 1; i < arguments.length; i++) { + var other = arguments[i]; + for (var prop in other) { + a[prop] = other[prop]; } - }, { - key: 'DOMtoCanvas', + } + return a; + }; - /** - * - * @param {object} pos = {x: number, y: number} - * @returns {{x: number, y: number}} - * @constructor - */ - value: function DOMtoCanvas(pos) { - return { x: this._XconvertDOMtoCanvas(pos.x), y: this._YconvertDOMtoCanvas(pos.y) }; + /** + * Extend object a with the properties of object b or a series of objects + * Only properties with defined values are copied + * @param {Object} a + * @param {... Object} b + * @return {Object} a + */ + exports.extend = function (a, b) { + for (var i = 1; i < arguments.length; i++) { + var other = arguments[i]; + for (var prop in other) { + if (other.hasOwnProperty(prop)) { + a[prop] = other[prop]; + } } - }]); - - return Canvas; - })(); - - exports['default'] = Canvas; - module.exports = exports['default']; - -/***/ }, -/* 5 */ -/***/ function(module, exports, __webpack_require__) { - - // first check if moment.js is already loaded in the browser window, if so, - // use this instance. Else, load via commonjs. - 'use strict'; - - module.exports = typeof window !== 'undefined' && window['moment'] || __webpack_require__(6); - -/***/ }, -/* 6 */ -/***/ function(module, exports, __webpack_require__) { - - /* WEBPACK VAR INJECTION */(function(module) {//! moment.js - //! version : 2.10.3 - //! authors : Tim Wood, Iskren Chernev, Moment.js contributors - //! license : MIT - //! momentjs.com - - (function (global, factory) { - true ? module.exports = factory() : - typeof define === 'function' && define.amd ? define(factory) : - global.moment = factory() - }(this, function () { 'use strict'; - - var hookCallback; + } + return a; + }; - function utils_hooks__hooks () { - return hookCallback.apply(null, arguments); - } + /** + * Extend object a with selected properties of object b or a series of objects + * Only properties with defined values are copied + * @param {Array.} props + * @param {Object} a + * @param {Object} b + * @return {Object} a + */ + exports.selectiveExtend = function (props, a, b) { + if (!Array.isArray(props)) { + throw new Error('Array with property names expected as first argument'); + } - // This is done to register the method called with moment() - // without creating circular dependencies. - function setHookCallback (callback) { - hookCallback = callback; - } + for (var i = 2; i < arguments.length; i++) { + var other = arguments[i]; - function isArray(input) { - return Object.prototype.toString.call(input) === '[object Array]'; + for (var p = 0; p < props.length; p++) { + var prop = props[p]; + if (other.hasOwnProperty(prop)) { + a[prop] = other[prop]; + } } + } + return a; + }; - function isDate(input) { - return input instanceof Date || Object.prototype.toString.call(input) === '[object Date]'; - } + /** + * Extend object a with selected properties of object b or a series of objects + * Only properties with defined values are copied + * @param {Array.} props + * @param {Object} a + * @param {Object} b + * @return {Object} a + */ + exports.selectiveDeepExtend = function (props, a, b) { + var allowDeletion = arguments[3] === undefined ? false : arguments[3]; - function map(arr, fn) { - var res = [], i; - for (i = 0; i < arr.length; ++i) { - res.push(fn(arr[i], i)); + // TODO: add support for Arrays to deepExtend + if (Array.isArray(b)) { + throw new TypeError('Arrays are not supported by deepExtend'); + } + for (var i = 2; i < arguments.length; i++) { + var other = arguments[i]; + for (var p = 0; p < props.length; p++) { + var prop = props[p]; + if (other.hasOwnProperty(prop)) { + if (b[prop] && b[prop].constructor === Object) { + if (a[prop] === undefined) { + a[prop] = {}; + } + if (a[prop].constructor === Object) { + exports.deepExtend(a[prop], b[prop], false, allowDeletion); + } else { + if (b[prop] === null && a[prop] !== undefined && allowDeletion === true) { + delete a[prop]; + } else { + a[prop] = b[prop]; + } + } + } else if (Array.isArray(b[prop])) { + throw new TypeError('Arrays are not supported by deepExtend'); + } else { + a[prop] = b[prop]; } - return res; + } } + } + return a; + }; - function hasOwnProp(a, b) { - return Object.prototype.hasOwnProperty.call(a, b); - } + /** + * Extend object a with selected properties of object b or a series of objects + * Only properties with defined values are copied + * @param {Array.} props + * @param {Object} a + * @param {Object} b + * @return {Object} a + */ + exports.selectiveNotDeepExtend = function (props, a, b) { + var allowDeletion = arguments[3] === undefined ? false : arguments[3]; - function extend(a, b) { - for (var i in b) { - if (hasOwnProp(b, i)) { - a[i] = b[i]; + // TODO: add support for Arrays to deepExtend + if (Array.isArray(b)) { + throw new TypeError('Arrays are not supported by deepExtend'); + } + for (var prop in b) { + if (b.hasOwnProperty(prop)) { + if (props.indexOf(prop) == -1) { + if (b[prop] && b[prop].constructor === Object) { + if (a[prop] === undefined) { + a[prop] = {}; + } + if (a[prop].constructor === Object) { + exports.deepExtend(a[prop], b[prop]); + } else { + if (b[prop] === null && a[prop] !== undefined && allowDeletion === true) { + delete a[prop]; + } else { + a[prop] = b[prop]; } + } + } else if (Array.isArray(b[prop])) { + throw new TypeError('Arrays are not supported by deepExtend'); + } else { + a[prop] = b[prop]; } + } + } + } + return a; + }; - if (hasOwnProp(b, 'toString')) { - a.toString = b.toString; + /** + * Deep extend an object a with the properties of object b + * @param {Object} a + * @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 null will not deleted + * @returns {Object} + */ + exports.deepExtend = function (a, b, protoExtend, allowDeletion) { + for (var prop in b) { + if (b.hasOwnProperty(prop) || protoExtend === true) { + if (b[prop] && b[prop].constructor === Object) { + if (a[prop] === undefined) { + a[prop] = {}; } - - if (hasOwnProp(b, 'valueOf')) { - a.valueOf = b.valueOf; + if (a[prop].constructor === Object) { + exports.deepExtend(a[prop], b[prop], protoExtend); + } else { + if (b[prop] === null && a[prop] !== undefined && allowDeletion === true) { + delete a[prop]; + } else { + a[prop] = b[prop]; + } } - - return a; + } else if (Array.isArray(b[prop])) { + a[prop] = []; + for (var i = 0; i < b[prop].length; i++) { + a[prop].push(b[prop][i]); + } + } else { + a[prop] = b[prop]; + } } + } + return a; + }; - function create_utc__createUTC (input, format, locale, strict) { - return createLocalOrUTC(input, format, locale, strict, true).utc(); - } + /** + * Test whether all elements in two arrays are equal. + * @param {Array} a + * @param {Array} b + * @return {boolean} Returns true if both arrays have the same length and same + * elements. + */ + exports.equalArray = function (a, b) { + if (a.length != b.length) return false; - function defaultParsingFlags() { - // We need to deep clone this object. - return { - empty : false, - unusedTokens : [], - unusedInput : [], - overflow : -2, - charsLeftOver : 0, - nullInput : false, - invalidMonth : null, - invalidFormat : false, - userInvalidated : false, - iso : false - }; - } + for (var i = 0, len = a.length; i < len; i++) { + if (a[i] != b[i]) return false; + } - function getParsingFlags(m) { - if (m._pf == null) { - m._pf = defaultParsingFlags(); - } - return m._pf; - } + return true; + }; - function valid__isValid(m) { - if (m._isValid == null) { - var flags = getParsingFlags(m); - m._isValid = !isNaN(m._d.getTime()) && - flags.overflow < 0 && - !flags.empty && - !flags.invalidMonth && - !flags.nullInput && - !flags.invalidFormat && - !flags.userInvalidated; + /** + * Convert an object to another type + * @param {Boolean | Number | String | Date | Moment | Null | undefined} object + * @param {String | undefined} type Name of the type. Available types: + * 'Boolean', 'Number', 'String', + * 'Date', 'Moment', ISODate', 'ASPDate'. + * @return {*} object + * @throws Error + */ + exports.convert = function (object, type) { + var match; - if (m._strict) { - m._isValid = m._isValid && - flags.charsLeftOver === 0 && - flags.unusedTokens.length === 0 && - flags.bigHour === undefined; - } - } - return m._isValid; - } + if (object === undefined) { + return undefined; + } + if (object === null) { + return null; + } - function valid__createInvalid (flags) { - var m = create_utc__createUTC(NaN); - if (flags != null) { - extend(getParsingFlags(m), flags); - } - else { - getParsingFlags(m).userInvalidated = true; - } + if (!type) { + return object; + } + if (!(typeof type === 'string') && !(type instanceof String)) { + throw new Error('Type must be a string'); + } - return m; - } + //noinspection FallthroughInSwitchStatementJS + switch (type) { + case 'boolean': + case 'Boolean': + return Boolean(object); - var momentProperties = utils_hooks__hooks.momentProperties = []; + case 'number': + case 'Number': + return Number(object.valueOf()); - function copyConfig(to, from) { - var i, prop, val; + case 'string': + case 'String': + return String(object); - if (typeof from._isAMomentObject !== 'undefined') { - to._isAMomentObject = from._isAMomentObject; - } - if (typeof from._i !== 'undefined') { - to._i = from._i; - } - if (typeof from._f !== 'undefined') { - to._f = from._f; - } - if (typeof from._l !== 'undefined') { - to._l = from._l; - } - if (typeof from._strict !== 'undefined') { - to._strict = from._strict; - } - if (typeof from._tzm !== 'undefined') { - to._tzm = from._tzm; - } - if (typeof from._isUTC !== 'undefined') { - to._isUTC = from._isUTC; - } - if (typeof from._offset !== 'undefined') { - to._offset = from._offset; - } - if (typeof from._pf !== 'undefined') { - to._pf = getParsingFlags(from); - } - if (typeof from._locale !== 'undefined') { - to._locale = from._locale; + case 'Date': + if (exports.isNumber(object)) { + return new Date(object); + } + if (object instanceof Date) { + return new Date(object.valueOf()); + } else if (moment.isMoment(object)) { + return new Date(object.valueOf()); + } + if (exports.isString(object)) { + match = ASPDateRegex.exec(object); + if (match) { + // object is an ASP date + return new Date(Number(match[1])); // parse number + } else { + return moment(object).toDate(); // parse string } + } else { + throw new Error('Cannot convert object of type ' + exports.getType(object) + ' to type Date'); + } - if (momentProperties.length > 0) { - for (i in momentProperties) { - prop = momentProperties[i]; - val = from[prop]; - if (typeof val !== 'undefined') { - to[prop] = val; - } - } + case 'Moment': + if (exports.isNumber(object)) { + return moment(object); + } + if (object instanceof Date) { + return moment(object.valueOf()); + } else if (moment.isMoment(object)) { + return moment(object); + } + if (exports.isString(object)) { + match = ASPDateRegex.exec(object); + if (match) { + // object is an ASP date + return moment(Number(match[1])); // parse number + } else { + return moment(object); // parse string } + } else { + throw new Error('Cannot convert object of type ' + exports.getType(object) + ' to type Date'); + } - return to; - } - - var updateInProgress = false; + case 'ISODate': + if (exports.isNumber(object)) { + return new Date(object); + } else if (object instanceof Date) { + return object.toISOString(); + } else if (moment.isMoment(object)) { + return object.toDate().toISOString(); + } else if (exports.isString(object)) { + match = ASPDateRegex.exec(object); + if (match) { + // object is an ASP date + return new Date(Number(match[1])).toISOString(); // parse number + } else { + return new Date(object).toISOString(); // parse string + } + } else { + throw new Error('Cannot convert object of type ' + exports.getType(object) + ' to type ISODate'); + } - // Moment prototype object - function Moment(config) { - copyConfig(this, config); - this._d = new Date(+config._d); - // Prevent infinite loop in case updateOffset creates new moment - // objects. - if (updateInProgress === false) { - updateInProgress = true; - utils_hooks__hooks.updateOffset(this); - updateInProgress = false; + case 'ASPDate': + if (exports.isNumber(object)) { + return '/Date(' + object + ')/'; + } else if (object instanceof Date) { + return '/Date(' + object.valueOf() + ')/'; + } else if (exports.isString(object)) { + match = ASPDateRegex.exec(object); + var value; + if (match) { + // object is an ASP date + value = new Date(Number(match[1])).valueOf(); // parse number + } else { + value = new Date(object).valueOf(); // parse string } - } + return '/Date(' + value + ')/'; + } else { + throw new Error('Cannot convert object of type ' + exports.getType(object) + ' to type ASPDate'); + } - function isMoment (obj) { - return obj instanceof Moment || (obj != null && obj._isAMomentObject != null); - } + default: + throw new Error('Unknown type "' + type + '"'); + } + }; - function toInt(argumentForCoercion) { - var coercedNumber = +argumentForCoercion, - value = 0; + // parse ASP.Net Date pattern, + // for example '/Date(1198908717056)/' or '/Date(1198908717056-0700)/' + // code from http://momentjs.com/ + var ASPDateRegex = /^\/?Date\((\-?\d+)/i; - if (coercedNumber !== 0 && isFinite(coercedNumber)) { - if (coercedNumber >= 0) { - value = Math.floor(coercedNumber); - } else { - value = Math.ceil(coercedNumber); - } - } + /** + * Get the type of an object, for example exports.getType([]) returns 'Array' + * @param {*} object + * @return {String} type + */ + exports.getType = function (object) { + var type = typeof object; - return value; + if (type == 'object') { + if (object === null) { + return 'null'; } - - function compareArrays(array1, array2, dontConvert) { - var len = Math.min(array1.length, array2.length), - lengthDiff = Math.abs(array1.length - array2.length), - diffs = 0, - i; - for (i = 0; i < len; i++) { - if ((dontConvert && array1[i] !== array2[i]) || - (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) { - diffs++; - } - } - return diffs + lengthDiff; + if (object instanceof Boolean) { + return 'Boolean'; } - - function Locale() { + if (object instanceof Number) { + return 'Number'; } - - var locales = {}; - var globalLocale; - - function normalizeLocale(key) { - return key ? key.toLowerCase().replace('_', '-') : key; + if (object instanceof String) { + return 'String'; } - - // pick the locale from the array - // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each - // substring from most specific to least, but move to the next array item if it's a more specific variant than the current root - function chooseLocale(names) { - var i = 0, j, next, locale, split; - - while (i < names.length) { - split = normalizeLocale(names[i]).split('-'); - j = split.length; - next = normalizeLocale(names[i + 1]); - next = next ? next.split('-') : null; - while (j > 0) { - locale = loadLocale(split.slice(0, j).join('-')); - if (locale) { - return locale; - } - if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) { - //the next array item is better than a shallower substring of this one - break; - } - j--; - } - i++; - } - return null; + if (Array.isArray(object)) { + return 'Array'; } - - function loadLocale(name) { - var oldLocale = null; - // TODO: Find a better way to register and load all the locales in Node - if (!locales[name] && typeof module !== 'undefined' && - module && module.exports) { - try { - oldLocale = globalLocale._abbr; - !(function webpackMissingModule() { var e = new Error("Cannot find module \"./locale\""); e.code = 'MODULE_NOT_FOUND'; throw e; }()); - // because defineLocale currently also sets the global locale, we - // want to undo that for lazy loaded locales - locale_locales__getSetGlobalLocale(oldLocale); - } catch (e) { } - } - return locales[name]; + if (object instanceof Date) { + return 'Date'; } + return 'Object'; + } else if (type == 'number') { + return 'Number'; + } else if (type == 'boolean') { + return 'Boolean'; + } else if (type == 'string') { + return 'String'; + } else if (type === undefined) { + return 'undefined'; + } - // This function will load locale and then set the global locale. If - // no arguments are passed in, it will simply return the current global - // locale key. - function locale_locales__getSetGlobalLocale (key, values) { - var data; - if (key) { - if (typeof values === 'undefined') { - data = locale_locales__getLocale(key); - } - else { - data = defineLocale(key, values); - } + return type; + }; - if (data) { - // moment.duration._locale = moment._locale = data; - globalLocale = data; - } - } + /** + * Used to extend an array and copy it. This is used to propagate paths recursively. + * + * @param arr + * @param newValue + * @returns {Array} + */ + exports.copyAndExtendArray = function (arr, newValue) { + var newArr = []; + for (var i = 0; i < arr.length; i++) { + newArr.push(arr[i]); + } + newArr.push(newValue); + return newArr; + }; - return globalLocale._abbr; - } + /** + * Used to extend an array and copy it. This is used to propagate paths recursively. + * + * @param arr + * @param newValue + * @returns {Array} + */ + exports.copyArray = function (arr) { + var newArr = []; + for (var i = 0; i < arr.length; i++) { + newArr.push(arr[i]); + } + return newArr; + }; - function defineLocale (name, values) { - if (values !== null) { - values.abbr = name; - if (!locales[name]) { - locales[name] = new Locale(); - } - locales[name].set(values); + /** + * Retrieve the absolute left value of a DOM element + * @param {Element} elem A dom element, for example a div + * @return {number} left The absolute left position of this element + * in the browser page. + */ + exports.getAbsoluteLeft = function (elem) { + return elem.getBoundingClientRect().left; + }; - // backwards compat for now: also set the locale - locale_locales__getSetGlobalLocale(name); + /** + * Retrieve the absolute top value of a DOM element + * @param {Element} elem A dom element, for example a div + * @return {number} top The absolute top position of this element + * in the browser page. + */ + exports.getAbsoluteTop = function (elem) { + return elem.getBoundingClientRect().top; + }; - return locales[name]; - } else { - // useful for testing - delete locales[name]; - return null; - } - } + /** + * add a className to the given elements style + * @param {Element} elem + * @param {String} className + */ + exports.addClassName = function (elem, className) { + var classes = elem.className.split(' '); + if (classes.indexOf(className) == -1) { + classes.push(className); // add the class to the array + elem.className = classes.join(' '); + } + }; - // returns locale data - function locale_locales__getLocale (key) { - var locale; + /** + * add a className to the given elements style + * @param {Element} elem + * @param {String} className + */ + exports.removeClassName = function (elem, className) { + var classes = elem.className.split(' '); + var index = classes.indexOf(className); + if (index != -1) { + classes.splice(index, 1); // remove the class from the array + elem.className = classes.join(' '); + } + }; - if (key && key._locale && key._locale._abbr) { - key = key._locale._abbr; - } + /** + * For each method for both arrays and objects. + * In case of an array, the built-in Array.forEach() is applied. + * In case of an Object, the method loops over all properties of the object. + * @param {Object | Array} object An Object or Array + * @param {function} callback Callback method, called for each item in + * the object or array with three parameters: + * callback(value, index, object) + */ + exports.forEach = function (object, callback) { + var i, len; + if (Array.isArray(object)) { + // array + for (i = 0, len = object.length; i < len; i++) { + callback(object[i], i, object); + } + } else { + // object + for (i in object) { + if (object.hasOwnProperty(i)) { + callback(object[i], i, object); + } + } + } + }; - if (!key) { - return globalLocale; - } + /** + * Convert an object into an array: all objects properties are put into the + * array. The resulting array is unordered. + * @param {Object} object + * @param {Array} array + */ + exports.toArray = function (object) { + var array = []; - if (!isArray(key)) { - //short-circuit everything else - locale = loadLocale(key); - if (locale) { - return locale; - } - key = [key]; - } + for (var prop in object) { + if (object.hasOwnProperty(prop)) array.push(object[prop]); + } - return chooseLocale(key); - } + return array; + }; - var aliases = {}; - - function addUnitAlias (unit, shorthand) { - var lowerCase = unit.toLowerCase(); - aliases[lowerCase] = aliases[lowerCase + 's'] = aliases[shorthand] = unit; - } - - function normalizeUnits(units) { - return typeof units === 'string' ? aliases[units] || aliases[units.toLowerCase()] : undefined; - } - - function normalizeObjectUnits(inputObject) { - var normalizedInput = {}, - normalizedProp, - prop; + /** + * Update a property in an object + * @param {Object} object + * @param {String} key + * @param {*} value + * @return {Boolean} changed + */ + exports.updateProperty = function (object, key, value) { + if (object[key] !== value) { + object[key] = value; + return true; + } else { + return false; + } + }; - for (prop in inputObject) { - if (hasOwnProp(inputObject, prop)) { - normalizedProp = normalizeUnits(prop); - if (normalizedProp) { - normalizedInput[normalizedProp] = inputObject[prop]; - } - } - } + /** + * Add and event listener. Works for all browsers + * @param {Element} element An html element + * @param {string} action The action, for example "click", + * without the prefix "on" + * @param {function} listener The callback function to be executed + * @param {boolean} [useCapture] + */ + exports.addEventListener = function (element, action, listener, useCapture) { + if (element.addEventListener) { + if (useCapture === undefined) useCapture = false; - return normalizedInput; + if (action === 'mousewheel' && navigator.userAgent.indexOf('Firefox') >= 0) { + action = 'DOMMouseScroll'; // For Firefox } - function makeGetSet (unit, keepTime) { - return function (value) { - if (value != null) { - get_set__set(this, unit, value); - utils_hooks__hooks.updateOffset(this, keepTime); - return this; - } else { - return get_set__get(this, unit); - } - }; - } + element.addEventListener(action, listener, useCapture); + } else { + element.attachEvent('on' + action, listener); // IE browsers + } + }; - function get_set__get (mom, unit) { - return mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit](); - } + /** + * Remove an event listener from an element + * @param {Element} element An html dom element + * @param {string} action The name of the event, for example "mousedown" + * @param {function} listener The listener function + * @param {boolean} [useCapture] + */ + exports.removeEventListener = function (element, action, listener, useCapture) { + if (element.removeEventListener) { + // non-IE browsers + if (useCapture === undefined) useCapture = false; - function get_set__set (mom, unit, value) { - return mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value); + if (action === 'mousewheel' && navigator.userAgent.indexOf('Firefox') >= 0) { + action = 'DOMMouseScroll'; // For Firefox } - // MOMENTS + element.removeEventListener(action, listener, useCapture); + } else { + // IE browsers + element.detachEvent('on' + action, listener); + } + }; - function getSet (units, value) { - var unit; - if (typeof units === 'object') { - for (unit in units) { - this.set(unit, units[unit]); - } - } else { - units = normalizeUnits(units); - if (typeof this[units] === 'function') { - return this[units](value); - } - } - return this; - } + /** + * Cancels the event if it is cancelable, without stopping further propagation of the event. + */ + exports.preventDefault = function (event) { + if (!event) event = window.event; - function zeroFill(number, targetLength, forceSign) { - var output = '' + Math.abs(number), - sign = number >= 0; + if (event.preventDefault) { + event.preventDefault(); // non-IE browsers + } else { + event.returnValue = false; // IE browsers + } + }; - while (output.length < targetLength) { - output = '0' + output; - } - return (sign ? (forceSign ? '+' : '') : '-') + output; - } + /** + * Get HTML element which is the target of the event + * @param {Event} event + * @return {Element} target element + */ + exports.getTarget = function (event) { + // code from http://www.quirksmode.org/js/events_properties.html + if (!event) { + event = window.event; + } - var formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Q|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|x|X|zz?|ZZ?|.)/g; + var target; - var localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g; + if (event.target) { + target = event.target; + } else if (event.srcElement) { + target = event.srcElement; + } - var formatFunctions = {}; + if (target.nodeType != undefined && target.nodeType == 3) { + // defeat Safari bug + target = target.parentNode; + } - var formatTokenFunctions = {}; + return target; + }; - // token: 'M' - // padded: ['MM', 2] - // ordinal: 'Mo' - // callback: function () { this.month() + 1 } - function addFormatToken (token, padded, ordinal, callback) { - var func = callback; - if (typeof callback === 'string') { - func = function () { - return this[callback](); - }; - } - if (token) { - formatTokenFunctions[token] = func; - } - if (padded) { - formatTokenFunctions[padded[0]] = function () { - return zeroFill(func.apply(this, arguments), padded[1], padded[2]); - }; - } - if (ordinal) { - formatTokenFunctions[ordinal] = function () { - return this.localeData().ordinal(func.apply(this, arguments), token); - }; - } - } + /** + * Check if given element contains given parent somewhere in the DOM tree + * @param {Element} element + * @param {Element} parent + */ + exports.hasParent = function (element, parent) { + var e = element; - function removeFormattingTokens(input) { - if (input.match(/\[[\s\S]/)) { - return input.replace(/^\[|\]$/g, ''); - } - return input.replace(/\\/g, ''); + while (e) { + if (e === parent) { + return true; } + e = e.parentNode; + } - function makeFormatFunction(format) { - var array = format.match(formattingTokens), i, length; - - for (i = 0, length = array.length; i < length; i++) { - if (formatTokenFunctions[array[i]]) { - array[i] = formatTokenFunctions[array[i]]; - } else { - array[i] = removeFormattingTokens(array[i]); - } - } + return false; + }; - return function (mom) { - var output = ''; - for (i = 0; i < length; i++) { - output += array[i] instanceof Function ? array[i].call(mom, format) : array[i]; - } - return output; - }; - } + exports.option = {}; - // format date using native date object - function formatMoment(m, format) { - if (!m.isValid()) { - return m.localeData().invalidDate(); - } + /** + * Convert a value into a boolean + * @param {Boolean | function | undefined} value + * @param {Boolean} [defaultValue] + * @returns {Boolean} bool + */ + exports.option.asBoolean = function (value, defaultValue) { + if (typeof value == 'function') { + value = value(); + } - format = expandFormat(format, m.localeData()); + if (value != null) { + return value != false; + } - if (!formatFunctions[format]) { - formatFunctions[format] = makeFormatFunction(format); - } + return defaultValue || null; + }; - return formatFunctions[format](m); - } + /** + * Convert a value into a number + * @param {Boolean | function | undefined} value + * @param {Number} [defaultValue] + * @returns {Number} number + */ + exports.option.asNumber = function (value, defaultValue) { + if (typeof value == 'function') { + value = value(); + } - function expandFormat(format, locale) { - var i = 5; + if (value != null) { + return Number(value) || defaultValue || null; + } - function replaceLongDateFormatTokens(input) { - return locale.longDateFormat(input) || input; - } + return defaultValue || null; + }; - localFormattingTokens.lastIndex = 0; - while (i >= 0 && localFormattingTokens.test(format)) { - format = format.replace(localFormattingTokens, replaceLongDateFormatTokens); - localFormattingTokens.lastIndex = 0; - i -= 1; - } + /** + * Convert a value into a string + * @param {String | function | undefined} value + * @param {String} [defaultValue] + * @returns {String} str + */ + exports.option.asString = function (value, defaultValue) { + if (typeof value == 'function') { + value = value(); + } - return format; - } + if (value != null) { + return String(value); + } - var match1 = /\d/; // 0 - 9 - var match2 = /\d\d/; // 00 - 99 - var match3 = /\d{3}/; // 000 - 999 - var match4 = /\d{4}/; // 0000 - 9999 - var match6 = /[+-]?\d{6}/; // -999999 - 999999 - var match1to2 = /\d\d?/; // 0 - 99 - var match1to3 = /\d{1,3}/; // 0 - 999 - var match1to4 = /\d{1,4}/; // 0 - 9999 - var match1to6 = /[+-]?\d{1,6}/; // -999999 - 999999 + return defaultValue || null; + }; - var matchUnsigned = /\d+/; // 0 - inf - var matchSigned = /[+-]?\d+/; // -inf - inf + /** + * Convert a size or location into a string with pixels or a percentage + * @param {String | Number | function | undefined} value + * @param {String} [defaultValue] + * @returns {String} size + */ + exports.option.asSize = function (value, defaultValue) { + if (typeof value == 'function') { + value = value(); + } - var matchOffset = /Z|[+-]\d\d:?\d\d/gi; // +00:00 -00:00 +0000 -0000 or Z + if (exports.isString(value)) { + return value; + } else if (exports.isNumber(value)) { + return value + 'px'; + } else { + return defaultValue || null; + } + }; - var matchTimestamp = /[+-]?\d+(\.\d{1,3})?/; // 123456789 123456789.123 + /** + * Convert a value into a DOM element + * @param {HTMLElement | function | undefined} value + * @param {HTMLElement} [defaultValue] + * @returns {HTMLElement | null} dom + */ + exports.option.asElement = function (value, defaultValue) { + if (typeof value == 'function') { + value = value(); + } - // any word (or two) characters or numbers including two/three word month in arabic. - var matchWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i; + return value || defaultValue || null; + }; - var regexes = {}; + /** + * http://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb + * + * @param {String} hex + * @returns {{r: *, g: *, b: *}} | 255 range + */ + exports.hexToRGB = function (hex) { + // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF") + var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; + hex = hex.replace(shorthandRegex, function (m, r, g, b) { + return r + r + g + g + b + b; + }); + var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + return result ? { + r: parseInt(result[1], 16), + g: parseInt(result[2], 16), + b: parseInt(result[3], 16) + } : null; + }; - function addRegexToken (token, regex, strictRegex) { - regexes[token] = typeof regex === 'function' ? regex : function (isStrict) { - return (isStrict && strictRegex) ? strictRegex : regex; - }; + /** + * This function takes color in hex format or rgb() or rgba() format and overrides the opacity. Returns rgba() string. + * @param color + * @param opacity + * @returns {*} + */ + exports.overrideOpacity = function (color, opacity) { + if (color.indexOf('rgba') != -1) { + return color; + } else if (color.indexOf('rgb') != -1) { + var rgb = color.substr(color.indexOf('(') + 1).replace(')', '').split(','); + return 'rgba(' + rgb[0] + ',' + rgb[1] + ',' + rgb[2] + ',' + opacity + ')'; + } else { + var rgb = exports.hexToRGB(color); + if (rgb == null) { + return color; + } else { + return 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + opacity + ')'; } + } + }; - function getParseRegexForToken (token, config) { - if (!hasOwnProp(regexes, token)) { - return new RegExp(unescapeFormat(token)); - } - - return regexes[token](config._strict, config._locale); - } + /** + * + * @param red 0 -- 255 + * @param green 0 -- 255 + * @param blue 0 -- 255 + * @returns {string} + * @constructor + */ + exports.RGBToHex = function (red, green, blue) { + return '#' + ((1 << 24) + (red << 16) + (green << 8) + blue).toString(16).slice(1); + }; - // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript - function unescapeFormat(s) { - return s.replace('\\', '').replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) { - return p1 || p2 || p3 || p4; - }).replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); + /** + * Parse a color property into an object with border, background, and + * highlight colors + * @param {Object | String} color + * @return {Object} colorObject + */ + exports.parseColor = function (color) { + var c; + if (exports.isString(color) === true) { + if (exports.isValidRGB(color) === true) { + var rgb = color.substr(4).substr(0, color.length - 5).split(',').map(function (value) { + return parseInt(value); + }); + color = exports.RGBToHex(rgb[0], rgb[1], rgb[2]); } - - var tokens = {}; - - function addParseToken (token, callback) { - var i, func = callback; - if (typeof token === 'string') { - token = [token]; - } - if (typeof callback === 'number') { - func = function (input, array) { - array[callback] = toInt(input); - }; + if (exports.isValidHex(color) === true) { + var hsv = exports.hexToHSV(color); + var lighterColorHSV = { h: hsv.h, s: hsv.s * 0.8, v: Math.min(1, hsv.v * 1.02) }; + var darkerColorHSV = { h: hsv.h, s: Math.min(1, hsv.s * 1.25), v: hsv.v * 0.8 }; + var darkerColorHex = exports.HSVToHex(darkerColorHSV.h, darkerColorHSV.s, darkerColorHSV.v); + var lighterColorHex = exports.HSVToHex(lighterColorHSV.h, lighterColorHSV.s, lighterColorHSV.v); + c = { + background: color, + border: darkerColorHex, + highlight: { + background: lighterColorHex, + border: darkerColorHex + }, + hover: { + background: lighterColorHex, + border: darkerColorHex } - for (i = 0; i < token.length; i++) { - tokens[token[i]] = func; + }; + } else { + c = { + background: color, + border: color, + highlight: { + background: color, + border: color + }, + hover: { + background: color, + border: color } + }; } + } else { + c = {}; + c.background = color.background || undefined; + c.border = color.border || undefined; - function addWeekParseToken (token, callback) { - addParseToken(token, function (input, array, config, token) { - config._w = config._w || {}; - callback(input, config._w, config, token); - }); + if (exports.isString(color.highlight)) { + c.highlight = { + border: color.highlight, + background: color.highlight + }; + } else { + c.highlight = {}; + c.highlight.background = color.highlight && color.highlight.background || undefined; + c.highlight.border = color.highlight && color.highlight.border || undefined; } - function addTimeToArrayFromToken(token, input, config) { - if (input != null && hasOwnProp(tokens, token)) { - tokens[token](input, config._a, config, token); - } + if (exports.isString(color.hover)) { + c.hover = { + border: color.hover, + background: color.hover + }; + } else { + c.hover = {}; + c.hover.background = color.hover && color.hover.background || undefined; + c.hover.border = color.hover && color.hover.border || undefined; } + } - var YEAR = 0; - var MONTH = 1; - var DATE = 2; - var HOUR = 3; - var MINUTE = 4; - var SECOND = 5; - var MILLISECOND = 6; - - function daysInMonth(year, month) { - return new Date(Date.UTC(year, month + 1, 0)).getUTCDate(); - } + return c; + }; - // FORMATTING + /** + * http://www.javascripter.net/faq/rgb2hsv.htm + * + * @param red + * @param green + * @param blue + * @returns {*} + * @constructor + */ + exports.RGBToHSV = function (red, green, blue) { + red = red / 255;green = green / 255;blue = blue / 255; + var minRGB = Math.min(red, Math.min(green, blue)); + var maxRGB = Math.max(red, Math.max(green, blue)); - addFormatToken('M', ['MM', 2], 'Mo', function () { - return this.month() + 1; - }); - - addFormatToken('MMM', 0, 0, function (format) { - return this.localeData().monthsShort(this, format); - }); - - addFormatToken('MMMM', 0, 0, function (format) { - return this.localeData().months(this, format); - }); - - // ALIASES - - addUnitAlias('month', 'M'); - - // PARSING + // Black-gray-white + if (minRGB == maxRGB) { + return { h: 0, s: 0, v: minRGB }; + } - addRegexToken('M', match1to2); - addRegexToken('MM', match1to2, match2); - addRegexToken('MMM', matchWord); - addRegexToken('MMMM', matchWord); + // Colors other than black-gray-white: + var d = red == minRGB ? green - blue : blue == minRGB ? red - green : blue - red; + var h = red == minRGB ? 3 : blue == minRGB ? 1 : 5; + var hue = 60 * (h - d / (maxRGB - minRGB)) / 360; + var saturation = (maxRGB - minRGB) / maxRGB; + var value = maxRGB; + return { h: hue, s: saturation, v: value }; + }; - addParseToken(['M', 'MM'], function (input, array) { - array[MONTH] = toInt(input) - 1; - }); + var cssUtil = { + // split a string with css styles into an object with key/values + split: function split(cssText) { + var styles = {}; - addParseToken(['MMM', 'MMMM'], function (input, array, config, token) { - var month = config._locale.monthsParse(input, token, config._strict); - // if we didn't find a month name, mark the date as invalid. - if (month != null) { - array[MONTH] = month; - } else { - getParsingFlags(config).invalidMonth = input; - } + cssText.split(';').forEach(function (style) { + if (style.trim() != '') { + var parts = style.split(':'); + var key = parts[0].trim(); + var value = parts[1].trim(); + styles[key] = value; + } }); - // LOCALES - - var defaultLocaleMonths = 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'); - function localeMonths (m) { - return this._months[m.month()]; - } - - var defaultLocaleMonthsShort = 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'); - function localeMonthsShort (m) { - return this._monthsShort[m.month()]; - } - - function localeMonthsParse (monthName, format, strict) { - var i, mom, regex; - - if (!this._monthsParse) { - this._monthsParse = []; - this._longMonthsParse = []; - this._shortMonthsParse = []; - } - - for (i = 0; i < 12; i++) { - // make the regex if we don't have it already - mom = create_utc__createUTC([2000, i]); - if (strict && !this._longMonthsParse[i]) { - this._longMonthsParse[i] = new RegExp('^' + this.months(mom, '').replace('.', '') + '$', 'i'); - this._shortMonthsParse[i] = new RegExp('^' + this.monthsShort(mom, '').replace('.', '') + '$', 'i'); - } - if (!strict && !this._monthsParse[i]) { - regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, ''); - this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i'); - } - // test the regex - if (strict && format === 'MMMM' && this._longMonthsParse[i].test(monthName)) { - return i; - } else if (strict && format === 'MMM' && this._shortMonthsParse[i].test(monthName)) { - return i; - } else if (!strict && this._monthsParse[i].test(monthName)) { - return i; - } - } - } - - // MOMENTS + return styles; + }, - function setMonth (mom, value) { - var dayOfMonth; + // build a css text string from an object with key/values + join: function join(styles) { + return Object.keys(styles).map(function (key) { + return key + ': ' + styles[key]; + }).join('; '); + } + }; - // TODO: Move this out of here! - if (typeof value === 'string') { - value = mom.localeData().monthsParse(value); - // TODO: Another silent failure? - if (typeof value !== 'number') { - return mom; - } - } + /** + * Append a string with css styles to an element + * @param {Element} element + * @param {String} cssText + */ + exports.addCssText = function (element, cssText) { + var currentStyles = cssUtil.split(element.style.cssText); + var newStyles = cssUtil.split(cssText); + var styles = exports.extend(currentStyles, newStyles); - dayOfMonth = Math.min(mom.date(), daysInMonth(mom.year(), value)); - mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth); - return mom; - } + element.style.cssText = cssUtil.join(styles); + }; - function getSetMonth (value) { - if (value != null) { - setMonth(this, value); - utils_hooks__hooks.updateOffset(this, true); - return this; - } else { - return get_set__get(this, 'Month'); - } - } + /** + * Remove a string with css styles from an element + * @param {Element} element + * @param {String} cssText + */ + exports.removeCssText = function (element, cssText) { + var styles = cssUtil.split(element.style.cssText); + var removeStyles = cssUtil.split(cssText); - function getDaysInMonth () { - return daysInMonth(this.year(), this.month()); + for (var key in removeStyles) { + if (removeStyles.hasOwnProperty(key)) { + delete styles[key]; } + } - function checkOverflow (m) { - var overflow; - var a = m._a; + element.style.cssText = cssUtil.join(styles); + }; - if (a && getParsingFlags(m).overflow === -2) { - overflow = - a[MONTH] < 0 || a[MONTH] > 11 ? MONTH : - a[DATE] < 1 || a[DATE] > daysInMonth(a[YEAR], a[MONTH]) ? DATE : - a[HOUR] < 0 || a[HOUR] > 24 || (a[HOUR] === 24 && (a[MINUTE] !== 0 || a[SECOND] !== 0 || a[MILLISECOND] !== 0)) ? HOUR : - a[MINUTE] < 0 || a[MINUTE] > 59 ? MINUTE : - a[SECOND] < 0 || a[SECOND] > 59 ? SECOND : - a[MILLISECOND] < 0 || a[MILLISECOND] > 999 ? MILLISECOND : - -1; + /** + * https://gist.github.com/mjijackson/5311256 + * @param h + * @param s + * @param v + * @returns {{r: number, g: number, b: number}} + * @constructor + */ + exports.HSVToRGB = function (h, s, v) { + var r, g, b; - if (getParsingFlags(m)._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) { - overflow = DATE; - } + var i = Math.floor(h * 6); + var f = h * 6 - i; + var p = v * (1 - s); + var q = v * (1 - f * s); + var t = v * (1 - (1 - f) * s); - getParsingFlags(m).overflow = overflow; - } + switch (i % 6) { + case 0: + r = v, g = t, b = p;break; + case 1: + r = q, g = v, b = p;break; + case 2: + r = p, g = v, b = t;break; + case 3: + r = p, g = q, b = v;break; + case 4: + r = t, g = p, b = v;break; + case 5: + r = v, g = p, b = q;break; + } - return m; - } + return { r: Math.floor(r * 255), g: Math.floor(g * 255), b: Math.floor(b * 255) }; + }; - function warn(msg) { - if (utils_hooks__hooks.suppressDeprecationWarnings === false && typeof console !== 'undefined' && console.warn) { - console.warn('Deprecation warning: ' + msg); - } - } + exports.HSVToHex = function (h, s, v) { + var rgb = exports.HSVToRGB(h, s, v); + return exports.RGBToHex(rgb.r, rgb.g, rgb.b); + }; - function deprecate(msg, fn) { - var firstTime = true, - msgWithStack = msg + '\n' + (new Error()).stack; + exports.hexToHSV = function (hex) { + var rgb = exports.hexToRGB(hex); + return exports.RGBToHSV(rgb.r, rgb.g, rgb.b); + }; - return extend(function () { - if (firstTime) { - warn(msgWithStack); - firstTime = false; - } - return fn.apply(this, arguments); - }, fn); - } + exports.isValidHex = function (hex) { + var isOk = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(hex); + return isOk; + }; - var deprecations = {}; + exports.isValidRGB = function (rgb) { + rgb = rgb.replace(' ', ''); + var isOk = /rgb\((\d{1,3}),(\d{1,3}),(\d{1,3})\)/i.test(rgb); + return isOk; + }; + exports.isValidRGBA = function (rgba) { + rgba = rgba.replace(' ', ''); + var isOk = /rgba\((\d{1,3}),(\d{1,3}),(\d{1,3}),(.{1,3})\)/i.test(rgba); + return isOk; + }; - function deprecateSimple(name, msg) { - if (!deprecations[name]) { - warn(msg); - deprecations[name] = true; + /** + * This recursively redirects the prototype of JSON objects to the referenceObject + * This is used for default options. + * + * @param referenceObject + * @returns {*} + */ + exports.selectiveBridgeObject = function (fields, referenceObject) { + if (typeof referenceObject == 'object') { + var objectTo = Object.create(referenceObject); + for (var i = 0; i < fields.length; i++) { + if (referenceObject.hasOwnProperty(fields[i])) { + if (typeof referenceObject[fields[i]] == 'object') { + objectTo[fields[i]] = exports.bridgeObject(referenceObject[fields[i]]); } + } } + return objectTo; + } else { + return null; + } + }; - utils_hooks__hooks.suppressDeprecationWarnings = false; - - var from_string__isoRegex = /^\s*(?:[+-]\d{6}|\d{4})-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/; - - var isoDates = [ - ['YYYYYY-MM-DD', /[+-]\d{6}-\d{2}-\d{2}/], - ['YYYY-MM-DD', /\d{4}-\d{2}-\d{2}/], - ['GGGG-[W]WW-E', /\d{4}-W\d{2}-\d/], - ['GGGG-[W]WW', /\d{4}-W\d{2}/], - ['YYYY-DDD', /\d{4}-\d{3}/] - ]; - - // iso time formats and regexes - var isoTimes = [ - ['HH:mm:ss.SSSS', /(T| )\d\d:\d\d:\d\d\.\d+/], - ['HH:mm:ss', /(T| )\d\d:\d\d:\d\d/], - ['HH:mm', /(T| )\d\d:\d\d/], - ['HH', /(T| )\d\d/] - ]; - - var aspNetJsonRegex = /^\/?Date\((\-?\d+)/i; - - // date from iso format - function configFromISO(config) { - var i, l, - string = config._i, - match = from_string__isoRegex.exec(string); - - if (match) { - getParsingFlags(config).iso = true; - for (i = 0, l = isoDates.length; i < l; i++) { - if (isoDates[i][1].exec(string)) { - // match[5] should be 'T' or undefined - config._f = isoDates[i][0] + (match[6] || ' '); - break; - } - } - for (i = 0, l = isoTimes.length; i < l; i++) { - if (isoTimes[i][1].exec(string)) { - config._f += isoTimes[i][0]; - break; - } - } - if (string.match(matchOffset)) { - config._f += 'Z'; - } - configFromStringAndFormat(config); - } else { - config._isValid = false; + /** + * This recursively redirects the prototype of JSON objects to the referenceObject + * This is used for default options. + * + * @param referenceObject + * @returns {*} + */ + exports.bridgeObject = function (referenceObject) { + if (typeof referenceObject == 'object') { + var objectTo = Object.create(referenceObject); + for (var i in referenceObject) { + if (referenceObject.hasOwnProperty(i)) { + if (typeof referenceObject[i] == 'object') { + objectTo[i] = exports.bridgeObject(referenceObject[i]); } + } } + return objectTo; + } else { + return null; + } + }; - // date from iso format or fallback - function configFromString(config) { - var matched = aspNetJsonRegex.exec(config._i); + /** + * this is used to set the options of subobjects in the options object. A requirement of these subobjects + * is that they have an 'enabled' element which is optional for the user but mandatory for the program. + * + * @param [object] mergeTarget | this is either this.options or the options used for the groups. + * @param [object] options | options + * @param [String] option | this is the option key in the options argument + * @private + */ + exports.mergeOptions = function (mergeTarget, options, option) { + var allowDeletion = arguments[3] === undefined ? false : arguments[3]; - if (matched !== null) { - config._d = new Date(+matched[1]); - return; + 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 { + if (options[option].enabled === undefined) { + mergeTarget[option].enabled = true; } - - configFromISO(config); - if (config._isValid === false) { - delete config._isValid; - utils_hooks__hooks.createFromInputFallback(config); + for (var prop in options[option]) { + if (options[option].hasOwnProperty(prop)) { + mergeTarget[option][prop] = options[option][prop]; + } } + } } + } + }; - utils_hooks__hooks.createFromInputFallback = deprecate( - 'moment construction falls back to js Date. This is ' + - 'discouraged and will be removed in upcoming major ' + - 'release. Please refer to ' + - 'https://github.com/moment/moment/issues/1407 for more info.', - function (config) { - config._d = new Date(config._i + (config._useUTC ? ' UTC' : '')); - } - ); + /** + * This function does a binary search for a visible item in a sorted list. If we find a visible item, the code that uses + * this function will then iterate in both directions over this sorted list to find all visible items. + * + * @param {Item[]} orderedItems | Items ordered by start + * @param {function} searchFunction | -1 is lower, 0 is found, 1 is higher + * @param {String} field + * @param {String} field2 + * @returns {number} + * @private + */ + exports.binarySearchCustom = function (orderedItems, searchFunction, field, field2) { + var maxIterations = 10000; + var iteration = 0; + var low = 0; + var high = orderedItems.length - 1; - function createDate (y, m, d, h, M, s, ms) { - //can't just apply() to create a date: - //http://stackoverflow.com/questions/181348/instantiating-a-javascript-object-by-calling-prototype-constructor-apply - var date = new Date(y, m, d, h, M, s, ms); + while (low <= high && iteration < maxIterations) { + var middle = Math.floor((low + high) / 2); - //the date constructor doesn't accept years < 1970 - if (y < 1970) { - date.setFullYear(y); - } - return date; - } + var item = orderedItems[middle]; + var value = field2 === undefined ? item[field] : item[field][field2]; - function createUTCDate (y) { - var date = new Date(Date.UTC.apply(null, arguments)); - if (y < 1970) { - date.setUTCFullYear(y); - } - return date; + var searchResult = searchFunction(value); + if (searchResult == 0) { + // jihaa, found a visible item! + return middle; + } else if (searchResult == -1) { + // it is too small --> increase low + low = middle + 1; + } else { + // it is too big --> decrease high + high = middle - 1; } - addFormatToken(0, ['YY', 2], 0, function () { - return this.year() % 100; - }); - - addFormatToken(0, ['YYYY', 4], 0, 'year'); - addFormatToken(0, ['YYYYY', 5], 0, 'year'); - addFormatToken(0, ['YYYYYY', 6, true], 0, 'year'); - - // ALIASES - - addUnitAlias('year', 'y'); - - // PARSING - - addRegexToken('Y', matchSigned); - addRegexToken('YY', match1to2, match2); - addRegexToken('YYYY', match1to4, match4); - addRegexToken('YYYYY', match1to6, match6); - addRegexToken('YYYYYY', match1to6, match6); + iteration++; + } - addParseToken(['YYYY', 'YYYYY', 'YYYYYY'], YEAR); - addParseToken('YY', function (input, array) { - array[YEAR] = utils_hooks__hooks.parseTwoDigitYear(input); - }); + return -1; + }; - // HELPERS + /** + * This function does a binary search for a specific value in a sorted array. If it does not exist but is in between of + * two values, we return either the one before or the one after, depending on user input + * If it is found, we return the index, else -1. + * + * @param {Array} orderedItems + * @param {{start: number, end: number}} target + * @param {String} field + * @param {String} sidePreference 'before' or 'after' + * @returns {number} + * @private + */ + exports.binarySearchValue = function (orderedItems, target, field, sidePreference) { + var maxIterations = 10000; + var iteration = 0; + var low = 0; + var high = orderedItems.length - 1; + var prevValue, value, nextValue, middle; - function daysInYear(year) { - return isLeapYear(year) ? 366 : 365; - } + while (low <= high && iteration < maxIterations) { + // get a new guess + middle = Math.floor(0.5 * (high + low)); + prevValue = orderedItems[Math.max(0, middle - 1)][field]; + value = orderedItems[middle][field]; + nextValue = orderedItems[Math.min(orderedItems.length - 1, middle + 1)][field]; - function isLeapYear(year) { - return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; + if (value == target) { + // we found the target + return middle; + } else if (prevValue < target && value > target) { + // target is in between of the previous and the current + return sidePreference == 'before' ? Math.max(0, middle - 1) : middle; + } else if (value < target && nextValue > target) { + // target is in between of the current and the next + return sidePreference == 'before' ? middle : Math.min(orderedItems.length - 1, middle + 1); + } else { + // didnt find the target, we need to change our boundaries. + if (value < target) { + // it is too small --> increase low + low = middle + 1; + } else { + // it is too big --> decrease high + high = middle - 1; + } } + iteration++; + } - // HOOKS + // didnt find anything. Return -1. + return -1; + }; + + /* + * Easing Functions - inspired from http://gizma.com/easing/ + * only considering the t value for the range [0, 1] => [0, 1] + * https://gist.github.com/gre/1650294 + */ + exports.easingFunctions = { + // no easing, no acceleration + linear: function linear(t) { + return t; + }, + // accelerating from zero velocity + easeInQuad: function easeInQuad(t) { + return t * t; + }, + // decelerating to zero velocity + easeOutQuad: function easeOutQuad(t) { + return t * (2 - t); + }, + // acceleration until halfway, then deceleration + easeInOutQuad: function easeInOutQuad(t) { + return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t; + }, + // accelerating from zero velocity + easeInCubic: function easeInCubic(t) { + return t * t * t; + }, + // decelerating to zero velocity + easeOutCubic: function easeOutCubic(t) { + return --t * t * t + 1; + }, + // acceleration until halfway, then deceleration + easeInOutCubic: function easeInOutCubic(t) { + return t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1; + }, + // accelerating from zero velocity + easeInQuart: function easeInQuart(t) { + return t * t * t * t; + }, + // decelerating to zero velocity + easeOutQuart: function easeOutQuart(t) { + return 1 - --t * t * t * t; + }, + // acceleration until halfway, then deceleration + easeInOutQuart: function easeInOutQuart(t) { + return t < 0.5 ? 8 * t * t * t * t : 1 - 8 * --t * t * t * t; + }, + // accelerating from zero velocity + easeInQuint: function easeInQuint(t) { + return t * t * t * t * t; + }, + // decelerating to zero velocity + easeOutQuint: function easeOutQuint(t) { + return 1 + --t * t * t * t * t; + }, + // acceleration until halfway, then deceleration + easeInOutQuint: function easeInOutQuint(t) { + return t < 0.5 ? 16 * t * t * t * t * t : 1 + 16 * --t * t * t * t * t; + } + }; + +/***/ }, +/* 5 */ +/***/ function(module, exports, __webpack_require__) { + + /** + * This object contains all possible options. It will check if the types are correct, if required if the option is one + * of the allowed values. + * + * __any__ means that the name of the property does not matter. + * __type__ is a required field for all objects and contains the allowed types of all objects + */ + 'use strict'; + + Object.defineProperty(exports, '__esModule', { + value: true + }); + var string = 'string'; + var boolean = 'boolean'; + var number = 'number'; + var array = 'array'; + var object = 'object'; // should only be in a __type__ property + var dom = 'dom'; + var any = 'any'; + + var allOptions = { + configure: { + enabled: { boolean: boolean }, + filter: { boolean: boolean, string: string, array: array, 'function': 'function' }, + container: { dom: dom }, + showButton: { boolean: boolean }, + __type__: { object: object, boolean: boolean, string: string, array: array, 'function': 'function' } + }, + edges: { + arrows: { + to: { enabled: { boolean: boolean }, scaleFactor: { number: number }, __type__: { object: object, boolean: boolean } }, + middle: { enabled: { boolean: boolean }, scaleFactor: { number: number }, __type__: { object: object, boolean: boolean } }, + from: { enabled: { boolean: boolean }, scaleFactor: { number: number }, __type__: { object: object, boolean: boolean } }, + __type__: { string: ['from', 'to', 'middle'], object: object } + }, + color: { + color: { string: string }, + highlight: { string: string }, + hover: { string: string }, + inherit: { string: ['from', 'to', 'both'], boolean: boolean }, + opacity: { number: number }, + __type__: { object: object, string: string } + }, + dashes: { boolean: boolean, array: array }, + font: { + color: { string: string }, + size: { number: number }, // px + face: { string: string }, + background: { string: string }, + strokeWidth: { number: number }, // px + strokeColor: { string: string }, + align: { string: ['horizontal', 'top', 'middle', 'bottom'] }, + __type__: { object: object, string: string } + }, + hidden: { boolean: boolean }, + hoverWidth: { 'function': 'function', number: number }, + label: { string: string, 'undefined': 'undefined' }, + length: { number: number, 'undefined': 'undefined' }, + physics: { boolean: boolean }, + scaling: { + min: { number: number }, + max: { number: number }, + label: { + enabled: { boolean: boolean }, + min: { number: number }, + max: { number: number }, + maxVisible: { number: number }, + drawThreshold: { number: number }, + __type__: { object: object, boolean: boolean } + }, + customScalingFunction: { 'function': 'function' }, + __type__: { object: object } + }, + selectionWidth: { 'function': 'function', number: number }, + selfReferenceSize: { number: number }, + shadow: { + enabled: { boolean: boolean }, + size: { number: number }, + x: { number: number }, + y: { number: number }, + __type__: { object: object, boolean: boolean } + }, + smooth: { + enabled: { boolean: boolean }, + type: { string: ['dynamic', 'continuous', 'discrete', 'diagonalCross', 'straightCross', 'horizontal', 'vertical', 'curvedCW', 'curvedCCW'] }, + roundness: { number: number }, + __type__: { object: object, boolean: boolean } + }, + title: { string: string, 'undefined': 'undefined' }, + width: { number: number }, + value: { number: number, 'undefined': 'undefined' }, + __type__: { object: object } + }, + groups: { + useDefaultGroups: { boolean: boolean }, + __any__: 'get from nodes, will be overwritten below', + __type__: { object: object } + }, + interaction: { + dragNodes: { boolean: boolean }, + dragView: { boolean: boolean }, + hideEdgesOnDrag: { boolean: boolean }, + hideNodesOnDrag: { boolean: boolean }, + hover: { boolean: boolean }, + keyboard: { + enabled: { boolean: boolean }, + speed: { x: { number: number }, y: { number: number }, zoom: { number: number }, __type__: { object: object } }, + bindToWindow: { boolean: boolean }, + __type__: { object: object, boolean: boolean } + }, + multiselect: { boolean: boolean }, + navigationButtons: { boolean: boolean }, + selectable: { boolean: boolean }, + selectConnectedEdges: { boolean: boolean }, + hoverConnectedEdges: { boolean: boolean }, + tooltipDelay: { number: number }, + zoomView: { boolean: boolean }, + __type__: { object: object } + }, + layout: { + randomSeed: { 'undefined': 'undefined', number: number }, + hierarchical: { + enabled: { boolean: boolean }, + levelSeparation: { number: number }, + direction: { string: ['UD', 'DU', 'LR', 'RL'] }, // UD, DU, LR, RL + sortMethod: { string: ['hubsize', 'directed'] }, // hubsize, directed + __type__: { object: object, boolean: boolean } + }, + __type__: { object: object } + }, + manipulation: { + enabled: { boolean: boolean }, + initiallyActive: { boolean: boolean }, + addNode: { boolean: boolean, 'function': 'function' }, + addEdge: { boolean: boolean, 'function': 'function' }, + editNode: { 'function': 'function' }, + editEdge: { boolean: boolean, 'function': 'function' }, + deleteNode: { boolean: boolean, 'function': 'function' }, + deleteEdge: { boolean: boolean, 'function': 'function' }, + controlNodeStyle: 'get from nodes, will be overwritten below', + __type__: { object: object, boolean: boolean } + }, + nodes: { + borderWidth: { number: number }, + borderWidthSelected: { number: number, 'undefined': 'undefined' }, + brokenImage: { string: string, 'undefined': 'undefined' }, + color: { + border: { string: string }, + background: { string: string }, + highlight: { + border: { string: string }, + background: { string: string }, + __type__: { object: object, string: string } + }, + hover: { + border: { string: string }, + background: { string: string }, + __type__: { object: object, string: string } + }, + __type__: { object: object, string: string } + }, + fixed: { + x: { boolean: boolean }, + y: { boolean: boolean }, + __type__: { object: object, boolean: boolean } + }, + font: { + color: { string: string }, + size: { number: number }, // px + face: { string: string }, + background: { string: string }, + strokeWidth: { number: number }, // px + strokeColor: { string: string }, + __type__: { object: object, string: string } + }, + group: { string: string, number: number, 'undefined': 'undefined' }, + hidden: { boolean: boolean }, + icon: { + face: { string: string }, + code: { string: string }, //'\uf007', + size: { number: number }, //50, + color: { string: string }, + __type__: { object: object } + }, + id: { string: string, number: number }, + image: { string: string, 'undefined': 'undefined' }, // --> URL + label: { string: string, 'undefined': 'undefined' }, + level: { number: number, 'undefined': 'undefined' }, + mass: { number: number }, + physics: { boolean: boolean }, + scaling: { + min: { number: number }, + max: { number: number }, + label: { + enabled: { boolean: boolean }, + min: { number: number }, + max: { number: number }, + maxVisible: { number: number }, + drawThreshold: { number: number }, + __type__: { object: object, boolean: boolean } + }, + customScalingFunction: { 'function': 'function' }, + __type__: { object: object } + }, + shadow: { + enabled: { boolean: boolean }, + size: { number: number }, + x: { number: number }, + y: { number: number }, + __type__: { object: object, boolean: boolean } + }, + shape: { string: ['ellipse', 'circle', 'database', 'box', 'text', 'image', 'circularImage', 'diamond', 'dot', 'star', 'triangle', 'triangleDown', 'square', 'icon'] }, + size: { number: number }, + title: { string: string, 'undefined': 'undefined' }, + value: { number: number, 'undefined': 'undefined' }, + x: { number: number }, + y: { number: number }, + __type__: { object: object } + }, + physics: { + enabled: { boolean: boolean }, + barnesHut: { + gravitationalConstant: { number: number }, + centralGravity: { number: number }, + springLength: { number: number }, + springConstant: { number: number }, + damping: { number: number }, + avoidOverlap: { number: number }, + __type__: { object: object } + }, + forceAtlas2Based: { + gravitationalConstant: { number: number }, + centralGravity: { number: number }, + springLength: { number: number }, + springConstant: { number: number }, + damping: { number: number }, + avoidOverlap: { number: number }, + __type__: { object: object } + }, + repulsion: { + centralGravity: { number: number }, + springLength: { number: number }, + springConstant: { number: number }, + nodeDistance: { number: number }, + damping: { number: number }, + __type__: { object: object } + }, + hierarchicalRepulsion: { + centralGravity: { number: number }, + springLength: { number: number }, + springConstant: { number: number }, + nodeDistance: { number: number }, + damping: { number: number }, + __type__: { object: object } + }, + maxVelocity: { number: number }, + minVelocity: { number: number }, // px/s + solver: { string: ['barnesHut', 'repulsion', 'hierarchicalRepulsion', 'forceAtlas2Based'] }, + stabilization: { + enabled: { boolean: boolean }, + iterations: { number: number }, // maximum number of iteration to stabilize + updateInterval: { number: number }, + onlyDynamicEdges: { boolean: boolean }, + fit: { boolean: boolean }, + __type__: { object: object, boolean: boolean } + }, + timestep: { number: number }, + __type__: { object: object, boolean: boolean } + }, + + //globals : + autoResize: { boolean: boolean }, + clickToUse: { boolean: boolean }, + locale: { string: string }, + locales: { + __any__: { object: object }, + __type__: { object: object } + }, + height: { string: string }, + width: { string: string }, + __type__: { object: object } + }; + + allOptions.groups.__any__ = allOptions.nodes; + allOptions.manipulation.controlNodeStyle = allOptions.nodes; + + var configureOptions = { + nodes: { + borderWidth: [1, 0, 10, 1], + borderWidthSelected: [2, 0, 10, 1], + color: { + border: ['color', '#2B7CE9'], + background: ['color', '#97C2FC'], + highlight: { + border: ['color', '#2B7CE9'], + background: ['color', '#D2E5FF'] + }, + hover: { + border: ['color', '#2B7CE9'], + background: ['color', '#D2E5FF'] + } + }, + fixed: { + x: false, + y: false + }, + font: { + color: ['color', '#343434'], + size: [14, 0, 100, 1], // px + face: ['arial', 'verdana', 'tahoma'], + background: ['color', 'none'], + strokeWidth: [0, 0, 50, 1], // px + strokeColor: ['color', '#ffffff'] + }, + //group: 'string', + hidden: false, + //icon: { + // face: 'string', //'FontAwesome', + // code: 'string', //'\uf007', + // size: [50, 0, 200, 1], //50, + // color: ['color','#2B7CE9'] //'#aa00ff' + //}, + //image: 'string', // --> URL + physics: true, + scaling: { + min: [10, 0, 200, 1], + max: [30, 0, 200, 1], + label: { + enabled: false, + min: [14, 0, 200, 1], + max: [30, 0, 200, 1], + maxVisible: [30, 0, 200, 1], + drawThreshold: [5, 0, 20, 1] + } + }, + shadow: { + enabled: false, + size: [10, 0, 20, 1], + x: [5, -30, 30, 1], + y: [5, -30, 30, 1] + }, + shape: ['ellipse', 'box', 'circle', 'database', 'diamond', 'dot', 'square', 'star', 'text', 'triangle', 'triangleDown'], + size: [25, 0, 200, 1] + }, + edges: { + arrows: { + to: { enabled: false, scaleFactor: [1, 0, 3, 0.05] }, // boolean / {arrowScaleFactor:1} / {enabled: false, arrowScaleFactor:1} + middle: { enabled: false, scaleFactor: [1, 0, 3, 0.05] }, + from: { enabled: false, scaleFactor: [1, 0, 3, 0.05] } + }, + color: { + color: ['color', '#848484'], + highlight: ['color', '#848484'], + hover: ['color', '#848484'], + inherit: ['from', 'to', 'both', true, false], + opacity: [1, 0, 1, 0.05] + }, + dashes: false, + font: { + color: ['color', '#343434'], + size: [14, 0, 100, 1], // px + face: ['arial', 'verdana', 'tahoma'], + background: ['color', 'none'], + strokeWidth: [2, 0, 50, 1], // px + strokeColor: ['color', '#ffffff'], + align: ['horizontal', 'top', 'middle', 'bottom'] + }, + hidden: false, + hoverWidth: [1.5, 0, 5, 0.1], + physics: true, + scaling: { + min: [1, 0, 100, 1], + max: [15, 0, 100, 1], + label: { + enabled: true, + min: [14, 0, 200, 1], + max: [30, 0, 200, 1], + maxVisible: [30, 0, 200, 1], + drawThreshold: [5, 0, 20, 1] + } + }, + selectionWidth: [1.5, 0, 5, 0.1], + selfReferenceSize: [20, 0, 200, 1], + shadow: { + enabled: false, + size: [10, 0, 20, 1], + x: [5, -30, 30, 1], + y: [5, -30, 30, 1] + }, + smooth: { + enabled: true, + type: ['dynamic', 'continuous', 'discrete', 'diagonalCross', 'straightCross', 'horizontal', 'vertical', 'curvedCW', 'curvedCCW'], + roundness: [0.5, 0, 1, 0.05] + }, + width: [1, 0, 30, 1] + }, + layout: { + //randomSeed: [0, 0, 500, 1], + hierarchical: { + enabled: false, + levelSeparation: [150, 20, 500, 5], + direction: ['UD', 'DU', 'LR', 'RL'], // UD, DU, LR, RL + sortMethod: ['hubsize', 'directed'] // hubsize, directed + } + }, + interaction: { + dragNodes: true, + dragView: true, + hideEdgesOnDrag: false, + hideNodesOnDrag: false, + hover: false, + keyboard: { + enabled: false, + speed: { x: [10, 0, 40, 1], y: [10, 0, 40, 1], zoom: [0.02, 0, 0.1, 0.005] }, + bindToWindow: true + }, + multiselect: false, + navigationButtons: false, + selectable: true, + selectConnectedEdges: true, + hoverConnectedEdges: true, + tooltipDelay: [300, 0, 1000, 25], + zoomView: true + }, + manipulation: { + enabled: false, + initiallyActive: false + }, + physics: { + enabled: true, + barnesHut: { + //theta: [0.5, 0.1, 1, 0.05], + gravitationalConstant: [-2000, -30000, 0, 50], + centralGravity: [0.3, 0, 10, 0.05], + springLength: [95, 0, 500, 5], + springConstant: [0.04, 0, 1.2, 0.005], + damping: [0.09, 0, 1, 0.01], + avoidOverlap: [0, 0, 1, 0.01] + }, + forceAtlas2Based: { + //theta: [0.5, 0.1, 1, 0.05], + gravitationalConstant: [-50, -500, 0, 1], + centralGravity: [0.01, 0, 1, 0.005], + springLength: [95, 0, 500, 5], + springConstant: [0.08, 0, 1.2, 0.005], + damping: [0.4, 0, 1, 0.01], + avoidOverlap: [0, 0, 1, 0.01] + }, + repulsion: { + centralGravity: [0.2, 0, 10, 0.05], + springLength: [200, 0, 500, 5], + springConstant: [0.05, 0, 1.2, 0.005], + nodeDistance: [100, 0, 500, 5], + damping: [0.09, 0, 1, 0.01] + }, + hierarchicalRepulsion: { + centralGravity: [0.2, 0, 10, 0.05], + springLength: [100, 0, 500, 5], + springConstant: [0.01, 0, 1.2, 0.005], + nodeDistance: [120, 0, 500, 5], + damping: [0.09, 0, 1, 0.01] + }, + maxVelocity: [50, 0, 150, 1], + minVelocity: [0.1, 0.01, 0.5, 0.01], + solver: ['barnesHut', 'forceAtlas2Based', 'repulsion', 'hierarchicalRepulsion'], + timestep: [0.5, 0.01, 1, 0.01] + }, + global: { + locale: ['en', 'nl'] + } + }; + + exports.allOptions = allOptions; + exports.configureOptions = configureOptions; + +/***/ }, +/* 6 */ +/***/ function(module, exports, __webpack_require__) { + + // first check if moment.js is already loaded in the browser window, if so, + // use this instance. Else, load via commonjs. + 'use strict'; + + module.exports = typeof window !== 'undefined' && window['moment'] || __webpack_require__(7); + +/***/ }, +/* 7 */ +/***/ function(module, exports, __webpack_require__) { + + /* WEBPACK VAR INJECTION */(function(module) {//! moment.js + //! version : 2.10.3 + //! authors : Tim Wood, Iskren Chernev, Moment.js contributors + //! license : MIT + //! momentjs.com + + (function (global, factory) { + true ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + global.moment = factory() + }(this, function () { 'use strict'; + + var hookCallback; + + function utils_hooks__hooks () { + return hookCallback.apply(null, arguments); + } + + // This is done to register the method called with moment() + // without creating circular dependencies. + function setHookCallback (callback) { + hookCallback = callback; + } + + function isArray(input) { + return Object.prototype.toString.call(input) === '[object Array]'; + } + + function isDate(input) { + return input instanceof Date || Object.prototype.toString.call(input) === '[object Date]'; + } + + function map(arr, fn) { + var res = [], i; + for (i = 0; i < arr.length; ++i) { + res.push(fn(arr[i], i)); + } + return res; + } + + function hasOwnProp(a, b) { + return Object.prototype.hasOwnProperty.call(a, b); + } + + function extend(a, b) { + for (var i in b) { + if (hasOwnProp(b, i)) { + a[i] = b[i]; + } + } + + if (hasOwnProp(b, 'toString')) { + a.toString = b.toString; + } + + if (hasOwnProp(b, 'valueOf')) { + a.valueOf = b.valueOf; + } + + return a; + } + + function create_utc__createUTC (input, format, locale, strict) { + return createLocalOrUTC(input, format, locale, strict, true).utc(); + } + + function defaultParsingFlags() { + // We need to deep clone this object. + return { + empty : false, + unusedTokens : [], + unusedInput : [], + overflow : -2, + charsLeftOver : 0, + nullInput : false, + invalidMonth : null, + invalidFormat : false, + userInvalidated : false, + iso : false + }; + } + + function getParsingFlags(m) { + if (m._pf == null) { + m._pf = defaultParsingFlags(); + } + return m._pf; + } + + function valid__isValid(m) { + if (m._isValid == null) { + var flags = getParsingFlags(m); + m._isValid = !isNaN(m._d.getTime()) && + flags.overflow < 0 && + !flags.empty && + !flags.invalidMonth && + !flags.nullInput && + !flags.invalidFormat && + !flags.userInvalidated; + + if (m._strict) { + m._isValid = m._isValid && + flags.charsLeftOver === 0 && + flags.unusedTokens.length === 0 && + flags.bigHour === undefined; + } + } + return m._isValid; + } + + function valid__createInvalid (flags) { + var m = create_utc__createUTC(NaN); + if (flags != null) { + extend(getParsingFlags(m), flags); + } + else { + getParsingFlags(m).userInvalidated = true; + } + + return m; + } + + var momentProperties = utils_hooks__hooks.momentProperties = []; + + function copyConfig(to, from) { + var i, prop, val; + + if (typeof from._isAMomentObject !== 'undefined') { + to._isAMomentObject = from._isAMomentObject; + } + if (typeof from._i !== 'undefined') { + to._i = from._i; + } + if (typeof from._f !== 'undefined') { + to._f = from._f; + } + if (typeof from._l !== 'undefined') { + to._l = from._l; + } + if (typeof from._strict !== 'undefined') { + to._strict = from._strict; + } + if (typeof from._tzm !== 'undefined') { + to._tzm = from._tzm; + } + if (typeof from._isUTC !== 'undefined') { + to._isUTC = from._isUTC; + } + if (typeof from._offset !== 'undefined') { + to._offset = from._offset; + } + if (typeof from._pf !== 'undefined') { + to._pf = getParsingFlags(from); + } + if (typeof from._locale !== 'undefined') { + to._locale = from._locale; + } + + if (momentProperties.length > 0) { + for (i in momentProperties) { + prop = momentProperties[i]; + val = from[prop]; + if (typeof val !== 'undefined') { + to[prop] = val; + } + } + } + + return to; + } + + var updateInProgress = false; + + // Moment prototype object + function Moment(config) { + copyConfig(this, config); + this._d = new Date(+config._d); + // Prevent infinite loop in case updateOffset creates new moment + // objects. + if (updateInProgress === false) { + updateInProgress = true; + utils_hooks__hooks.updateOffset(this); + updateInProgress = false; + } + } + + function isMoment (obj) { + return obj instanceof Moment || (obj != null && obj._isAMomentObject != null); + } + + function toInt(argumentForCoercion) { + var coercedNumber = +argumentForCoercion, + value = 0; + + if (coercedNumber !== 0 && isFinite(coercedNumber)) { + if (coercedNumber >= 0) { + value = Math.floor(coercedNumber); + } else { + value = Math.ceil(coercedNumber); + } + } + + return value; + } + + function compareArrays(array1, array2, dontConvert) { + var len = Math.min(array1.length, array2.length), + lengthDiff = Math.abs(array1.length - array2.length), + diffs = 0, + i; + for (i = 0; i < len; i++) { + if ((dontConvert && array1[i] !== array2[i]) || + (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) { + diffs++; + } + } + return diffs + lengthDiff; + } + + function Locale() { + } + + var locales = {}; + var globalLocale; + + function normalizeLocale(key) { + return key ? key.toLowerCase().replace('_', '-') : key; + } + + // pick the locale from the array + // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each + // substring from most specific to least, but move to the next array item if it's a more specific variant than the current root + function chooseLocale(names) { + var i = 0, j, next, locale, split; + + while (i < names.length) { + split = normalizeLocale(names[i]).split('-'); + j = split.length; + next = normalizeLocale(names[i + 1]); + next = next ? next.split('-') : null; + while (j > 0) { + locale = loadLocale(split.slice(0, j).join('-')); + if (locale) { + return locale; + } + if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) { + //the next array item is better than a shallower substring of this one + break; + } + j--; + } + i++; + } + return null; + } + + function loadLocale(name) { + var oldLocale = null; + // TODO: Find a better way to register and load all the locales in Node + if (!locales[name] && typeof module !== 'undefined' && + module && module.exports) { + try { + oldLocale = globalLocale._abbr; + !(function webpackMissingModule() { var e = new Error("Cannot find module \"./locale\""); e.code = 'MODULE_NOT_FOUND'; throw e; }()); + // because defineLocale currently also sets the global locale, we + // want to undo that for lazy loaded locales + locale_locales__getSetGlobalLocale(oldLocale); + } catch (e) { } + } + return locales[name]; + } + + // This function will load locale and then set the global locale. If + // no arguments are passed in, it will simply return the current global + // locale key. + function locale_locales__getSetGlobalLocale (key, values) { + var data; + if (key) { + if (typeof values === 'undefined') { + data = locale_locales__getLocale(key); + } + else { + data = defineLocale(key, values); + } + + if (data) { + // moment.duration._locale = moment._locale = data; + globalLocale = data; + } + } + + return globalLocale._abbr; + } + + function defineLocale (name, values) { + if (values !== null) { + values.abbr = name; + if (!locales[name]) { + locales[name] = new Locale(); + } + locales[name].set(values); + + // backwards compat for now: also set the locale + locale_locales__getSetGlobalLocale(name); + + return locales[name]; + } else { + // useful for testing + delete locales[name]; + return null; + } + } + + // returns locale data + function locale_locales__getLocale (key) { + var locale; + + if (key && key._locale && key._locale._abbr) { + key = key._locale._abbr; + } + + if (!key) { + return globalLocale; + } + + if (!isArray(key)) { + //short-circuit everything else + locale = loadLocale(key); + if (locale) { + return locale; + } + key = [key]; + } + + return chooseLocale(key); + } + + var aliases = {}; + + function addUnitAlias (unit, shorthand) { + var lowerCase = unit.toLowerCase(); + aliases[lowerCase] = aliases[lowerCase + 's'] = aliases[shorthand] = unit; + } + + function normalizeUnits(units) { + return typeof units === 'string' ? aliases[units] || aliases[units.toLowerCase()] : undefined; + } + + function normalizeObjectUnits(inputObject) { + var normalizedInput = {}, + normalizedProp, + prop; + + for (prop in inputObject) { + if (hasOwnProp(inputObject, prop)) { + normalizedProp = normalizeUnits(prop); + if (normalizedProp) { + normalizedInput[normalizedProp] = inputObject[prop]; + } + } + } + + return normalizedInput; + } + + function makeGetSet (unit, keepTime) { + return function (value) { + if (value != null) { + get_set__set(this, unit, value); + utils_hooks__hooks.updateOffset(this, keepTime); + return this; + } else { + return get_set__get(this, unit); + } + }; + } + + function get_set__get (mom, unit) { + return mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit](); + } + + function get_set__set (mom, unit, value) { + return mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value); + } + + // MOMENTS + + function getSet (units, value) { + var unit; + if (typeof units === 'object') { + for (unit in units) { + this.set(unit, units[unit]); + } + } else { + units = normalizeUnits(units); + if (typeof this[units] === 'function') { + return this[units](value); + } + } + return this; + } + + function zeroFill(number, targetLength, forceSign) { + var output = '' + Math.abs(number), + sign = number >= 0; + + while (output.length < targetLength) { + output = '0' + output; + } + return (sign ? (forceSign ? '+' : '') : '-') + output; + } + + var formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Q|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|x|X|zz?|ZZ?|.)/g; + + var localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g; + + var formatFunctions = {}; + + var formatTokenFunctions = {}; + + // token: 'M' + // padded: ['MM', 2] + // ordinal: 'Mo' + // callback: function () { this.month() + 1 } + function addFormatToken (token, padded, ordinal, callback) { + var func = callback; + if (typeof callback === 'string') { + func = function () { + return this[callback](); + }; + } + if (token) { + formatTokenFunctions[token] = func; + } + if (padded) { + formatTokenFunctions[padded[0]] = function () { + return zeroFill(func.apply(this, arguments), padded[1], padded[2]); + }; + } + if (ordinal) { + formatTokenFunctions[ordinal] = function () { + return this.localeData().ordinal(func.apply(this, arguments), token); + }; + } + } + + function removeFormattingTokens(input) { + if (input.match(/\[[\s\S]/)) { + return input.replace(/^\[|\]$/g, ''); + } + return input.replace(/\\/g, ''); + } + + function makeFormatFunction(format) { + var array = format.match(formattingTokens), i, length; + + for (i = 0, length = array.length; i < length; i++) { + if (formatTokenFunctions[array[i]]) { + array[i] = formatTokenFunctions[array[i]]; + } else { + array[i] = removeFormattingTokens(array[i]); + } + } + + return function (mom) { + var output = ''; + for (i = 0; i < length; i++) { + output += array[i] instanceof Function ? array[i].call(mom, format) : array[i]; + } + return output; + }; + } + + // format date using native date object + function formatMoment(m, format) { + if (!m.isValid()) { + return m.localeData().invalidDate(); + } + + format = expandFormat(format, m.localeData()); + + if (!formatFunctions[format]) { + formatFunctions[format] = makeFormatFunction(format); + } + + return formatFunctions[format](m); + } + + function expandFormat(format, locale) { + var i = 5; + + function replaceLongDateFormatTokens(input) { + return locale.longDateFormat(input) || input; + } + + localFormattingTokens.lastIndex = 0; + while (i >= 0 && localFormattingTokens.test(format)) { + format = format.replace(localFormattingTokens, replaceLongDateFormatTokens); + localFormattingTokens.lastIndex = 0; + i -= 1; + } + + return format; + } + + var match1 = /\d/; // 0 - 9 + var match2 = /\d\d/; // 00 - 99 + var match3 = /\d{3}/; // 000 - 999 + var match4 = /\d{4}/; // 0000 - 9999 + var match6 = /[+-]?\d{6}/; // -999999 - 999999 + var match1to2 = /\d\d?/; // 0 - 99 + var match1to3 = /\d{1,3}/; // 0 - 999 + var match1to4 = /\d{1,4}/; // 0 - 9999 + var match1to6 = /[+-]?\d{1,6}/; // -999999 - 999999 + + var matchUnsigned = /\d+/; // 0 - inf + var matchSigned = /[+-]?\d+/; // -inf - inf + + var matchOffset = /Z|[+-]\d\d:?\d\d/gi; // +00:00 -00:00 +0000 -0000 or Z + + var matchTimestamp = /[+-]?\d+(\.\d{1,3})?/; // 123456789 123456789.123 + + // any word (or two) characters or numbers including two/three word month in arabic. + var matchWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i; + + var regexes = {}; + + function addRegexToken (token, regex, strictRegex) { + regexes[token] = typeof regex === 'function' ? regex : function (isStrict) { + return (isStrict && strictRegex) ? strictRegex : regex; + }; + } + + function getParseRegexForToken (token, config) { + if (!hasOwnProp(regexes, token)) { + return new RegExp(unescapeFormat(token)); + } + + return regexes[token](config._strict, config._locale); + } + + // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript + function unescapeFormat(s) { + return s.replace('\\', '').replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) { + return p1 || p2 || p3 || p4; + }).replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); + } + + var tokens = {}; + + function addParseToken (token, callback) { + var i, func = callback; + if (typeof token === 'string') { + token = [token]; + } + if (typeof callback === 'number') { + func = function (input, array) { + array[callback] = toInt(input); + }; + } + for (i = 0; i < token.length; i++) { + tokens[token[i]] = func; + } + } + + function addWeekParseToken (token, callback) { + addParseToken(token, function (input, array, config, token) { + config._w = config._w || {}; + callback(input, config._w, config, token); + }); + } + + function addTimeToArrayFromToken(token, input, config) { + if (input != null && hasOwnProp(tokens, token)) { + tokens[token](input, config._a, config, token); + } + } + + var YEAR = 0; + var MONTH = 1; + var DATE = 2; + var HOUR = 3; + var MINUTE = 4; + var SECOND = 5; + var MILLISECOND = 6; + + function daysInMonth(year, month) { + return new Date(Date.UTC(year, month + 1, 0)).getUTCDate(); + } + + // FORMATTING + + addFormatToken('M', ['MM', 2], 'Mo', function () { + return this.month() + 1; + }); + + addFormatToken('MMM', 0, 0, function (format) { + return this.localeData().monthsShort(this, format); + }); + + addFormatToken('MMMM', 0, 0, function (format) { + return this.localeData().months(this, format); + }); + + // ALIASES + + addUnitAlias('month', 'M'); + + // PARSING + + addRegexToken('M', match1to2); + addRegexToken('MM', match1to2, match2); + addRegexToken('MMM', matchWord); + addRegexToken('MMMM', matchWord); + + addParseToken(['M', 'MM'], function (input, array) { + array[MONTH] = toInt(input) - 1; + }); + + addParseToken(['MMM', 'MMMM'], function (input, array, config, token) { + var month = config._locale.monthsParse(input, token, config._strict); + // if we didn't find a month name, mark the date as invalid. + if (month != null) { + array[MONTH] = month; + } else { + getParsingFlags(config).invalidMonth = input; + } + }); + + // LOCALES + + var defaultLocaleMonths = 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'); + function localeMonths (m) { + return this._months[m.month()]; + } + + var defaultLocaleMonthsShort = 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'); + function localeMonthsShort (m) { + return this._monthsShort[m.month()]; + } + + function localeMonthsParse (monthName, format, strict) { + var i, mom, regex; + + if (!this._monthsParse) { + this._monthsParse = []; + this._longMonthsParse = []; + this._shortMonthsParse = []; + } + + for (i = 0; i < 12; i++) { + // make the regex if we don't have it already + mom = create_utc__createUTC([2000, i]); + if (strict && !this._longMonthsParse[i]) { + this._longMonthsParse[i] = new RegExp('^' + this.months(mom, '').replace('.', '') + '$', 'i'); + this._shortMonthsParse[i] = new RegExp('^' + this.monthsShort(mom, '').replace('.', '') + '$', 'i'); + } + if (!strict && !this._monthsParse[i]) { + regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, ''); + this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i'); + } + // test the regex + if (strict && format === 'MMMM' && this._longMonthsParse[i].test(monthName)) { + return i; + } else if (strict && format === 'MMM' && this._shortMonthsParse[i].test(monthName)) { + return i; + } else if (!strict && this._monthsParse[i].test(monthName)) { + return i; + } + } + } + + // MOMENTS + + function setMonth (mom, value) { + var dayOfMonth; + + // TODO: Move this out of here! + if (typeof value === 'string') { + value = mom.localeData().monthsParse(value); + // TODO: Another silent failure? + if (typeof value !== 'number') { + return mom; + } + } + + dayOfMonth = Math.min(mom.date(), daysInMonth(mom.year(), value)); + mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth); + return mom; + } + + function getSetMonth (value) { + if (value != null) { + setMonth(this, value); + utils_hooks__hooks.updateOffset(this, true); + return this; + } else { + return get_set__get(this, 'Month'); + } + } + + function getDaysInMonth () { + return daysInMonth(this.year(), this.month()); + } + + function checkOverflow (m) { + var overflow; + var a = m._a; + + if (a && getParsingFlags(m).overflow === -2) { + overflow = + a[MONTH] < 0 || a[MONTH] > 11 ? MONTH : + a[DATE] < 1 || a[DATE] > daysInMonth(a[YEAR], a[MONTH]) ? DATE : + a[HOUR] < 0 || a[HOUR] > 24 || (a[HOUR] === 24 && (a[MINUTE] !== 0 || a[SECOND] !== 0 || a[MILLISECOND] !== 0)) ? HOUR : + a[MINUTE] < 0 || a[MINUTE] > 59 ? MINUTE : + a[SECOND] < 0 || a[SECOND] > 59 ? SECOND : + a[MILLISECOND] < 0 || a[MILLISECOND] > 999 ? MILLISECOND : + -1; + + if (getParsingFlags(m)._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) { + overflow = DATE; + } + + getParsingFlags(m).overflow = overflow; + } + + return m; + } + + function warn(msg) { + if (utils_hooks__hooks.suppressDeprecationWarnings === false && typeof console !== 'undefined' && console.warn) { + console.warn('Deprecation warning: ' + msg); + } + } + + function deprecate(msg, fn) { + var firstTime = true, + msgWithStack = msg + '\n' + (new Error()).stack; + + return extend(function () { + if (firstTime) { + warn(msgWithStack); + firstTime = false; + } + return fn.apply(this, arguments); + }, fn); + } + + var deprecations = {}; + + function deprecateSimple(name, msg) { + if (!deprecations[name]) { + warn(msg); + deprecations[name] = true; + } + } + + utils_hooks__hooks.suppressDeprecationWarnings = false; + + var from_string__isoRegex = /^\s*(?:[+-]\d{6}|\d{4})-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/; + + var isoDates = [ + ['YYYYYY-MM-DD', /[+-]\d{6}-\d{2}-\d{2}/], + ['YYYY-MM-DD', /\d{4}-\d{2}-\d{2}/], + ['GGGG-[W]WW-E', /\d{4}-W\d{2}-\d/], + ['GGGG-[W]WW', /\d{4}-W\d{2}/], + ['YYYY-DDD', /\d{4}-\d{3}/] + ]; + + // iso time formats and regexes + var isoTimes = [ + ['HH:mm:ss.SSSS', /(T| )\d\d:\d\d:\d\d\.\d+/], + ['HH:mm:ss', /(T| )\d\d:\d\d:\d\d/], + ['HH:mm', /(T| )\d\d:\d\d/], + ['HH', /(T| )\d\d/] + ]; + + var aspNetJsonRegex = /^\/?Date\((\-?\d+)/i; + + // date from iso format + function configFromISO(config) { + var i, l, + string = config._i, + match = from_string__isoRegex.exec(string); + + if (match) { + getParsingFlags(config).iso = true; + for (i = 0, l = isoDates.length; i < l; i++) { + if (isoDates[i][1].exec(string)) { + // match[5] should be 'T' or undefined + config._f = isoDates[i][0] + (match[6] || ' '); + break; + } + } + for (i = 0, l = isoTimes.length; i < l; i++) { + if (isoTimes[i][1].exec(string)) { + config._f += isoTimes[i][0]; + break; + } + } + if (string.match(matchOffset)) { + config._f += 'Z'; + } + configFromStringAndFormat(config); + } else { + config._isValid = false; + } + } + + // date from iso format or fallback + function configFromString(config) { + var matched = aspNetJsonRegex.exec(config._i); + + if (matched !== null) { + config._d = new Date(+matched[1]); + return; + } + + configFromISO(config); + if (config._isValid === false) { + delete config._isValid; + utils_hooks__hooks.createFromInputFallback(config); + } + } + + utils_hooks__hooks.createFromInputFallback = deprecate( + 'moment construction falls back to js Date. This is ' + + 'discouraged and will be removed in upcoming major ' + + 'release. Please refer to ' + + 'https://github.com/moment/moment/issues/1407 for more info.', + function (config) { + config._d = new Date(config._i + (config._useUTC ? ' UTC' : '')); + } + ); + + function createDate (y, m, d, h, M, s, ms) { + //can't just apply() to create a date: + //http://stackoverflow.com/questions/181348/instantiating-a-javascript-object-by-calling-prototype-constructor-apply + var date = new Date(y, m, d, h, M, s, ms); + + //the date constructor doesn't accept years < 1970 + if (y < 1970) { + date.setFullYear(y); + } + return date; + } + + function createUTCDate (y) { + var date = new Date(Date.UTC.apply(null, arguments)); + if (y < 1970) { + date.setUTCFullYear(y); + } + return date; + } + + addFormatToken(0, ['YY', 2], 0, function () { + return this.year() % 100; + }); + + addFormatToken(0, ['YYYY', 4], 0, 'year'); + addFormatToken(0, ['YYYYY', 5], 0, 'year'); + addFormatToken(0, ['YYYYYY', 6, true], 0, 'year'); + + // ALIASES + + addUnitAlias('year', 'y'); + + // PARSING + + addRegexToken('Y', matchSigned); + addRegexToken('YY', match1to2, match2); + addRegexToken('YYYY', match1to4, match4); + addRegexToken('YYYYY', match1to6, match6); + addRegexToken('YYYYYY', match1to6, match6); + + addParseToken(['YYYY', 'YYYYY', 'YYYYYY'], YEAR); + addParseToken('YY', function (input, array) { + array[YEAR] = utils_hooks__hooks.parseTwoDigitYear(input); + }); + + // HELPERS + + function daysInYear(year) { + return isLeapYear(year) ? 366 : 365; + } + + function isLeapYear(year) { + return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; + } + + // HOOKS utils_hooks__hooks.parseTwoDigitYear = function (input) { return toInt(input) + (toInt(input) > 68 ? 1900 : 2000); @@ -5409,10 +6970,10 @@ return /******/ (function(modules) { // webpackBootstrap return _moment; })); - /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(7)(module))) + /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(8)(module))) /***/ }, -/* 7 */ +/* 8 */ /***/ function(module, exports, __webpack_require__) { module.exports = function(module) { @@ -5428,7 +6989,7 @@ return /******/ (function(modules) { // webpackBootstrap /***/ }, -/* 8 */ +/* 9 */ /***/ function(module, exports, __webpack_require__) { /* WEBPACK VAR INJECTION */(function(global) {'use strict'; @@ -5644,7 +7205,7 @@ return /******/ (function(modules) { // webpackBootstrap /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()))) /***/ }, -/* 9 */ +/* 10 */ /***/ function(module, exports, __webpack_require__) { // DOM utility methods @@ -5846,13 +7407,13 @@ return /******/ (function(modules) { // webpackBootstrap }; /***/ }, -/* 10 */ +/* 11 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var util = __webpack_require__(2); - var Queue = __webpack_require__(11); + var util = __webpack_require__(4); + var Queue = __webpack_require__(12); /** * DataSet @@ -6741,7 +8302,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = DataSet; /***/ }, -/* 11 */ +/* 12 */ /***/ function(module, exports, __webpack_require__) { /** @@ -6946,13 +8507,13 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = Queue; /***/ }, -/* 12 */ +/* 13 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var util = __webpack_require__(2); - var DataSet = __webpack_require__(10); + var util = __webpack_require__(4); + var DataSet = __webpack_require__(11); /** * DataView @@ -7294,21 +8855,21 @@ return /******/ (function(modules) { // webpackBootstrap // nothing interesting for me :-( /***/ }, -/* 13 */ +/* 14 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var Emitter = __webpack_require__(15); - var DataSet = __webpack_require__(10); - var DataView = __webpack_require__(12); - var util = __webpack_require__(2); - var Point3d = __webpack_require__(16); - var Point2d = __webpack_require__(14); - var Camera = __webpack_require__(17); - var Filter = __webpack_require__(18); - var Slider = __webpack_require__(19); - var StepNumber = __webpack_require__(20); + var Emitter = __webpack_require__(16); + var DataSet = __webpack_require__(11); + var DataView = __webpack_require__(13); + var util = __webpack_require__(4); + var Point3d = __webpack_require__(17); + var Point2d = __webpack_require__(15); + var Camera = __webpack_require__(18); + var Filter = __webpack_require__(19); + var Slider = __webpack_require__(20); + var StepNumber = __webpack_require__(21); /** * @constructor Graph3d @@ -9512,7 +11073,7 @@ return /******/ (function(modules) { // webpackBootstrap // use use defaults /***/ }, -/* 14 */ +/* 15 */ /***/ function(module, exports, __webpack_require__) { /** @@ -9530,7 +11091,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = Point2d; /***/ }, -/* 15 */ +/* 16 */ /***/ function(module, exports, __webpack_require__) { @@ -9700,7 +11261,7 @@ return /******/ (function(modules) { // webpackBootstrap /***/ }, -/* 16 */ +/* 17 */ /***/ function(module, exports, __webpack_require__) { /** @@ -9783,12 +11344,12 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = Point3d; /***/ }, -/* 17 */ +/* 18 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var Point3d = __webpack_require__(16); + var Point3d = __webpack_require__(17); /** * @class Camera @@ -9924,12 +11485,12 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = Camera; /***/ }, -/* 18 */ +/* 19 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var DataView = __webpack_require__(12); + var DataView = __webpack_require__(13); /** * @class Filter @@ -10135,12 +11696,12 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = Filter; /***/ }, -/* 19 */ +/* 20 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var util = __webpack_require__(2); + var util = __webpack_require__(4); /** * @constructor Slider @@ -10483,7 +12044,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = Slider; /***/ }, -/* 20 */ +/* 21 */ /***/ function(module, exports, __webpack_require__) { /** @@ -10627,28 +12188,28 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = StepNumber; /***/ }, -/* 21 */ +/* 22 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var Emitter = __webpack_require__(15); - var Hammer = __webpack_require__(25); - var util = __webpack_require__(2); - var DataSet = __webpack_require__(10); - var DataView = __webpack_require__(12); - var Range = __webpack_require__(29); - var Core = __webpack_require__(32); - var TimeAxis = __webpack_require__(43); - var CurrentTime = __webpack_require__(22); - var CustomTime = __webpack_require__(46); - var ItemSet = __webpack_require__(33); - - var Configurator = __webpack_require__(47); - var Validator = __webpack_require__(49)['default']; - var printStyle = __webpack_require__(49).printStyle; - var allOptions = __webpack_require__(50).allOptions; - var configureOptions = __webpack_require__(50).configureOptions; + var Emitter = __webpack_require__(16); + var Hammer = __webpack_require__(26); + var util = __webpack_require__(4); + var DataSet = __webpack_require__(11); + var DataView = __webpack_require__(13); + var Range = __webpack_require__(30); + var Core = __webpack_require__(33); + var TimeAxis = __webpack_require__(44); + var CurrentTime = __webpack_require__(23); + var CustomTime = __webpack_require__(47); + var ItemSet = __webpack_require__(34); + + var Configurator = __webpack_require__(48); + var Validator = __webpack_require__(50)['default']; + var printStyle = __webpack_require__(50).printStyle; + var allOptions = __webpack_require__(51).allOptions; + var configureOptions = __webpack_require__(51).configureOptions; /** * Create a timeline visualization @@ -11151,15 +12712,15 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = Timeline; /***/ }, -/* 22 */ +/* 23 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var util = __webpack_require__(2); - var Component = __webpack_require__(23); - var moment = __webpack_require__(5); - var locales = __webpack_require__(24); + var util = __webpack_require__(4); + var Component = __webpack_require__(24); + var moment = __webpack_require__(6); + var locales = __webpack_require__(25); /** * A current time bar @@ -11327,7 +12888,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = CurrentTime; /***/ }, -/* 23 */ +/* 24 */ /***/ function(module, exports, __webpack_require__) { /** @@ -11387,7 +12948,7 @@ return /******/ (function(modules) { // webpackBootstrap // should be implemented by the component /***/ }, -/* 24 */ +/* 25 */ /***/ function(module, exports, __webpack_require__) { // English @@ -11409,7 +12970,7 @@ return /******/ (function(modules) { // webpackBootstrap exports['nl_BE'] = exports['nl']; /***/ }, -/* 25 */ +/* 26 */ /***/ function(module, exports, __webpack_require__) { // Only load hammer.js when in a browser environment @@ -11417,8 +12978,8 @@ return /******/ (function(modules) { // webpackBootstrap 'use strict'; if (typeof window !== 'undefined') { - var propagating = __webpack_require__(26); - var Hammer = window['Hammer'] || __webpack_require__(27); + var propagating = __webpack_require__(27); + var Hammer = window['Hammer'] || __webpack_require__(28); module.exports = propagating(Hammer, { preventDefault: 'mouse' }); @@ -11429,7 +12990,7 @@ return /******/ (function(modules) { // webpackBootstrap } /***/ }, -/* 26 */ +/* 27 */ /***/ function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;'use strict'; @@ -11657,7 +13218,7 @@ return /******/ (function(modules) { // webpackBootstrap /***/ }, -/* 27 */ +/* 28 */ /***/ function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_RESULT__;/*! Hammer.JS - v2.0.4 - 2014-09-28 @@ -14112,7 +15673,7 @@ return /******/ (function(modules) { // webpackBootstrap prefixed: prefixed }); - if ("function" == TYPE_FUNCTION && __webpack_require__(28)) { + if ("function" == TYPE_FUNCTION && __webpack_require__(29)) { !(__WEBPACK_AMD_DEFINE_RESULT__ = function() { return Hammer; }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); @@ -14126,7 +15687,7 @@ return /******/ (function(modules) { // webpackBootstrap /***/ }, -/* 28 */ +/* 29 */ /***/ function(module, exports, __webpack_require__) { /* WEBPACK VAR INJECTION */(function(__webpack_amd_options__) {module.exports = __webpack_amd_options__; @@ -14134,16 +15695,16 @@ return /******/ (function(modules) { // webpackBootstrap /* WEBPACK VAR INJECTION */}.call(exports, {})) /***/ }, -/* 29 */ +/* 30 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var util = __webpack_require__(2); - var hammerUtil = __webpack_require__(30); - var moment = __webpack_require__(5); - var Component = __webpack_require__(23); - var DateUtil = __webpack_require__(31); + var util = __webpack_require__(4); + var hammerUtil = __webpack_require__(31); + var moment = __webpack_require__(6); + var Component = __webpack_require__(24); + var DateUtil = __webpack_require__(32); /** * @constructor Range @@ -14810,12 +16371,12 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = Range; /***/ }, -/* 30 */ +/* 31 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var Hammer = __webpack_require__(25); + var Hammer = __webpack_require__(26); /** * Register a touch event, taking place before a gesture @@ -14882,12 +16443,12 @@ return /******/ (function(modules) { // webpackBootstrap exports.offRelease = exports.offTouch; /***/ }, -/* 31 */ +/* 32 */ /***/ function(module, exports, __webpack_require__) { "use strict"; - var moment = __webpack_require__(5); + var moment = __webpack_require__(6); /** * used in Core to convert the options into a volatile variable @@ -15342,23 +16903,23 @@ return /******/ (function(modules) { // webpackBootstrap }; /***/ }, -/* 32 */ +/* 33 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var Emitter = __webpack_require__(15); - var Hammer = __webpack_require__(25); - var hammerUtil = __webpack_require__(30); - var util = __webpack_require__(2); - var DataSet = __webpack_require__(10); - var DataView = __webpack_require__(12); - var Range = __webpack_require__(29); - var ItemSet = __webpack_require__(33); - var TimeAxis = __webpack_require__(43); - var Activator = __webpack_require__(44); - var DateUtil = __webpack_require__(31); - var CustomTime = __webpack_require__(46); + var Emitter = __webpack_require__(16); + var Hammer = __webpack_require__(26); + var hammerUtil = __webpack_require__(31); + var util = __webpack_require__(4); + var DataSet = __webpack_require__(11); + var DataView = __webpack_require__(13); + var Range = __webpack_require__(30); + var ItemSet = __webpack_require__(34); + var TimeAxis = __webpack_require__(44); + var Activator = __webpack_require__(45); + var DateUtil = __webpack_require__(32); + var CustomTime = __webpack_require__(47); /** * Create a timeline visualization @@ -16313,23 +17874,23 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = Core; /***/ }, -/* 33 */ +/* 34 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var Hammer = __webpack_require__(25); - var util = __webpack_require__(2); - var DataSet = __webpack_require__(10); - var DataView = __webpack_require__(12); - var TimeStep = __webpack_require__(38); - var Component = __webpack_require__(23); - var Group = __webpack_require__(34); - var BackgroundGroup = __webpack_require__(39); - var BoxItem = __webpack_require__(40); - var PointItem = __webpack_require__(41); - var RangeItem = __webpack_require__(36); - var BackgroundItem = __webpack_require__(42); + var Hammer = __webpack_require__(26); + var util = __webpack_require__(4); + var DataSet = __webpack_require__(11); + var DataView = __webpack_require__(13); + var TimeStep = __webpack_require__(39); + var Component = __webpack_require__(24); + var Group = __webpack_require__(35); + var BackgroundGroup = __webpack_require__(40); + var BoxItem = __webpack_require__(41); + var PointItem = __webpack_require__(42); + var RangeItem = __webpack_require__(37); + var BackgroundItem = __webpack_require__(43); var UNGROUPED = '__ungrouped__'; // reserved group id for ungrouped items var BACKGROUND = '__background__'; // reserved group id for background items without group @@ -17924,14 +19485,14 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = ItemSet; /***/ }, -/* 34 */ +/* 35 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var util = __webpack_require__(2); - var stack = __webpack_require__(35); - var RangeItem = __webpack_require__(36); + var util = __webpack_require__(4); + var stack = __webpack_require__(36); + var RangeItem = __webpack_require__(37); /** * @constructor Group @@ -18510,7 +20071,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = Group; /***/ }, -/* 35 */ +/* 36 */ /***/ function(module, exports, __webpack_require__) { // Utility functions for ordering and stacking of items @@ -18634,13 +20195,13 @@ return /******/ (function(modules) { // webpackBootstrap }; /***/ }, -/* 36 */ +/* 37 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var Hammer = __webpack_require__(25); - var Item = __webpack_require__(37); + var Hammer = __webpack_require__(26); + var Item = __webpack_require__(38); /** * @constructor RangeItem @@ -18928,13 +20489,13 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = RangeItem; /***/ }, -/* 37 */ +/* 38 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var Hammer = __webpack_require__(25); - var util = __webpack_require__(2); + var Hammer = __webpack_require__(26); + var util = __webpack_require__(4); /** * @constructor Item @@ -19218,14 +20779,14 @@ return /******/ (function(modules) { // webpackBootstrap // should be implemented by the item /***/ }, -/* 38 */ +/* 39 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var moment = __webpack_require__(5); - var DateUtil = __webpack_require__(31); - var util = __webpack_require__(2); + var moment = __webpack_require__(6); + var DateUtil = __webpack_require__(32); + var util = __webpack_require__(4); /** * @constructor TimeStep @@ -19908,13 +21469,13 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = TimeStep; /***/ }, -/* 39 */ +/* 40 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var util = __webpack_require__(2); - var Group = __webpack_require__(34); + var util = __webpack_require__(4); + var Group = __webpack_require__(35); /** * @constructor BackgroundGroup @@ -19972,13 +21533,13 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = BackgroundGroup; /***/ }, -/* 40 */ +/* 41 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var Item = __webpack_require__(37); - var util = __webpack_require__(2); + var Item = __webpack_require__(38); + var util = __webpack_require__(4); /** * @constructor BoxItem @@ -20210,12 +21771,12 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = BoxItem; /***/ }, -/* 41 */ +/* 42 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var Item = __webpack_require__(37); + var Item = __webpack_require__(38); /** * @constructor PointItem @@ -20413,15 +21974,15 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = PointItem; /***/ }, -/* 42 */ +/* 43 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var Hammer = __webpack_require__(25); - var Item = __webpack_require__(37); - var BackgroundGroup = __webpack_require__(39); - var RangeItem = __webpack_require__(36); + var Hammer = __webpack_require__(26); + var Item = __webpack_require__(38); + var BackgroundGroup = __webpack_require__(40); + var RangeItem = __webpack_require__(37); /** * @constructor BackgroundItem @@ -20634,16 +22195,16 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = BackgroundItem; /***/ }, -/* 43 */ +/* 44 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var util = __webpack_require__(2); - var Component = __webpack_require__(23); - var TimeStep = __webpack_require__(38); - var DateUtil = __webpack_require__(31); - var moment = __webpack_require__(5); + var util = __webpack_require__(4); + var Component = __webpack_require__(24); + var TimeStep = __webpack_require__(39); + var DateUtil = __webpack_require__(32); + var moment = __webpack_require__(6); /** * A horizontal time axis @@ -21073,15 +22634,15 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = TimeAxis; /***/ }, -/* 44 */ +/* 45 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var keycharm = __webpack_require__(45); - var Emitter = __webpack_require__(15); - var Hammer = __webpack_require__(25); - var util = __webpack_require__(2); + var keycharm = __webpack_require__(46); + var Emitter = __webpack_require__(16); + var Hammer = __webpack_require__(26); + var util = __webpack_require__(4); /** * Turn an element into an clickToUse element. @@ -21232,7 +22793,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = Activator; /***/ }, -/* 45 */ +/* 46 */ /***/ function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;"use strict"; @@ -21431,16 +22992,16 @@ return /******/ (function(modules) { // webpackBootstrap /***/ }, -/* 46 */ +/* 47 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var Hammer = __webpack_require__(25); - var util = __webpack_require__(2); - var Component = __webpack_require__(23); - var moment = __webpack_require__(5); - var locales = __webpack_require__(24); + var Hammer = __webpack_require__(26); + var util = __webpack_require__(4); + var Component = __webpack_require__(24); + var moment = __webpack_require__(6); + var locales = __webpack_require__(25); /** * A custom time bar @@ -21670,7 +23231,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = CustomTime; /***/ }, -/* 47 */ +/* 48 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -21685,11 +23246,11 @@ return /******/ (function(modules) { // webpackBootstrap function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - var _ColorPicker = __webpack_require__(48); + var _ColorPicker = __webpack_require__(49); var _ColorPicker2 = _interopRequireDefault(_ColorPicker); - var util = __webpack_require__(2); + var util = __webpack_require__(4); /** * The way this works is for all properties of this.possible options, you can supply the property name in any form to list the options. @@ -22351,7 +23912,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 48 */ +/* 49 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -22364,9 +23925,9 @@ return /******/ (function(modules) { // webpackBootstrap function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - var Hammer = __webpack_require__(25); - var hammerUtil = __webpack_require__(30); - var util = __webpack_require__(2); + var Hammer = __webpack_require__(26); + var hammerUtil = __webpack_require__(31); + var util = __webpack_require__(4); var ColorPicker = (function () { function ColorPicker() { @@ -22931,7 +24492,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 49 */ +/* 50 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -22944,7 +24505,7 @@ return /******/ (function(modules) { // webpackBootstrap function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - var util = __webpack_require__(2); + var util = __webpack_require__(4); var errorFound = false; var allOptions = undefined; @@ -23244,7 +24805,7 @@ return /******/ (function(modules) { // webpackBootstrap exports.printStyle = printStyle; /***/ }, -/* 50 */ +/* 51 */ /***/ function(module, exports, __webpack_require__) { /** @@ -23458,28 +25019,28 @@ return /******/ (function(modules) { // webpackBootstrap exports.configureOptions = configureOptions; /***/ }, -/* 51 */ +/* 52 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var Emitter = __webpack_require__(15); - var Hammer = __webpack_require__(25); - var util = __webpack_require__(2); - var DataSet = __webpack_require__(10); - var DataView = __webpack_require__(12); - var Range = __webpack_require__(29); - var Core = __webpack_require__(32); - var TimeAxis = __webpack_require__(43); - var CurrentTime = __webpack_require__(22); - var CustomTime = __webpack_require__(46); - var LineGraph = __webpack_require__(52); - - var Configurator = __webpack_require__(47); - var Validator = __webpack_require__(49)['default']; - var printStyle = __webpack_require__(49).printStyle; - var allOptions = __webpack_require__(60).allOptions; - var configureOptions = __webpack_require__(60).configureOptions; + var Emitter = __webpack_require__(16); + var Hammer = __webpack_require__(26); + var util = __webpack_require__(4); + var DataSet = __webpack_require__(11); + var DataView = __webpack_require__(13); + var Range = __webpack_require__(30); + var Core = __webpack_require__(33); + var TimeAxis = __webpack_require__(44); + var CurrentTime = __webpack_require__(23); + var CustomTime = __webpack_require__(47); + var LineGraph = __webpack_require__(53); + + var Configurator = __webpack_require__(48); + var Validator = __webpack_require__(50)['default']; + var printStyle = __webpack_require__(50).printStyle; + var allOptions = __webpack_require__(61).allOptions; + var configureOptions = __webpack_require__(61).configureOptions; /** * Create a timeline visualization @@ -23788,21 +25349,21 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = Graph2d; /***/ }, -/* 52 */ +/* 53 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var util = __webpack_require__(2); - var DOMutil = __webpack_require__(9); - var DataSet = __webpack_require__(10); - var DataView = __webpack_require__(12); - var Component = __webpack_require__(23); - var DataAxis = __webpack_require__(53); - var GraphGroup = __webpack_require__(55); - var Legend = __webpack_require__(59); - var BarFunctions = __webpack_require__(58); - var LineFunctions = __webpack_require__(56); + var util = __webpack_require__(4); + var DOMutil = __webpack_require__(10); + var DataSet = __webpack_require__(11); + var DataView = __webpack_require__(13); + var Component = __webpack_require__(24); + var DataAxis = __webpack_require__(54); + var GraphGroup = __webpack_require__(56); + var Legend = __webpack_require__(60); + var BarFunctions = __webpack_require__(59); + var LineFunctions = __webpack_require__(57); var UNGROUPED = '__ungrouped__'; // reserved group id for ungrouped items @@ -24764,15 +26325,15 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = LineGraph; /***/ }, -/* 53 */ +/* 54 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var util = __webpack_require__(2); - var DOMutil = __webpack_require__(9); - var Component = __webpack_require__(23); - var DataStep = __webpack_require__(54); + var util = __webpack_require__(4); + var DOMutil = __webpack_require__(10); + var Component = __webpack_require__(24); + var DataStep = __webpack_require__(55); /** * A horizontal time axis @@ -25368,7 +26929,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = DataAxis; /***/ }, -/* 54 */ +/* 55 */ /***/ function(module, exports, __webpack_require__) { /** @@ -25595,16 +27156,16 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = DataStep; /***/ }, -/* 55 */ +/* 56 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var util = __webpack_require__(2); - var DOMutil = __webpack_require__(9); - var Line = __webpack_require__(56); - var Bar = __webpack_require__(58); - var Points = __webpack_require__(57); + var util = __webpack_require__(4); + var DOMutil = __webpack_require__(10); + var Line = __webpack_require__(57); + var Bar = __webpack_require__(59); + var Points = __webpack_require__(58); /** * /** @@ -25789,13 +27350,13 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = GraphGroup; /***/ }, -/* 56 */ +/* 57 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var DOMutil = __webpack_require__(9); - var Points = __webpack_require__(57); + var DOMutil = __webpack_require__(10); + var Points = __webpack_require__(58); function Line(groupId, options) { this.groupId = groupId; @@ -26084,12 +27645,12 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = Line; /***/ }, -/* 57 */ +/* 58 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var DOMutil = __webpack_require__(9); + var DOMutil = __webpack_require__(10); function Points(groupId, options) { this.groupId = groupId; @@ -26131,13 +27692,13 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = Points; /***/ }, -/* 58 */ +/* 59 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var DOMutil = __webpack_require__(9); - var Points = __webpack_require__(57); + var DOMutil = __webpack_require__(10); + var Points = __webpack_require__(58); function Bargraph(groupId, options) { this.groupId = groupId; @@ -26379,14 +27940,14 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = Bargraph; /***/ }, -/* 59 */ +/* 60 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var util = __webpack_require__(2); - var DOMutil = __webpack_require__(9); - var Component = __webpack_require__(23); + var util = __webpack_require__(4); + var DOMutil = __webpack_require__(10); + var Component = __webpack_require__(24); /** * Legend for Graph2d @@ -26593,7 +28154,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = Legend; /***/ }, -/* 60 */ +/* 61 */ /***/ function(module, exports, __webpack_require__) { /** @@ -26861,7 +28422,7 @@ return /******/ (function(modules) { // webpackBootstrap exports.configureOptions = configureOptions; /***/ }, -/* 61 */ +/* 62 */ /***/ function(module, exports, __webpack_require__) { // Load custom shapes into CanvasRenderingContext2D @@ -26869,19 +28430,19 @@ return /******/ (function(modules) { // webpackBootstrap function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - var _modulesGroups = __webpack_require__(62); + var _modulesGroups = __webpack_require__(63); var _modulesGroups2 = _interopRequireDefault(_modulesGroups); - var _modulesNodesHandler = __webpack_require__(63); + var _modulesNodesHandler = __webpack_require__(64); var _modulesNodesHandler2 = _interopRequireDefault(_modulesNodesHandler); - var _modulesEdgesHandler = __webpack_require__(83); + var _modulesEdgesHandler = __webpack_require__(84); var _modulesEdgesHandler2 = _interopRequireDefault(_modulesEdgesHandler); - var _modulesPhysicsEngine = __webpack_require__(90); + var _modulesPhysicsEngine = __webpack_require__(3); var _modulesPhysicsEngine2 = _interopRequireDefault(_modulesPhysicsEngine); @@ -26889,55 +28450,55 @@ return /******/ (function(modules) { // webpackBootstrap var _modulesClustering2 = _interopRequireDefault(_modulesClustering); - var _modulesCanvasRenderer = __webpack_require__(3); + var _modulesCanvasRenderer = __webpack_require__(101); var _modulesCanvasRenderer2 = _interopRequireDefault(_modulesCanvasRenderer); - var _modulesCanvas = __webpack_require__(4); + var _modulesCanvas = __webpack_require__(102); var _modulesCanvas2 = _interopRequireDefault(_modulesCanvas); - var _modulesView = __webpack_require__(101); + var _modulesView = __webpack_require__(103); var _modulesView2 = _interopRequireDefault(_modulesView); - var _modulesInteractionHandler = __webpack_require__(102); + var _modulesInteractionHandler = __webpack_require__(104); var _modulesInteractionHandler2 = _interopRequireDefault(_modulesInteractionHandler); - var _modulesSelectionHandler = __webpack_require__(105); + var _modulesSelectionHandler = __webpack_require__(107); var _modulesSelectionHandler2 = _interopRequireDefault(_modulesSelectionHandler); - var _modulesLayoutEngine = __webpack_require__(106); + var _modulesLayoutEngine = __webpack_require__(108); var _modulesLayoutEngine2 = _interopRequireDefault(_modulesLayoutEngine); - var _modulesManipulationSystem = __webpack_require__(107); + var _modulesManipulationSystem = __webpack_require__(2); var _modulesManipulationSystem2 = _interopRequireDefault(_modulesManipulationSystem); - var _sharedConfigurator = __webpack_require__(47); + var _sharedConfigurator = __webpack_require__(48); var _sharedConfigurator2 = _interopRequireDefault(_sharedConfigurator); - var _sharedValidator = __webpack_require__(49); + var _sharedValidator = __webpack_require__(50); var _sharedValidator2 = _interopRequireDefault(_sharedValidator); - var _optionsJs = __webpack_require__(108); + var _optionsJs = __webpack_require__(5); __webpack_require__(109); - var Emitter = __webpack_require__(15); - var Hammer = __webpack_require__(25); - var util = __webpack_require__(2); - var DataSet = __webpack_require__(10); - var DataView = __webpack_require__(12); + var Emitter = __webpack_require__(16); + var Hammer = __webpack_require__(26); + var util = __webpack_require__(4); + var DataSet = __webpack_require__(11); + var DataView = __webpack_require__(13); var dotparser = __webpack_require__(110); var gephiParser = __webpack_require__(111); var Images = __webpack_require__(112); - var Activator = __webpack_require__(44); + var Activator = __webpack_require__(45); var locales = __webpack_require__(113); /** @@ -27467,7 +29028,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = Network; /***/ }, -/* 62 */ +/* 63 */ /***/ function(module, exports, __webpack_require__) { "use strict"; @@ -27480,7 +29041,7 @@ return /******/ (function(modules) { // webpackBootstrap function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - var util = __webpack_require__(2); + var util = __webpack_require__(4); /** * @class Groups @@ -27609,7 +29170,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports["default"]; /***/ }, -/* 63 */ +/* 64 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -27624,17 +29185,17 @@ 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__(64); + var _componentsNode = __webpack_require__(65); var _componentsNode2 = _interopRequireDefault(_componentsNode); - var _componentsSharedLabel = __webpack_require__(65); + var _componentsSharedLabel = __webpack_require__(66); var _componentsSharedLabel2 = _interopRequireDefault(_componentsSharedLabel); - var util = __webpack_require__(2); - var DataSet = __webpack_require__(10); - var DataView = __webpack_require__(12); + var util = __webpack_require__(4); + var DataSet = __webpack_require__(11); + var DataView = __webpack_require__(13); var NodesHandler = (function () { function NodesHandler(body, images, groups, layoutEngine) { @@ -28087,7 +29648,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 64 */ +/* 65 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -28102,71 +29663,71 @@ return /******/ (function(modules) { // webpackBootstrap function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - var _sharedLabel = __webpack_require__(65); + var _sharedLabel = __webpack_require__(66); var _sharedLabel2 = _interopRequireDefault(_sharedLabel); - var _nodesShapesBox = __webpack_require__(66); + var _nodesShapesBox = __webpack_require__(67); var _nodesShapesBox2 = _interopRequireDefault(_nodesShapesBox); - var _nodesShapesCircle = __webpack_require__(68); + var _nodesShapesCircle = __webpack_require__(69); var _nodesShapesCircle2 = _interopRequireDefault(_nodesShapesCircle); - var _nodesShapesCircularImage = __webpack_require__(70); + var _nodesShapesCircularImage = __webpack_require__(71); var _nodesShapesCircularImage2 = _interopRequireDefault(_nodesShapesCircularImage); - var _nodesShapesDatabase = __webpack_require__(71); + var _nodesShapesDatabase = __webpack_require__(72); var _nodesShapesDatabase2 = _interopRequireDefault(_nodesShapesDatabase); - var _nodesShapesDiamond = __webpack_require__(72); + var _nodesShapesDiamond = __webpack_require__(73); var _nodesShapesDiamond2 = _interopRequireDefault(_nodesShapesDiamond); - var _nodesShapesDot = __webpack_require__(74); + var _nodesShapesDot = __webpack_require__(75); var _nodesShapesDot2 = _interopRequireDefault(_nodesShapesDot); - var _nodesShapesEllipse = __webpack_require__(75); + var _nodesShapesEllipse = __webpack_require__(76); var _nodesShapesEllipse2 = _interopRequireDefault(_nodesShapesEllipse); - var _nodesShapesIcon = __webpack_require__(76); + var _nodesShapesIcon = __webpack_require__(77); var _nodesShapesIcon2 = _interopRequireDefault(_nodesShapesIcon); - var _nodesShapesImage = __webpack_require__(77); + var _nodesShapesImage = __webpack_require__(78); var _nodesShapesImage2 = _interopRequireDefault(_nodesShapesImage); - var _nodesShapesSquare = __webpack_require__(78); + var _nodesShapesSquare = __webpack_require__(79); var _nodesShapesSquare2 = _interopRequireDefault(_nodesShapesSquare); - var _nodesShapesStar = __webpack_require__(79); + var _nodesShapesStar = __webpack_require__(80); var _nodesShapesStar2 = _interopRequireDefault(_nodesShapesStar); - var _nodesShapesText = __webpack_require__(80); + var _nodesShapesText = __webpack_require__(81); var _nodesShapesText2 = _interopRequireDefault(_nodesShapesText); - var _nodesShapesTriangle = __webpack_require__(81); + var _nodesShapesTriangle = __webpack_require__(82); var _nodesShapesTriangle2 = _interopRequireDefault(_nodesShapesTriangle); - var _nodesShapesTriangleDown = __webpack_require__(82); + var _nodesShapesTriangleDown = __webpack_require__(83); var _nodesShapesTriangleDown2 = _interopRequireDefault(_nodesShapesTriangleDown); - var _sharedValidator = __webpack_require__(49); + var _sharedValidator = __webpack_require__(50); var _sharedValidator2 = _interopRequireDefault(_sharedValidator); - var util = __webpack_require__(2); + var util = __webpack_require__(4); /** * @class Node @@ -28607,7 +30168,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 65 */ +/* 66 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -28622,7 +30183,7 @@ return /******/ (function(modules) { // webpackBootstrap function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - var util = __webpack_require__(2); + var util = __webpack_require__(4); var Label = (function () { function Label(body, options) { @@ -28923,7 +30484,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 66 */ +/* 67 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -28942,7 +30503,7 @@ return /******/ (function(modules) { // webpackBootstrap 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 _utilNodeBase = __webpack_require__(67); + var _utilNodeBase = __webpack_require__(68); var _utilNodeBase2 = _interopRequireDefault(_utilNodeBase); @@ -29028,7 +30589,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 67 */ +/* 68 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -29096,7 +30657,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 68 */ +/* 69 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -29115,7 +30676,7 @@ return /******/ (function(modules) { // webpackBootstrap 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 _utilCircleImageBase = __webpack_require__(69); + var _utilCircleImageBase = __webpack_require__(70); var _utilCircleImageBase2 = _interopRequireDefault(_utilCircleImageBase); @@ -29186,7 +30747,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 69 */ +/* 70 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -29205,7 +30766,7 @@ return /******/ (function(modules) { // webpackBootstrap 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 _utilNodeBase = __webpack_require__(67); + var _utilNodeBase = __webpack_require__(68); var _utilNodeBase2 = _interopRequireDefault(_utilNodeBase); @@ -29335,7 +30896,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 70 */ +/* 71 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -29354,7 +30915,7 @@ return /******/ (function(modules) { // webpackBootstrap 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 _utilCircleImageBase = __webpack_require__(69); + var _utilCircleImageBase = __webpack_require__(70); var _utilCircleImageBase2 = _interopRequireDefault(_utilCircleImageBase); @@ -29440,7 +31001,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 71 */ +/* 72 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -29459,7 +31020,7 @@ return /******/ (function(modules) { // webpackBootstrap 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 _utilNodeBase = __webpack_require__(67); + var _utilNodeBase = __webpack_require__(68); var _utilNodeBase2 = _interopRequireDefault(_utilNodeBase); @@ -29545,7 +31106,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 72 */ +/* 73 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -29564,7 +31125,7 @@ return /******/ (function(modules) { // webpackBootstrap 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 _utilShapeBase = __webpack_require__(73); + var _utilShapeBase = __webpack_require__(74); var _utilShapeBase2 = _interopRequireDefault(_utilShapeBase); @@ -29601,7 +31162,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 73 */ +/* 74 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -29620,7 +31181,7 @@ return /******/ (function(modules) { // webpackBootstrap 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 _utilNodeBase = __webpack_require__(67); + var _utilNodeBase = __webpack_require__(68); var _utilNodeBase2 = _interopRequireDefault(_utilNodeBase); @@ -29700,7 +31261,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 74 */ +/* 75 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -29719,7 +31280,7 @@ return /******/ (function(modules) { // webpackBootstrap 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 _utilShapeBase = __webpack_require__(73); + var _utilShapeBase = __webpack_require__(74); var _utilShapeBase2 = _interopRequireDefault(_utilShapeBase); @@ -29756,7 +31317,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 75 */ +/* 76 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -29775,7 +31336,7 @@ return /******/ (function(modules) { // webpackBootstrap 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 _utilNodeBase = __webpack_require__(67); + var _utilNodeBase = __webpack_require__(68); var _utilNodeBase2 = _interopRequireDefault(_utilNodeBase); @@ -29863,7 +31424,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 76 */ +/* 77 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -29882,7 +31443,7 @@ return /******/ (function(modules) { // webpackBootstrap 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 _utilNodeBase = __webpack_require__(67); + var _utilNodeBase = __webpack_require__(68); var _utilNodeBase2 = _interopRequireDefault(_utilNodeBase); @@ -29979,7 +31540,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 77 */ +/* 78 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -29998,7 +31559,7 @@ return /******/ (function(modules) { // webpackBootstrap 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 _utilCircleImageBase = __webpack_require__(69); + var _utilCircleImageBase = __webpack_require__(70); var _utilCircleImageBase2 = _interopRequireDefault(_utilCircleImageBase); @@ -30066,7 +31627,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 78 */ +/* 79 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -30085,7 +31646,7 @@ return /******/ (function(modules) { // webpackBootstrap 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 _utilShapeBase = __webpack_require__(73); + var _utilShapeBase = __webpack_require__(74); var _utilShapeBase2 = _interopRequireDefault(_utilShapeBase); @@ -30123,7 +31684,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 79 */ +/* 80 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -30142,7 +31703,7 @@ return /******/ (function(modules) { // webpackBootstrap 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 _utilShapeBase = __webpack_require__(73); + var _utilShapeBase = __webpack_require__(74); var _utilShapeBase2 = _interopRequireDefault(_utilShapeBase); @@ -30179,7 +31740,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 80 */ +/* 81 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -30198,7 +31759,7 @@ return /******/ (function(modules) { // webpackBootstrap 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 _utilNodeBase = __webpack_require__(67); + var _utilNodeBase = __webpack_require__(68); var _utilNodeBase2 = _interopRequireDefault(_utilNodeBase); @@ -30264,7 +31825,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 81 */ +/* 82 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -30283,7 +31844,7 @@ return /******/ (function(modules) { // webpackBootstrap 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 _utilShapeBase = __webpack_require__(73); + var _utilShapeBase = __webpack_require__(74); var _utilShapeBase2 = _interopRequireDefault(_utilShapeBase); @@ -30320,7 +31881,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 82 */ +/* 83 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -30339,7 +31900,7 @@ return /******/ (function(modules) { // webpackBootstrap 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 _utilShapeBase = __webpack_require__(73); + var _utilShapeBase = __webpack_require__(74); var _utilShapeBase2 = _interopRequireDefault(_utilShapeBase); @@ -30376,7 +31937,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 83 */ +/* 84 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -30391,17 +31952,17 @@ 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__(84); + var _componentsEdge = __webpack_require__(85); var _componentsEdge2 = _interopRequireDefault(_componentsEdge); - var _componentsSharedLabel = __webpack_require__(65); + var _componentsSharedLabel = __webpack_require__(66); var _componentsSharedLabel2 = _interopRequireDefault(_componentsSharedLabel); - var util = __webpack_require__(2); - var DataSet = __webpack_require__(10); - var DataView = __webpack_require__(12); + var util = __webpack_require__(4); + var DataSet = __webpack_require__(11); + var DataView = __webpack_require__(13); var EdgesHandler = (function () { function EdgesHandler(body, images, groups) { @@ -30813,7 +32374,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 84 */ +/* 85 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -30828,23 +32389,23 @@ return /******/ (function(modules) { // webpackBootstrap function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - var _sharedLabel = __webpack_require__(65); + var _sharedLabel = __webpack_require__(66); var _sharedLabel2 = _interopRequireDefault(_sharedLabel); - var _edgesBezierEdgeDynamic = __webpack_require__(85); + var _edgesBezierEdgeDynamic = __webpack_require__(86); var _edgesBezierEdgeDynamic2 = _interopRequireDefault(_edgesBezierEdgeDynamic); - var _edgesBezierEdgeStatic = __webpack_require__(88); + var _edgesBezierEdgeStatic = __webpack_require__(89); var _edgesBezierEdgeStatic2 = _interopRequireDefault(_edgesBezierEdgeStatic); - var _edgesStraightEdge = __webpack_require__(89); + var _edgesStraightEdge = __webpack_require__(90); var _edgesStraightEdge2 = _interopRequireDefault(_edgesStraightEdge); - var util = __webpack_require__(2); + var util = __webpack_require__(4); /** * @class Edge @@ -31377,7 +32938,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 85 */ +/* 86 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -31396,7 +32957,7 @@ return /******/ (function(modules) { // webpackBootstrap 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 _utilBezierEdgeBase = __webpack_require__(86); + var _utilBezierEdgeBase = __webpack_require__(87); var _utilBezierEdgeBase2 = _interopRequireDefault(_utilBezierEdgeBase); @@ -31542,7 +33103,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 86 */ +/* 87 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -31561,7 +33122,7 @@ return /******/ (function(modules) { // webpackBootstrap 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 _EdgeBase2 = __webpack_require__(87); + var _EdgeBase2 = __webpack_require__(88); var _EdgeBase3 = _interopRequireDefault(_EdgeBase2); @@ -31689,7 +33250,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 87 */ +/* 88 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -31704,7 +33265,7 @@ return /******/ (function(modules) { // webpackBootstrap function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - var util = __webpack_require__(2); + var util = __webpack_require__(4); var EdgeBase = (function () { function EdgeBase(options, body, labelModule) { @@ -32283,7 +33844,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 88 */ +/* 89 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -32302,7 +33863,7 @@ return /******/ (function(modules) { // webpackBootstrap 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 _utilBezierEdgeBase = __webpack_require__(86); + var _utilBezierEdgeBase = __webpack_require__(87); var _utilBezierEdgeBase2 = _interopRequireDefault(_utilBezierEdgeBase); @@ -32423,851 +33984,227 @@ return /******/ (function(modules) { // webpackBootstrap yVia = this.from.y; } else if (type === 'vertical') { xVia = this.from.x; - if (this.from.y < this.to.y) { - yVia = this.to.y - (1 - factor) * dy; - } else { - yVia = this.to.y + (1 - factor) * dy; - } - } else if (type === 'curvedCW') { - dx = this.to.x - this.from.x; - dy = this.from.y - this.to.y; - var radius = Math.sqrt(dx * dx + dy * dy); - var pi = Math.PI; - - var originalAngle = Math.atan2(dy, dx); - var myAngle = (originalAngle + (factor * 0.5 + 0.5) * pi) % (2 * pi); - - xVia = this.from.x + (factor * 0.5 + 0.5) * radius * Math.sin(myAngle); - yVia = this.from.y + (factor * 0.5 + 0.5) * radius * Math.cos(myAngle); - } else if (type === 'curvedCCW') { - dx = this.to.x - this.from.x; - dy = this.from.y - this.to.y; - var radius = Math.sqrt(dx * dx + dy * dy); - var pi = Math.PI; - - var originalAngle = Math.atan2(dy, dx); - var myAngle = (originalAngle + (-factor * 0.5 + 0.5) * pi) % (2 * pi); - - xVia = this.from.x + (factor * 0.5 + 0.5) * radius * Math.sin(myAngle); - yVia = this.from.y + (factor * 0.5 + 0.5) * radius * Math.cos(myAngle); - } else { - // continuous - if (Math.abs(this.from.x - this.to.x) < Math.abs(this.from.y - this.to.y)) { - if (this.from.y > this.to.y) { - if (this.from.x < this.to.x) { - xVia = this.from.x + factor * dy; - yVia = this.from.y - factor * dy; - xVia = this.to.x < xVia ? this.to.x : xVia; - } else if (this.from.x > this.to.x) { - xVia = this.from.x - factor * dy; - yVia = this.from.y - factor * dy; - xVia = this.to.x > xVia ? this.to.x : xVia; - } - } else if (this.from.y < this.to.y) { - if (this.from.x < this.to.x) { - xVia = this.from.x + factor * dy; - yVia = this.from.y + factor * dy; - xVia = this.to.x < xVia ? this.to.x : xVia; - } else if (this.from.x > this.to.x) { - xVia = this.from.x - factor * dy; - yVia = this.from.y + factor * dy; - xVia = this.to.x > xVia ? this.to.x : xVia; - } - } - } else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) { - if (this.from.y > this.to.y) { - if (this.from.x < this.to.x) { - xVia = this.from.x + factor * dx; - yVia = this.from.y - factor * dx; - yVia = this.to.y > yVia ? this.to.y : yVia; - } else if (this.from.x > this.to.x) { - xVia = this.from.x - factor * dx; - yVia = this.from.y - factor * dx; - yVia = this.to.y > yVia ? this.to.y : yVia; - } - } else if (this.from.y < this.to.y) { - if (this.from.x < this.to.x) { - xVia = this.from.x + factor * dx; - yVia = this.from.y + factor * dx; - yVia = this.to.y < yVia ? this.to.y : yVia; - } else if (this.from.x > this.to.x) { - xVia = this.from.x - factor * dx; - yVia = this.from.y + factor * dx; - yVia = this.to.y < yVia ? this.to.y : yVia; - } - } - } - } - return { x: xVia, y: yVia }; - } - }, { - key: '_findBorderPosition', - value: function _findBorderPosition(nearNode, ctx) { - var options = arguments[2] === undefined ? {} : arguments[2]; - - return this._findBorderPositionBezier(nearNode, ctx, options.via); - } - }, { - key: '_getDistanceToEdge', - value: function _getDistanceToEdge(x1, y1, x2, y2, x3, y3) { - var via = arguments[6] === undefined ? this._getViaCoordinates() : arguments[6]; - // x3,y3 is the point - return this._getDistanceToBezierEdge(x1, y1, x2, y2, x3, y3, via); - } - }, { - key: 'getPoint', - - /** - * Combined function of pointOnLine and pointOnBezier. This gives the coordinates of a point on the line at a certain percentage of the way - * @param percentage - * @param via - * @returns {{x: number, y: number}} - * @private - */ - value: function getPoint(percentage) { - var via = arguments[1] === undefined ? this._getViaCoordinates() : arguments[1]; - - var t = percentage; - var x = Math.pow(1 - t, 2) * this.from.x + 2 * t * (1 - t) * via.x + Math.pow(t, 2) * this.to.x; - var y = Math.pow(1 - t, 2) * this.from.y + 2 * t * (1 - t) * via.y + Math.pow(t, 2) * this.to.y; - - return { x: x, y: y }; - } - }]); - - return BezierEdgeStatic; - })(_utilBezierEdgeBase2['default']); - - exports['default'] = BezierEdgeStatic; - module.exports = exports['default']; - -/***/ }, -/* 89 */ -/***/ function(module, exports, __webpack_require__) { - - 'use strict'; - - Object.defineProperty(exports, '__esModule', { - value: true - }); - - var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); - - var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; - - function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - - function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } - - var _utilEdgeBase = __webpack_require__(87); - - var _utilEdgeBase2 = _interopRequireDefault(_utilEdgeBase); - - var StraightEdge = (function (_EdgeBase) { - function StraightEdge(options, body, labelModule) { - _classCallCheck(this, StraightEdge); - - _get(Object.getPrototypeOf(StraightEdge.prototype), 'constructor', this).call(this, options, body, labelModule); - } - - _inherits(StraightEdge, _EdgeBase); - - _createClass(StraightEdge, [{ - key: '_line', - - /** - * Draw a line between two nodes - * @param {CanvasRenderingContext2D} ctx - * @private - */ - value: function _line(ctx) { - // draw a straight line - ctx.beginPath(); - ctx.moveTo(this.from.x, this.from.y); - ctx.lineTo(this.to.x, this.to.y); - // draw shadow if enabled - this.enableShadow(ctx); - ctx.stroke(); - this.disableShadow(ctx); - return undefined; - } - }, { - key: 'getPoint', - - /** - * Combined function of pointOnLine and pointOnBezier. This gives the coordinates of a point on the line at a certain percentage of the way - * @param percentage - * @param via - * @returns {{x: number, y: number}} - * @private - */ - value: function getPoint(percentage) { - return { - x: (1 - percentage) * this.from.x + percentage * this.to.x, - y: (1 - percentage) * this.from.y + percentage * this.to.y - }; - } - }, { - key: '_findBorderPosition', - value: function _findBorderPosition(nearNode, ctx) { - var node1 = this.to; - var node2 = this.from; - if (nearNode.id === this.from.id) { - node1 = this.from; - node2 = this.to; - } - - var angle = Math.atan2(node1.y - node2.y, node1.x - node2.x); - var dx = node1.x - node2.x; - var dy = node1.y - node2.y; - var edgeSegmentLength = Math.sqrt(dx * dx + dy * dy); - var toBorderDist = nearNode.distanceToBorder(ctx, angle); - var toBorderPoint = (edgeSegmentLength - toBorderDist) / edgeSegmentLength; - - var borderPos = {}; - borderPos.x = (1 - toBorderPoint) * node2.x + toBorderPoint * node1.x; - borderPos.y = (1 - toBorderPoint) * node2.y + toBorderPoint * node1.y; - - return borderPos; - } - }, { - key: '_getDistanceToEdge', - value: function _getDistanceToEdge(x1, y1, x2, y2, x3, y3) { - // x3,y3 is the point - return this._getDistanceToLine(x1, y1, x2, y2, x3, y3); - } - }]); - - return StraightEdge; - })(_utilEdgeBase2['default']); - - exports['default'] = StraightEdge; - module.exports = exports['default']; - -/***/ }, -/* 90 */ -/***/ 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 _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 _componentsPhysicsBarnesHutSolver = __webpack_require__(91); - - var _componentsPhysicsBarnesHutSolver2 = _interopRequireDefault(_componentsPhysicsBarnesHutSolver); - - var _componentsPhysicsRepulsionSolver = __webpack_require__(92); - - var _componentsPhysicsRepulsionSolver2 = _interopRequireDefault(_componentsPhysicsRepulsionSolver); - - var _componentsPhysicsHierarchicalRepulsionSolver = __webpack_require__(93); - - var _componentsPhysicsHierarchicalRepulsionSolver2 = _interopRequireDefault(_componentsPhysicsHierarchicalRepulsionSolver); - - var _componentsPhysicsSpringSolver = __webpack_require__(94); - - var _componentsPhysicsSpringSolver2 = _interopRequireDefault(_componentsPhysicsSpringSolver); - - var _componentsPhysicsHierarchicalSpringSolver = __webpack_require__(95); - - var _componentsPhysicsHierarchicalSpringSolver2 = _interopRequireDefault(_componentsPhysicsHierarchicalSpringSolver); - - var _componentsPhysicsCentralGravitySolver = __webpack_require__(96); - - var _componentsPhysicsCentralGravitySolver2 = _interopRequireDefault(_componentsPhysicsCentralGravitySolver); - - var _componentsPhysicsFA2BasedRepulsionSolver = __webpack_require__(97); - - var _componentsPhysicsFA2BasedRepulsionSolver2 = _interopRequireDefault(_componentsPhysicsFA2BasedRepulsionSolver); - - var _componentsPhysicsFA2BasedCentralGravitySolver = __webpack_require__(98); - - var _componentsPhysicsFA2BasedCentralGravitySolver2 = _interopRequireDefault(_componentsPhysicsFA2BasedCentralGravitySolver); - - var util = __webpack_require__(2); - - var PhysicsEngine = (function () { - function PhysicsEngine(body) { - _classCallCheck(this, PhysicsEngine); - - this.body = body; - this.physicsBody = { physicsNodeIndices: [], physicsEdgeIndices: [], forces: {}, velocities: {} }; - - this.physicsEnabled = true; - this.simulationInterval = 1000 / 60; - this.requiresTimeout = true; - this.previousStates = {}; - this.freezeCache = {}; - this.renderTimer = undefined; - this.initialStabilizationEmitted = false; - - this.stabilized = false; - this.startedStabilization = false; - this.stabilizationIterations = 0; - this.ready = false; // will be set to true if the stabilize - - // default options - this.options = {}; - this.defaultOptions = { - barnesHut: { - theta: 0.5, - gravitationalConstant: -2000, - centralGravity: 0.3, - springLength: 95, - springConstant: 0.04, - damping: 0.09, - avoidOverlap: 0 - }, - forceAtlas2Based: { - theta: 0.5, - gravitationalConstant: -50, - centralGravity: 0.01, - springConstant: 0.08, - springLength: 100, - damping: 0.4, - avoidOverlap: 0 - }, - repulsion: { - centralGravity: 0.2, - springLength: 200, - springConstant: 0.05, - nodeDistance: 100, - damping: 0.09, - avoidOverlap: 0 - }, - hierarchicalRepulsion: { - centralGravity: 0, - springLength: 100, - springConstant: 0.01, - nodeDistance: 120, - damping: 0.09 - }, - maxVelocity: 50, - minVelocity: 0.1, // px/s - solver: 'barnesHut', - stabilization: { - enabled: true, - iterations: 1000, // maximum number of iteration to stabilize - updateInterval: 50, - onlyDynamicEdges: false, - fit: true - }, - timestep: 0.5 - }; - util.extend(this.options, this.defaultOptions); - - this.bindEventListeners(); - } - - _createClass(PhysicsEngine, [{ - key: 'bindEventListeners', - value: function bindEventListeners() { - var _this = this; - - this.body.emitter.on('initPhysics', function () { - _this.initPhysics(); - }); - this.body.emitter.on('resetPhysics', function () { - _this.stopSimulation();_this.ready = false; - }); - this.body.emitter.on('disablePhysics', function () { - _this.physicsEnabled = false;_this.stopSimulation(); - }); - this.body.emitter.on('restorePhysics', function () { - _this.setOptions(_this.options); - if (_this.ready === true) { - _this.startSimulation(); - } - }); - this.body.emitter.on('startSimulation', function () { - if (_this.ready === true) { - _this.startSimulation(); - } - }); - this.body.emitter.on('stopSimulation', function () { - _this.stopSimulation(); - }); - this.body.emitter.on('destroy', function () { - _this.stopSimulation(false); - _this.body.emitter.off(); - }); - } - }, { - key: 'setOptions', - value: function setOptions(options) { - if (options !== undefined) { - if (options === false) { - this.physicsEnabled = false; - this.stopSimulation(); - } else { - this.physicsEnabled = true; - util.selectiveNotDeepExtend(['stabilization'], this.options, options); - util.mergeOptions(this.options, options, 'stabilization'); - } - } - this.init(); - } - }, { - key: 'init', - value: function init() { - var options; - if (this.options.solver === 'forceAtlas2Based') { - options = this.options.forceAtlas2Based; - this.nodesSolver = new _componentsPhysicsFA2BasedRepulsionSolver2['default'](this.body, this.physicsBody, options); - this.edgesSolver = new _componentsPhysicsSpringSolver2['default'](this.body, this.physicsBody, options); - this.gravitySolver = new _componentsPhysicsFA2BasedCentralGravitySolver2['default'](this.body, this.physicsBody, options); - } else if (this.options.solver === 'repulsion') { - options = this.options.repulsion; - this.nodesSolver = new _componentsPhysicsRepulsionSolver2['default'](this.body, this.physicsBody, options); - this.edgesSolver = new _componentsPhysicsSpringSolver2['default'](this.body, this.physicsBody, options); - this.gravitySolver = new _componentsPhysicsCentralGravitySolver2['default'](this.body, this.physicsBody, options); - } else if (this.options.solver === 'hierarchicalRepulsion') { - options = this.options.hierarchicalRepulsion; - this.nodesSolver = new _componentsPhysicsHierarchicalRepulsionSolver2['default'](this.body, this.physicsBody, options); - this.edgesSolver = new _componentsPhysicsHierarchicalSpringSolver2['default'](this.body, this.physicsBody, options); - this.gravitySolver = new _componentsPhysicsCentralGravitySolver2['default'](this.body, this.physicsBody, options); - } else { - // barnesHut - options = this.options.barnesHut; - this.nodesSolver = new _componentsPhysicsBarnesHutSolver2['default'](this.body, this.physicsBody, options); - this.edgesSolver = new _componentsPhysicsSpringSolver2['default'](this.body, this.physicsBody, options); - this.gravitySolver = new _componentsPhysicsCentralGravitySolver2['default'](this.body, this.physicsBody, options); - } - - this.modelOptions = options; - } - }, { - key: 'initPhysics', - value: function initPhysics() { - if (this.physicsEnabled === true) { - if (this.options.stabilization.enabled === true) { - this.stabilize(); - } else { - this.stabilized = false; - this.ready = true; - this.body.emitter.emit('fit', {}, true); - this.startSimulation(); + if (this.from.y < this.to.y) { + yVia = this.to.y - (1 - factor) * dy; + } else { + yVia = this.to.y + (1 - factor) * dy; } - } else { - this.ready = true; - this.body.emitter.emit('fit'); - } - } - }, { - key: 'startSimulation', + } else if (type === 'curvedCW') { + dx = this.to.x - this.from.x; + dy = this.from.y - this.to.y; + var radius = Math.sqrt(dx * dx + dy * dy); + var pi = Math.PI; - /** - * Start the simulation - */ - value: function startSimulation() { - if (this.physicsEnabled === true) { - this.stabilized = false; + var originalAngle = Math.atan2(dy, dx); + var myAngle = (originalAngle + (factor * 0.5 + 0.5) * pi) % (2 * pi); - // this sets the width of all nodes initially which could be required for the avoidOverlap - this.body.emitter.emit('_resizeNodes'); - if (this.viewFunction === undefined) { - this.viewFunction = this.simulationStep.bind(this); - this.body.emitter.on('initRedraw', this.viewFunction); - this.body.emitter.emit('_startRendering'); - } - } else { - this.body.emitter.emit('_redraw'); - } - } - }, { - key: 'stopSimulation', + xVia = this.from.x + (factor * 0.5 + 0.5) * radius * Math.sin(myAngle); + yVia = this.from.y + (factor * 0.5 + 0.5) * radius * Math.cos(myAngle); + } else if (type === 'curvedCCW') { + dx = this.to.x - this.from.x; + dy = this.from.y - this.to.y; + var radius = Math.sqrt(dx * dx + dy * dy); + var pi = Math.PI; - /** - * Stop the simulation, force stabilization. - */ - value: function stopSimulation() { - var emit = arguments[0] === undefined ? true : arguments[0]; + var originalAngle = Math.atan2(dy, dx); + var myAngle = (originalAngle + (-factor * 0.5 + 0.5) * pi) % (2 * pi); - this.stabilized = true; - if (emit === true) { - this._emitStabilized(); - } - if (this.viewFunction !== undefined) { - this.body.emitter.off('initRedraw', this.viewFunction); - this.viewFunction = undefined; - if (emit === true) { - this.body.emitter.emit('_stopRendering'); + xVia = this.from.x + (factor * 0.5 + 0.5) * radius * Math.sin(myAngle); + yVia = this.from.y + (factor * 0.5 + 0.5) * radius * Math.cos(myAngle); + } else { + // continuous + if (Math.abs(this.from.x - this.to.x) < Math.abs(this.from.y - this.to.y)) { + if (this.from.y > this.to.y) { + if (this.from.x < this.to.x) { + xVia = this.from.x + factor * dy; + yVia = this.from.y - factor * dy; + xVia = this.to.x < xVia ? this.to.x : xVia; + } else if (this.from.x > this.to.x) { + xVia = this.from.x - factor * dy; + yVia = this.from.y - factor * dy; + xVia = this.to.x > xVia ? this.to.x : xVia; + } + } else if (this.from.y < this.to.y) { + if (this.from.x < this.to.x) { + xVia = this.from.x + factor * dy; + yVia = this.from.y + factor * dy; + xVia = this.to.x < xVia ? this.to.x : xVia; + } else if (this.from.x > this.to.x) { + xVia = this.from.x - factor * dy; + yVia = this.from.y + factor * dy; + xVia = this.to.x > xVia ? this.to.x : xVia; + } + } + } else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) { + if (this.from.y > this.to.y) { + if (this.from.x < this.to.x) { + xVia = this.from.x + factor * dx; + yVia = this.from.y - factor * dx; + yVia = this.to.y > yVia ? this.to.y : yVia; + } else if (this.from.x > this.to.x) { + xVia = this.from.x - factor * dx; + yVia = this.from.y - factor * dx; + yVia = this.to.y > yVia ? this.to.y : yVia; + } + } else if (this.from.y < this.to.y) { + if (this.from.x < this.to.x) { + xVia = this.from.x + factor * dx; + yVia = this.from.y + factor * dx; + yVia = this.to.y < yVia ? this.to.y : yVia; + } else if (this.from.x > this.to.x) { + xVia = this.from.x - factor * dx; + yVia = this.from.y + factor * dx; + yVia = this.to.y < yVia ? this.to.y : yVia; + } + } } } + return { x: xVia, y: yVia }; } }, { - key: 'simulationStep', - - /** - * The viewFunction inserts this step into each renderloop. It calls the physics tick and handles the cleanup at stabilized. - * - */ - value: function simulationStep() { - // check if the physics have settled - var startTime = Date.now(); - this.physicsTick(); - var physicsTime = Date.now() - startTime; - - // run double speed if it is a little graph - if ((physicsTime < 0.4 * this.simulationInterval || this.runDoubleSpeed === true) && this.stabilized === false) { - this.physicsTick(); - - // this makes sure there is no jitter. The decision is taken once to run it at double speed. - this.runDoubleSpeed = true; - } + key: '_findBorderPosition', + value: function _findBorderPosition(nearNode, ctx) { + var options = arguments[2] === undefined ? {} : arguments[2]; - if (this.stabilized === true) { - if (this.stabilizationIterations > 1) { - // trigger the 'stabilized' event. - // The event is triggered on the next tick, to prevent the case that - // it is fired while initializing the Network, in which case you would not - // be able to catch it - this.startedStabilization = false; - //this._emitStabilized(); - } - this.stopSimulation(); - } + return this._findBorderPositionBezier(nearNode, ctx, options.via); } }, { - key: '_emitStabilized', - value: function _emitStabilized() { - var _this2 = this; - - if (this.stabilizationIterations > 1 || this.initialStabilizationEmitted === false) { - this.initialStabilizationEmitted = true; - setTimeout(function () { - _this2.body.emitter.emit('stabilized', { iterations: _this2.stabilizationIterations }); - _this2.stabilizationIterations = 0; - }, 0); - } + key: '_getDistanceToEdge', + value: function _getDistanceToEdge(x1, y1, x2, y2, x3, y3) { + var via = arguments[6] === undefined ? this._getViaCoordinates() : arguments[6]; + // x3,y3 is the point + return this._getDistanceToBezierEdge(x1, y1, x2, y2, x3, y3, via); } }, { - key: 'physicsTick', + key: 'getPoint', /** - * A single simulation step (or 'tick') in the physics simulation - * + * Combined function of pointOnLine and pointOnBezier. This gives the coordinates of a point on the line at a certain percentage of the way + * @param percentage + * @param via + * @returns {{x: number, y: number}} * @private */ - value: function physicsTick() { - if (this.stabilized === false) { - this.calculateForces(); - this.stabilized = this.moveNodes(); + value: function getPoint(percentage) { + var via = arguments[1] === undefined ? this._getViaCoordinates() : arguments[1]; - // determine if the network has stabilzied - if (this.stabilized === true) { - this.revert(); - } else { - // this is here to ensure that there is no start event when the network is already stable. - if (this.startedStabilization === false) { - this.body.emitter.emit('startStabilizing'); - this.startedStabilization = true; - } - } + var t = percentage; + var x = Math.pow(1 - t, 2) * this.from.x + 2 * t * (1 - t) * via.x + Math.pow(t, 2) * this.to.x; + var y = Math.pow(1 - t, 2) * this.from.y + 2 * t * (1 - t) * via.y + Math.pow(t, 2) * this.to.y; - this.stabilizationIterations++; - } + return { x: x, y: y }; } - }, { - key: 'updatePhysicsData', - - /** - * Nodes and edges can have the physics toggles on or off. A collection of indices is created here so we can skip the check all the time. - * - * @private - */ - value: function updatePhysicsData() { - this.physicsBody.forces = {}; - this.physicsBody.physicsNodeIndices = []; - this.physicsBody.physicsEdgeIndices = []; - var nodes = this.body.nodes; - var edges = this.body.edges; + }]); - // get node indices for physics - for (var nodeId in nodes) { - if (nodes.hasOwnProperty(nodeId)) { - if (nodes[nodeId].options.physics === true) { - this.physicsBody.physicsNodeIndices.push(nodeId); - } - } - } + return BezierEdgeStatic; + })(_utilBezierEdgeBase2['default']); - // get edge indices for physics - for (var edgeId in edges) { - if (edges.hasOwnProperty(edgeId)) { - if (edges[edgeId].options.physics === true) { - this.physicsBody.physicsEdgeIndices.push(edgeId); - } - } - } + exports['default'] = BezierEdgeStatic; + module.exports = exports['default']; - // get the velocity and the forces vector - for (var i = 0; i < this.physicsBody.physicsNodeIndices.length; i++) { - var nodeId = this.physicsBody.physicsNodeIndices[i]; - this.physicsBody.forces[nodeId] = { x: 0, y: 0 }; +/***/ }, +/* 90 */ +/***/ function(module, exports, __webpack_require__) { - // forces can be reset because they are recalculated. Velocities have to persist. - if (this.physicsBody.velocities[nodeId] === undefined) { - this.physicsBody.velocities[nodeId] = { x: 0, y: 0 }; - } - } + 'use strict'; - // clean deleted nodes from the velocity vector - for (var nodeId in this.physicsBody.velocities) { - if (nodes[nodeId] === undefined) { - delete this.physicsBody.velocities[nodeId]; - } - } - } - }, { - key: 'revert', + Object.defineProperty(exports, '__esModule', { + value: true + }); - /** - * Revert the simulation one step. This is done so after stabilization, every new start of the simulation will also say stabilized. - */ - value: function revert() { - var nodeIds = Object.keys(this.previousStates); - var nodes = this.body.nodes; - var velocities = this.physicsBody.velocities; + 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; }; })(); - for (var i = 0; i < nodeIds.length; i++) { - var nodeId = nodeIds[i]; - if (nodes[nodeId] !== undefined) { - if (nodes[nodeId].options.physics === true) { - velocities[nodeId].x = this.previousStates[nodeId].vx; - velocities[nodeId].y = this.previousStates[nodeId].vy; - nodes[nodeId].x = this.previousStates[nodeId].x; - nodes[nodeId].y = this.previousStates[nodeId].y; - } - } else { - delete this.previousStates[nodeId]; - } - } - } - }, { - key: 'moveNodes', + var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; 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); } } }; - /** - * move the nodes one timestap and check if they are stabilized - * @returns {boolean} - */ - value: function moveNodes() { - var nodesPresent = false; - var nodeIndices = this.physicsBody.physicsNodeIndices; - var maxVelocity = this.options.maxVelocity ? this.options.maxVelocity : 1000000000; - var stabilized = true; - var vminCorrected = this.options.minVelocity / Math.max(this.body.view.scale, 0.05); + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - for (var i = 0; i < nodeIndices.length; i++) { - var nodeId = nodeIndices[i]; - var nodeVelocity = this._performStep(nodeId, maxVelocity); - // stabilized is true if stabilized is true and velocity is smaller than vmin --> all nodes must be stabilized - stabilized = nodeVelocity < vminCorrected && stabilized === true; - nodesPresent = true; - } + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - if (nodesPresent === true) { - if (vminCorrected > 0.5 * this.options.maxVelocity) { - return false; - } else { - return stabilized; - } - } - return true; - } - }, { - key: '_performStep', + 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; } - /** - * Perform the actual step - * - * @param nodeId - * @param maxVelocity - * @returns {number} - * @private - */ - value: function _performStep(nodeId, maxVelocity) { - var node = this.body.nodes[nodeId]; - var timestep = this.options.timestep; - var forces = this.physicsBody.forces; - var velocities = this.physicsBody.velocities; + var _utilEdgeBase = __webpack_require__(88); - // store the state so we can revert - this.previousStates[nodeId] = { x: node.x, y: node.y, vx: velocities[nodeId].x, vy: velocities[nodeId].y }; + var _utilEdgeBase2 = _interopRequireDefault(_utilEdgeBase); - if (node.options.fixed.x === false) { - var dx = this.modelOptions.damping * velocities[nodeId].x; // damping force - var ax = (forces[nodeId].x - dx) / node.options.mass; // acceleration - velocities[nodeId].x += ax * timestep; // velocity - velocities[nodeId].x = Math.abs(velocities[nodeId].x) > maxVelocity ? velocities[nodeId].x > 0 ? maxVelocity : -maxVelocity : velocities[nodeId].x; - node.x += velocities[nodeId].x * timestep; // position - } else { - forces[nodeId].x = 0; - velocities[nodeId].x = 0; - } + var StraightEdge = (function (_EdgeBase) { + function StraightEdge(options, body, labelModule) { + _classCallCheck(this, StraightEdge); - if (node.options.fixed.y === false) { - var dy = this.modelOptions.damping * velocities[nodeId].y; // damping force - var ay = (forces[nodeId].y - dy) / node.options.mass; // acceleration - velocities[nodeId].y += ay * timestep; // velocity - velocities[nodeId].y = Math.abs(velocities[nodeId].y) > maxVelocity ? velocities[nodeId].y > 0 ? maxVelocity : -maxVelocity : velocities[nodeId].y; - node.y += velocities[nodeId].y * timestep; // position - } else { - forces[nodeId].y = 0; - velocities[nodeId].y = 0; - } + _get(Object.getPrototypeOf(StraightEdge.prototype), 'constructor', this).call(this, options, body, labelModule); + } - var totalVelocity = Math.sqrt(Math.pow(velocities[nodeId].x, 2) + Math.pow(velocities[nodeId].y, 2)); - return totalVelocity; - } - }, { - key: 'calculateForces', + _inherits(StraightEdge, _EdgeBase); - /** - * calculate the forces for one physics iteration. - */ - value: function calculateForces() { - this.gravitySolver.solve(); - this.nodesSolver.solve(); - this.edgesSolver.solve(); - } - }, { - key: '_freezeNodes', + _createClass(StraightEdge, [{ + key: '_line', /** - * When initializing and stabilizing, we can freeze nodes with a predefined position. This greatly speeds up stabilization - * because only the supportnodes for the smoothCurves have to settle. - * + * Draw a line between two nodes + * @param {CanvasRenderingContext2D} ctx * @private */ - value: function _freezeNodes() { - var nodes = this.body.nodes; - for (var id in nodes) { - if (nodes.hasOwnProperty(id)) { - if (nodes[id].x && nodes[id].y) { - this.freezeCache[id] = { x: nodes[id].options.fixed.x, y: nodes[id].options.fixed.y }; - nodes[id].options.fixed.x = true; - nodes[id].options.fixed.y = true; - } - } - } + value: function _line(ctx) { + // draw a straight line + ctx.beginPath(); + ctx.moveTo(this.from.x, this.from.y); + ctx.lineTo(this.to.x, this.to.y); + // draw shadow if enabled + this.enableShadow(ctx); + ctx.stroke(); + this.disableShadow(ctx); + return undefined; } }, { - key: '_restoreFrozenNodes', + key: 'getPoint', /** - * Unfreezes the nodes that have been frozen by _freezeDefinedNodes. - * + * Combined function of pointOnLine and pointOnBezier. This gives the coordinates of a point on the line at a certain percentage of the way + * @param percentage + * @param via + * @returns {{x: number, y: number}} * @private */ - value: function _restoreFrozenNodes() { - var nodes = this.body.nodes; - for (var id in nodes) { - if (nodes.hasOwnProperty(id)) { - if (this.freezeCache[id] !== undefined) { - nodes[id].options.fixed.x = this.freezeCache[id].x; - nodes[id].options.fixed.y = this.freezeCache[id].y; - } - } - } - this.freezeCache = {}; + value: function getPoint(percentage) { + return { + x: (1 - percentage) * this.from.x + percentage * this.to.x, + y: (1 - percentage) * this.from.y + percentage * this.to.y + }; } }, { - key: 'stabilize', - - /** - * Find a stable position for all nodes - * @private - */ - value: function stabilize() { - var _this3 = this; - - var iterations = arguments[0] === undefined ? this.options.stabilization.iterations : arguments[0]; - - if (typeof iterations !== 'number') { - console.log('The stabilize method needs a numeric amount of iterations. Switching to default: ', this.options.stabilization.iterations); - iterations = this.options.stabilization.iterations; + key: '_findBorderPosition', + value: function _findBorderPosition(nearNode, ctx) { + var node1 = this.to; + var node2 = this.from; + if (nearNode.id === this.from.id) { + node1 = this.from; + node2 = this.to; } - // this sets the width of all nodes initially which could be required for the avoidOverlap - this.body.emitter.emit('_resizeNodes'); - - // stop the render loop - this.stopSimulation(); - - // set stabilze to false - this.stabilized = false; - - // block redraw requests - this.body.emitter.emit('_blockRedrawRequests'); - this.targetIterations = iterations; - - // start the stabilization - if (this.options.stabilization.onlyDynamicEdges === true) { - this._freezeNodes(); - } - this.stabilizationIterations = 0; + var angle = Math.atan2(node1.y - node2.y, node1.x - node2.x); + var dx = node1.x - node2.x; + var dy = node1.y - node2.y; + var edgeSegmentLength = Math.sqrt(dx * dx + dy * dy); + var toBorderDist = nearNode.distanceToBorder(ctx, angle); + var toBorderPoint = (edgeSegmentLength - toBorderDist) / edgeSegmentLength; - setTimeout(function () { - return _this3._stabilizationBatch(); - }, 0); - } - }, { - key: '_stabilizationBatch', - value: function _stabilizationBatch() { - var count = 0; - while (this.stabilized === false && count < this.options.stabilization.updateInterval && this.stabilizationIterations < this.targetIterations) { - this.physicsTick(); - this.stabilizationIterations++; - count++; - } + var borderPos = {}; + borderPos.x = (1 - toBorderPoint) * node2.x + toBorderPoint * node1.x; + borderPos.y = (1 - toBorderPoint) * node2.y + toBorderPoint * node1.y; - if (this.stabilized === false && this.stabilizationIterations < this.targetIterations) { - this.body.emitter.emit('stabilizationProgress', { iterations: this.stabilizationIterations, total: this.targetIterations }); - setTimeout(this._stabilizationBatch.bind(this), 0); - } else { - this._finalizeStabilization(); - } + return borderPos; } }, { - key: '_finalizeStabilization', - value: function _finalizeStabilization() { - this.body.emitter.emit('_allowRedrawRequests'); - if (this.options.stabilization.fit === true) { - this.body.emitter.emit('fit'); - } - - if (this.options.stabilization.onlyDynamicEdges === true) { - this._restoreFrozenNodes(); - } - - this.body.emitter.emit('stabilizationIterationsDone'); - this.body.emitter.emit('_requestRedraw'); - - if (this.stabilized === true) { - this._emitStabilized(); - } else { - this.startSimulation(); - } - - this.ready = true; + key: '_getDistanceToEdge', + value: function _getDistanceToEdge(x1, y1, x2, y2, x3, y3) { + // x3,y3 is the point + return this._getDistanceToLine(x1, y1, x2, y2, x3, y3); } }]); - return PhysicsEngine; - })(); + return StraightEdge; + })(_utilEdgeBase2['default']); - exports['default'] = PhysicsEngine; + exports['default'] = StraightEdge; module.exports = exports['default']; /***/ }, @@ -34413,7 +35350,7 @@ return /******/ (function(modules) { // webpackBootstrap var _componentsNodesCluster2 = _interopRequireDefault(_componentsNodesCluster); - var util = __webpack_require__(2); + var util = __webpack_require__(4); var ClusterEngine = (function () { function ClusterEngine(body) { @@ -35031,573 +35968,125 @@ return /******/ (function(modules) { // webpackBootstrap // remove all temporary edges for (var i = 0; i < clusterNode.edges.length; i++) { - var edgeId = clusterNode.edges[i].id; - this.body.edges[edgeId].edgeType.cleanup(); - // this removes the edge from node.edges, which is why edgeIds is formed - this.body.edges[edgeId].disconnect(); - delete this.body.edges[edgeId]; - } - - // remove clusterNode - delete this.body.nodes[clusterNodeId]; - - if (refreshData === true) { - this.body.emitter.emit('_dataChanged'); - } - } - }, { - key: 'getNodesInCluster', - value: function getNodesInCluster(clusterId) { - var nodesArray = []; - if (this.isCluster(clusterId) === true) { - var containedNodes = this.body.nodes[clusterId].containedNodes; - for (var nodeId in containedNodes) { - if (containedNodes.hasOwnProperty(nodeId)) { - nodesArray.push(nodeId); - } - } - } - - return nodesArray; - } - }, { - key: 'findNode', - - /** - * Get the stack clusterId's that a certain node resides in. cluster A -> cluster B -> cluster C -> node - * @param nodeId - * @returns {Array} - * @private - */ - value: function findNode(nodeId) { - var stack = []; - var max = 100; - var counter = 0; - - while (this.clusteredNodes[nodeId] !== undefined && counter < max) { - stack.push(this.clusteredNodes[nodeId].node); - nodeId = this.clusteredNodes[nodeId].clusterId; - counter++; - } - stack.push(this.body.nodes[nodeId]); - return stack; - } - }, { - key: '_getConnectedId', - - /** - * Get the Id the node is connected to - * @param edge - * @param nodeId - * @returns {*} - * @private - */ - value: function _getConnectedId(edge, nodeId) { - if (edge.toId != nodeId) { - return edge.toId; - } else if (edge.fromId != nodeId) { - return edge.fromId; - } else { - return edge.fromId; - } - } - }, { - key: '_getHubSize', - - /** - * We determine how many connections denote an important hub. - * We take the mean + 2*std as the important hub size. (Assuming a normal distribution of data, ~2.2%) - * - * @private - */ - value: function _getHubSize() { - var average = 0; - var averageSquared = 0; - var hubCounter = 0; - var largestHub = 0; - - for (var i = 0; i < this.body.nodeIndices.length; i++) { - var node = this.body.nodes[this.body.nodeIndices[i]]; - if (node.edges.length > largestHub) { - largestHub = node.edges.length; - } - average += node.edges.length; - averageSquared += Math.pow(node.edges.length, 2); - hubCounter += 1; - } - average = average / hubCounter; - averageSquared = averageSquared / hubCounter; - - var letiance = averageSquared - Math.pow(average, 2); - var standardDeviation = Math.sqrt(letiance); - - var hubThreshold = Math.floor(average + 2 * standardDeviation); - - // always have at least one to cluster - if (hubThreshold > largestHub) { - hubThreshold = largestHub; - } - - return hubThreshold; - } - }]); - - return ClusterEngine; - })(); - - exports['default'] = ClusterEngine; - module.exports = exports['default']; - -/***/ }, -/* 100 */ -/***/ function(module, exports, __webpack_require__) { - - 'use strict'; - - Object.defineProperty(exports, '__esModule', { - value: true - }); - - var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; - - function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - - function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } - - var _Node2 = __webpack_require__(64); - - var _Node3 = _interopRequireDefault(_Node2); - - /** - * - */ - - var Cluster = (function (_Node) { - function Cluster(options, body, imagelist, grouplist, globalOptions) { - _classCallCheck(this, Cluster); - - _get(Object.getPrototypeOf(Cluster.prototype), 'constructor', this).call(this, options, body, imagelist, grouplist, globalOptions); - - this.isCluster = true; - this.containedNodes = {}; - this.containedEdges = {}; - } - - _inherits(Cluster, _Node); - - return Cluster; - })(_Node3['default']); - - exports['default'] = Cluster; - module.exports = exports['default']; - -/***/ }, -/* 101 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - - Object.defineProperty(exports, "__esModule", { - value: true - }); - - var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); - - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - - var util = __webpack_require__(2); - - var View = (function () { - function View(body, canvas) { - var _this = this; - - _classCallCheck(this, View); - - this.body = body; - this.canvas = canvas; - - this.animationSpeed = 1 / this.renderRefreshRate; - this.animationEasingFunction = "easeInOutQuint"; - this.easingTime = 0; - this.sourceScale = 0; - this.targetScale = 0; - this.sourceTranslation = 0; - this.targetTranslation = 0; - this.lockedOnNodeId = undefined; - this.lockedOnNodeOffset = undefined; - this.touchTime = 0; - - this.viewFunction = undefined; - - this.body.emitter.on("fit", this.fit.bind(this)); - this.body.emitter.on("animationFinished", function () { - _this.body.emitter.emit("_stopRendering"); - }); - this.body.emitter.on("unlockNode", this.releaseNode.bind(this)); - } - - _createClass(View, [{ - key: "setOptions", - value: function setOptions() { - var options = arguments[0] === undefined ? {} : arguments[0]; - - this.options = options; - } - }, { - key: "_getRange", - - /** - * Find the center position of the network - * @private - */ - value: function _getRange() { - var specificNodes = arguments[0] === undefined ? [] : arguments[0]; - - var minY = 1000000000, - maxY = -1000000000, - minX = 1000000000, - maxX = -1000000000, - node; - if (specificNodes.length > 0) { - for (var i = 0; i < specificNodes.length; i++) { - node = this.body.nodes[specificNodes[i]]; - if (minX > node.shape.boundingBox.left) { - minX = node.shape.boundingBox.left; - } - if (maxX < node.shape.boundingBox.right) { - maxX = node.shape.boundingBox.right; - } - if (minY > node.shape.boundingBox.top) { - minY = node.shape.boundingBox.top; - } // top is negative, bottom is positive - if (maxY < node.shape.boundingBox.bottom) { - maxY = node.shape.boundingBox.bottom; - } // top is negative, bottom is positive - } - } else { - for (var nodeId in this.body.nodes) { - if (this.body.nodes.hasOwnProperty(nodeId)) { - node = this.body.nodes[nodeId]; - if (minX > node.shape.boundingBox.left) { - minX = node.shape.boundingBox.left; - } - if (maxX < node.shape.boundingBox.right) { - maxX = node.shape.boundingBox.right; - } - if (minY > node.shape.boundingBox.top) { - minY = node.shape.boundingBox.top; - } // top is negative, bottom is positive - if (maxY < node.shape.boundingBox.bottom) { - maxY = node.shape.boundingBox.bottom; - } // top is negative, bottom is positive - } - } - } - - if (minX === 1000000000 && maxX === -1000000000 && minY === 1000000000 && maxY === -1000000000) { - minY = 0, maxY = 0, minX = 0, maxX = 0; - } - return { minX: minX, maxX: maxX, minY: minY, maxY: maxY }; - } - }, { - key: "_findCenter", - - /** - * @param {object} range = {minX: minX, maxX: maxX, minY: minY, maxY: maxY}; - * @returns {{x: number, y: number}} - * @private - */ - value: function _findCenter(range) { - return { x: 0.5 * (range.maxX + range.minX), - y: 0.5 * (range.maxY + range.minY) }; - } - }, { - key: "fit", - - /** - * This function zooms out to fit all data on screen based on amount of nodes - * @param {Object} Options - * @param {Boolean} [initialZoom] | zoom based on fitted formula or range, true = fitted, default = false; - */ - value: function fit() { - var options = arguments[0] === undefined ? { nodes: [] } : arguments[0]; - var initialZoom = arguments[1] === undefined ? false : arguments[1]; - - var range; - var zoomLevel; - - if (initialZoom === true) { - // check if more than half of the nodes have a predefined position. If so, we use the range, not the approximation. - var positionDefined = 0; - for (var nodeId in this.body.nodes) { - if (this.body.nodes.hasOwnProperty(nodeId)) { - var node = this.body.nodes[nodeId]; - if (node.predefinedPosition === true) { - positionDefined += 1; - } - } - } - if (positionDefined > 0.5 * this.body.nodeIndices.length) { - this.fit(options, false); - return; - } - - range = this._getRange(options.nodes); - - var numberOfNodes = this.body.nodeIndices.length; - zoomLevel = 12.662 / (numberOfNodes + 7.4147) + 0.0964822; // this is obtained from fitting a dataset from 5 points with scale levels that looked good. - - // correct for larger canvasses. - var factor = Math.min(this.canvas.frame.canvas.clientWidth / 600, this.canvas.frame.canvas.clientHeight / 600); - zoomLevel *= factor; - } else { - this.body.emitter.emit("_resizeNodes"); - range = this._getRange(options.nodes); - var xDistance = Math.abs(range.maxX - range.minX) * 1.1; - var yDistance = Math.abs(range.maxY - range.minY) * 1.1; - - var xZoomLevel = this.canvas.frame.canvas.clientWidth / xDistance; - var yZoomLevel = this.canvas.frame.canvas.clientHeight / yDistance; - - zoomLevel = xZoomLevel <= yZoomLevel ? xZoomLevel : yZoomLevel; - } - - if (zoomLevel > 1) { - zoomLevel = 1; - } else if (zoomLevel === 0) { - zoomLevel = 1; - } - - var center = this._findCenter(range); - var animationOptions = { position: center, scale: zoomLevel, animation: options.animation }; - this.moveTo(animationOptions); - } - }, { - key: "focus", - - // animation - - /** - * Center a node in view. - * - * @param {Number} nodeId - * @param {Number} [options] - */ - value: function focus(nodeId) { - var options = arguments[1] === undefined ? {} : arguments[1]; - - if (this.body.nodes[nodeId] !== undefined) { - var nodePosition = { x: this.body.nodes[nodeId].x, y: this.body.nodes[nodeId].y }; - options.position = nodePosition; - options.lockedOnNode = nodeId; - - this.moveTo(options); - } else { - console.log("Node: " + nodeId + " cannot be found."); - } - } - }, { - key: "moveTo", - - /** - * - * @param {Object} options | options.offset = {x:Number, y:Number} // offset from the center in DOM pixels - * | options.scale = Number // scale to move to - * | options.position = {x:Number, y:Number} // position to move to - * | options.animation = {duration:Number, easingFunction:String} || Boolean // position to move to - */ - value: function moveTo(options) { - if (options === undefined) { - options = {}; - return; - } - if (options.offset === undefined) { - options.offset = { x: 0, y: 0 }; - } - if (options.offset.x === undefined) { - options.offset.x = 0; - } - if (options.offset.y === undefined) { - options.offset.y = 0; - } - if (options.scale === undefined) { - options.scale = this.body.view.scale; - } - if (options.position === undefined) { - options.position = this.getViewPosition(); - } - if (options.animation === undefined) { - options.animation = { duration: 0 }; - } - if (options.animation === false) { - options.animation = { duration: 0 }; - } - if (options.animation === true) { - options.animation = {}; - } - if (options.animation.duration === undefined) { - options.animation.duration = 1000; - } // default duration - if (options.animation.easingFunction === undefined) { - options.animation.easingFunction = "easeInOutQuad"; - } // default easing function - - this.animateView(options); - } - }, { - key: "animateView", - - /** - * - * @param {Object} options | options.offset = {x:Number, y:Number} // offset from the center in DOM pixels - * | options.time = Number // animation time in milliseconds - * | options.scale = Number // scale to animate to - * | options.position = {x:Number, y:Number} // position to animate to - * | options.easingFunction = String // linear, easeInQuad, easeOutQuad, easeInOutQuad, - * // easeInCubic, easeOutCubic, easeInOutCubic, - * // easeInQuart, easeOutQuart, easeInOutQuart, - * // easeInQuint, easeOutQuint, easeInOutQuint - */ - value: function animateView(options) { - if (options === undefined) { - return; - } - this.animationEasingFunction = options.animation.easingFunction; - // release if something focussed on the node - this.releaseNode(); - if (options.locked === true) { - this.lockedOnNodeId = options.lockedOnNode; - this.lockedOnNodeOffset = options.offset; - } - - // forcefully complete the old animation if it was still running - if (this.easingTime != 0) { - this._transitionRedraw(true); // by setting easingtime to 1, we finish the animation. - } - - this.sourceScale = this.body.view.scale; - this.sourceTranslation = this.body.view.translation; - this.targetScale = options.scale; - - // set the scale so the viewCenter is based on the correct zoom level. This is overridden in the transitionRedraw - // but at least then we'll have the target transition - this.body.view.scale = this.targetScale; - var viewCenter = this.canvas.DOMtoCanvas({ x: 0.5 * this.canvas.frame.canvas.clientWidth, y: 0.5 * this.canvas.frame.canvas.clientHeight }); - - var distanceFromCenter = { // offset from view, distance view has to change by these x and y to center the node - x: viewCenter.x - options.position.x, - y: viewCenter.y - options.position.y - }; - this.targetTranslation = { - x: this.sourceTranslation.x + distanceFromCenter.x * this.targetScale + options.offset.x, - y: this.sourceTranslation.y + distanceFromCenter.y * this.targetScale + options.offset.y - }; - - // if the time is set to 0, don't do an animation - if (options.animation.duration === 0) { - if (this.lockedOnNodeId != undefined) { - this.viewFunction = this._lockedRedraw.bind(this); - this.body.emitter.on("initRedraw", this.viewFunction); - } else { - this.body.view.scale = this.targetScale; - this.body.view.translation = this.targetTranslation; - this.body.emitter.emit("_requestRedraw"); - } - } else { - this.animationSpeed = 1 / (60 * options.animation.duration * 0.001) || 1 / 60; // 60 for 60 seconds, 0.001 for milli's - this.animationEasingFunction = options.animation.easingFunction; + var edgeId = clusterNode.edges[i].id; + this.body.edges[edgeId].edgeType.cleanup(); + // this removes the edge from node.edges, which is why edgeIds is formed + this.body.edges[edgeId].disconnect(); + delete this.body.edges[edgeId]; + } - this.viewFunction = this._transitionRedraw.bind(this); - this.body.emitter.on("initRedraw", this.viewFunction); - this.body.emitter.emit("_startRendering"); + // remove clusterNode + delete this.body.nodes[clusterNodeId]; + + if (refreshData === true) { + this.body.emitter.emit('_dataChanged'); } } }, { - key: "_lockedRedraw", + key: 'getNodesInCluster', + value: function getNodesInCluster(clusterId) { + var nodesArray = []; + if (this.isCluster(clusterId) === true) { + var containedNodes = this.body.nodes[clusterId].containedNodes; + for (var nodeId in containedNodes) { + if (containedNodes.hasOwnProperty(nodeId)) { + nodesArray.push(nodeId); + } + } + } + + return nodesArray; + } + }, { + key: 'findNode', /** - * used to animate smoothly by hijacking the redraw function. - * @private - */ - value: function _lockedRedraw() { - var nodePosition = { x: this.body.nodes[this.lockedOnNodeId].x, y: this.body.nodes[this.lockedOnNodeId].y }; - var viewCenter = this.DOMtoCanvas({ x: 0.5 * this.frame.canvas.clientWidth, y: 0.5 * this.frame.canvas.clientHeight }); - var distanceFromCenter = { // offset from view, distance view has to change by these x and y to center the node - x: viewCenter.x - nodePosition.x, - y: viewCenter.y - nodePosition.y - }; - var sourceTranslation = this.body.view.translation; - var targetTranslation = { - x: sourceTranslation.x + distanceFromCenter.x * this.body.view.scale + this.lockedOnNodeOffset.x, - y: sourceTranslation.y + distanceFromCenter.y * this.body.view.scale + this.lockedOnNodeOffset.y - }; + * Get the stack clusterId's that a certain node resides in. cluster A -> cluster B -> cluster C -> node + * @param nodeId + * @returns {Array} + * @private + */ + value: function findNode(nodeId) { + var stack = []; + var max = 100; + var counter = 0; - this.body.view.translation = targetTranslation; + while (this.clusteredNodes[nodeId] !== undefined && counter < max) { + stack.push(this.clusteredNodes[nodeId].node); + nodeId = this.clusteredNodes[nodeId].clusterId; + counter++; + } + stack.push(this.body.nodes[nodeId]); + return stack; } }, { - key: "releaseNode", - value: function releaseNode() { - if (this.lockedOnNodeId !== undefined && this.viewFunction !== undefined) { - this.body.emitter.off("initRedraw", this.viewFunction); - this.lockedOnNodeId = undefined; - this.lockedOnNodeOffset = undefined; + key: '_getConnectedId', + + /** + * Get the Id the node is connected to + * @param edge + * @param nodeId + * @returns {*} + * @private + */ + value: function _getConnectedId(edge, nodeId) { + if (edge.toId != nodeId) { + return edge.toId; + } else if (edge.fromId != nodeId) { + return edge.fromId; + } else { + return edge.fromId; } } }, { - key: "_transitionRedraw", + key: '_getHubSize', /** - * - * @param easingTime - * @private - */ - value: function _transitionRedraw() { - var finished = arguments[0] === undefined ? false : arguments[0]; + * We determine how many connections denote an important hub. + * We take the mean + 2*std as the important hub size. (Assuming a normal distribution of data, ~2.2%) + * + * @private + */ + value: function _getHubSize() { + var average = 0; + var averageSquared = 0; + var hubCounter = 0; + var largestHub = 0; - this.easingTime += this.animationSpeed; - this.easingTime = finished === true ? 1 : this.easingTime; + for (var i = 0; i < this.body.nodeIndices.length; i++) { + var node = this.body.nodes[this.body.nodeIndices[i]]; + if (node.edges.length > largestHub) { + largestHub = node.edges.length; + } + average += node.edges.length; + averageSquared += Math.pow(node.edges.length, 2); + hubCounter += 1; + } + average = average / hubCounter; + averageSquared = averageSquared / hubCounter; - var progress = util.easingFunctions[this.animationEasingFunction](this.easingTime); + var letiance = averageSquared - Math.pow(average, 2); + var standardDeviation = Math.sqrt(letiance); - this.body.view.scale = this.sourceScale + (this.targetScale - this.sourceScale) * progress; - this.body.view.translation = { - x: this.sourceTranslation.x + (this.targetTranslation.x - this.sourceTranslation.x) * progress, - y: this.sourceTranslation.y + (this.targetTranslation.y - this.sourceTranslation.y) * progress - }; + var hubThreshold = Math.floor(average + 2 * standardDeviation); - // cleanup - if (this.easingTime >= 1) { - this.body.emitter.off("initRedraw", this.viewFunction); - this.easingTime = 0; - if (this.lockedOnNodeId != undefined) { - this.viewFunction = this._lockedRedraw.bind(this); - this.body.emitter.on("initRedraw", this.viewFunction); - } - this.body.emitter.emit("animationFinished"); + // always have at least one to cluster + if (hubThreshold > largestHub) { + hubThreshold = largestHub; } - } - }, { - key: "getScale", - value: function getScale() { - return this.body.view.scale; - } - }, { - key: "getViewPosition", - value: function getViewPosition() { - return this.canvas.DOMtoCanvas({ x: 0.5 * this.canvas.frame.canvas.clientWidth, y: 0.5 * this.canvas.frame.canvas.clientHeight }); + + return hubThreshold; } }]); - return View; + return ClusterEngine; })(); - exports["default"] = View; - module.exports = exports["default"]; + exports['default'] = ClusterEngine; + module.exports = exports['default']; /***/ }, -/* 102 */ +/* 100 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -35606,1924 +36095,2295 @@ return /******/ (function(modules) { // webpackBootstrap value: true }); - var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - var _componentsNavigationHandler = __webpack_require__(103); + 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 _componentsNavigationHandler2 = _interopRequireDefault(_componentsNavigationHandler); + var _Node2 = __webpack_require__(65); - var _componentsPopup = __webpack_require__(104); + var _Node3 = _interopRequireDefault(_Node2); - var _componentsPopup2 = _interopRequireDefault(_componentsPopup); + /** + * + */ - var util = __webpack_require__(2); + var Cluster = (function (_Node) { + function Cluster(options, body, imagelist, grouplist, globalOptions) { + _classCallCheck(this, Cluster); - var InteractionHandler = (function () { - function InteractionHandler(body, canvas, selectionHandler) { - _classCallCheck(this, InteractionHandler); + _get(Object.getPrototypeOf(Cluster.prototype), 'constructor', this).call(this, options, body, imagelist, grouplist, globalOptions); - this.body = body; - this.canvas = canvas; - this.selectionHandler = selectionHandler; - this.navigationHandler = new _componentsNavigationHandler2['default'](body, canvas); + this.isCluster = true; + this.containedNodes = {}; + this.containedEdges = {}; + } - // bind the events from hammer to functions in this object - this.body.eventListeners.onTap = this.onTap.bind(this); - this.body.eventListeners.onTouch = this.onTouch.bind(this); - this.body.eventListeners.onDoubleTap = this.onDoubleTap.bind(this); - this.body.eventListeners.onHold = this.onHold.bind(this); - this.body.eventListeners.onDragStart = this.onDragStart.bind(this); - this.body.eventListeners.onDrag = this.onDrag.bind(this); - this.body.eventListeners.onDragEnd = this.onDragEnd.bind(this); - this.body.eventListeners.onMouseWheel = this.onMouseWheel.bind(this); - this.body.eventListeners.onPinch = this.onPinch.bind(this); - this.body.eventListeners.onMouseMove = this.onMouseMove.bind(this); - this.body.eventListeners.onRelease = this.onRelease.bind(this); - this.body.eventListeners.onContext = this.onContext.bind(this); + _inherits(Cluster, _Node); - this.touchTime = 0; - this.drag = {}; - this.pinch = {}; - this.popup = undefined; - this.popupObj = undefined; - this.popupTimer = undefined; + return Cluster; + })(_Node3['default']); - this.body.functions.getPointer = this.getPointer.bind(this); + exports['default'] = Cluster; + module.exports = exports['default']; + +/***/ }, +/* 101 */ +/***/ 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'); } } + + if (typeof window !== 'undefined') { + window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame; + } + var util = __webpack_require__(4); + + var CanvasRenderer = (function () { + function CanvasRenderer(body, canvas) { + _classCallCheck(this, CanvasRenderer); + + this.body = body; + this.canvas = canvas; + + this.redrawRequested = false; + this.renderTimer = undefined; + this.requiresTimeout = true; + this.renderingActive = false; + this.renderRequests = 0; + this.pixelRatio = undefined; + this.allowRedrawRequests = true; + + this.dragging = false; this.options = {}; this.defaultOptions = { - dragNodes: true, - dragView: true, - hover: false, - keyboard: { - enabled: false, - speed: { x: 10, y: 10, zoom: 0.02 }, - bindToWindow: true - }, - navigationButtons: false, - tooltipDelay: 300, - zoomView: true + hideEdgesOnDrag: false, + hideNodesOnDrag: false }; util.extend(this.options, this.defaultOptions); + this._determineBrowserMethod(); this.bindEventListeners(); } - _createClass(InteractionHandler, [{ + _createClass(CanvasRenderer, [{ key: 'bindEventListeners', value: function bindEventListeners() { var _this = this; + this.body.emitter.on('dragStart', function () { + _this.dragging = true; + }); + this.body.emitter.on('dragEnd', function () { + return _this.dragging = false; + }); + this.body.emitter.on('_resizeNodes', function () { + return _this._resizeNodes(); + }); + this.body.emitter.on('_redraw', function () { + if (_this.renderingActive === false) { + _this._redraw(); + } + }); + this.body.emitter.on('_blockRedrawRequests', function () { + _this.allowRedrawRequests = false; + }); + this.body.emitter.on('_allowRedrawRequests', function () { + _this.allowRedrawRequests = true; + }); + this.body.emitter.on('_requestRedraw', this._requestRedraw.bind(this)); + this.body.emitter.on('_startRendering', function () { + _this.renderRequests += 1; + _this.renderingActive = true; + _this._startRendering(); + }); + this.body.emitter.on('_stopRendering', function () { + _this.renderRequests -= 1; + _this.renderingActive = _this.renderRequests > 0; + _this.renderTimer = undefined; + }); this.body.emitter.on('destroy', function () { - clearTimeout(_this.popupTimer); - delete _this.body.functions.getPointer; + _this.renderRequests = 0; + _this.renderingActive = false; + if (_this.requiresTimeout === true) { + clearTimeout(_this.renderTimer); + } else { + cancelAnimationFrame(_this.renderTimer); + } + _this.body.emitter.off(); }); } }, { key: 'setOptions', value: function setOptions(options) { if (options !== undefined) { - // extend all but the values in fields - var fields = ['hideEdgesOnDrag', 'hideNodesOnDrag', 'keyboard', 'multiselect', 'selectable', 'selectConnectedEdges']; - util.selectiveNotDeepExtend(fields, this.options, options); - - // merge the keyboard options in. - util.mergeOptions(this.options, options, 'keyboard'); - - if (options.tooltip) { - util.extend(this.options.tooltip, options.tooltip); - if (options.tooltip.color) { - this.options.tooltip.color = util.parseColor(options.tooltip.color); - } - } - } - - this.navigationHandler.setOptions(this.options); - } - }, { - key: 'getPointer', - - /** - * Get the pointer location from a touch location - * @param {{x: Number, y: Number}} touch - * @return {{x: Number, y: Number}} pointer - * @private - */ - value: function getPointer(touch) { - return { - x: touch.x - util.getAbsoluteLeft(this.canvas.frame.canvas), - y: touch.y - util.getAbsoluteTop(this.canvas.frame.canvas) - }; - } - }, { - key: 'onTouch', - - /** - * On start of a touch gesture, store the pointer - * @param event - * @private - */ - value: function onTouch(event) { - if (new Date().valueOf() - this.touchTime > 50) { - this.drag.pointer = this.getPointer(event.center); - this.drag.pinched = false; - this.pinch.scale = this.body.view.scale; - // to avoid double fireing of this event because we have two hammer instances. (on canvas and on frame) - this.touchTime = new Date().valueOf(); - } - } - }, { - key: 'onTap', - - /** - * handle tap/click event: select/unselect a node - * @private - */ - value: function onTap(event) { - var pointer = this.getPointer(event.center); - var multiselect = this.selectionHandler.options.multiselect && (event.changedPointers[0].ctrlKey || event.changedPointers[0].metaKey); - - this.checkSelectionChanges(pointer, event, multiselect); - this.selectionHandler._generateClickEvent('click', event, pointer); - } - }, { - key: 'onDoubleTap', - - /** - * handle doubletap event - * @private - */ - value: function onDoubleTap(event) { - var pointer = this.getPointer(event.center); - this.selectionHandler._generateClickEvent('doubleClick', event, pointer); - } - }, { - key: 'onHold', - - /** - * handle long tap event: multi select nodes - * @private - */ - value: function onHold(event) { - var pointer = this.getPointer(event.center); - var multiselect = this.selectionHandler.options.multiselect; - - this.checkSelectionChanges(pointer, event, multiselect); - - this.selectionHandler._generateClickEvent('click', event, pointer); - this.selectionHandler._generateClickEvent('hold', event, pointer); - } - }, { - key: 'onRelease', - - /** - * handle the release of the screen - * - * @private - */ - value: function onRelease(event) { - if (new Date().valueOf() - this.touchTime > 10) { - var pointer = this.getPointer(event.center); - this.selectionHandler._generateClickEvent('release', event, pointer); - // to avoid double fireing of this event because we have two hammer instances. (on canvas and on frame) - this.touchTime = new Date().valueOf(); + var fields = ['hideEdgesOnDrag', 'hideNodesOnDrag']; + util.selectiveDeepExtend(fields, this.options, options); } } }, { - key: 'onContext', - value: function onContext(event) { - var pointer = this.getPointer({ x: event.clientX, y: event.clientY }); - this.selectionHandler._generateClickEvent('oncontext', event, pointer); - } - }, { - key: 'checkSelectionChanges', - - /** - * - * @param pointer - * @param add - */ - value: function checkSelectionChanges(pointer, event) { - var add = arguments[2] === undefined ? false : arguments[2]; - - var previouslySelectedEdgeCount = this.selectionHandler._getSelectedEdgeCount(); - var previouslySelectedNodeCount = this.selectionHandler._getSelectedNodeCount(); - var previousSelection = this.selectionHandler.getSelection(); - var selected = undefined; - if (add === true) { - selected = this.selectionHandler.selectAdditionalOnPoint(pointer); - } else { - selected = this.selectionHandler.selectOnPoint(pointer); - } - var selectedEdgesCount = this.selectionHandler._getSelectedEdgeCount(); - var selectedNodesCount = this.selectionHandler._getSelectedNodeCount(); - var currentSelection = this.selectionHandler.getSelection(); - - var _determineIfDifferent = this._determineIfDifferent(previousSelection, currentSelection); - - var nodesChanges = _determineIfDifferent.nodesChanges; - var edgesChanges = _determineIfDifferent.edgesChanges; - - if (selectedNodesCount - previouslySelectedNodeCount > 0) { - // node was selected - this.selectionHandler._generateClickEvent('selectNode', event, pointer); - selected = true; - } else if (selectedNodesCount - previouslySelectedNodeCount < 0) { - // node was deselected - this.selectionHandler._generateClickEvent('deselectNode', event, pointer, previousSelection); - selected = true; - } else if (selectedNodesCount === previouslySelectedNodeCount && nodesChanges === true) { - this.selectionHandler._generateClickEvent('deselectNode', event, pointer, previousSelection); - this.selectionHandler._generateClickEvent('selectNode', event, pointer); - selected = true; - } - - if (selectedEdgesCount - previouslySelectedEdgeCount > 0) { - // edge was selected - this.selectionHandler._generateClickEvent('selectEdge', event, pointer); - selected = true; - } else if (selectedEdgesCount - previouslySelectedEdgeCount < 0) { - // edge was deselected - this.selectionHandler._generateClickEvent('deselectEdge', event, pointer, previousSelection); - selected = true; - } else if (selectedEdgesCount === previouslySelectedEdgeCount && edgesChanges === true) { - this.selectionHandler._generateClickEvent('deselectEdge', event, pointer, previousSelection); - this.selectionHandler._generateClickEvent('selectEdge', event, pointer); - selected = true; - } - - if (selected === true) { - // select or unselect - this.selectionHandler._generateClickEvent('select', event, pointer); + key: '_startRendering', + value: function _startRendering() { + if (this.renderingActive === true) { + if (this.renderTimer === undefined) { + if (this.requiresTimeout === true) { + this.renderTimer = window.setTimeout(this._renderStep.bind(this), this.simulationInterval); // wait this.renderTimeStep milliseconds and perform the animation step function + } else { + this.renderTimer = window.requestAnimationFrame(this._renderStep.bind(this)); // wait this.renderTimeStep milliseconds and perform the animation step function + } + } } } }, { - key: '_determineIfDifferent', - - /** - * This function checks if the nodes and edges previously selected have changed. - * @param previousSelection - * @param currentSelection - * @returns {{nodesChanges: boolean, edgesChanges: boolean}} - * @private - */ - value: function _determineIfDifferent(previousSelection, currentSelection) { - var nodesChanges = false; - var edgesChanges = false; + key: '_renderStep', + value: function _renderStep() { + if (this.renderingActive === true) { + // reset the renderTimer so a new scheduled animation step can be set + this.renderTimer = undefined; - for (var i = 0; i < previousSelection.nodes.length; i++) { - if (currentSelection.nodes.indexOf(previousSelection.nodes[i]) === -1) { - nodesChanges = true; - } - } - for (var i = 0; i < currentSelection.nodes.length; i++) { - if (previousSelection.nodes.indexOf(previousSelection.nodes[i]) === -1) { - nodesChanges = true; - } - } - for (var i = 0; i < previousSelection.edges.length; i++) { - if (currentSelection.edges.indexOf(previousSelection.edges[i]) === -1) { - edgesChanges = true; + if (this.requiresTimeout === true) { + // this schedules a new simulation step + this._startRendering(); } - } - for (var i = 0; i < currentSelection.edges.length; i++) { - if (previousSelection.edges.indexOf(previousSelection.edges[i]) === -1) { - edgesChanges = true; + + this._redraw(); + + if (this.requiresTimeout === false) { + // this schedules a new simulation step + this._startRendering(); } } + } + }, { + key: 'redraw', - return { nodesChanges: nodesChanges, edgesChanges: edgesChanges }; + /** + * Redraw the network with the current data + * chart will be resized too. + */ + value: function redraw() { + this.body.emitter.emit('setSize'); + this._redraw(); } }, { - key: 'onDragStart', + key: '_requestRedraw', /** - * This function is called by onDragStart. - * It is separated out because we can then overload it for the datamanipulation system. - * + * Redraw the network with the current data + * @param hidden | used to get the first estimate of the node sizes. only the nodes are drawn after which they are quickly drawn over. * @private */ - value: function onDragStart(event) { - //in case the touch event was triggered on an external div, do the initial touch now. - if (this.drag.pointer === undefined) { - this.onTouch(event); + value: function _requestRedraw() { + var _this2 = this; + + if (this.redrawRequested !== true && this.renderingActive === false && this.allowRedrawRequests === true) { + this.redrawRequested = true; + if (this.requiresTimeout === true) { + window.setTimeout(function () { + _this2._redraw(false); + }, 0); + } else { + window.requestAnimationFrame(function () { + _this2._redraw(false); + }); + } } + } + }, { + key: '_redraw', + value: function _redraw() { + var hidden = arguments[0] === undefined ? false : arguments[0]; - // note: drag.pointer is set in onTouch to get the initial touch location - var node = this.selectionHandler.getNodeAt(this.drag.pointer); + this.body.emitter.emit('initRedraw'); - this.drag.dragging = true; - this.drag.selection = []; - this.drag.translation = util.extend({}, this.body.view.translation); // copy the object - this.drag.nodeId = undefined; + this.redrawRequested = false; + var ctx = this.canvas.frame.canvas.getContext('2d'); - this.selectionHandler._generateClickEvent('dragStart', event, this.drag.pointer); + // when the container div was hidden, this fixes it back up! + if (this.canvas.frame.canvas.width === 0 || this.canvas.frame.canvas.height === 0) { + this.canvas.setSize(); + } - if (node !== undefined && this.options.dragNodes === true) { - this.drag.nodeId = node.id; - // select the clicked node if not yet selected - if (node.isSelected() === false) { - this.selectionHandler.unselectAll(); - this.selectionHandler.selectObject(node); - } + if (this.pixelRatio === undefined) { + this.pixelRatio = (window.devicePixelRatio || 1) / (ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1); + } - var selection = this.selectionHandler.selectionObj.nodes; - // create an array with the selected nodes and their original location and status - for (var nodeId in selection) { - if (selection.hasOwnProperty(nodeId)) { - var object = selection[nodeId]; - var s = { - id: object.id, - node: object, + ctx.setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0); - // store original x, y, xFixed and yFixed, make the node temporarily Fixed - x: object.x, - y: object.y, - xFixed: object.options.fixed.x, - yFixed: object.options.fixed.y - }; + // clear the canvas + var w = this.canvas.frame.canvas.clientWidth; + var h = this.canvas.frame.canvas.clientHeight; + ctx.clearRect(0, 0, w, h); - object.options.fixed.x = true; - object.options.fixed.y = true; + // set scaling and translation + ctx.save(); + ctx.translate(this.body.view.translation.x, this.body.view.translation.y); + ctx.scale(this.body.view.scale, this.body.view.scale); - this.drag.selection.push(s); - } + ctx.beginPath(); + this.body.emitter.emit('beforeDrawing', ctx); + ctx.closePath(); + + if (hidden === false) { + if (this.dragging === false || this.dragging === true && this.options.hideEdgesOnDrag === false) { + this._drawEdges(ctx); } } + + if (this.dragging === false || this.dragging === true && this.options.hideNodesOnDrag === false) { + this._drawNodes(ctx, hidden); + } + + if (this.controlNodesActive === true) { + this._drawControlNodes(ctx); + } + + ctx.beginPath(); + //this.physics.nodesSolver._debug(ctx,"#F00F0F"); + this.body.emitter.emit('afterDrawing', ctx); + ctx.closePath(); + // restore original scaling and translation + ctx.restore(); + + if (hidden === true) { + ctx.clearRect(0, 0, w, h); + } } }, { - key: 'onDrag', + key: '_resizeNodes', /** - * handle drag event + * Redraw all nodes + * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d'); + * @param {CanvasRenderingContext2D} ctx + * @param {Boolean} [alwaysShow] * @private */ - value: function onDrag(event) { - var _this2 = this; - - if (this.drag.pinched === true) { - return; + value: function _resizeNodes() { + var ctx = this.canvas.frame.canvas.getContext('2d'); + if (this.pixelRatio === undefined) { + this.pixelRatio = (window.devicePixelRatio || 1) / (ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1); } + ctx.setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0); + ctx.save(); + ctx.translate(this.body.view.translation.x, this.body.view.translation.y); + ctx.scale(this.body.view.scale, this.body.view.scale); - // remove the focus on node if it is focussed on by the focusOnNode - this.body.emitter.emit('unlockNode'); + var nodes = this.body.nodes; + var node = undefined; - var pointer = this.getPointer(event.center); + // resize all nodes + for (var nodeId in nodes) { + if (nodes.hasOwnProperty(nodeId)) { + node = nodes[nodeId]; + node.resize(ctx); + node.updateBoundingBox(ctx); + } + } - this.selectionHandler._generateClickEvent('dragging', event, pointer); + // restore original scaling and translation + ctx.restore(); + } + }, { + key: '_drawNodes', - var selection = this.drag.selection; - if (selection && selection.length && this.options.dragNodes === true) { - (function () { - // calculate delta's and new location - var deltaX = pointer.x - _this2.drag.pointer.x; - var deltaY = pointer.y - _this2.drag.pointer.y; + /** + * Redraw all nodes + * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d'); + * @param {CanvasRenderingContext2D} ctx + * @param {Boolean} [alwaysShow] + * @private + */ + value: function _drawNodes(ctx) { + var alwaysShow = arguments[1] === undefined ? false : arguments[1]; - // update position of all selected nodes - selection.forEach(function (selection) { - var node = selection.node; - // only move the node if it was not fixed initially - if (selection.xFixed === false) { - node.x = _this2.canvas._XconvertDOMtoCanvas(_this2.canvas._XconvertCanvasToDOM(selection.x) + deltaX); - } - // only move the node if it was not fixed initially - if (selection.yFixed === false) { - node.y = _this2.canvas._YconvertDOMtoCanvas(_this2.canvas._YconvertCanvasToDOM(selection.y) + deltaY); - } - }); + var nodes = this.body.nodes; + var nodeIndices = this.body.nodeIndices; + var node = undefined; + var selected = []; + var margin = 20; + var topLeft = this.canvas.DOMtoCanvas({ x: -margin, y: -margin }); + var bottomRight = this.canvas.DOMtoCanvas({ + x: this.canvas.frame.canvas.clientWidth + margin, + y: this.canvas.frame.canvas.clientHeight + margin + }); + var viewableArea = { top: topLeft.y, left: topLeft.x, bottom: bottomRight.y, right: bottomRight.x }; - // start the simulation of the physics - _this2.body.emitter.emit('startSimulation'); - })(); - } else { - // move the network - if (this.options.dragView === true) { - // if the drag was not started properly because the click started outside the network div, start it now. - if (this.drag.pointer === undefined) { - this._handleDragStart(event); - return; + // draw unselected nodes; + for (var i = 0; i < nodeIndices.length; i++) { + node = nodes[nodeIndices[i]]; + // set selected nodes aside + if (node.isSelected()) { + selected.push(nodeIndices[i]); + } else { + if (alwaysShow === true) { + node.draw(ctx); + } else if (node.isBoundingBoxOverlappingWith(viewableArea) === true) { + node.draw(ctx); + } else { + node.updateBoundingBox(ctx); } - var diffX = pointer.x - this.drag.pointer.x; - var diffY = pointer.y - this.drag.pointer.y; - - this.body.view.translation = { x: this.drag.translation.x + diffX, y: this.drag.translation.y + diffY }; - this.body.emitter.emit('_redraw'); } } + + // draw the selected nodes on top + for (var i = 0; i < selected.length; i++) { + node = nodes[selected[i]]; + node.draw(ctx); + } } }, { - key: 'onDragEnd', + key: '_drawEdges', /** - * handle drag start event + * Redraw all edges + * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d'); + * @param {CanvasRenderingContext2D} ctx * @private */ - value: function onDragEnd(event) { - this.drag.dragging = false; - var selection = this.drag.selection; - if (selection && selection.length) { - selection.forEach(function (s) { - // restore original xFixed and yFixed - s.node.options.fixed.x = s.xFixed; - s.node.options.fixed.y = s.yFixed; - }); - this.body.emitter.emit('startSimulation'); - } else { - this.body.emitter.emit('_requestRedraw'); + value: function _drawEdges(ctx) { + var edges = this.body.edges; + var edgeIndices = this.body.edgeIndices; + var edge = undefined; + + for (var i = 0; i < edgeIndices.length; i++) { + edge = edges[edgeIndices[i]]; + if (edge.connected === true) { + edge.draw(ctx); + } } - this.selectionHandler._generateClickEvent('dragEnd', event, this.getPointer(event.center)); } }, { - key: 'onPinch', + key: '_drawControlNodes', /** - * Handle pinch event - * @param event + * Redraw all edges + * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d'); + * @param {CanvasRenderingContext2D} ctx * @private */ - value: function onPinch(event) { - var pointer = this.getPointer(event.center); + value: function _drawControlNodes(ctx) { + var edges = this.body.edges; + var edgeIndices = this.body.edgeIndices; + var edge = undefined; - this.drag.pinched = true; - if (this.pinch['scale'] === undefined) { - this.pinch.scale = 1; + for (var i = 0; i < edgeIndices.length; i++) { + edge = edges[edgeIndices[i]]; + edge._drawControlNodes(ctx); } - - // TODO: enabled moving while pinching? - var scale = this.pinch.scale * event.scale; - this.zoom(scale, pointer); } }, { - key: 'zoom', + key: '_determineBrowserMethod', /** - * Zoom the network in or out - * @param {Number} scale a number around 1, and between 0.01 and 10 - * @param {{x: Number, y: Number}} pointer Position on screen - * @return {Number} appliedScale scale is limited within the boundaries + * Determine if the browser requires a setTimeout or a requestAnimationFrame. This was required because + * some implementations (safari and IE9) did not support requestAnimationFrame * @private */ - value: function zoom(scale, pointer) { - if (this.options.zoomView === true) { - var scaleOld = this.body.view.scale; - if (scale < 0.00001) { - scale = 0.00001; - } - if (scale > 10) { - scale = 10; - } - - var preScaleDragPointer = undefined; - if (this.drag !== undefined) { - if (this.drag.dragging === true) { - preScaleDragPointer = this.canvas.DOMtoCanvas(this.drag.pointer); + value: function _determineBrowserMethod() { + if (typeof window !== 'undefined') { + var browserType = navigator.userAgent.toLowerCase(); + this.requiresTimeout = false; + if (browserType.indexOf('msie 9.0') != -1) { + // IE 9 + this.requiresTimeout = true; + } else if (browserType.indexOf('safari') != -1) { + // safari + if (browserType.indexOf('chrome') <= -1) { + this.requiresTimeout = true; } } - // + this.canvas.frame.canvas.clientHeight / 2 - var translation = this.body.view.translation; + } else { + this.requiresTimeout = true; + } + } + }]); - var scaleFrac = scale / scaleOld; - var tx = (1 - scaleFrac) * pointer.x + translation.x * scaleFrac; - var ty = (1 - scaleFrac) * pointer.y + translation.y * scaleFrac; + return CanvasRenderer; + })(); - this.body.view.scale = scale; - this.body.view.translation = { x: tx, y: ty }; + exports['default'] = CanvasRenderer; + module.exports = exports['default']; - if (preScaleDragPointer != undefined) { - var postScaleDragPointer = this.canvas.canvasToDOM(preScaleDragPointer); - this.drag.pointer.x = postScaleDragPointer.x; - this.drag.pointer.y = postScaleDragPointer.y; +/***/ }, +/* 102 */ +/***/ 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 Hammer = __webpack_require__(26); + var hammerUtil = __webpack_require__(31); + + var util = __webpack_require__(4); + + /** + * Create the main frame for the Network. + * This function is executed once when a Network object is created. The frame + * contains a canvas, and this canvas contains all objects like the axis and + * nodes. + * @private + */ + + var Canvas = (function () { + function Canvas(body) { + _classCallCheck(this, Canvas); + + this.body = body; + this.pixelRatio = 1; + this.resizeTimer = undefined; + this.resizeFunction = this._onResize.bind(this); + + this.options = {}; + this.defaultOptions = { + autoResize: true, + height: '100%', + width: '100%' + }; + util.extend(this.options, this.defaultOptions); + + this.bindEventListeners(); + } + + _createClass(Canvas, [{ + key: 'bindEventListeners', + value: function bindEventListeners() { + var _this = this; + + // bind the events + this.body.emitter.once('resize', function (obj) { + if (obj.width !== 0) { + _this.body.view.translation.x = obj.width * 0.5; + } + if (obj.height !== 0) { + _this.body.view.translation.y = obj.height * 0.5; } + }); + this.body.emitter.on('setSize', this.setSize.bind(this)); + this.body.emitter.on('destroy', function () { + _this.hammerFrame.destroy(); + _this.hammer.destroy(); + _this._cleanUp(); + }); + } + }, { + key: 'setOptions', + value: function setOptions(options) { + var _this2 = this; - this.body.emitter.emit('_requestRedraw'); + if (options !== undefined) { + var fields = ['width', 'height', 'autoResize']; + util.selectiveDeepExtend(fields, this.options, options); + } - if (scaleOld < scale) { - this.body.emitter.emit('zoom', { direction: '+', scale: this.body.view.scale }); - } else { - this.body.emitter.emit('zoom', { direction: '-', scale: this.body.view.scale }); + if (this.options.autoResize === true) { + // automatically adapt to a changing size of the browser. + this._cleanUp(); + this.resizeTimer = setInterval(function () { + var changed = _this2.setSize(); + if (changed === true) { + _this2.body.emitter.emit('_requestRedraw'); + } + }, 1000); + this.resizeFunction = this._onResize.bind(this); + util.addEventListener(window, 'resize', this.resizeFunction); + } + } + }, { + key: '_cleanUp', + value: function _cleanUp() { + // automatically adapt to a changing size of the browser. + if (this.resizeTimer !== undefined) { + clearInterval(this.resizeTimer); + } + util.removeEventListener(window, 'resize', this.resizeFunction); + this.resizeFunction = undefined; + } + }, { + key: '_onResize', + value: function _onResize() { + this.setSize(); + this.body.emitter.emit('_redraw'); + } + }, { + key: '_prepareValue', + value: function _prepareValue(value) { + if (typeof value === 'number') { + return value + 'px'; + } else if (typeof value === 'string') { + if (value.indexOf('%') !== -1 || value.indexOf('px') !== -1) { + return value; + } else if (value.indexOf('%') === -1) { + return value + 'px'; } } + throw new Error('Could not use the value supplie for width or height:' + value); } }, { - key: 'onMouseWheel', + key: '_create', /** - * Event handler for mouse wheel event, used to zoom the timeline - * See http://adomas.org/javascript-mouse-wheel/ - * https://github.com/EightMedia/hammer.js/issues/256 - * @param {MouseEvent} event - * @private + * Create the HTML */ - value: function onMouseWheel(event) { - // retrieve delta - var delta = 0; - if (event.wheelDelta) { - /* IE/Opera. */ - delta = event.wheelDelta / 120; - } else if (event.detail) { - /* Mozilla case. */ - // In Mozilla, sign of delta is different than in IE. - // Also, delta is multiple of 3. - delta = -event.detail / 3; + value: function _create() { + // remove all elements from the container element. + while (this.body.container.hasChildNodes()) { + this.body.container.removeChild(this.body.container.firstChild); } - // If delta is nonzero, handle it. - // Basically, delta is now positive if wheel was scrolled up, - // and negative, if wheel was scrolled down. - if (delta !== 0) { + this.frame = document.createElement('div'); + this.frame.className = 'vis-network'; + this.frame.style.position = 'relative'; + this.frame.style.overflow = 'hidden'; + this.frame.tabIndex = 900; // tab index is required for keycharm to bind keystrokes to the div instead of the window - // calculate the new scale - var scale = this.body.view.scale; - var zoom = delta / 10; - if (delta < 0) { - zoom = zoom / (1 - zoom); - } - scale *= 1 + zoom; + ////////////////////////////////////////////////////////////////// - // calculate the pointer location - var pointer = this.getPointer({ x: event.clientX, y: event.clientY }); + this.frame.canvas = document.createElement('canvas'); + this.frame.canvas.style.position = 'relative'; + this.frame.appendChild(this.frame.canvas); + + if (!this.frame.canvas.getContext) { + var noCanvas = document.createElement('DIV'); + noCanvas.style.color = 'red'; + noCanvas.style.fontWeight = 'bold'; + noCanvas.style.padding = '10px'; + noCanvas.innerHTML = 'Error: your browser does not support HTML canvas'; + this.frame.canvas.appendChild(noCanvas); + } else { + var ctx = this.frame.canvas.getContext('2d'); + this.pixelRatio = (window.devicePixelRatio || 1) / (ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1); - // apply the new scale - this.zoom(scale, pointer); + this.frame.canvas.getContext('2d').setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0); } - // Prevent default actions caused by mouse wheel. - event.preventDefault(); + // add the frame to the container element + this.body.container.appendChild(this.frame); + + this.body.view.scale = 1; + this.body.view.translation = { x: 0.5 * this.frame.canvas.clientWidth, y: 0.5 * this.frame.canvas.clientHeight }; + + this._bindHammer(); } }, { - key: 'onMouseMove', + key: '_bindHammer', /** - * Mouse move handler for checking whether the title moves over a node with a title. - * @param {Event} event + * This function binds hammer, it can be repeated over and over due to the uniqueness check. * @private */ - value: function onMouseMove(event) { + value: function _bindHammer() { var _this3 = this; - var pointer = this.getPointer({ x: event.clientX, y: event.clientY }); - var popupVisible = false; + if (this.hammer !== undefined) { + this.hammer.destroy(); + } + this.drag = {}; + this.pinch = {}; - // check if the previously selected node is still selected - if (this.popup !== undefined) { - if (this.popup.hidden === false) { - this._checkHidePopup(pointer); - } + // init hammer + this.hammer = new Hammer(this.frame.canvas); + this.hammer.get('pinch').set({ enable: true }); + // enable to get better response, todo: test on mobile. + //this.hammer.get('pan').set({threshold:2}); - // if the popup was not hidden above - if (this.popup.hidden === false) { - popupVisible = true; - this.popup.setPosition(pointer.x + 3, pointer.y - 5); - this.popup.show(); - } - } + hammerUtil.onTouch(this.hammer, function (event) { + _this3.body.eventListeners.onTouch(event); + }); + this.hammer.on('tap', function (event) { + _this3.body.eventListeners.onTap(event); + }); + this.hammer.on('doubletap', function (event) { + _this3.body.eventListeners.onDoubleTap(event); + }); + this.hammer.on('press', function (event) { + _this3.body.eventListeners.onHold(event); + }); + this.hammer.on('panstart', function (event) { + _this3.body.eventListeners.onDragStart(event); + }); + this.hammer.on('panmove', function (event) { + _this3.body.eventListeners.onDrag(event); + }); + this.hammer.on('panend', function (event) { + _this3.body.eventListeners.onDragEnd(event); + }); + this.hammer.on('pinch', function (event) { + _this3.body.eventListeners.onPinch(event); + }); - // if we bind the keyboard to the div, we have to highlight it to use it. This highlights it on mouse over. - if (this.options.keyboard.bindToWindow === false && this.options.keyboard.enabled === true) { - this.canvas.frame.focus(); - } + // TODO: neatly cleanup these handlers when re-creating the Canvas, IF these are done with hammer, event.stopPropagation will not work? + this.frame.canvas.addEventListener('mousewheel', function (event) { + _this3.body.eventListeners.onMouseWheel(event); + }); + this.frame.canvas.addEventListener('DOMMouseScroll', function (event) { + _this3.body.eventListeners.onMouseWheel(event); + }); - // start a timeout that will check if the mouse is positioned above an element - if (popupVisible === false) { - if (this.popupTimer !== undefined) { - clearInterval(this.popupTimer); // stop any running calculationTimer - this.popupTimer = undefined; - } - if (!this.drag.dragging) { - this.popupTimer = setTimeout(function () { - return _this3._checkShowPopup(pointer); - }, this.options.tooltipDelay); - } - } + this.frame.canvas.addEventListener('mousemove', function (event) { + _this3.body.eventListeners.onMouseMove(event); + }); + this.frame.canvas.addEventListener('contextmenu', function (event) { + _this3.body.eventListeners.onContext(event); + }); - /** - * Adding hover highlights - */ - if (this.options.hover === true) { - // adding hover highlights - var obj = this.selectionHandler.getNodeAt(pointer); - if (obj === undefined) { - obj = this.selectionHandler.getEdgeAt(pointer); - } - this.selectionHandler.hoverObject(obj); - } + this.hammerFrame = new Hammer(this.frame); + hammerUtil.onRelease(this.hammerFrame, function (event) { + _this3.body.eventListeners.onRelease(event); + }); } }, { - key: '_checkShowPopup', + key: 'setSize', /** - * Check if there is an element on the given position in the network - * (a node or edge). If so, and if this element has a title, - * show a popup window with its title. - * - * @param {{x:Number, y:Number}} pointer - * @private + * Set a new size for the network + * @param {string} width Width in pixels or percentage (for example '800px' + * or '50%') + * @param {string} height Height in pixels or percentage (for example '400px' + * or '30%') */ - value: function _checkShowPopup(pointer) { - var x = this.canvas._XconvertDOMtoCanvas(pointer.x); - var y = this.canvas._YconvertDOMtoCanvas(pointer.y); - var pointerObj = { - left: x, - top: y, - right: x, - bottom: y - }; + value: function setSize() { + var width = arguments[0] === undefined ? this.options.width : arguments[0]; + var height = arguments[1] === undefined ? this.options.height : arguments[1]; - var previousPopupObjId = this.popupObj === undefined ? undefined : this.popupObj.id; - var nodeUnderCursor = false; - var popupType = 'node'; + width = this._prepareValue(width); + height = this._prepareValue(height); - // check if a node is under the cursor. - if (this.popupObj === undefined) { - // search the nodes for overlap, select the top one in case of multiple nodes - var nodeIndices = this.body.nodeIndices; - var nodes = this.body.nodes; - var node = undefined; - var overlappingNodes = []; - for (var i = 0; i < nodeIndices.length; i++) { - node = nodes[nodeIndices[i]]; - if (node.isOverlappingWith(pointerObj) === true) { - if (node.getTitle() !== undefined) { - overlappingNodes.push(nodeIndices[i]); - } - } - } + var emitEvent = false; + var oldWidth = this.frame.canvas.width; + var oldHeight = this.frame.canvas.height; - if (overlappingNodes.length > 0) { - // if there are overlapping nodes, select the last one, this is the one which is drawn on top of the others - this.popupObj = nodes[overlappingNodes[overlappingNodes.length - 1]]; - // if you hover over a node, the title of the edge is not supposed to be shown. - nodeUnderCursor = true; - } - } + if (width != this.options.width || height != this.options.height || this.frame.style.width != width || this.frame.style.height != height) { + this.frame.style.width = width; + this.frame.style.height = height; - if (this.popupObj === undefined && nodeUnderCursor === false) { - // search the edges for overlap - var edgeIndices = this.body.edgeIndices; - var edges = this.body.edges; - var edge = undefined; - var overlappingEdges = []; - for (var i = 0; i < edgeIndices.length; i++) { - edge = edges[edgeIndices[i]]; - if (edge.isOverlappingWith(pointerObj) === true) { - if (edge.connected === true && edge.getTitle() !== undefined) { - overlappingEdges.push(edgeIndices[i]); - } - } - } + this.frame.canvas.style.width = '100%'; + this.frame.canvas.style.height = '100%'; - if (overlappingEdges.length > 0) { - this.popupObj = edges[overlappingEdges[overlappingEdges.length - 1]]; - popupType = 'edge'; - } - } + this.frame.canvas.width = Math.round(this.frame.canvas.clientWidth * this.pixelRatio); + this.frame.canvas.height = Math.round(this.frame.canvas.clientHeight * this.pixelRatio); - if (this.popupObj !== undefined) { - // show popup message window - if (this.popupObj.id !== previousPopupObjId) { - if (this.popup === undefined) { - this.popup = new _componentsPopup2['default'](this.canvas.frame); - } + this.options.width = width; + this.options.height = height; - this.popup.popupTargetType = popupType; - this.popup.popupTargetId = this.popupObj.id; + emitEvent = true; + } else { + // this would adapt the width of the canvas to the width from 100% if and only if + // there is a change. - // adjust a small offset such that the mouse cursor is located in the - // bottom left location of the popup, and you can easily move over the - // popup area - this.popup.setPosition(pointer.x + 3, pointer.y - 5); - this.popup.setText(this.popupObj.getTitle()); - this.popup.show(); - this.body.emitter.emit('showPopup', this.popupObj.id); + if (this.frame.canvas.width != Math.round(this.frame.canvas.clientWidth * this.pixelRatio)) { + this.frame.canvas.width = Math.round(this.frame.canvas.clientWidth * this.pixelRatio); + emitEvent = true; } - } else { - if (this.popup !== undefined) { - this.popup.hide(); - this.body.emitter.emit('hidePopup'); + if (this.frame.canvas.height != Math.round(this.frame.canvas.clientHeight * this.pixelRatio)) { + this.frame.canvas.height = Math.round(this.frame.canvas.clientHeight * this.pixelRatio); + emitEvent = true; } } + + if (emitEvent === true) { + this.body.emitter.emit('resize', { + width: Math.round(this.frame.canvas.width / this.pixelRatio), + height: Math.round(this.frame.canvas.height / this.pixelRatio), + oldWidth: Math.round(oldWidth / this.pixelRatio), + oldHeight: Math.round(oldHeight / this.pixelRatio) + }); + } + + return emitEvent; } }, { - key: '_checkHidePopup', + key: '_XconvertDOMtoCanvas', /** - * Check if the popup must be hidden, which is the case when the mouse is no - * longer hovering on the object - * @param {{x:Number, y:Number}} pointer + * Convert the X coordinate in DOM-space (coordinate point in browser relative to the container div) to + * the X coordinate in canvas-space (the simulation sandbox, which the camera looks upon) + * @param {number} x + * @returns {number} * @private */ - value: function _checkHidePopup(pointer) { - var pointerObj = this.selectionHandler._pointerToPositionObject(pointer); + value: function _XconvertDOMtoCanvas(x) { + return (x - this.body.view.translation.x) / this.body.view.scale; + } + }, { + key: '_XconvertCanvasToDOM', - var stillOnObj = false; - if (this.popup.popupTargetType === 'node') { - if (this.body.nodes[this.popup.popupTargetId] !== undefined) { - stillOnObj = this.body.nodes[this.popup.popupTargetId].isOverlappingWith(pointerObj); + /** + * Convert the X coordinate in canvas-space (the simulation sandbox, which the camera looks upon) to + * the X coordinate in DOM-space (coordinate point in browser relative to the container div) + * @param {number} x + * @returns {number} + * @private + */ + value: function _XconvertCanvasToDOM(x) { + return x * this.body.view.scale + this.body.view.translation.x; + } + }, { + key: '_YconvertDOMtoCanvas', - // if the mouse is still one the node, we have to check if it is not also on one that is drawn on top of it. - // we initially only check stillOnObj because this is much faster. - if (stillOnObj === true) { - var overNode = this.selectionHandler.getNodeAt(pointer); - stillOnObj = overNode.id === this.popup.popupTargetId; - } - } - } else { - if (this.selectionHandler.getNodeAt(pointer) === undefined) { - if (this.body.edges[this.popup.popupTargetId] !== undefined) { - stillOnObj = this.body.edges[this.popup.popupTargetId].isOverlappingWith(pointerObj); - } - } - } + /** + * Convert the Y coordinate in DOM-space (coordinate point in browser relative to the container div) to + * the Y coordinate in canvas-space (the simulation sandbox, which the camera looks upon) + * @param {number} y + * @returns {number} + * @private + */ + value: function _YconvertDOMtoCanvas(y) { + return (y - this.body.view.translation.y) / this.body.view.scale; + } + }, { + key: '_YconvertCanvasToDOM', - if (stillOnObj === false) { - this.popupObj = undefined; - this.popup.hide(); - this.body.emitter.emit('hidePopup'); - } + /** + * Convert the Y coordinate in canvas-space (the simulation sandbox, which the camera looks upon) to + * the Y coordinate in DOM-space (coordinate point in browser relative to the container div) + * @param {number} y + * @returns {number} + * @private + */ + value: function _YconvertCanvasToDOM(y) { + return y * this.body.view.scale + this.body.view.translation.y; + } + }, { + key: 'canvasToDOM', + + /** + * + * @param {object} pos = {x: number, y: number} + * @returns {{x: number, y: number}} + * @constructor + */ + value: function canvasToDOM(pos) { + return { x: this._XconvertCanvasToDOM(pos.x), y: this._YconvertCanvasToDOM(pos.y) }; + } + }, { + key: 'DOMtoCanvas', + + /** + * + * @param {object} pos = {x: number, y: number} + * @returns {{x: number, y: number}} + * @constructor + */ + value: function DOMtoCanvas(pos) { + return { x: this._XconvertDOMtoCanvas(pos.x), y: this._YconvertDOMtoCanvas(pos.y) }; } }]); - return InteractionHandler; + return Canvas; })(); - exports['default'] = InteractionHandler; + exports['default'] = Canvas; module.exports = exports['default']; /***/ }, /* 103 */ /***/ 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 util = __webpack_require__(2); - var Hammer = __webpack_require__(25); - var hammerUtil = __webpack_require__(30); - var keycharm = __webpack_require__(45); + var util = __webpack_require__(4); - var NavigationHandler = (function () { - function NavigationHandler(body, canvas) { + var View = (function () { + function View(body, canvas) { var _this = this; - _classCallCheck(this, NavigationHandler); + _classCallCheck(this, View); this.body = body; this.canvas = canvas; - this.iconsCreated = false; - this.navigationHammers = []; - this.boundFunctions = {}; + this.animationSpeed = 1 / this.renderRefreshRate; + this.animationEasingFunction = "easeInOutQuint"; + this.easingTime = 0; + this.sourceScale = 0; + this.targetScale = 0; + this.sourceTranslation = 0; + this.targetTranslation = 0; + this.lockedOnNodeId = undefined; + this.lockedOnNodeOffset = undefined; this.touchTime = 0; - this.activated = false; - this.body.emitter.on('release', function () { - _this._stopMovement(); - }); - this.body.emitter.on('activate', function () { - _this.activated = true;_this.configureKeyboardBindings(); - }); - this.body.emitter.on('deactivate', function () { - _this.activated = false;_this.configureKeyboardBindings(); - }); - this.body.emitter.on('destroy', function () { - if (_this.keycharm !== undefined) { - _this.keycharm.destroy(); - } - }); + this.viewFunction = undefined; - this.options = {}; + this.body.emitter.on("fit", this.fit.bind(this)); + this.body.emitter.on("animationFinished", function () { + _this.body.emitter.emit("_stopRendering"); + }); + this.body.emitter.on("unlockNode", this.releaseNode.bind(this)); } - _createClass(NavigationHandler, [{ - key: 'setOptions', - value: function setOptions(options) { - if (options !== undefined) { - this.options = options; - this.create(); - } - } - }, { - key: 'create', - value: function create() { - if (this.options.navigationButtons === true) { - if (this.iconsCreated === false) { - this.loadNavigationElements(); - } - } else if (this.iconsCreated === true) { - this.cleanNavigation(); - } + _createClass(View, [{ + key: "setOptions", + value: function setOptions() { + var options = arguments[0] === undefined ? {} : arguments[0]; - this.configureKeyboardBindings(); + this.options = options; } }, { - 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(); + key: "_getRange", + + /** + * Find the center position of the network + * @private + */ + value: function _getRange() { + var specificNodes = arguments[0] === undefined ? [] : arguments[0]; + + var minY = 1000000000, + maxY = -1000000000, + minX = 1000000000, + maxX = -1000000000, + node; + if (specificNodes.length > 0) { + for (var i = 0; i < specificNodes.length; i++) { + node = this.body.nodes[specificNodes[i]]; + if (minX > node.shape.boundingBox.left) { + minX = node.shape.boundingBox.left; + } + if (maxX < node.shape.boundingBox.right) { + maxX = node.shape.boundingBox.right; + } + if (minY > node.shape.boundingBox.top) { + minY = node.shape.boundingBox.top; + } // top is negative, bottom is positive + if (maxY < node.shape.boundingBox.bottom) { + maxY = node.shape.boundingBox.bottom; + } // top is negative, bottom is positive + } + } else { + for (var nodeId in this.body.nodes) { + if (this.body.nodes.hasOwnProperty(nodeId)) { + node = this.body.nodes[nodeId]; + if (minX > node.shape.boundingBox.left) { + minX = node.shape.boundingBox.left; + } + if (maxX < node.shape.boundingBox.right) { + maxX = node.shape.boundingBox.right; + } + if (minY > node.shape.boundingBox.top) { + minY = node.shape.boundingBox.top; + } // top is negative, bottom is positive + if (maxY < node.shape.boundingBox.bottom) { + maxY = node.shape.boundingBox.bottom; + } // top is negative, bottom is positive + } } - this.navigationHammers = []; } - // clean up previous navigation items - if (this.navigationDOM && this.navigationDOM['wrapper'] && this.navigationDOM['wrapper'].parentNode) { - this.navigationDOM['wrapper'].parentNode.removeChild(this.navigationDOM['wrapper']); + if (minX === 1000000000 && maxX === -1000000000 && minY === 1000000000 && maxY === -1000000000) { + minY = 0, maxY = 0, minX = 0, maxX = 0; } - - this.iconsCreated = false; + return { minX: minX, maxX: maxX, minY: minY, maxY: maxY }; } }, { - key: 'loadNavigationElements', + key: "_findCenter", /** - * 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 {object} range = {minX: minX, maxX: maxX, minY: minY, maxY: maxY}; + * @returns {{x: number, y: number}} * @private */ - value: function loadNavigationElements() { - this.cleanNavigation(); + value: function _findCenter(range) { + return { x: 0.5 * (range.maxX + range.minX), + y: 0.5 * (range.maxY + range.minY) }; + } + }, { + key: "fit", + + /** + * This function zooms out to fit all data on screen based on amount of nodes + * @param {Object} Options + * @param {Boolean} [initialZoom] | zoom based on fitted formula or range, true = fitted, default = false; + */ + value: function fit() { + var options = arguments[0] === undefined ? { nodes: [] } : arguments[0]; + var initialZoom = arguments[1] === undefined ? false : arguments[1]; + + var range; + var zoomLevel; + + if (initialZoom === true) { + // check if more than half of the nodes have a predefined position. If so, we use the range, not the approximation. + var positionDefined = 0; + for (var nodeId in this.body.nodes) { + if (this.body.nodes.hasOwnProperty(nodeId)) { + var node = this.body.nodes[nodeId]; + if (node.predefinedPosition === true) { + positionDefined += 1; + } + } + } + if (positionDefined > 0.5 * this.body.nodeIndices.length) { + this.fit(options, false); + return; + } - this.navigationDOM = {}; - var navigationDivs = ['up', 'down', 'left', 'right', 'zoomIn', 'zoomOut', 'zoomExtends']; - var navigationDivActions = ['_moveUp', '_moveDown', '_moveLeft', '_moveRight', '_zoomIn', '_zoomOut', '_fit']; + range = this._getRange(options.nodes); - this.navigationDOM['wrapper'] = document.createElement('div'); - this.navigationDOM['wrapper'].className = 'vis-navigation'; - this.canvas.frame.appendChild(this.navigationDOM['wrapper']); + var numberOfNodes = this.body.nodeIndices.length; + zoomLevel = 12.662 / (numberOfNodes + 7.4147) + 0.0964822; // this is obtained from fitting a dataset from 5 points with scale levels that looked good. - 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]]); + // correct for larger canvasses. + var factor = Math.min(this.canvas.frame.canvas.clientWidth / 600, this.canvas.frame.canvas.clientHeight / 600); + zoomLevel *= factor; + } else { + this.body.emitter.emit("_resizeNodes"); + range = this._getRange(options.nodes); + var xDistance = Math.abs(range.maxX - range.minX) * 1.1; + var yDistance = Math.abs(range.maxY - range.minY) * 1.1; - 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])); - } + var xZoomLevel = this.canvas.frame.canvas.clientWidth / xDistance; + var yZoomLevel = this.canvas.frame.canvas.clientHeight / yDistance; - this.navigationHammers.push(hammer); + zoomLevel = xZoomLevel <= yZoomLevel ? xZoomLevel : yZoomLevel; } - this.iconsCreated = true; - } - }, { - key: 'bindToRedraw', - value: function bindToRedraw(action) { - if (this.boundFunctions[action] === undefined) { - this.boundFunctions[action] = this[action].bind(this); - this.body.emitter.on('initRedraw', this.boundFunctions[action]); - this.body.emitter.emit('_startRendering'); - } - } - }, { - key: 'unbindFromRedraw', - value: function unbindFromRedraw(action) { - if (this.boundFunctions[action] !== undefined) { - this.body.emitter.off('initRedraw', this.boundFunctions[action]); - this.body.emitter.emit('_stopRendering'); - delete this.boundFunctions[action]; + if (zoomLevel > 1) { + zoomLevel = 1; + } else if (zoomLevel === 0) { + zoomLevel = 1; } + + var center = this._findCenter(range); + var animationOptions = { position: center, scale: zoomLevel, animation: options.animation }; + this.moveTo(animationOptions); } }, { - key: '_fit', + key: "focus", + + // animation /** - * this stops all movement induced by the navigation buttons + * Center a node in view. * - * @private + * @param {Number} nodeId + * @param {Number} [options] */ - 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 focus(nodeId) { + var options = arguments[1] === undefined ? {} : arguments[1]; + + if (this.body.nodes[nodeId] !== undefined) { + var nodePosition = { x: this.body.nodes[nodeId].x, y: this.body.nodes[nodeId].y }; + options.position = nodePosition; + options.lockedOnNode = nodeId; + + this.moveTo(options); + } else { + console.log("Node: " + nodeId + " cannot be found."); } } }, { - key: '_stopMovement', + key: "moveTo", /** - * this stops all movement induced by the navigation buttons * - * @private + * @param {Object} options | options.offset = {x:Number, y:Number} // offset from the center in DOM pixels + * | options.scale = Number // scale to move to + * | options.position = {x:Number, y:Number} // position to move to + * | options.animation = {duration:Number, easingFunction:String} || Boolean // position to move to */ - 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'); - } + value: function moveTo(options) { + if (options === undefined) { + options = {}; + return; } - this.boundFunctions = {}; - } - }, { - key: '_moveUp', - value: function _moveUp() { - this.body.view.translation.y += this.options.keyboard.speed.y; - } - }, { - key: '_moveDown', - value: function _moveDown() { - this.body.view.translation.y -= this.options.keyboard.speed.y; - } - }, { - key: '_moveLeft', - value: function _moveLeft() { - this.body.view.translation.x += this.options.keyboard.speed.x; - } - }, { - key: '_moveRight', - value: function _moveRight() { - this.body.view.translation.x -= this.options.keyboard.speed.x; - } - }, { - key: '_zoomIn', - value: function _zoomIn() { - this.body.view.scale *= 1 + this.options.keyboard.speed.zoom; - } - }, { - key: '_zoomOut', - value: function _zoomOut() { - this.body.view.scale /= 1 + this.options.keyboard.speed.zoom; + if (options.offset === undefined) { + options.offset = { x: 0, y: 0 }; + } + if (options.offset.x === undefined) { + options.offset.x = 0; + } + if (options.offset.y === undefined) { + options.offset.y = 0; + } + if (options.scale === undefined) { + options.scale = this.body.view.scale; + } + if (options.position === undefined) { + options.position = this.getViewPosition(); + } + if (options.animation === undefined) { + options.animation = { duration: 0 }; + } + if (options.animation === false) { + options.animation = { duration: 0 }; + } + if (options.animation === true) { + options.animation = {}; + } + if (options.animation.duration === undefined) { + options.animation.duration = 1000; + } // default duration + if (options.animation.easingFunction === undefined) { + options.animation.easingFunction = "easeInOutQuad"; + } // default easing function + + this.animateView(options); } }, { - key: 'configureKeyboardBindings', + key: "animateView", /** - * bind all keys using keycharm. + * + * @param {Object} options | options.offset = {x:Number, y:Number} // offset from the center in DOM pixels + * | options.time = Number // animation time in milliseconds + * | options.scale = Number // scale to animate to + * | options.position = {x:Number, y:Number} // position to animate to + * | options.easingFunction = String // linear, easeInQuad, easeOutQuad, easeInOutQuad, + * // easeInCubic, easeOutCubic, easeInOutCubic, + * // easeInQuart, easeOutQuart, easeInOutQuart, + * // easeInQuint, easeOutQuint, easeInOutQuint */ - value: function configureKeyboardBindings() { - var _this2 = this; + value: function animateView(options) { + if (options === undefined) { + return; + } + this.animationEasingFunction = options.animation.easingFunction; + // release if something focussed on the node + this.releaseNode(); + if (options.locked === true) { + this.lockedOnNodeId = options.lockedOnNode; + this.lockedOnNodeOffset = options.offset; + } - if (this.keycharm !== undefined) { - this.keycharm.destroy(); + // forcefully complete the old animation if it was still running + if (this.easingTime != 0) { + this._transitionRedraw(true); // by setting easingtime to 1, we finish the animation. } - if (this.options.keyboard.enabled === true) { - if (this.options.keyboard.bindToWindow === true) { - this.keycharm = keycharm({ container: window, preventDefault: true }); - } else { - this.keycharm = keycharm({ container: this.canvas.frame, preventDefault: true }); - } + this.sourceScale = this.body.view.scale; + this.sourceTranslation = this.body.view.translation; + this.targetScale = options.scale; - this.keycharm.reset(); + // set the scale so the viewCenter is based on the correct zoom level. This is overridden in the transitionRedraw + // but at least then we'll have the target transition + this.body.view.scale = this.targetScale; + var viewCenter = this.canvas.DOMtoCanvas({ x: 0.5 * this.canvas.frame.canvas.clientWidth, y: 0.5 * this.canvas.frame.canvas.clientHeight }); - if (this.activated === true) { - this.keycharm.bind('up', function () { - _this2.bindToRedraw('_moveUp'); - }, 'keydown'); - this.keycharm.bind('down', function () { - _this2.bindToRedraw('_moveDown'); - }, 'keydown'); - this.keycharm.bind('left', function () { - _this2.bindToRedraw('_moveLeft'); - }, 'keydown'); - this.keycharm.bind('right', function () { - _this2.bindToRedraw('_moveRight'); - }, 'keydown'); - this.keycharm.bind('=', function () { - _this2.bindToRedraw('_zoomIn'); - }, 'keydown'); - this.keycharm.bind('num+', function () { - _this2.bindToRedraw('_zoomIn'); - }, 'keydown'); - this.keycharm.bind('num-', function () { - _this2.bindToRedraw('_zoomOut'); - }, 'keydown'); - this.keycharm.bind('-', function () { - _this2.bindToRedraw('_zoomOut'); - }, 'keydown'); - this.keycharm.bind('[', function () { - _this2.bindToRedraw('_zoomOut'); - }, 'keydown'); - this.keycharm.bind(']', function () { - _this2.bindToRedraw('_zoomIn'); - }, 'keydown'); - this.keycharm.bind('pageup', function () { - _this2.bindToRedraw('_zoomIn'); - }, 'keydown'); - this.keycharm.bind('pagedown', function () { - _this2.bindToRedraw('_zoomOut'); - }, 'keydown'); + var distanceFromCenter = { // offset from view, distance view has to change by these x and y to center the node + x: viewCenter.x - options.position.x, + y: viewCenter.y - options.position.y + }; + this.targetTranslation = { + x: this.sourceTranslation.x + distanceFromCenter.x * this.targetScale + options.offset.x, + y: this.sourceTranslation.y + distanceFromCenter.y * this.targetScale + options.offset.y + }; - this.keycharm.bind('up', function () { - _this2.unbindFromRedraw('_moveUp'); - }, 'keyup'); - this.keycharm.bind('down', function () { - _this2.unbindFromRedraw('_moveDown'); - }, 'keyup'); - this.keycharm.bind('left', function () { - _this2.unbindFromRedraw('_moveLeft'); - }, 'keyup'); - this.keycharm.bind('right', function () { - _this2.unbindFromRedraw('_moveRight'); - }, 'keyup'); - this.keycharm.bind('=', function () { - _this2.unbindFromRedraw('_zoomIn'); - }, 'keyup'); - this.keycharm.bind('num+', function () { - _this2.unbindFromRedraw('_zoomIn'); - }, 'keyup'); - this.keycharm.bind('num-', function () { - _this2.unbindFromRedraw('_zoomOut'); - }, 'keyup'); - this.keycharm.bind('-', function () { - _this2.unbindFromRedraw('_zoomOut'); - }, 'keyup'); - this.keycharm.bind('[', function () { - _this2.unbindFromRedraw('_zoomOut'); - }, 'keyup'); - this.keycharm.bind(']', function () { - _this2.unbindFromRedraw('_zoomIn'); - }, 'keyup'); - this.keycharm.bind('pageup', function () { - _this2.unbindFromRedraw('_zoomIn'); - }, 'keyup'); - this.keycharm.bind('pagedown', function () { - _this2.unbindFromRedraw('_zoomOut'); - }, 'keyup'); + // if the time is set to 0, don't do an animation + if (options.animation.duration === 0) { + if (this.lockedOnNodeId != undefined) { + this.viewFunction = this._lockedRedraw.bind(this); + this.body.emitter.on("initRedraw", this.viewFunction); + } else { + this.body.view.scale = this.targetScale; + this.body.view.translation = this.targetTranslation; + this.body.emitter.emit("_requestRedraw"); } + } else { + this.animationSpeed = 1 / (60 * options.animation.duration * 0.001) || 1 / 60; // 60 for 60 seconds, 0.001 for milli's + this.animationEasingFunction = options.animation.easingFunction; + + this.viewFunction = this._transitionRedraw.bind(this); + this.body.emitter.on("initRedraw", this.viewFunction); + this.body.emitter.emit("_startRendering"); } } - }]); - - return NavigationHandler; - })(); - - exports['default'] = NavigationHandler; - module.exports = exports['default']; - -/***/ }, -/* 104 */ -/***/ function(module, exports, __webpack_require__) { - - /** - * 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. - */ - '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 Popup = (function () { - function Popup(container) { - _classCallCheck(this, Popup); - - this.container = container; - - this.x = 0; - this.y = 0; - this.padding = 5; - this.hidden = false; - - // create the frame - this.frame = document.createElement('div'); - this.frame.className = 'vis-network-tooltip'; - this.container.appendChild(this.frame); - } - - _createClass(Popup, [{ - key: 'setPosition', + }, { + key: "_lockedRedraw", /** - * @param {number} x Horizontal position of the popup window - * @param {number} y Vertical position of the popup window + * used to animate smoothly by hijacking the redraw function. + * @private */ - value: function setPosition(x, y) { - this.x = parseInt(x); - this.y = parseInt(y); + value: function _lockedRedraw() { + var nodePosition = { x: this.body.nodes[this.lockedOnNodeId].x, y: this.body.nodes[this.lockedOnNodeId].y }; + var viewCenter = this.DOMtoCanvas({ x: 0.5 * this.frame.canvas.clientWidth, y: 0.5 * this.frame.canvas.clientHeight }); + var distanceFromCenter = { // offset from view, distance view has to change by these x and y to center the node + x: viewCenter.x - nodePosition.x, + y: viewCenter.y - nodePosition.y + }; + var sourceTranslation = this.body.view.translation; + var targetTranslation = { + x: sourceTranslation.x + distanceFromCenter.x * this.body.view.scale + this.lockedOnNodeOffset.x, + y: sourceTranslation.y + distanceFromCenter.y * this.body.view.scale + this.lockedOnNodeOffset.y + }; + + this.body.view.translation = targetTranslation; } }, { - key: 'setText', - - /** - * Set the content for the popup window. This can be HTML code or text. - * @param {string | Element} content - */ - value: function setText(content) { - if (content instanceof Element) { - this.frame.innerHTML = ''; - this.frame.appendChild(content); - } else { - this.frame.innerHTML = content; // string containing text or HTML + key: "releaseNode", + value: function releaseNode() { + if (this.lockedOnNodeId !== undefined && this.viewFunction !== undefined) { + this.body.emitter.off("initRedraw", this.viewFunction); + this.lockedOnNodeId = undefined; + this.lockedOnNodeOffset = undefined; } } }, { - key: 'show', + key: "_transitionRedraw", /** - * Show the popup window - * @param {boolean} [doShow] Show or hide the window + * + * @param easingTime + * @private */ - value: function show(doShow) { - if (doShow === undefined) { - doShow = true; - } + value: function _transitionRedraw() { + var finished = arguments[0] === undefined ? false : arguments[0]; - 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; + this.easingTime += this.animationSpeed; + this.easingTime = finished === true ? 1 : this.easingTime; - var top = this.y - height; - if (top + height + this.padding > maxHeight) { - top = maxHeight - height - this.padding; - } - if (top < this.padding) { - top = this.padding; - } + var progress = util.easingFunctions[this.animationEasingFunction](this.easingTime); - var left = this.x; - if (left + width + this.padding > maxWidth) { - left = maxWidth - width - this.padding; - } - if (left < this.padding) { - left = this.padding; - } + this.body.view.scale = this.sourceScale + (this.targetScale - this.sourceScale) * progress; + this.body.view.translation = { + x: this.sourceTranslation.x + (this.targetTranslation.x - this.sourceTranslation.x) * progress, + y: this.sourceTranslation.y + (this.targetTranslation.y - this.sourceTranslation.y) * progress + }; - this.frame.style.left = left + 'px'; - this.frame.style.top = top + 'px'; - this.frame.style.visibility = 'visible'; - this.hidden = false; - } else { - this.hide(); + // cleanup + if (this.easingTime >= 1) { + this.body.emitter.off("initRedraw", this.viewFunction); + this.easingTime = 0; + if (this.lockedOnNodeId != undefined) { + this.viewFunction = this._lockedRedraw.bind(this); + this.body.emitter.on("initRedraw", this.viewFunction); + } + this.body.emitter.emit("animationFinished"); } } }, { - key: 'hide', - - /** - * Hide the popup window - */ - value: function hide() { - this.hidden = true; - this.frame.style.visibility = 'hidden'; + key: "getScale", + value: function getScale() { + return this.body.view.scale; + } + }, { + key: "getViewPosition", + value: function getViewPosition() { + return this.canvas.DOMtoCanvas({ x: 0.5 * this.canvas.frame.canvas.clientWidth, y: 0.5 * this.canvas.frame.canvas.clientHeight }); } }]); - return Popup; + return View; })(); - exports['default'] = Popup; - module.exports = exports['default']; + exports["default"] = View; + module.exports = exports["default"]; /***/ }, -/* 105 */ +/* 104 */ /***/ function(module, exports, __webpack_require__) { - "use strict"; + 'use strict'; - Object.defineProperty(exports, "__esModule", { + Object.defineProperty(exports, '__esModule', { value: true }); - var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - var Node = __webpack_require__(64); - var Edge = __webpack_require__(84); - var util = __webpack_require__(2); + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - var SelectionHandler = (function () { - function SelectionHandler(body, canvas) { - var _this = this; + var _componentsNavigationHandler = __webpack_require__(105); + + var _componentsNavigationHandler2 = _interopRequireDefault(_componentsNavigationHandler); + + var _componentsPopup = __webpack_require__(106); + + var _componentsPopup2 = _interopRequireDefault(_componentsPopup); + + var util = __webpack_require__(4); + + var InteractionHandler = (function () { + function InteractionHandler(body, canvas, selectionHandler) { + _classCallCheck(this, InteractionHandler); + + this.body = body; + this.canvas = canvas; + this.selectionHandler = selectionHandler; + this.navigationHandler = new _componentsNavigationHandler2['default'](body, canvas); + + // bind the events from hammer to functions in this object + this.body.eventListeners.onTap = this.onTap.bind(this); + this.body.eventListeners.onTouch = this.onTouch.bind(this); + this.body.eventListeners.onDoubleTap = this.onDoubleTap.bind(this); + this.body.eventListeners.onHold = this.onHold.bind(this); + this.body.eventListeners.onDragStart = this.onDragStart.bind(this); + this.body.eventListeners.onDrag = this.onDrag.bind(this); + this.body.eventListeners.onDragEnd = this.onDragEnd.bind(this); + this.body.eventListeners.onMouseWheel = this.onMouseWheel.bind(this); + this.body.eventListeners.onPinch = this.onPinch.bind(this); + this.body.eventListeners.onMouseMove = this.onMouseMove.bind(this); + this.body.eventListeners.onRelease = this.onRelease.bind(this); + this.body.eventListeners.onContext = this.onContext.bind(this); - _classCallCheck(this, SelectionHandler); + this.touchTime = 0; + this.drag = {}; + this.pinch = {}; + this.popup = undefined; + this.popupObj = undefined; + this.popupTimer = undefined; - this.body = body; - this.canvas = canvas; - this.selectionObj = { nodes: [], edges: [] }; - this.hoverObj = { nodes: {}, edges: {} }; + this.body.functions.getPointer = this.getPointer.bind(this); this.options = {}; this.defaultOptions = { - multiselect: false, - selectable: true, - selectConnectedEdges: true, - hoverConnectedEdges: true + dragNodes: true, + dragView: true, + hover: false, + keyboard: { + enabled: false, + speed: { x: 10, y: 10, zoom: 0.02 }, + bindToWindow: true + }, + navigationButtons: false, + tooltipDelay: 300, + zoomView: true }; util.extend(this.options, this.defaultOptions); - this.body.emitter.on("_dataChanged", function () { - _this.updateSelection(); - }); + this.bindEventListeners(); } - _createClass(SelectionHandler, [{ - key: "setOptions", - value: function setOptions(options) { - if (options !== undefined) { - var fields = ["multiselect", "hoverConnectedEdges", "selectable", "selectConnectedEdges"]; - util.selectiveDeepExtend(fields, this.options, options); - } - } - }, { - key: "selectOnPoint", - - /** - * handles the selection part of the tap; - * - * @param {Object} pointer - * @private - */ - value: function selectOnPoint(pointer) { - var selected = false; - if (this.options.selectable === true) { - var obj = this.getNodeAt(pointer) || this.getEdgeAt(pointer); - - // unselect after getting the objects in order to restore width and height. - this.unselectAll(); - - if (obj !== undefined) { - selected = this.selectObject(obj); - } - this.body.emitter.emit("_requestRedraw"); - } - return selected; - } - }, { - key: "selectAdditionalOnPoint", - value: function selectAdditionalOnPoint(pointer) { - var selectionChanged = false; - if (this.options.selectable === true) { - var obj = this.getNodeAt(pointer) || this.getEdgeAt(pointer); - - if (obj !== undefined) { - selectionChanged = true; - if (obj.isSelected() === true) { - this.deselectObject(obj); - } else { - this.selectObject(obj); - } + _createClass(InteractionHandler, [{ + key: 'bindEventListeners', + value: function bindEventListeners() { + var _this = this; - this.body.emitter.emit("_requestRedraw"); - } - } - return selectionChanged; + this.body.emitter.on('destroy', function () { + clearTimeout(_this.popupTimer); + delete _this.body.functions.getPointer; + }); } }, { - key: "_generateClickEvent", - value: function _generateClickEvent(eventType, event, pointer, oldSelection) { - var properties = this.getSelection(); - properties["pointer"] = { - DOM: { x: pointer.x, y: pointer.y }, - canvas: this.canvas.DOMtoCanvas(pointer) - }; - properties["event"] = event; + key: 'setOptions', + value: function setOptions(options) { + if (options !== undefined) { + // extend all but the values in fields + var fields = ['hideEdgesOnDrag', 'hideNodesOnDrag', 'keyboard', 'multiselect', 'selectable', 'selectConnectedEdges']; + util.selectiveNotDeepExtend(fields, this.options, options); - if (oldSelection !== undefined) { - properties["previousSelection"] = oldSelection; - } - this.body.emitter.emit(eventType, properties); - } - }, { - key: "selectObject", - value: function selectObject(obj) { - var highlightEdges = arguments[1] === undefined ? this.options.selectConnectedEdges : arguments[1]; + // merge the keyboard options in. + util.mergeOptions(this.options, options, 'keyboard'); - if (obj !== undefined) { - if (obj instanceof Node) { - if (highlightEdges === true) { - this._selectConnectedEdges(obj); + if (options.tooltip) { + util.extend(this.options.tooltip, options.tooltip); + if (options.tooltip.color) { + this.options.tooltip.color = util.parseColor(options.tooltip.color); } } - obj.select(); - this._addToSelection(obj); - return true; - } - return false; - } - }, { - key: "deselectObject", - value: function deselectObject(obj) { - if (obj.isSelected() === true) { - obj.selected = false; - this._removeFromSelection(obj); } - } - }, { - key: "_getAllNodesOverlappingWith", - /** - * retrieve all nodes overlapping with given object - * @param {Object} object An object with parameters left, top, right, bottom - * @return {Number[]} An array with id's of the overlapping nodes - * @private - */ - value: function _getAllNodesOverlappingWith(object) { - var overlappingNodes = []; - var nodes = this.body.nodes; - for (var i = 0; i < this.body.nodeIndices.length; i++) { - var nodeId = this.body.nodeIndices[i]; - if (nodes[nodeId].isOverlappingWith(object)) { - overlappingNodes.push(nodeId); - } - } - return overlappingNodes; + this.navigationHandler.setOptions(this.options); } }, { - key: "_pointerToPositionObject", + key: 'getPointer', /** - * Return a position object in canvasspace from a single point in screenspace - * - * @param pointer - * @returns {{left: number, top: number, right: number, bottom: number}} + * Get the pointer location from a touch location + * @param {{x: Number, y: Number}} touch + * @return {{x: Number, y: Number}} pointer * @private */ - value: function _pointerToPositionObject(pointer) { - var canvasPos = this.canvas.DOMtoCanvas(pointer); + value: function getPointer(touch) { return { - left: canvasPos.x - 1, - top: canvasPos.y + 1, - right: canvasPos.x + 1, - bottom: canvasPos.y - 1 + x: touch.x - util.getAbsoluteLeft(this.canvas.frame.canvas), + y: touch.y - util.getAbsoluteTop(this.canvas.frame.canvas) }; } }, { - key: "getNodeAt", + key: 'onTouch', /** - * Get the top node at the a specific point (like a click) - * - * @param {{x: Number, y: Number}} pointer - * @return {Node | undefined} node + * On start of a touch gesture, store the pointer + * @param event * @private */ - value: function getNodeAt(pointer) { - var returnNode = arguments[1] === undefined ? true : arguments[1]; - - // we first check if this is an navigation controls element - var positionObject = this._pointerToPositionObject(pointer); - var overlappingNodes = this._getAllNodesOverlappingWith(positionObject); - // if there are overlapping nodes, select the last one, this is the - // one which is drawn on top of the others - if (overlappingNodes.length > 0) { - if (returnNode === true) { - return this.body.nodes[overlappingNodes[overlappingNodes.length - 1]]; - } else { - return overlappingNodes[overlappingNodes.length - 1]; - } - } else { - return undefined; + value: function onTouch(event) { + if (new Date().valueOf() - this.touchTime > 50) { + this.drag.pointer = this.getPointer(event.center); + this.drag.pinched = false; + this.pinch.scale = this.body.view.scale; + // to avoid double fireing of this event because we have two hammer instances. (on canvas and on frame) + this.touchTime = new Date().valueOf(); } } }, { - key: "_getEdgesOverlappingWith", + key: 'onTap', /** - * retrieve all edges overlapping with given object, selector is around center - * @param {Object} object An object with parameters left, top, right, bottom - * @return {Number[]} An array with id's of the overlapping nodes + * handle tap/click event: select/unselect a node * @private */ - value: function _getEdgesOverlappingWith(object, overlappingEdges) { - var edges = this.body.edges; - for (var i = 0; i < this.body.edgeIndices.length; i++) { - var edgeId = this.body.edgeIndices[i]; - if (edges[edgeId].isOverlappingWith(object)) { - overlappingEdges.push(edgeId); - } - } + value: function onTap(event) { + var pointer = this.getPointer(event.center); + var multiselect = this.selectionHandler.options.multiselect && (event.changedPointers[0].ctrlKey || event.changedPointers[0].metaKey); + + this.checkSelectionChanges(pointer, event, multiselect); + this.selectionHandler._generateClickEvent('click', event, pointer); } }, { - key: "_getAllEdgesOverlappingWith", + key: 'onDoubleTap', /** - * retrieve all nodes overlapping with given object - * @param {Object} object An object with parameters left, top, right, bottom - * @return {Number[]} An array with id's of the overlapping nodes + * handle doubletap event * @private */ - value: function _getAllEdgesOverlappingWith(object) { - var overlappingEdges = []; - this._getEdgesOverlappingWith(object, overlappingEdges); - return overlappingEdges; + value: function onDoubleTap(event) { + var pointer = this.getPointer(event.center); + this.selectionHandler._generateClickEvent('doubleClick', event, pointer); } }, { - key: "getEdgeAt", + key: 'onHold', /** - * Place holder. To implement change the getNodeAt to a _getObjectAt. Have the _getObjectAt call - * getNodeAt and _getEdgesAt, then priortize the selection to user preferences. - * - * @param pointer - * @returns {undefined} + * handle long tap event: multi select nodes * @private */ - value: function getEdgeAt(pointer) { - var returnEdge = arguments[1] === undefined ? true : arguments[1]; + value: function onHold(event) { + var pointer = this.getPointer(event.center); + var multiselect = this.selectionHandler.options.multiselect; - var positionObject = this._pointerToPositionObject(pointer); - var overlappingEdges = this._getAllEdgesOverlappingWith(positionObject); + this.checkSelectionChanges(pointer, event, multiselect); - if (overlappingEdges.length > 0) { - if (returnEdge === true) { - return this.body.edges[overlappingEdges[overlappingEdges.length - 1]]; - } else { - return overlappingEdges[overlappingEdges.length - 1]; - } - } else { - return undefined; - } + this.selectionHandler._generateClickEvent('click', event, pointer); + this.selectionHandler._generateClickEvent('hold', event, pointer); } }, { - key: "_addToSelection", + key: 'onRelease', /** - * Add object to the selection array. + * handle the release of the screen * - * @param obj * @private */ - value: function _addToSelection(obj) { - if (obj instanceof Node) { - this.selectionObj.nodes[obj.id] = obj; - } else { - this.selectionObj.edges[obj.id] = obj; + value: function onRelease(event) { + if (new Date().valueOf() - this.touchTime > 10) { + var pointer = this.getPointer(event.center); + this.selectionHandler._generateClickEvent('release', event, pointer); + // to avoid double fireing of this event because we have two hammer instances. (on canvas and on frame) + this.touchTime = new Date().valueOf(); } } }, { - key: "_addToHover", + key: 'onContext', + value: function onContext(event) { + var pointer = this.getPointer({ x: event.clientX, y: event.clientY }); + this.selectionHandler._generateClickEvent('oncontext', event, pointer); + } + }, { + key: 'checkSelectionChanges', /** - * Add object to the selection array. * - * @param obj - * @private + * @param pointer + * @param add */ - value: function _addToHover(obj) { - if (obj instanceof Node) { - this.hoverObj.nodes[obj.id] = obj; + value: function checkSelectionChanges(pointer, event) { + var add = arguments[2] === undefined ? false : arguments[2]; + + var previouslySelectedEdgeCount = this.selectionHandler._getSelectedEdgeCount(); + var previouslySelectedNodeCount = this.selectionHandler._getSelectedNodeCount(); + var previousSelection = this.selectionHandler.getSelection(); + var selected = undefined; + if (add === true) { + selected = this.selectionHandler.selectAdditionalOnPoint(pointer); } else { - this.hoverObj.edges[obj.id] = obj; + selected = this.selectionHandler.selectOnPoint(pointer); + } + var selectedEdgesCount = this.selectionHandler._getSelectedEdgeCount(); + var selectedNodesCount = this.selectionHandler._getSelectedNodeCount(); + var currentSelection = this.selectionHandler.getSelection(); + + var _determineIfDifferent = this._determineIfDifferent(previousSelection, currentSelection); + + var nodesChanges = _determineIfDifferent.nodesChanges; + var edgesChanges = _determineIfDifferent.edgesChanges; + + if (selectedNodesCount - previouslySelectedNodeCount > 0) { + // node was selected + this.selectionHandler._generateClickEvent('selectNode', event, pointer); + selected = true; + } else if (selectedNodesCount - previouslySelectedNodeCount < 0) { + // node was deselected + this.selectionHandler._generateClickEvent('deselectNode', event, pointer, previousSelection); + selected = true; + } else if (selectedNodesCount === previouslySelectedNodeCount && nodesChanges === true) { + this.selectionHandler._generateClickEvent('deselectNode', event, pointer, previousSelection); + this.selectionHandler._generateClickEvent('selectNode', event, pointer); + selected = true; + } + + if (selectedEdgesCount - previouslySelectedEdgeCount > 0) { + // edge was selected + this.selectionHandler._generateClickEvent('selectEdge', event, pointer); + selected = true; + } else if (selectedEdgesCount - previouslySelectedEdgeCount < 0) { + // edge was deselected + this.selectionHandler._generateClickEvent('deselectEdge', event, pointer, previousSelection); + selected = true; + } else if (selectedEdgesCount === previouslySelectedEdgeCount && edgesChanges === true) { + this.selectionHandler._generateClickEvent('deselectEdge', event, pointer, previousSelection); + this.selectionHandler._generateClickEvent('selectEdge', event, pointer); + selected = true; + } + + if (selected === true) { + // select or unselect + this.selectionHandler._generateClickEvent('select', event, pointer); } } }, { - key: "_removeFromSelection", + key: '_determineIfDifferent', /** - * Remove a single option from selection. - * - * @param {Object} obj + * This function checks if the nodes and edges previously selected have changed. + * @param previousSelection + * @param currentSelection + * @returns {{nodesChanges: boolean, edgesChanges: boolean}} * @private */ - value: function _removeFromSelection(obj) { - if (obj instanceof Node) { - delete this.selectionObj.nodes[obj.id]; - } else { - delete this.selectionObj.edges[obj.id]; + value: function _determineIfDifferent(previousSelection, currentSelection) { + var nodesChanges = false; + var edgesChanges = false; + + for (var i = 0; i < previousSelection.nodes.length; i++) { + if (currentSelection.nodes.indexOf(previousSelection.nodes[i]) === -1) { + nodesChanges = true; + } + } + for (var i = 0; i < currentSelection.nodes.length; i++) { + if (previousSelection.nodes.indexOf(previousSelection.nodes[i]) === -1) { + nodesChanges = true; + } + } + for (var i = 0; i < previousSelection.edges.length; i++) { + if (currentSelection.edges.indexOf(previousSelection.edges[i]) === -1) { + edgesChanges = true; + } + } + for (var i = 0; i < currentSelection.edges.length; i++) { + if (previousSelection.edges.indexOf(previousSelection.edges[i]) === -1) { + edgesChanges = true; + } } + + return { nodesChanges: nodesChanges, edgesChanges: edgesChanges }; } }, { - key: "unselectAll", + key: 'onDragStart', /** - * Unselect all. The selectionObj is useful for this. + * This function is called by onDragStart. + * It is separated out because we can then overload it for the datamanipulation system. * * @private */ - value: function unselectAll() { - for (var nodeId in this.selectionObj.nodes) { - if (this.selectionObj.nodes.hasOwnProperty(nodeId)) { - this.selectionObj.nodes[nodeId].unselect(); - } + value: function onDragStart(event) { + //in case the touch event was triggered on an external div, do the initial touch now. + if (this.drag.pointer === undefined) { + this.onTouch(event); } - for (var edgeId in this.selectionObj.edges) { - if (this.selectionObj.edges.hasOwnProperty(edgeId)) { - this.selectionObj.edges[edgeId].unselect(); + + // note: drag.pointer is set in onTouch to get the initial touch location + var node = this.selectionHandler.getNodeAt(this.drag.pointer); + + this.drag.dragging = true; + this.drag.selection = []; + this.drag.translation = util.extend({}, this.body.view.translation); // copy the object + this.drag.nodeId = undefined; + + this.selectionHandler._generateClickEvent('dragStart', event, this.drag.pointer); + + if (node !== undefined && this.options.dragNodes === true) { + this.drag.nodeId = node.id; + // select the clicked node if not yet selected + if (node.isSelected() === false) { + this.selectionHandler.unselectAll(); + this.selectionHandler.selectObject(node); } - } - this.selectionObj = { nodes: {}, edges: {} }; + var selection = this.selectionHandler.selectionObj.nodes; + // create an array with the selected nodes and their original location and status + for (var nodeId in selection) { + if (selection.hasOwnProperty(nodeId)) { + var object = selection[nodeId]; + var s = { + id: object.id, + node: object, + + // store original x, y, xFixed and yFixed, make the node temporarily Fixed + x: object.x, + y: object.y, + xFixed: object.options.fixed.x, + yFixed: object.options.fixed.y + }; + + object.options.fixed.x = true; + object.options.fixed.y = true; + + this.drag.selection.push(s); + } + } + } } }, { - key: "_getSelectedNodeCount", + key: 'onDrag', /** - * return the number of selected nodes - * - * @returns {number} + * handle drag event * @private */ - value: function _getSelectedNodeCount() { - var count = 0; - for (var nodeId in this.selectionObj.nodes) { - if (this.selectionObj.nodes.hasOwnProperty(nodeId)) { - count += 1; + value: function onDrag(event) { + var _this2 = this; + + if (this.drag.pinched === true) { + return; + } + + // remove the focus on node if it is focussed on by the focusOnNode + this.body.emitter.emit('unlockNode'); + + var pointer = this.getPointer(event.center); + + this.selectionHandler._generateClickEvent('dragging', event, pointer); + + var selection = this.drag.selection; + if (selection && selection.length && this.options.dragNodes === true) { + (function () { + // calculate delta's and new location + var deltaX = pointer.x - _this2.drag.pointer.x; + var deltaY = pointer.y - _this2.drag.pointer.y; + + // update position of all selected nodes + selection.forEach(function (selection) { + var node = selection.node; + // only move the node if it was not fixed initially + if (selection.xFixed === false) { + node.x = _this2.canvas._XconvertDOMtoCanvas(_this2.canvas._XconvertCanvasToDOM(selection.x) + deltaX); + } + // only move the node if it was not fixed initially + if (selection.yFixed === false) { + node.y = _this2.canvas._YconvertDOMtoCanvas(_this2.canvas._YconvertCanvasToDOM(selection.y) + deltaY); + } + }); + + // start the simulation of the physics + _this2.body.emitter.emit('startSimulation'); + })(); + } else { + // move the network + if (this.options.dragView === true) { + // if the drag was not started properly because the click started outside the network div, start it now. + if (this.drag.pointer === undefined) { + this._handleDragStart(event); + return; + } + var diffX = pointer.x - this.drag.pointer.x; + var diffY = pointer.y - this.drag.pointer.y; + + this.body.view.translation = { x: this.drag.translation.x + diffX, y: this.drag.translation.y + diffY }; + this.body.emitter.emit('_redraw'); } } - return count; } }, { - key: "_getSelectedNode", + key: 'onDragEnd', /** - * return the selected node - * - * @returns {number} + * handle drag start event * @private */ - value: function _getSelectedNode() { - for (var nodeId in this.selectionObj.nodes) { - if (this.selectionObj.nodes.hasOwnProperty(nodeId)) { - return this.selectionObj.nodes[nodeId]; - } + value: function onDragEnd(event) { + this.drag.dragging = false; + var selection = this.drag.selection; + if (selection && selection.length) { + selection.forEach(function (s) { + // restore original xFixed and yFixed + s.node.options.fixed.x = s.xFixed; + s.node.options.fixed.y = s.yFixed; + }); + this.body.emitter.emit('startSimulation'); + } else { + this.body.emitter.emit('_requestRedraw'); } - return undefined; + this.selectionHandler._generateClickEvent('dragEnd', event, this.getPointer(event.center)); } }, { - key: "_getSelectedEdge", + key: 'onPinch', /** - * return the selected edge - * - * @returns {number} + * Handle pinch event + * @param event * @private */ - value: function _getSelectedEdge() { - for (var edgeId in this.selectionObj.edges) { - if (this.selectionObj.edges.hasOwnProperty(edgeId)) { - return this.selectionObj.edges[edgeId]; - } + value: function onPinch(event) { + var pointer = this.getPointer(event.center); + + this.drag.pinched = true; + if (this.pinch['scale'] === undefined) { + this.pinch.scale = 1; } - return undefined; + + // TODO: enabled moving while pinching? + var scale = this.pinch.scale * event.scale; + this.zoom(scale, pointer); } }, { - key: "_getSelectedEdgeCount", + key: 'zoom', /** - * return the number of selected edges - * - * @returns {number} + * Zoom the network in or out + * @param {Number} scale a number around 1, and between 0.01 and 10 + * @param {{x: Number, y: Number}} pointer Position on screen + * @return {Number} appliedScale scale is limited within the boundaries * @private */ - value: function _getSelectedEdgeCount() { - var count = 0; - for (var edgeId in this.selectionObj.edges) { - if (this.selectionObj.edges.hasOwnProperty(edgeId)) { - count += 1; + value: function zoom(scale, pointer) { + if (this.options.zoomView === true) { + var scaleOld = this.body.view.scale; + if (scale < 0.00001) { + scale = 0.00001; + } + if (scale > 10) { + scale = 10; + } + + var preScaleDragPointer = undefined; + if (this.drag !== undefined) { + if (this.drag.dragging === true) { + preScaleDragPointer = this.canvas.DOMtoCanvas(this.drag.pointer); + } + } + // + this.canvas.frame.canvas.clientHeight / 2 + var translation = this.body.view.translation; + + var scaleFrac = scale / scaleOld; + var tx = (1 - scaleFrac) * pointer.x + translation.x * scaleFrac; + var ty = (1 - scaleFrac) * pointer.y + translation.y * scaleFrac; + + this.body.view.scale = scale; + this.body.view.translation = { x: tx, y: ty }; + + if (preScaleDragPointer != undefined) { + var postScaleDragPointer = this.canvas.canvasToDOM(preScaleDragPointer); + this.drag.pointer.x = postScaleDragPointer.x; + this.drag.pointer.y = postScaleDragPointer.y; + } + + this.body.emitter.emit('_requestRedraw'); + + if (scaleOld < scale) { + this.body.emitter.emit('zoom', { direction: '+', scale: this.body.view.scale }); + } else { + this.body.emitter.emit('zoom', { direction: '-', scale: this.body.view.scale }); } } - return count; } }, { - key: "_getSelectedObjectCount", + key: 'onMouseWheel', /** - * return the number of selected objects. - * - * @returns {number} + * Event handler for mouse wheel event, used to zoom the timeline + * See http://adomas.org/javascript-mouse-wheel/ + * https://github.com/EightMedia/hammer.js/issues/256 + * @param {MouseEvent} event * @private */ - value: function _getSelectedObjectCount() { - var count = 0; - for (var nodeId in this.selectionObj.nodes) { - if (this.selectionObj.nodes.hasOwnProperty(nodeId)) { - count += 1; - } + value: function onMouseWheel(event) { + // retrieve delta + var delta = 0; + if (event.wheelDelta) { + /* IE/Opera. */ + delta = event.wheelDelta / 120; + } else if (event.detail) { + /* Mozilla case. */ + // In Mozilla, sign of delta is different than in IE. + // Also, delta is multiple of 3. + delta = -event.detail / 3; } - for (var edgeId in this.selectionObj.edges) { - if (this.selectionObj.edges.hasOwnProperty(edgeId)) { - count += 1; + + // If delta is nonzero, handle it. + // Basically, delta is now positive if wheel was scrolled up, + // and negative, if wheel was scrolled down. + if (delta !== 0) { + + // calculate the new scale + var scale = this.body.view.scale; + var zoom = delta / 10; + if (delta < 0) { + zoom = zoom / (1 - zoom); } + scale *= 1 + zoom; + + // calculate the pointer location + var pointer = this.getPointer({ x: event.clientX, y: event.clientY }); + + // apply the new scale + this.zoom(scale, pointer); } - return count; + + // Prevent default actions caused by mouse wheel. + event.preventDefault(); } }, { - key: "_selectionIsEmpty", + key: 'onMouseMove', /** - * Check if anything is selected - * - * @returns {boolean} + * Mouse move handler for checking whether the title moves over a node with a title. + * @param {Event} event * @private */ - value: function _selectionIsEmpty() { - for (var nodeId in this.selectionObj.nodes) { - if (this.selectionObj.nodes.hasOwnProperty(nodeId)) { - return false; + value: function onMouseMove(event) { + var _this3 = this; + + var pointer = this.getPointer({ x: event.clientX, y: event.clientY }); + var popupVisible = false; + + // check if the previously selected node is still selected + if (this.popup !== undefined) { + if (this.popup.hidden === false) { + this._checkHidePopup(pointer); + } + + // if the popup was not hidden above + if (this.popup.hidden === false) { + popupVisible = true; + this.popup.setPosition(pointer.x + 3, pointer.y - 5); + this.popup.show(); } } - for (var edgeId in this.selectionObj.edges) { - if (this.selectionObj.edges.hasOwnProperty(edgeId)) { - return false; + + // if we bind the keyboard to the div, we have to highlight it to use it. This highlights it on mouse over. + if (this.options.keyboard.bindToWindow === false && this.options.keyboard.enabled === true) { + this.canvas.frame.focus(); + } + + // start a timeout that will check if the mouse is positioned above an element + if (popupVisible === false) { + if (this.popupTimer !== undefined) { + clearInterval(this.popupTimer); // stop any running calculationTimer + this.popupTimer = undefined; + } + if (!this.drag.dragging) { + this.popupTimer = setTimeout(function () { + return _this3._checkShowPopup(pointer); + }, this.options.tooltipDelay); } } - return true; + + /** + * Adding hover highlights + */ + if (this.options.hover === true) { + // adding hover highlights + var obj = this.selectionHandler.getNodeAt(pointer); + if (obj === undefined) { + obj = this.selectionHandler.getEdgeAt(pointer); + } + this.selectionHandler.hoverObject(obj); + } } }, { - key: "_clusterInSelection", + key: '_checkShowPopup', /** - * check if one of the selected nodes is a cluster. + * Check if there is an element on the given position in the network + * (a node or edge). If so, and if this element has a title, + * show a popup window with its title. * - * @returns {boolean} + * @param {{x:Number, y:Number}} pointer * @private */ - value: function _clusterInSelection() { - for (var nodeId in this.selectionObj.nodes) { - if (this.selectionObj.nodes.hasOwnProperty(nodeId)) { - if (this.selectionObj.nodes[nodeId].clusterSize > 1) { - return true; + value: function _checkShowPopup(pointer) { + var x = this.canvas._XconvertDOMtoCanvas(pointer.x); + var y = this.canvas._YconvertDOMtoCanvas(pointer.y); + var pointerObj = { + left: x, + top: y, + right: x, + bottom: y + }; + + var previousPopupObjId = this.popupObj === undefined ? undefined : this.popupObj.id; + var nodeUnderCursor = false; + var popupType = 'node'; + + // check if a node is under the cursor. + if (this.popupObj === undefined) { + // search the nodes for overlap, select the top one in case of multiple nodes + var nodeIndices = this.body.nodeIndices; + var nodes = this.body.nodes; + var node = undefined; + var overlappingNodes = []; + for (var i = 0; i < nodeIndices.length; i++) { + node = nodes[nodeIndices[i]]; + if (node.isOverlappingWith(pointerObj) === true) { + if (node.getTitle() !== undefined) { + overlappingNodes.push(nodeIndices[i]); + } } } + + if (overlappingNodes.length > 0) { + // if there are overlapping nodes, select the last one, this is the one which is drawn on top of the others + this.popupObj = nodes[overlappingNodes[overlappingNodes.length - 1]]; + // if you hover over a node, the title of the edge is not supposed to be shown. + nodeUnderCursor = true; + } + } + + if (this.popupObj === undefined && nodeUnderCursor === false) { + // search the edges for overlap + var edgeIndices = this.body.edgeIndices; + var edges = this.body.edges; + var edge = undefined; + var overlappingEdges = []; + for (var i = 0; i < edgeIndices.length; i++) { + edge = edges[edgeIndices[i]]; + if (edge.isOverlappingWith(pointerObj) === true) { + if (edge.connected === true && edge.getTitle() !== undefined) { + overlappingEdges.push(edgeIndices[i]); + } + } + } + + if (overlappingEdges.length > 0) { + this.popupObj = edges[overlappingEdges[overlappingEdges.length - 1]]; + popupType = 'edge'; + } + } + + if (this.popupObj !== undefined) { + // show popup message window + if (this.popupObj.id !== previousPopupObjId) { + if (this.popup === undefined) { + this.popup = new _componentsPopup2['default'](this.canvas.frame); + } + + this.popup.popupTargetType = popupType; + this.popup.popupTargetId = this.popupObj.id; + + // adjust a small offset such that the mouse cursor is located in the + // bottom left location of the popup, and you can easily move over the + // popup area + this.popup.setPosition(pointer.x + 3, pointer.y - 5); + this.popup.setText(this.popupObj.getTitle()); + this.popup.show(); + this.body.emitter.emit('showPopup', this.popupObj.id); + } + } else { + if (this.popup !== undefined) { + this.popup.hide(); + this.body.emitter.emit('hidePopup'); + } } - return false; } }, { - key: "_selectConnectedEdges", + key: '_checkHidePopup', /** - * select the edges connected to the node that is being selected - * - * @param {Node} node + * Check if the popup must be hidden, which is the case when the mouse is no + * longer hovering on the object + * @param {{x:Number, y:Number}} pointer * @private */ - value: function _selectConnectedEdges(node) { - for (var i = 0; i < node.edges.length; i++) { - var edge = node.edges[i]; - edge.select(); - this._addToSelection(edge); + value: function _checkHidePopup(pointer) { + var pointerObj = this.selectionHandler._pointerToPositionObject(pointer); + + var stillOnObj = false; + if (this.popup.popupTargetType === 'node') { + if (this.body.nodes[this.popup.popupTargetId] !== undefined) { + stillOnObj = this.body.nodes[this.popup.popupTargetId].isOverlappingWith(pointerObj); + + // if the mouse is still one the node, we have to check if it is not also on one that is drawn on top of it. + // we initially only check stillOnObj because this is much faster. + if (stillOnObj === true) { + var overNode = this.selectionHandler.getNodeAt(pointer); + stillOnObj = overNode.id === this.popup.popupTargetId; + } + } + } else { + if (this.selectionHandler.getNodeAt(pointer) === undefined) { + if (this.body.edges[this.popup.popupTargetId] !== undefined) { + stillOnObj = this.body.edges[this.popup.popupTargetId].isOverlappingWith(pointerObj); + } + } + } + + if (stillOnObj === false) { + this.popupObj = undefined; + this.popup.hide(); + this.body.emitter.emit('hidePopup'); } } - }, { - key: "_hoverConnectedEdges", + }]); - /** - * select the edges connected to the node that is being selected - * - * @param {Node} node - * @private - */ - value: function _hoverConnectedEdges(node) { - for (var i = 0; i < node.edges.length; i++) { - var edge = node.edges[i]; - edge.hover = true; - this._addToHover(edge); + return InteractionHandler; + })(); + + exports['default'] = InteractionHandler; + module.exports = exports['default']; + +/***/ }, +/* 105 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, '__esModule', { + value: true + }); + + var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + + var util = __webpack_require__(4); + var Hammer = __webpack_require__(26); + var hammerUtil = __webpack_require__(31); + var keycharm = __webpack_require__(46); + + var NavigationHandler = (function () { + function NavigationHandler(body, canvas) { + var _this = this; + + _classCallCheck(this, NavigationHandler); + + this.body = body; + this.canvas = canvas; + + this.iconsCreated = false; + this.navigationHammers = []; + this.boundFunctions = {}; + this.touchTime = 0; + this.activated = false; + + this.body.emitter.on('release', function () { + _this._stopMovement(); + }); + this.body.emitter.on('activate', function () { + _this.activated = true;_this.configureKeyboardBindings(); + }); + this.body.emitter.on('deactivate', function () { + _this.activated = false;_this.configureKeyboardBindings(); + }); + this.body.emitter.on('destroy', function () { + if (_this.keycharm !== undefined) { + _this.keycharm.destroy(); + } + }); + + this.options = {}; + } + + _createClass(NavigationHandler, [{ + key: 'setOptions', + value: function setOptions(options) { + if (options !== undefined) { + this.options = options; + this.create(); } } }, { - key: "_unselectConnectedEdges", - - /** - * unselect the edges connected to the node that is being selected - * - * @param {Node} node - * @private - */ - value: function _unselectConnectedEdges(node) { - for (var i = 0; i < node.edges.length; i++) { - var edge = node.edges[i]; - edge.unselect(); - this._removeFromSelection(edge); + key: 'create', + value: function create() { + if (this.options.navigationButtons === true) { + if (this.iconsCreated === false) { + this.loadNavigationElements(); + } + } else if (this.iconsCreated === true) { + this.cleanNavigation(); } + + this.configureKeyboardBindings(); } }, { - key: "blurObject", + key: 'cleanNavigation', + value: function cleanNavigation() { + // clean hammer bindings + if (this.navigationHammers.length != 0) { + for (var i = 0; i < this.navigationHammers.length; i++) { + this.navigationHammers[i].destroy(); + } + this.navigationHammers = []; + } - /** - * This is called when someone clicks on a node. either select or deselect it. - * If there is an existing selection and we don't want to append to it, clear the existing selection - * - * @param {Node || Edge} object - * @private - */ - value: function blurObject(object) { - if (object.hover === true) { - object.hover = false; - this.body.emitter.emit("blurNode", { node: object.id }); + // clean up previous navigation items + if (this.navigationDOM && this.navigationDOM['wrapper'] && this.navigationDOM['wrapper'].parentNode) { + this.navigationDOM['wrapper'].parentNode.removeChild(this.navigationDOM['wrapper']); } + + this.iconsCreated = false; } }, { - key: "hoverObject", + key: 'loadNavigationElements', /** - * This is called when someone clicks on a node. either select or deselect it. - * If there is an existing selection and we don't want to append to it, clear the existing selection + * 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 {Node || Edge} object * @private */ - value: function hoverObject(object) { - var hoverChanged = false; - // remove all node hover highlights - for (var nodeId in this.hoverObj.nodes) { - if (this.hoverObj.nodes.hasOwnProperty(nodeId)) { - if (object === undefined) { - this.blurObject(this.hoverObj.nodes[nodeId]); - hoverChanged = true; - } else if (object instanceof Node && object.id != nodeId || object instanceof Edge || object === undefined) { - this.blurObject(this.hoverObj.nodes[nodeId]); - hoverChanged = true; - delete this.hoverObj.nodes[nodeId]; - } - } - } + value: function loadNavigationElements() { + this.cleanNavigation(); + + this.navigationDOM = {}; + var navigationDivs = ['up', 'down', 'left', 'right', 'zoomIn', 'zoomOut', 'zoomExtends']; + var navigationDivActions = ['_moveUp', '_moveDown', '_moveLeft', '_moveRight', '_zoomIn', '_zoomOut', '_fit']; + + this.navigationDOM['wrapper'] = document.createElement('div'); + this.navigationDOM['wrapper'].className = 'vis-navigation'; + this.canvas.frame.appendChild(this.navigationDOM['wrapper']); + + for (var i = 0; i < navigationDivs.length; i++) { + this.navigationDOM[navigationDivs[i]] = document.createElement('div'); + this.navigationDOM[navigationDivs[i]].className = 'vis-button vis-' + navigationDivs[i]; + this.navigationDOM['wrapper'].appendChild(this.navigationDOM[navigationDivs[i]]); - // removing all edge hover highlights - for (var edgeId in this.hoverObj.edges) { - if (this.hoverObj.edges.hasOwnProperty(edgeId)) { - this.hoverObj.edges[edgeId].hover = false; - delete this.hoverObj.edges[edgeId]; + 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 (object !== undefined) { - if (object.hover === false) { - object.hover = true; - this._addToHover(object); - hoverChanged = true; - if (object instanceof Node) { - this.body.emitter.emit("hoverNode", { node: object.id }); - } - } - if (object instanceof Node && this.options.hoverConnectedEdges === true) { - this._hoverConnectedEdges(object); - } + this.navigationHammers.push(hammer); } - if (hoverChanged === true) { - this.body.emitter.emit("_requestRedraw"); + 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: "getSelection", - - /** - * - * retrieve the currently selected objects - * @return {{nodes: Array., edges: Array.}} selection - */ - value: function getSelection() { - var nodeIds = this.getSelectedNodes(); - var edgeIds = this.getSelectedEdges(); - return { nodes: nodeIds, edges: edgeIds }; + 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: "getSelectedNodes", + key: '_fit', /** + * this stops all movement induced by the navigation buttons * - * retrieve the currently selected nodes - * @return {String[]} selection An array with the ids of the - * selected nodes. + * @private */ - value: function getSelectedNodes() { - var idArray = []; - if (this.options.selectable === true) { - for (var nodeId in this.selectionObj.nodes) { - if (this.selectionObj.nodes.hasOwnProperty(nodeId)) { - idArray.push(nodeId); - } - } + 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(); } - return idArray; } }, { - key: "getSelectedEdges", + key: '_stopMovement', /** + * this stops all movement induced by the navigation buttons * - * retrieve the currently selected edges - * @return {Array} selection An array with the ids of the - * selected nodes. + * @private */ - value: function getSelectedEdges() { - var idArray = []; - if (this.options.selectable === true) { - for (var edgeId in this.selectionObj.edges) { - if (this.selectionObj.edges.hasOwnProperty(edgeId)) { - idArray.push(edgeId); - } + 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'); } } - return idArray; + this.boundFunctions = {}; } }, { - key: "selectNodes", - - /** - * select zero or more nodes with the option to highlight edges - * @param {Number[] | String[]} selection An array with the ids of the - * selected nodes. - * @param {boolean} [highlightEdges] - */ - value: function selectNodes(selection) { - var highlightEdges = arguments[1] === undefined ? true : arguments[1]; - - var i = undefined, - id = undefined; - - if (!selection || selection.length === undefined) throw "Selection must be an array with ids"; - - // first unselect any selected node - this.unselectAll(); - - for (i = 0; i < selection.length; i++) { - id = selection[i]; - - var node = this.body.nodes[id]; - if (!node) { - throw new RangeError("Node with id \"" + id + "\" not found"); - } - this.selectObject(node, highlightEdges); - } - this.body.emitter.emit("_requestRedraw"); + key: '_moveUp', + value: function _moveUp() { + this.body.view.translation.y += this.options.keyboard.speed.y; } }, { - key: "selectEdges", + key: '_moveDown', + value: function _moveDown() { + this.body.view.translation.y -= this.options.keyboard.speed.y; + } + }, { + key: '_moveLeft', + value: function _moveLeft() { + this.body.view.translation.x += this.options.keyboard.speed.x; + } + }, { + key: '_moveRight', + value: function _moveRight() { + this.body.view.translation.x -= this.options.keyboard.speed.x; + } + }, { + key: '_zoomIn', + value: function _zoomIn() { + this.body.view.scale *= 1 + this.options.keyboard.speed.zoom; + } + }, { + key: '_zoomOut', + value: function _zoomOut() { + this.body.view.scale /= 1 + this.options.keyboard.speed.zoom; + } + }, { + key: 'configureKeyboardBindings', /** - * select zero or more edges - * @param {Number[] | String[]} selection An array with the ids of the - * selected nodes. + * bind all keys using keycharm. */ - value: function selectEdges(selection) { - var i = undefined, - id = undefined; + value: function configureKeyboardBindings() { + var _this2 = this; - if (!selection || selection.length === undefined) throw "Selection must be an array with ids"; + if (this.keycharm !== undefined) { + this.keycharm.destroy(); + } - // first unselect any selected objects - this.unselectAll(); + if (this.options.keyboard.enabled === true) { + if (this.options.keyboard.bindToWindow === true) { + this.keycharm = keycharm({ container: window, preventDefault: true }); + } else { + this.keycharm = keycharm({ container: this.canvas.frame, preventDefault: true }); + } - for (i = 0; i < selection.length; i++) { - id = selection[i]; + this.keycharm.reset(); - var edge = this.body.edges[id]; - if (!edge) { - throw new RangeError("Edge with id \"" + id + "\" not found"); - } - this.selectObject(edge); - } - this.body.emitter.emit("_requestRedraw"); - } - }, { - key: "updateSelection", + if (this.activated === true) { + this.keycharm.bind('up', function () { + _this2.bindToRedraw('_moveUp'); + }, 'keydown'); + this.keycharm.bind('down', function () { + _this2.bindToRedraw('_moveDown'); + }, 'keydown'); + this.keycharm.bind('left', function () { + _this2.bindToRedraw('_moveLeft'); + }, 'keydown'); + this.keycharm.bind('right', function () { + _this2.bindToRedraw('_moveRight'); + }, 'keydown'); + this.keycharm.bind('=', function () { + _this2.bindToRedraw('_zoomIn'); + }, 'keydown'); + this.keycharm.bind('num+', function () { + _this2.bindToRedraw('_zoomIn'); + }, 'keydown'); + this.keycharm.bind('num-', function () { + _this2.bindToRedraw('_zoomOut'); + }, 'keydown'); + this.keycharm.bind('-', function () { + _this2.bindToRedraw('_zoomOut'); + }, 'keydown'); + this.keycharm.bind('[', function () { + _this2.bindToRedraw('_zoomOut'); + }, 'keydown'); + this.keycharm.bind(']', function () { + _this2.bindToRedraw('_zoomIn'); + }, 'keydown'); + this.keycharm.bind('pageup', function () { + _this2.bindToRedraw('_zoomIn'); + }, 'keydown'); + this.keycharm.bind('pagedown', function () { + _this2.bindToRedraw('_zoomOut'); + }, 'keydown'); - /** - * Validate the selection: remove ids of nodes which no longer exist - * @private - */ - value: function updateSelection() { - for (var nodeId in this.selectionObj.nodes) { - if (this.selectionObj.nodes.hasOwnProperty(nodeId)) { - if (!this.body.nodes.hasOwnProperty(nodeId)) { - delete this.selectionObj.nodes[nodeId]; - } - } - } - for (var edgeId in this.selectionObj.edges) { - if (this.selectionObj.edges.hasOwnProperty(edgeId)) { - if (!this.body.edges.hasOwnProperty(edgeId)) { - delete this.selectionObj.edges[edgeId]; - } + this.keycharm.bind('up', function () { + _this2.unbindFromRedraw('_moveUp'); + }, 'keyup'); + this.keycharm.bind('down', function () { + _this2.unbindFromRedraw('_moveDown'); + }, 'keyup'); + this.keycharm.bind('left', function () { + _this2.unbindFromRedraw('_moveLeft'); + }, 'keyup'); + this.keycharm.bind('right', function () { + _this2.unbindFromRedraw('_moveRight'); + }, 'keyup'); + this.keycharm.bind('=', function () { + _this2.unbindFromRedraw('_zoomIn'); + }, 'keyup'); + this.keycharm.bind('num+', function () { + _this2.unbindFromRedraw('_zoomIn'); + }, 'keyup'); + this.keycharm.bind('num-', function () { + _this2.unbindFromRedraw('_zoomOut'); + }, 'keyup'); + this.keycharm.bind('-', function () { + _this2.unbindFromRedraw('_zoomOut'); + }, 'keyup'); + this.keycharm.bind('[', function () { + _this2.unbindFromRedraw('_zoomOut'); + }, 'keyup'); + this.keycharm.bind(']', function () { + _this2.unbindFromRedraw('_zoomIn'); + }, 'keyup'); + this.keycharm.bind('pageup', function () { + _this2.unbindFromRedraw('_zoomIn'); + }, 'keyup'); + this.keycharm.bind('pagedown', function () { + _this2.unbindFromRedraw('_zoomOut'); + }, 'keyup'); } } } }]); - return SelectionHandler; + return NavigationHandler; })(); - exports["default"] = SelectionHandler; - module.exports = exports["default"]; + exports['default'] = NavigationHandler; + module.exports = exports['default']; /***/ }, /* 106 */ /***/ function(module, exports, __webpack_require__) { + /** + * 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. + */ 'use strict'; Object.defineProperty(exports, '__esModule', { @@ -37534,2190 +38394,1343 @@ return /******/ (function(modules) { // webpackBootstrap function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - var util = __webpack_require__(2); - - var LayoutEngine = (function () { - function LayoutEngine(body) { - _classCallCheck(this, LayoutEngine); - - this.body = body; - - this.initialRandomSeed = Math.round(Math.random() * 1000000); - this.randomSeed = this.initialRandomSeed; - this.options = {}; - this.optionsBackup = {}; + var Popup = (function () { + function Popup(container) { + _classCallCheck(this, Popup); - this.defaultOptions = { - randomSeed: undefined, - hierarchical: { - enabled: false, - levelSeparation: 150, - direction: 'UD', // UD, DU, LR, RL - sortMethod: 'hubsize' // hubsize, directed - } - }; - util.extend(this.options, this.defaultOptions); + this.container = container; - this.hierarchicalLevels = {}; + this.x = 0; + this.y = 0; + this.padding = 5; + this.hidden = false; - this.bindEventListeners(); + // create the frame + this.frame = document.createElement('div'); + this.frame.className = 'vis-network-tooltip'; + this.container.appendChild(this.frame); } - _createClass(LayoutEngine, [{ - key: 'bindEventListeners', - value: function bindEventListeners() { - var _this = this; + _createClass(Popup, [{ + key: 'setPosition', - this.body.emitter.on('_dataChanged', function () { - _this.setupHierarchicalLayout(); - }); - this.body.emitter.on('_resetHierarchicalLayout', function () { - _this.setupHierarchicalLayout(); - }); + /** + * @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: 'setOptions', - value: function setOptions(options, allOptions) { - if (options !== undefined) { - var prevHierarchicalState = this.options.hierarchical.enabled; - - util.mergeOptions(this.options, options, 'hierarchical'); - if (options.randomSeed !== undefined) { - this.initialRandomSeed = options.randomSeed; - } - - if (this.options.hierarchical.enabled === true) { - // make sure the level seperation is the right way up - if (this.options.hierarchical.direction === 'RL' || this.options.hierarchical.direction === 'DU') { - if (this.options.hierarchical.levelSeparation > 0) { - this.options.hierarchical.levelSeparation *= -1; - } - } else { - if (this.options.hierarchical.levelSeparation < 0) { - this.options.hierarchical.levelSeparation *= -1; - } - } + key: 'setText', - this.body.emitter.emit('_resetHierarchicalLayout'); - // because the hierarchical system needs it's own physics and smooth curve settings, we adapt the other options if needed. - return this.adaptAllOptions(allOptions); - } else { - if (prevHierarchicalState === true) { - // refresh the overridden options for nodes and edges. - this.body.emitter.emit('refresh'); - return util.deepExtend(allOptions, this.optionsBackup); - } - } + /** + * Set the content for the popup window. This can be HTML code or text. + * @param {string | Element} content + */ + value: function setText(content) { + if (content instanceof Element) { + this.frame.innerHTML = ''; + this.frame.appendChild(content); + } else { + this.frame.innerHTML = content; // string containing text or HTML } - return allOptions; } }, { - key: 'adaptAllOptions', - value: function adaptAllOptions(allOptions) { - if (this.options.hierarchical.enabled === true) { - // set the physics - if (allOptions.physics === undefined || allOptions.physics === true) { - allOptions.physics = { solver: 'hierarchicalRepulsion' }; - this.optionsBackup.physics = { solver: 'barnesHut' }; - } else if (typeof allOptions.physics === 'object') { - this.optionsBackup.physics = { solver: 'barnesHut' }; - if (allOptions.physics.solver !== undefined) { - this.optionsBackup.physics = { solver: allOptions.physics.solver }; - } - allOptions.physics['solver'] = 'hierarchicalRepulsion'; - } else if (allOptions.physics !== false) { - this.optionsBackup.physics = { solver: 'barnesHut' }; - allOptions.physics['solver'] = 'hierarchicalRepulsion'; - } + key: 'show', - // get the type of static smooth curve in case it is required - var type = 'horizontal'; - if (this.options.hierarchical.direction === 'RL' || this.options.hierarchical.direction === 'LR') { - type = 'vertical'; - } + /** + * Show the popup window + * @param {boolean} [doShow] Show or hide the window + */ + value: function show(doShow) { + if (doShow === undefined) { + doShow = true; + } - // disable smooth curves if nothing is defined. If smooth curves have been turned on, turn them into static smooth curves. - if (allOptions.edges === undefined) { - this.optionsBackup.edges = { smooth: { enabled: true, type: 'dynamic' } }; - allOptions.edges = { smooth: false }; - } else if (allOptions.edges.smooth === undefined) { - this.optionsBackup.edges = { smooth: { enabled: true, type: 'dynamic' } }; - allOptions.edges.smooth = false; - } else { - if (typeof allOptions.edges.smooth === 'boolean') { - this.optionsBackup.edges = { smooth: allOptions.edges.smooth }; - allOptions.edges.smooth = { enabled: allOptions.edges.smooth, type: type }; - } else { - // allow custom types except for dynamic - if (allOptions.edges.smooth.type !== undefined && allOptions.edges.smooth.type !== 'dynamic') { - type = allOptions.edges.smooth.type; - } + 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; - this.optionsBackup.edges = { - smooth: allOptions.edges.smooth.enabled === undefined ? true : allOptions.edges.smooth.enabled, - type: allOptions.edges.smooth.type === undefined ? 'dynamic' : allOptions.edges.smooth.type, - roundness: allOptions.edges.smooth.roundness === undefined ? 0.5 : allOptions.edges.smooth.roundness - }; - allOptions.edges.smooth = { - enabled: allOptions.edges.smooth.enabled === undefined ? true : allOptions.edges.smooth.enabled, - type: type, - roundness: allOptions.edges.smooth.roundness === undefined ? 0.5 : allOptions.edges.smooth.roundness - }; - } + var top = this.y - height; + if (top + height + this.padding > maxHeight) { + top = maxHeight - height - this.padding; + } + if (top < this.padding) { + top = this.padding; } - // force all edges into static smooth curves. Only applies to edges that do not use the global options for smooth. - this.body.emitter.emit('_forceDisableDynamicCurves', type); - } - return allOptions; - } - }, { - key: 'seededRandom', - value: function seededRandom() { - var x = Math.sin(this.randomSeed++) * 10000; - return x - Math.floor(x); - } - }, { - key: 'positionInitially', - value: function positionInitially(nodesArray) { - if (this.options.hierarchical.enabled !== true) { - this.randomSeed = this.initialRandomSeed; - for (var i = 0; i < nodesArray.length; i++) { - var node = nodesArray[i]; - if (!node.isFixed() && (node.x === undefined || node.y === undefined)) { - var radius = 10 * 0.1 * nodesArray.length + 10; - var angle = 2 * Math.PI * this.seededRandom(); - if (node.options.fixed.x === false) { - node.x = radius * Math.cos(angle); - } - if (node.options.fixed.x === false) { - node.y = radius * Math.sin(angle); - } - } + 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: 'getSeed', - value: function getSeed() { - return this.initialRandomSeed; - } - }, { - key: 'setupHierarchicalLayout', + key: 'hide', /** - * This is the main function to layout the nodes in a hierarchical way. - * It checks if the node details are supplied correctly - * - * @private + * Hide the popup window */ - value: function setupHierarchicalLayout() { - if (this.options.hierarchical.enabled === true && this.body.nodeIndices.length > 0) { - // get the size of the largest hubs and check if the user has defined a level for a node. - var node = undefined, - nodeId = undefined; - var definedLevel = false; - var undefinedLevel = false; - this.hierarchicalLevels = {}; - this.nodeSpacing = 100; + value: function hide() { + this.hidden = true; + this.frame.style.visibility = 'hidden'; + } + }]); - for (nodeId in this.body.nodes) { - if (this.body.nodes.hasOwnProperty(nodeId)) { - node = this.body.nodes[nodeId]; - if (node.options.level !== undefined) { - definedLevel = true; - this.hierarchicalLevels[nodeId] = node.options.level; - } else { - undefinedLevel = true; - } - } - } + return Popup; + })(); - // if the user defined some levels but not all, alert and run without hierarchical layout - if (undefinedLevel === true && definedLevel === true) { - throw new Error('To use the hierarchical layout, nodes require either no predefined levels or levels have to be defined for all nodes.'); - return; - } else { - // setup the system to use hierarchical method. - //this._changeConstants(); + exports['default'] = Popup; + module.exports = exports['default']; - // define levels if undefined by the users. Based on hubsize - if (undefinedLevel === true) { - if (this.options.hierarchical.sortMethod === 'hubsize') { - this._determineLevelsByHubsize(); - } else if (this.options.hierarchical.sortMethod === 'directed' || 'direction') { - this._determineLevelsDirected(); - } - } +/***/ }, +/* 107 */ +/***/ function(module, exports, __webpack_require__) { - // check the distribution of the nodes per level. - var distribution = this._getDistribution(); + "use strict"; - // place the nodes on the canvas. - this._placeNodesByHierarchy(distribution); - } + 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 Node = __webpack_require__(65); + var Edge = __webpack_require__(85); + var util = __webpack_require__(4); + + var SelectionHandler = (function () { + function SelectionHandler(body, canvas) { + var _this = this; + + _classCallCheck(this, SelectionHandler); + + this.body = body; + this.canvas = canvas; + this.selectionObj = { nodes: [], edges: [] }; + this.hoverObj = { nodes: {}, edges: {} }; + + this.options = {}; + this.defaultOptions = { + multiselect: false, + selectable: true, + selectConnectedEdges: true, + hoverConnectedEdges: true + }; + util.extend(this.options, this.defaultOptions); + + this.body.emitter.on("_dataChanged", function () { + _this.updateSelection(); + }); + } + + _createClass(SelectionHandler, [{ + key: "setOptions", + value: function setOptions(options) { + if (options !== undefined) { + var fields = ["multiselect", "hoverConnectedEdges", "selectable", "selectConnectedEdges"]; + util.selectiveDeepExtend(fields, this.options, options); } } }, { - key: '_placeNodesByHierarchy', + key: "selectOnPoint", /** - * This function places the nodes on the canvas based on the hierarchial distribution. + * handles the selection part of the tap; * - * @param {Object} distribution | obtained by the function this._getDistribution() + * @param {Object} pointer * @private */ - value: function _placeNodesByHierarchy(distribution) { - var nodeId = undefined, - node = undefined; - this.positionedNodes = {}; - // start placing all the level 0 nodes first. Then recursively position their branches. - for (var level in distribution) { - if (distribution.hasOwnProperty(level)) { - for (nodeId in distribution[level].nodes) { - if (distribution[level].nodes.hasOwnProperty(nodeId)) { + value: function selectOnPoint(pointer) { + var selected = false; + if (this.options.selectable === true) { + var obj = this.getNodeAt(pointer) || this.getEdgeAt(pointer); - node = distribution[level].nodes[nodeId]; + // unselect after getting the objects in order to restore width and height. + this.unselectAll(); - if (this.options.hierarchical.direction === 'UD' || this.options.hierarchical.direction === 'DU') { - if (node.x === undefined) { - node.x = distribution[level].distance; - } - distribution[level].distance = node.x + this.nodeSpacing; - } else { - if (node.y === undefined) { - node.y = distribution[level].distance; - } - distribution[level].distance = node.y + this.nodeSpacing; - } + if (obj !== undefined) { + selected = this.selectObject(obj); + } + this.body.emitter.emit("_requestRedraw"); + } + return selected; + } + }, { + key: "selectAdditionalOnPoint", + value: function selectAdditionalOnPoint(pointer) { + var selectionChanged = false; + if (this.options.selectable === true) { + var obj = this.getNodeAt(pointer) || this.getEdgeAt(pointer); - this.positionedNodes[nodeId] = true; - this._placeBranchNodes(node.edges, node.id, distribution, level); - } + if (obj !== undefined) { + selectionChanged = true; + if (obj.isSelected() === true) { + this.deselectObject(obj); + } else { + this.selectObject(obj); } + + this.body.emitter.emit("_requestRedraw"); } } + return selectionChanged; } }, { - key: '_getDistribution', + key: "_generateClickEvent", + value: function _generateClickEvent(eventType, event, pointer, oldSelection) { + var properties = this.getSelection(); + properties["pointer"] = { + DOM: { x: pointer.x, y: pointer.y }, + canvas: this.canvas.DOMtoCanvas(pointer) + }; + properties["event"] = event; - /** - * This function get the distribution of levels based on hubsize - * - * @returns {Object} - * @private - */ - value: function _getDistribution() { - var distribution = {}; - var nodeId = undefined, - node = undefined; + if (oldSelection !== undefined) { + properties["previousSelection"] = oldSelection; + } + this.body.emitter.emit(eventType, properties); + } + }, { + key: "selectObject", + value: function selectObject(obj) { + var highlightEdges = arguments[1] === undefined ? this.options.selectConnectedEdges : arguments[1]; - // we fix Y because the hierarchy is vertical, we fix X so we do not give a node an x position for a second time. - // the fix of X is removed after the x value has been set. - for (nodeId in this.body.nodes) { - if (this.body.nodes.hasOwnProperty(nodeId)) { - node = this.body.nodes[nodeId]; - var level = this.hierarchicalLevels[nodeId] === undefined ? 0 : this.hierarchicalLevels[nodeId]; - if (this.options.hierarchical.direction === 'UD' || this.options.hierarchical.direction === 'DU') { - node.y = this.options.hierarchical.levelSeparation * level; - node.options.fixed.y = true; - } else { - node.x = this.options.hierarchical.levelSeparation * level; - node.options.fixed.x = true; - } - if (distribution[level] === undefined) { - distribution[level] = { amount: 0, nodes: {}, distance: 0 }; + if (obj !== undefined) { + if (obj instanceof Node) { + if (highlightEdges === true) { + this._selectConnectedEdges(obj); } - distribution[level].amount += 1; - distribution[level].nodes[nodeId] = node; } + obj.select(); + this._addToSelection(obj); + return true; } - return distribution; + return false; } }, { - key: '_getHubSize', + key: "deselectObject", + value: function deselectObject(obj) { + if (obj.isSelected() === true) { + obj.selected = false; + this._removeFromSelection(obj); + } + } + }, { + key: "_getAllNodesOverlappingWith", /** - * Get the hubsize from all remaining unlevelled nodes. - * - * @returns {number} + * retrieve all nodes overlapping with given object + * @param {Object} object An object with parameters left, top, right, bottom + * @return {Number[]} An array with id's of the overlapping nodes * @private */ - value: function _getHubSize() { - var hubSize = 0; - for (var nodeId in this.body.nodes) { - if (this.body.nodes.hasOwnProperty(nodeId)) { - var node = this.body.nodes[nodeId]; - if (this.hierarchicalLevels[nodeId] === undefined) { - hubSize = node.edges.length < hubSize ? hubSize : node.edges.length; - } + value: function _getAllNodesOverlappingWith(object) { + var overlappingNodes = []; + var nodes = this.body.nodes; + for (var i = 0; i < this.body.nodeIndices.length; i++) { + var nodeId = this.body.nodeIndices[i]; + if (nodes[nodeId].isOverlappingWith(object)) { + overlappingNodes.push(nodeId); } } - return hubSize; + return overlappingNodes; } }, { - key: '_determineLevelsByHubsize', + key: "_pointerToPositionObject", /** - * this function allocates nodes in levels based on the recursive branching from the largest hubs. + * Return a position object in canvasspace from a single point in screenspace * - * @param hubsize + * @param pointer + * @returns {{left: number, top: number, right: number, bottom: number}} * @private */ - value: function _determineLevelsByHubsize() { - var nodeId = undefined, - node = undefined; - var hubSize = 1; - - while (hubSize > 0) { - // determine hubs - hubSize = this._getHubSize(); - if (hubSize === 0) break; - - for (nodeId in this.body.nodes) { - if (this.body.nodes.hasOwnProperty(nodeId)) { - node = this.body.nodes[nodeId]; - if (node.edges.length === hubSize) { - this._setLevelByHubsize(0, node); - } - } - } - } + value: function _pointerToPositionObject(pointer) { + var canvasPos = this.canvas.DOMtoCanvas(pointer); + return { + left: canvasPos.x - 1, + top: canvasPos.y + 1, + right: canvasPos.x + 1, + bottom: canvasPos.y - 1 + }; } }, { - key: '_setLevelByHubsize', + key: "getNodeAt", /** - * this function is called recursively to enumerate the barnches of the largest hubs and give each node a level. + * Get the top node at the a specific point (like a click) * - * @param level - * @param edges - * @param parentId + * @param {{x: Number, y: Number}} pointer + * @return {Node | undefined} node * @private */ - value: function _setLevelByHubsize(level, node) { - if (this.hierarchicalLevels[node.id] !== undefined) return; + value: function getNodeAt(pointer) { + var returnNode = arguments[1] === undefined ? true : arguments[1]; - var childNode = undefined; - this.hierarchicalLevels[node.id] = level; - for (var i = 0; i < node.edges.length; i++) { - if (node.edges[i].toId === node.id) { - childNode = node.edges[i].from; + // we first check if this is an navigation controls element + var positionObject = this._pointerToPositionObject(pointer); + var overlappingNodes = this._getAllNodesOverlappingWith(positionObject); + // if there are overlapping nodes, select the last one, this is the + // one which is drawn on top of the others + if (overlappingNodes.length > 0) { + if (returnNode === true) { + return this.body.nodes[overlappingNodes[overlappingNodes.length - 1]]; } else { - childNode = node.edges[i].to; + return overlappingNodes[overlappingNodes.length - 1]; } - this._setLevelByHubsize(level + 1, childNode); + } else { + return undefined; } } }, { - key: '_determineLevelsDirected', + key: "_getEdgesOverlappingWith", /** - * this function allocates nodes in levels based on the direction of the edges - * - * @param hubsize + * retrieve all edges overlapping with given object, selector is around center + * @param {Object} object An object with parameters left, top, right, bottom + * @return {Number[]} An array with id's of the overlapping nodes * @private */ - value: function _determineLevelsDirected() { - var nodeId = undefined, - node = undefined; - var minLevel = 10000; - - // set first node to source - for (nodeId in this.body.nodes) { - if (this.body.nodes.hasOwnProperty(nodeId)) { - node = this.body.nodes[nodeId]; - this._setLevelDirected(minLevel, node); - } - } - - // get the minimum level - for (nodeId in this.body.nodes) { - if (this.body.nodes.hasOwnProperty(nodeId)) { - minLevel = this.hierarchicalLevels[nodeId] < minLevel ? this.hierarchicalLevels[nodeId] : minLevel; - } - } - - // subtract the minimum from the set so we have a range starting from 0 - for (nodeId in this.body.nodes) { - if (this.body.nodes.hasOwnProperty(nodeId)) { - this.hierarchicalLevels[nodeId] -= minLevel; + value: function _getEdgesOverlappingWith(object, overlappingEdges) { + var edges = this.body.edges; + for (var i = 0; i < this.body.edgeIndices.length; i++) { + var edgeId = this.body.edgeIndices[i]; + if (edges[edgeId].isOverlappingWith(object)) { + overlappingEdges.push(edgeId); } } } }, { - key: '_setLevelDirected', + key: "_getAllEdgesOverlappingWith", /** - * this function is called recursively to enumerate the branched of the first node and give each node a level based on edge direction - * - * @param level - * @param edges - * @param parentId + * retrieve all nodes overlapping with given object + * @param {Object} object An object with parameters left, top, right, bottom + * @return {Number[]} An array with id's of the overlapping nodes * @private */ - value: function _setLevelDirected(level, node) { - if (this.hierarchicalLevels[node.id] !== undefined) return; - - var childNode = undefined; - this.hierarchicalLevels[node.id] = level; - - for (var i = 0; i < node.edges.length; i++) { - if (node.edges[i].toId === node.id) { - childNode = node.edges[i].from; - this._setLevelDirected(level - 1, childNode); - } else { - childNode = node.edges[i].to; - this._setLevelDirected(level + 1, childNode); - } - } + value: function _getAllEdgesOverlappingWith(object) { + var overlappingEdges = []; + this._getEdgesOverlappingWith(object, overlappingEdges); + return overlappingEdges; } }, { - key: '_placeBranchNodes', + key: "getEdgeAt", /** - * This is a recursively called function to enumerate the branches from the largest hubs and place the nodes - * on a X position that ensures there will be no overlap. + * Place holder. To implement change the getNodeAt to a _getObjectAt. Have the _getObjectAt call + * getNodeAt and _getEdgesAt, then priortize the selection to user preferences. * - * @param edges - * @param parentId - * @param distribution - * @param parentLevel + * @param pointer + * @returns {undefined} * @private */ - value: function _placeBranchNodes(edges, parentId, distribution, parentLevel) { - for (var i = 0; i < edges.length; i++) { - var childNode = undefined; - var parentNode = undefined; - if (edges[i].toId === parentId) { - childNode = edges[i].from; - parentNode = edges[i].to; - } else { - childNode = edges[i].to; - parentNode = edges[i].from; - } - var childNodeLevel = this.hierarchicalLevels[childNode.id]; + value: function getEdgeAt(pointer) { + var returnEdge = arguments[1] === undefined ? true : arguments[1]; - if (this.positionedNodes[childNode.id] === undefined) { - // if a node is conneceted to another node on the same level (or higher (means lower level))!, this is not handled here. - if (childNodeLevel > parentLevel) { - if (this.options.hierarchical.direction === 'UD' || this.options.hierarchical.direction === 'DU') { - if (childNode.x === undefined) { - childNode.x = Math.max(distribution[childNodeLevel].distance, parentNode.x); - } - distribution[childNodeLevel].distance = childNode.x + this.nodeSpacing; - this.positionedNodes[childNode.id] = true; - } else { - if (childNode.y === undefined) { - childNode.y = Math.max(distribution[childNodeLevel].distance, parentNode.y); - } - distribution[childNodeLevel].distance = childNode.y + this.nodeSpacing; - } - this.positionedNodes[childNode.id] = true; + var positionObject = this._pointerToPositionObject(pointer); + var overlappingEdges = this._getAllEdgesOverlappingWith(positionObject); - if (childNode.edges.length > 1) { - this._placeBranchNodes(childNode.edges, childNode.id, distribution, childNodeLevel); - } - } + if (overlappingEdges.length > 0) { + if (returnEdge === true) { + return this.body.edges[overlappingEdges[overlappingEdges.length - 1]]; + } else { + return overlappingEdges[overlappingEdges.length - 1]; } + } else { + return undefined; } } - }]); - - return LayoutEngine; - })(); - - exports['default'] = LayoutEngine; - module.exports = exports['default']; - -/***/ }, -/* 107 */ -/***/ function(module, exports, __webpack_require__) { - - 'use strict'; - - Object.defineProperty(exports, '__esModule', { - value: true - }); - - var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); - - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - - var util = __webpack_require__(2); - var Hammer = __webpack_require__(25); - var hammerUtil = __webpack_require__(30); - - /** - * clears the toolbar div element of children - * - * @private - */ - - var ManipulationSystem = (function () { - function ManipulationSystem(body, canvas, selectionHandler) { - var _this = this; - - _classCallCheck(this, ManipulationSystem); - - this.body = body; - this.canvas = canvas; - this.selectionHandler = selectionHandler; - - this.editMode = false; - this.manipulationDiv = undefined; - this.editModeDiv = undefined; - this.closeDiv = undefined; - - this.manipulationHammers = []; - this.temporaryUIFunctions = {}; - this.temporaryEventFunctions = []; - - this.touchTime = 0; - this.temporaryIds = { nodes: [], edges: [] }; - this.guiEnabled = false; - this.inMode = false; - this.selectedControlNode = undefined; - - this.options = {}; - this.defaultOptions = { - enabled: false, - initiallyActive: false, - addNode: true, - addEdge: true, - editNode: undefined, - editEdge: true, - deleteNode: true, - deleteEdge: true, - controlNodeStyle: { - shape: 'dot', - size: 6, - color: { background: '#ff0000', border: '#3c3c3c', highlight: { background: '#07f968', border: '#3c3c3c' } }, - borderWidth: 2, - borderWidthSelected: 2 - } - }; - util.extend(this.options, this.defaultOptions); - - this.body.emitter.on('destroy', function () { - _this._clean(); - }); - this.body.emitter.on('_dataChanged', this._restore.bind(this)); - this.body.emitter.on('_resetData', this._restore.bind(this)); - } + }, { + key: "_addToSelection", - _createClass(ManipulationSystem, [{ - key: '_restore', + /** + * Add object to the selection array. + * + * @param obj + * @private + */ + value: function _addToSelection(obj) { + if (obj instanceof Node) { + this.selectionObj.nodes[obj.id] = obj; + } else { + this.selectionObj.edges[obj.id] = obj; + } + } + }, { + key: "_addToHover", /** - * If something changes in the data during editing, switch back to the initial datamanipulation state and close all edit modes. + * Add object to the selection array. + * + * @param obj * @private */ - value: function _restore() { - if (this.inMode !== false) { - if (this.options.initiallyActive === true) { - this.enableEditMode(); - } else { - this.disableEditMode(); - } + value: function _addToHover(obj) { + if (obj instanceof Node) { + this.hoverObj.nodes[obj.id] = obj; + } else { + this.hoverObj.edges[obj.id] = obj; } } }, { - key: 'setOptions', + key: "_removeFromSelection", /** - * Set the Options - * @param options + * Remove a single option from selection. + * + * @param {Object} obj + * @private */ - value: function setOptions(options, allOptions, globalOptions) { - if (allOptions !== undefined) { - if (allOptions.locale !== undefined) { - this.options.locale = allOptions.locale; - } else { - this.options.locale = globalOptions.locale; - } - if (allOptions.locales !== undefined) { - this.options.locales = allOptions.locales; - } else { - this.options.locales = globalOptions.locales; - } + value: function _removeFromSelection(obj) { + if (obj instanceof Node) { + delete this.selectionObj.nodes[obj.id]; + } else { + delete this.selectionObj.edges[obj.id]; } + } + }, { + key: "unselectAll", - if (options !== undefined) { - if (typeof options === 'boolean') { - this.options.enabled = options; - } else { - this.options.enabled = true; - util.deepExtend(this.options, options); + /** + * Unselect all. The selectionObj is useful for this. + * + * @private + */ + value: function unselectAll() { + for (var nodeId in this.selectionObj.nodes) { + if (this.selectionObj.nodes.hasOwnProperty(nodeId)) { + this.selectionObj.nodes[nodeId].unselect(); } - if (this.options.initiallyActive === true) { - this.editMode = true; + } + for (var edgeId in this.selectionObj.edges) { + if (this.selectionObj.edges.hasOwnProperty(edgeId)) { + this.selectionObj.edges[edgeId].unselect(); } - this._setup(); } + + this.selectionObj = { nodes: {}, edges: {} }; } }, { - key: 'toggleEditMode', + key: "_getSelectedNodeCount", /** - * Enable or disable edit-mode. Draws the DOM required and cleans up after itself. + * return the number of selected nodes * + * @returns {number} * @private */ - value: function toggleEditMode() { - if (this.editMode === true) { - this.disableEditMode(); - } else { - this.enableEditMode(); + value: function _getSelectedNodeCount() { + var count = 0; + for (var nodeId in this.selectionObj.nodes) { + if (this.selectionObj.nodes.hasOwnProperty(nodeId)) { + count += 1; + } } + return count; } }, { - key: 'enableEditMode', - value: function enableEditMode() { - this.editMode = true; + key: "_getSelectedNode", - this._clean(); - if (this.guiEnabled === true) { - this.manipulationDiv.style.display = 'block'; - this.closeDiv.style.display = 'block'; - this.editModeDiv.style.display = 'none'; - this.showManipulatorToolbar(); + /** + * return the selected node + * + * @returns {number} + * @private + */ + value: function _getSelectedNode() { + for (var nodeId in this.selectionObj.nodes) { + if (this.selectionObj.nodes.hasOwnProperty(nodeId)) { + return this.selectionObj.nodes[nodeId]; + } } + return undefined; } }, { - key: 'disableEditMode', - value: function disableEditMode() { - this.editMode = false; + key: "_getSelectedEdge", - this._clean(); - if (this.guiEnabled === true) { - this.manipulationDiv.style.display = 'none'; - this.closeDiv.style.display = 'none'; - this.editModeDiv.style.display = 'block'; - this._createEditButton(); + /** + * return the selected edge + * + * @returns {number} + * @private + */ + value: function _getSelectedEdge() { + for (var edgeId in this.selectionObj.edges) { + if (this.selectionObj.edges.hasOwnProperty(edgeId)) { + return this.selectionObj.edges[edgeId]; + } } + return undefined; } }, { - key: 'showManipulatorToolbar', + key: "_getSelectedEdgeCount", /** - * Creates the main toolbar. Removes functions bound to the select event. Binds all the buttons of the toolbar. + * return the number of selected edges * + * @returns {number} * @private */ - value: function showManipulatorToolbar() { - // restore the state of any bound functions or events, remove control nodes, restore physics - this._clean(); - - // reset global letiables - this.manipulationDOM = {}; - - // if the gui is enabled, draw all elements. - if (this.guiEnabled === true) { - // a _restore will hide these menus - this.editMode = true; - this.manipulationDiv.style.display = 'block'; - this.closeDiv.style.display = 'block'; - - var selectedNodeCount = this.selectionHandler._getSelectedNodeCount(); - var selectedEdgeCount = this.selectionHandler._getSelectedEdgeCount(); - var selectedTotalCount = selectedNodeCount + selectedEdgeCount; - var locale = this.options.locales[this.options.locale]; - var needSeperator = false; - - if (this.options.addNode !== false) { - this._createAddNodeButton(locale); - needSeperator = true; - } - if (this.options.addEdge !== false) { - if (needSeperator === true) { - this._createSeperator(1); - } else { - needSeperator = true; - } - this._createAddEdgeButton(locale); + value: function _getSelectedEdgeCount() { + var count = 0; + for (var edgeId in this.selectionObj.edges) { + if (this.selectionObj.edges.hasOwnProperty(edgeId)) { + count += 1; } + } + return count; + } + }, { + key: "_getSelectedObjectCount", - if (selectedNodeCount === 1 && typeof this.options.editNode === 'function') { - if (needSeperator === true) { - this._createSeperator(2); - } else { - needSeperator = true; - } - this._createEditNodeButton(locale); - } else if (selectedEdgeCount === 1 && selectedNodeCount === 0 && this.options.editEdge !== false) { - if (needSeperator === true) { - this._createSeperator(3); - } else { - needSeperator = true; - } - this._createEditEdgeButton(locale); + /** + * return the number of selected objects. + * + * @returns {number} + * @private + */ + value: function _getSelectedObjectCount() { + var count = 0; + for (var nodeId in this.selectionObj.nodes) { + if (this.selectionObj.nodes.hasOwnProperty(nodeId)) { + count += 1; } - - // remove buttons - if (selectedTotalCount !== 0) { - if (selectedNodeCount === 1 && this.options.deleteNode !== false) { - if (needSeperator === true) { - this._createSeperator(4); - } - this._createDeleteButton(locale); - } else if (selectedNodeCount === 0 && this.options.deleteEdge !== false) { - if (needSeperator === true) { - this._createSeperator(4); - } - this._createDeleteButton(locale); - } + } + for (var edgeId in this.selectionObj.edges) { + if (this.selectionObj.edges.hasOwnProperty(edgeId)) { + count += 1; } - - // bind the close button - this._bindHammerToDiv(this.closeDiv, this.toggleEditMode.bind(this)); - - // refresh this bar based on what has been selected - this._temporaryBindEvent('select', this.showManipulatorToolbar.bind(this)); } - - // redraw to show any possible changes - this.body.emitter.emit('_redraw'); + return count; } }, { - key: 'addNodeMode', + key: "_selectionIsEmpty", /** - * Create the toolbar for adding Nodes + * Check if anything is selected * + * @returns {boolean} * @private */ - value: function addNodeMode() { - // when using the gui, enable edit mode if it wasnt already. - if (this.editMode !== true) { - this.enableEditMode(); + value: function _selectionIsEmpty() { + for (var nodeId in this.selectionObj.nodes) { + if (this.selectionObj.nodes.hasOwnProperty(nodeId)) { + return false; + } } - - // restore the state of any bound functions or events, remove control nodes, restore physics - this._clean(); - - this.inMode = 'addNode'; - if (this.guiEnabled === true) { - var locale = this.options.locales[this.options.locale]; - this.manipulationDOM = {}; - this._createBackButton(locale); - this._createSeperator(); - this._createDescription(locale['addDescription'] || this.options.locales['en']['addDescription']); - - // bind the close button - this._bindHammerToDiv(this.closeDiv, this.toggleEditMode.bind(this)); + for (var edgeId in this.selectionObj.edges) { + if (this.selectionObj.edges.hasOwnProperty(edgeId)) { + return false; + } } - - this._temporaryBindEvent('click', this._performAddNode.bind(this)); + return true; } }, { - key: 'editNode', + key: "_clusterInSelection", /** - * call the bound function to handle the editing of the node. The node has to be selected. + * check if one of the selected nodes is a cluster. * + * @returns {boolean} * @private */ - value: function editNode() { - var _this2 = this; - - // when using the gui, enable edit mode if it wasnt already. - if (this.editMode !== true) { - this.enableEditMode(); - } - - // restore the state of any bound functions or events, remove control nodes, restore physics - this._clean(); - var node = this.selectionHandler._getSelectedNode(); - if (node !== undefined) { - this.inMode = 'editNode'; - if (typeof this.options.editNode === 'function') { - if (node.isCluster !== true) { - var data = util.deepExtend({}, node.options, true); - data.x = node.x; - data.y = node.y; - - if (this.options.editNode.length === 2) { - this.options.editNode(data, function (finalizedData) { - if (finalizedData !== null && finalizedData !== undefined && _this2.inMode === 'editNode') { - // if for whatever reason the mode has changes (due to dataset change) disregard the callback) { - _this2.body.data.nodes.getDataSet().update(finalizedData); - } - _this2.showManipulatorToolbar(); - }); - } else { - throw new Error('The function for edit does not support two arguments (data, callback)'); - } - } else { - alert(this.options.locales[this.options.locale]['editClusterError'] || this.options.locales['en']['editClusterError']); + value: function _clusterInSelection() { + for (var nodeId in this.selectionObj.nodes) { + if (this.selectionObj.nodes.hasOwnProperty(nodeId)) { + if (this.selectionObj.nodes[nodeId].clusterSize > 1) { + return true; } - } else { - throw new Error('No function has been configured to handle the editing of nodes.'); } - } else { - this.showManipulatorToolbar(); } + return false; } }, { - key: 'addEdgeMode', + key: "_selectConnectedEdges", /** - * create the toolbar to connect nodes + * select the edges connected to the node that is being selected * + * @param {Node} node * @private */ - value: function addEdgeMode() { - // when using the gui, enable edit mode if it wasnt already. - if (this.editMode !== true) { - this.enableEditMode(); - } - - // restore the state of any bound functions or events, remove control nodes, restore physics - this._clean(); - - this.inMode = 'addEdge'; - if (this.guiEnabled === true) { - var locale = this.options.locales[this.options.locale]; - this.manipulationDOM = {}; - this._createBackButton(locale); - this._createSeperator(); - this._createDescription(locale['edgeDescription'] || this.options.locales['en']['edgeDescription']); - - // bind the close button - this._bindHammerToDiv(this.closeDiv, this.toggleEditMode.bind(this)); + value: function _selectConnectedEdges(node) { + for (var i = 0; i < node.edges.length; i++) { + var edge = node.edges[i]; + edge.select(); + this._addToSelection(edge); } - - // temporarily overload functions - this._temporaryBindUI('onTouch', this._handleConnect.bind(this)); - this._temporaryBindUI('onDragEnd', this._finishConnect.bind(this)); - this._temporaryBindUI('onDrag', this._dragControlNode.bind(this)); - this._temporaryBindUI('onRelease', this._finishConnect.bind(this)); - - this._temporaryBindUI('onDragStart', function () {}); - this._temporaryBindUI('onHold', function () {}); } }, { - key: 'editEdgeMode', + key: "_hoverConnectedEdges", /** - * create the toolbar to edit edges + * select the edges connected to the node that is being selected * + * @param {Node} node * @private */ - value: function editEdgeMode() { - var _this3 = this; - - // when using the gui, enable edit mode if it wasnt already. - if (this.editMode !== true) { - this.enableEditMode(); + value: function _hoverConnectedEdges(node) { + for (var i = 0; i < node.edges.length; i++) { + var edge = node.edges[i]; + edge.hover = true; + this._addToHover(edge); } + } + }, { + key: "_unselectConnectedEdges", - // restore the state of any bound functions or events, remove control nodes, restore physics - this._clean(); - - this.inMode = 'editEdge'; - if (this.guiEnabled === true) { - var locale = this.options.locales[this.options.locale]; - this.manipulationDOM = {}; - this._createBackButton(locale); - this._createSeperator(); - this._createDescription(locale['editEdgeDescription'] || this.options.locales['en']['editEdgeDescription']); - - // bind the close button - this._bindHammerToDiv(this.closeDiv, this.toggleEditMode.bind(this)); + /** + * unselect the edges connected to the node that is being selected + * + * @param {Node} node + * @private + */ + value: function _unselectConnectedEdges(node) { + for (var i = 0; i < node.edges.length; i++) { + var edge = node.edges[i]; + edge.unselect(); + this._removeFromSelection(edge); } + } + }, { + key: "blurObject", - this.edgeBeingEditedId = this.selectionHandler.getSelectedEdges()[0]; - if (this.edgeBeingEditedId !== undefined) { - (function () { - var edge = _this3.body.edges[_this3.edgeBeingEditedId]; - - // create control nodes - var controlNodeFrom = _this3._getNewTargetNode(edge.from.x, edge.from.y); - var controlNodeTo = _this3._getNewTargetNode(edge.to.x, edge.to.y); - - _this3.temporaryIds.nodes.push(controlNodeFrom.id); - _this3.temporaryIds.nodes.push(controlNodeTo.id); - - _this3.body.nodes[controlNodeFrom.id] = controlNodeFrom; - _this3.body.nodeIndices.push(controlNodeFrom.id); - _this3.body.nodes[controlNodeTo.id] = controlNodeTo; - _this3.body.nodeIndices.push(controlNodeTo.id); - - // temporarily overload UI functions, cleaned up automatically because of _temporaryBindUI - _this3._temporaryBindUI('onTouch', _this3._controlNodeTouch.bind(_this3)); // used to get the position - _this3._temporaryBindUI('onTap', function () {}); // disabled - _this3._temporaryBindUI('onHold', function () {}); // disabled - _this3._temporaryBindUI('onDragStart', _this3._controlNodeDragStart.bind(_this3)); // used to select control node - _this3._temporaryBindUI('onDrag', _this3._controlNodeDrag.bind(_this3)); // used to drag control node - _this3._temporaryBindUI('onDragEnd', _this3._controlNodeDragEnd.bind(_this3)); // used to connect or revert control nodes - _this3._temporaryBindUI('onMouseMove', function () {}); // disabled - - // create function to position control nodes correctly on movement - // automatically cleaned up because we use the temporary bind - _this3._temporaryBindEvent('beforeDrawing', function (ctx) { - var positions = edge.edgeType.findBorderPositions(ctx); - if (controlNodeFrom.selected === false) { - controlNodeFrom.x = positions.from.x; - controlNodeFrom.y = positions.from.y; - } - if (controlNodeTo.selected === false) { - controlNodeTo.x = positions.to.x; - controlNodeTo.y = positions.to.y; - } - }); - - _this3.body.emitter.emit('_redraw'); - })(); - } else { - this.showManipulatorToolbar(); + /** + * This is called when someone clicks on a node. either select or deselect it. + * If there is an existing selection and we don't want to append to it, clear the existing selection + * + * @param {Node || Edge} object + * @private + */ + value: function blurObject(object) { + if (object.hover === true) { + object.hover = false; + this.body.emitter.emit("blurNode", { node: object.id }); } } }, { - key: 'deleteSelected', + key: "hoverObject", /** - * delete everything in the selection + * This is called when someone clicks on a node. either select or deselect it. + * If there is an existing selection and we don't want to append to it, clear the existing selection * + * @param {Node || Edge} object * @private */ - value: function deleteSelected() { - var _this4 = this; - - // when using the gui, enable edit mode if it wasnt already. - if (this.editMode !== true) { - this.enableEditMode(); + value: function hoverObject(object) { + var hoverChanged = false; + // remove all node hover highlights + for (var nodeId in this.hoverObj.nodes) { + if (this.hoverObj.nodes.hasOwnProperty(nodeId)) { + if (object === undefined) { + this.blurObject(this.hoverObj.nodes[nodeId]); + hoverChanged = true; + } else if (object instanceof Node && object.id != nodeId || object instanceof Edge || object === undefined) { + this.blurObject(this.hoverObj.nodes[nodeId]); + hoverChanged = true; + delete this.hoverObj.nodes[nodeId]; + } + } } - // restore the state of any bound functions or events, remove control nodes, restore physics - this._clean(); + // removing all edge hover highlights + for (var edgeId in this.hoverObj.edges) { + if (this.hoverObj.edges.hasOwnProperty(edgeId)) { + this.hoverObj.edges[edgeId].hover = false; + delete this.hoverObj.edges[edgeId]; + } + } - this.inMode = 'delete'; - var selectedNodes = this.selectionHandler.getSelectedNodes(); - var selectedEdges = this.selectionHandler.getSelectedEdges(); - var deleteFunction = undefined; - if (selectedNodes.length > 0) { - for (var i = 0; i < selectedNodes.length; i++) { - if (this.body.nodes[selectedNodes[i]].isCluster === true) { - alert(this.options.locales[this.options.locale]['deleteClusterError'] || this.options.locales['en']['deleteClusterError']); - return; + if (object !== undefined) { + if (object.hover === false) { + object.hover = true; + this._addToHover(object); + hoverChanged = true; + if (object instanceof Node) { + this.body.emitter.emit("hoverNode", { node: object.id }); } } - - if (typeof this.options.deleteNode === 'function') { - deleteFunction = this.options.deleteNode; - } - } else if (selectedEdges.length > 0) { - if (typeof this.options.deleteEdge === 'function') { - deleteFunction = this.options.deleteEdge; + if (object instanceof Node && this.options.hoverConnectedEdges === true) { + this._hoverConnectedEdges(object); } } - if (typeof deleteFunction === 'function') { - var data = { nodes: selectedNodes, edges: selectedEdges }; - if (deleteFunction.length === 2) { - deleteFunction(data, function (finalizedData) { - if (finalizedData !== null && finalizedData !== undefined && _this4.inMode === 'delete') { - // if for whatever reason the mode has changes (due to dataset change) disregard the callback) { - _this4.body.data.edges.getDataSet().remove(finalizedData.edges); - _this4.body.data.nodes.getDataSet().remove(finalizedData.nodes); - _this4.body.emitter.emit('startSimulation'); - _this4.showManipulatorToolbar(); - } - }); - } else { - throw new Error('The function for delete does not support two arguments (data, callback)'); - } - } else { - this.body.data.edges.getDataSet().remove(selectedEdges); - this.body.data.nodes.getDataSet().remove(selectedNodes); - this.body.emitter.emit('startSimulation'); - this.showManipulatorToolbar(); + if (hoverChanged === true) { + this.body.emitter.emit("_requestRedraw"); } } }, { - key: '_setup', - - //********************************************** PRIVATE ***************************************// + key: "getSelection", /** - * draw or remove the DOM - * @private + * + * retrieve the currently selected objects + * @return {{nodes: Array., edges: Array.}} selection */ - value: function _setup() { - if (this.options.enabled === true) { - // Enable the GUI - this.guiEnabled = true; - - this._createWrappers(); - if (this.editMode === false) { - this._createEditButton(); - } else { - this.showManipulatorToolbar(); - } - } else { - this._removeManipulationDOM(); - - // disable the gui - this.guiEnabled = false; - } + value: function getSelection() { + var nodeIds = this.getSelectedNodes(); + var edgeIds = this.getSelectedEdges(); + return { nodes: nodeIds, edges: edgeIds }; } }, { - key: '_createWrappers', + key: "getSelectedNodes", /** - * create the div overlays that contain the DOM - * @private + * + * retrieve the currently selected nodes + * @return {String[]} selection An array with the ids of the + * selected nodes. */ - value: function _createWrappers() { - // load the manipulator HTML elements. All styling done in css. - if (this.manipulationDiv === undefined) { - this.manipulationDiv = document.createElement('div'); - this.manipulationDiv.className = 'vis-manipulation'; - if (this.editMode === true) { - this.manipulationDiv.style.display = 'block'; - } else { - this.manipulationDiv.style.display = 'none'; - } - this.canvas.frame.appendChild(this.manipulationDiv); - } - - // container for the edit button. - if (this.editModeDiv === undefined) { - this.editModeDiv = document.createElement('div'); - this.editModeDiv.className = 'vis-edit-mode'; - if (this.editMode === true) { - this.editModeDiv.style.display = 'none'; - } else { - this.editModeDiv.style.display = 'block'; + value: function getSelectedNodes() { + var idArray = []; + if (this.options.selectable === true) { + for (var nodeId in this.selectionObj.nodes) { + if (this.selectionObj.nodes.hasOwnProperty(nodeId)) { + idArray.push(nodeId); + } } - this.canvas.frame.appendChild(this.editModeDiv); - } - - // container for the close div button - if (this.closeDiv === undefined) { - this.closeDiv = document.createElement('div'); - this.closeDiv.className = 'vis-close'; - this.closeDiv.style.display = this.manipulationDiv.style.display; - this.canvas.frame.appendChild(this.closeDiv); } + return idArray; } }, { - key: '_getNewTargetNode', + key: "getSelectedEdges", /** - * generate a new target node. Used for creating new edges and editing edges - * @param x - * @param y - * @returns {*} - * @private + * + * retrieve the currently selected edges + * @return {Array} selection An array with the ids of the + * selected nodes. */ - value: function _getNewTargetNode(x, y) { - var controlNodeStyle = util.deepExtend({}, this.options.controlNodeStyle); - - controlNodeStyle.id = 'targetNode' + util.randomUUID(); - controlNodeStyle.hidden = false; - controlNodeStyle.physics = false; - controlNodeStyle.x = x; - controlNodeStyle.y = y; - - return this.body.functions.createNode(controlNodeStyle); + value: function getSelectedEdges() { + var idArray = []; + if (this.options.selectable === true) { + for (var edgeId in this.selectionObj.edges) { + if (this.selectionObj.edges.hasOwnProperty(edgeId)) { + idArray.push(edgeId); + } + } + } + return idArray; } }, { - key: '_createEditButton', + key: "selectNodes", /** - * Create the edit button + * select zero or more nodes with the option to highlight edges + * @param {Number[] | String[]} selection An array with the ids of the + * selected nodes. + * @param {boolean} [highlightEdges] */ - value: function _createEditButton() { - // restore everything to it's original state (if applicable) - this._clean(); + value: function selectNodes(selection) { + var highlightEdges = arguments[1] === undefined ? true : arguments[1]; - // reset the manipulationDOM - this.manipulationDOM = {}; + var i = undefined, + id = undefined; - // empty the editModeDiv - util.recursiveDOMDelete(this.editModeDiv); + if (!selection || selection.length === undefined) throw "Selection must be an array with ids"; - // create the contents for the editMode button - var locale = this.options.locales[this.options.locale]; - var button = this._createButton('editMode', 'vis-button vis-edit vis-edit-mode', locale['edit'] || this.options.locales['en']['edit']); - this.editModeDiv.appendChild(button); + // first unselect any selected node + this.unselectAll(); - // bind a hammer listener to the button, calling the function toggleEditMode. - this._bindHammerToDiv(button, this.toggleEditMode.bind(this)); + for (i = 0; i < selection.length; i++) { + id = selection[i]; + + var node = this.body.nodes[id]; + if (!node) { + throw new RangeError("Node with id \"" + id + "\" not found"); + } + this.selectObject(node, highlightEdges); + } + this.body.emitter.emit("_requestRedraw"); } }, { - key: '_clean', + key: "selectEdges", /** - * this function cleans up after everything this module does. Temporary elements, functions and events are removed, physics restored, hammers removed. - * @private + * select zero or more edges + * @param {Number[] | String[]} selection An array with the ids of the + * selected nodes. */ - value: function _clean() { - // not in mode - this.inMode = false; - - // _clean the divs - if (this.guiEnabled === true) { - util.recursiveDOMDelete(this.editModeDiv); - util.recursiveDOMDelete(this.manipulationDiv); - - // removes all the bindings and overloads - this._cleanManipulatorHammers(); - } + value: function selectEdges(selection) { + var i = undefined, + id = undefined; - // remove temporary nodes and edges - this._cleanupTemporaryNodesAndEdges(); + if (!selection || selection.length === undefined) throw "Selection must be an array with ids"; - // restore overloaded UI functions - this._unbindTemporaryUIs(); + // first unselect any selected objects + this.unselectAll(); - // remove the temporaryEventFunctions - this._unbindTemporaryEvents(); + for (i = 0; i < selection.length; i++) { + id = selection[i]; - // restore the physics if required - this.body.emitter.emit('restorePhysics'); + var edge = this.body.edges[id]; + if (!edge) { + throw new RangeError("Edge with id \"" + id + "\" not found"); + } + this.selectObject(edge); + } + this.body.emitter.emit("_requestRedraw"); } }, { - key: '_cleanManipulatorHammers', + key: "updateSelection", /** - * Each dom element has it's own hammer. They are stored in this.manipulationHammers. This cleans them up. + * Validate the selection: remove ids of nodes which no longer exist * @private */ - value: function _cleanManipulatorHammers() { - // _clean hammer bindings - if (this.manipulationHammers.length != 0) { - for (var i = 0; i < this.manipulationHammers.length; i++) { - this.manipulationHammers[i].destroy(); + value: function updateSelection() { + for (var nodeId in this.selectionObj.nodes) { + if (this.selectionObj.nodes.hasOwnProperty(nodeId)) { + if (!this.body.nodes.hasOwnProperty(nodeId)) { + delete this.selectionObj.nodes[nodeId]; + } + } + } + for (var edgeId in this.selectionObj.edges) { + if (this.selectionObj.edges.hasOwnProperty(edgeId)) { + if (!this.body.edges.hasOwnProperty(edgeId)) { + delete this.selectionObj.edges[edgeId]; + } } - this.manipulationHammers = []; } } - }, { - key: '_removeManipulationDOM', + }]); - /** - * Remove all DOM elements created by this module. - * @private - */ - value: function _removeManipulationDOM() { - // removes all the bindings and overloads - this._clean(); + return SelectionHandler; + })(); - // empty the manipulation divs - util.recursiveDOMDelete(this.manipulationDiv); - util.recursiveDOMDelete(this.editModeDiv); - util.recursiveDOMDelete(this.closeDiv); + exports["default"] = SelectionHandler; + module.exports = exports["default"]; - // remove the manipulation divs - if (this.manipulationDiv) { - this.canvas.frame.removeChild(this.manipulationDiv); - } - if (this.editModeDiv) { - this.canvas.frame.removeChild(this.editModeDiv); - } - if (this.closeDiv) { - this.canvas.frame.removeChild(this.manipulationDiv); - } +/***/ }, +/* 108 */ +/***/ function(module, exports, __webpack_require__) { - // set the references to undefined - this.manipulationDiv = undefined; - this.editModeDiv = undefined; - this.closeDiv = undefined; - } - }, { - key: '_createSeperator', + 'use strict'; - /** - * create a seperator line. the index is to differentiate in the manipulation dom - * @param index - * @private - */ - value: function _createSeperator() { - var index = arguments[0] === undefined ? 1 : arguments[0]; + Object.defineProperty(exports, '__esModule', { + value: true + }); - this.manipulationDOM['seperatorLineDiv' + index] = document.createElement('div'); - this.manipulationDOM['seperatorLineDiv' + index].className = 'vis-separator-line'; - this.manipulationDiv.appendChild(this.manipulationDOM['seperatorLineDiv' + index]); - } - }, { - key: '_createAddNodeButton', + 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; }; })(); - // ---------------------- DOM functions for buttons --------------------------// + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - value: function _createAddNodeButton(locale) { - var button = this._createButton('addNode', 'vis-button vis-add', locale['addNode'] || this.options.locales['en']['addNode']); - this.manipulationDiv.appendChild(button); - this._bindHammerToDiv(button, this.addNodeMode.bind(this)); - } - }, { - key: '_createAddEdgeButton', - value: function _createAddEdgeButton(locale) { - var button = this._createButton('addEdge', 'vis-button vis-connect', locale['addEdge'] || this.options.locales['en']['addEdge']); - this.manipulationDiv.appendChild(button); - this._bindHammerToDiv(button, this.addEdgeMode.bind(this)); - } - }, { - key: '_createEditNodeButton', - value: function _createEditNodeButton(locale) { - var button = this._createButton('editNode', 'vis-button vis-edit', locale['editNode'] || this.options.locales['en']['editNode']); - this.manipulationDiv.appendChild(button); - this._bindHammerToDiv(button, this.editNode.bind(this)); - } - }, { - key: '_createEditEdgeButton', - value: function _createEditEdgeButton(locale) { - var button = this._createButton('editEdge', 'vis-button vis-edit', locale['editEdge'] || this.options.locales['en']['editEdge']); - this.manipulationDiv.appendChild(button); - this._bindHammerToDiv(button, this.editEdgeMode.bind(this)); - } - }, { - key: '_createDeleteButton', - value: function _createDeleteButton(locale) { - var button = this._createButton('delete', 'vis-button vis-delete', locale['del'] || this.options.locales['en']['del']); - this.manipulationDiv.appendChild(button); - this._bindHammerToDiv(button, this.deleteSelected.bind(this)); - } - }, { - key: '_createBackButton', - value: function _createBackButton(locale) { - var button = this._createButton('back', 'vis-button vis-back', locale['back'] || this.options.locales['en']['back']); - this.manipulationDiv.appendChild(button); - this._bindHammerToDiv(button, this.showManipulatorToolbar.bind(this)); - } - }, { - key: '_createButton', - value: function _createButton(id, className, label) { - var labelClassName = arguments[3] === undefined ? 'vis-label' : arguments[3]; + var util = __webpack_require__(4); - this.manipulationDOM[id + 'Div'] = document.createElement('div'); - this.manipulationDOM[id + 'Div'].className = className; - this.manipulationDOM[id + 'Label'] = document.createElement('div'); - this.manipulationDOM[id + 'Label'].className = labelClassName; - this.manipulationDOM[id + 'Label'].innerHTML = label; - this.manipulationDOM[id + 'Div'].appendChild(this.manipulationDOM[id + 'Label']); - return this.manipulationDOM[id + 'Div']; - } - }, { - key: '_createDescription', - value: function _createDescription(label) { - this.manipulationDiv.appendChild(this._createButton('description', 'vis-button vis-none', label)); - } - }, { - key: '_temporaryBindEvent', + var LayoutEngine = (function () { + function LayoutEngine(body) { + _classCallCheck(this, LayoutEngine); - // -------------------------- End of DOM functions for buttons ------------------------------// + this.body = body; - /** - * this binds an event until cleanup by the clean functions. - * @param event - * @param newFunction - * @private - */ - value: function _temporaryBindEvent(event, newFunction) { - this.temporaryEventFunctions.push({ event: event, boundFunction: newFunction }); - this.body.emitter.on(event, newFunction); + this.initialRandomSeed = Math.round(Math.random() * 1000000); + this.randomSeed = this.initialRandomSeed; + this.options = {}; + this.optionsBackup = {}; + + this.defaultOptions = { + randomSeed: undefined, + hierarchical: { + enabled: false, + levelSeparation: 150, + direction: 'UD', // UD, DU, LR, RL + sortMethod: 'hubsize' // hubsize, directed + } + }; + util.extend(this.options, this.defaultOptions); + + this.hierarchicalLevels = {}; + + this.bindEventListeners(); + } + + _createClass(LayoutEngine, [{ + key: 'bindEventListeners', + value: function bindEventListeners() { + var _this = this; + + this.body.emitter.on('_dataChanged', function () { + _this.setupHierarchicalLayout(); + }); + this.body.emitter.on('_resetHierarchicalLayout', function () { + _this.setupHierarchicalLayout(); + }); } }, { - key: '_temporaryBindUI', + key: 'setOptions', + value: function setOptions(options, allOptions) { + if (options !== undefined) { + var prevHierarchicalState = this.options.hierarchical.enabled; - /** - * this overrides an UI function until cleanup by the clean function - * @param UIfunctionName - * @param newFunction - * @private - */ - value: function _temporaryBindUI(UIfunctionName, newFunction) { - if (this.body.eventListeners[UIfunctionName] !== undefined) { - this.temporaryUIFunctions[UIfunctionName] = this.body.eventListeners[UIfunctionName]; - this.body.eventListeners[UIfunctionName] = newFunction; - } else { - throw new Error('This UI function does not exist. Typo? You tried: ' + UIfunctionName + ' possible are: ' + JSON.stringify(Object.keys(this.body.eventListeners))); + util.mergeOptions(this.options, options, 'hierarchical'); + if (options.randomSeed !== undefined) { + this.initialRandomSeed = options.randomSeed; + } + + if (this.options.hierarchical.enabled === true) { + // make sure the level seperation is the right way up + if (this.options.hierarchical.direction === 'RL' || this.options.hierarchical.direction === 'DU') { + if (this.options.hierarchical.levelSeparation > 0) { + this.options.hierarchical.levelSeparation *= -1; + } + } else { + if (this.options.hierarchical.levelSeparation < 0) { + this.options.hierarchical.levelSeparation *= -1; + } + } + + this.body.emitter.emit('_resetHierarchicalLayout'); + // because the hierarchical system needs it's own physics and smooth curve settings, we adapt the other options if needed. + return this.adaptAllOptions(allOptions); + } else { + if (prevHierarchicalState === true) { + // refresh the overridden options for nodes and edges. + this.body.emitter.emit('refresh'); + return util.deepExtend(allOptions, this.optionsBackup); + } + } } + return allOptions; } }, { - key: '_unbindTemporaryUIs', + key: 'adaptAllOptions', + value: function adaptAllOptions(allOptions) { + if (this.options.hierarchical.enabled === true) { + // set the physics + if (allOptions.physics === undefined || allOptions.physics === true) { + allOptions.physics = { solver: 'hierarchicalRepulsion' }; + this.optionsBackup.physics = { solver: 'barnesHut' }; + } else if (typeof allOptions.physics === 'object') { + this.optionsBackup.physics = { solver: 'barnesHut' }; + if (allOptions.physics.solver !== undefined) { + this.optionsBackup.physics = { solver: allOptions.physics.solver }; + } + allOptions.physics['solver'] = 'hierarchicalRepulsion'; + } else if (allOptions.physics !== false) { + this.optionsBackup.physics = { solver: 'barnesHut' }; + allOptions.physics['solver'] = 'hierarchicalRepulsion'; + } - /** - * Restore the overridden UI functions to their original state. - * - * @private - */ - value: function _unbindTemporaryUIs() { - for (var functionName in this.temporaryUIFunctions) { - if (this.temporaryUIFunctions.hasOwnProperty(functionName)) { - this.body.eventListeners[functionName] = this.temporaryUIFunctions[functionName]; - delete this.temporaryUIFunctions[functionName]; + // get the type of static smooth curve in case it is required + var type = 'horizontal'; + if (this.options.hierarchical.direction === 'RL' || this.options.hierarchical.direction === 'LR') { + type = 'vertical'; } + + // disable smooth curves if nothing is defined. If smooth curves have been turned on, turn them into static smooth curves. + if (allOptions.edges === undefined) { + this.optionsBackup.edges = { smooth: { enabled: true, type: 'dynamic' } }; + allOptions.edges = { smooth: false }; + } else if (allOptions.edges.smooth === undefined) { + this.optionsBackup.edges = { smooth: { enabled: true, type: 'dynamic' } }; + allOptions.edges.smooth = false; + } else { + if (typeof allOptions.edges.smooth === 'boolean') { + this.optionsBackup.edges = { smooth: allOptions.edges.smooth }; + allOptions.edges.smooth = { enabled: allOptions.edges.smooth, type: type }; + } else { + // allow custom types except for dynamic + if (allOptions.edges.smooth.type !== undefined && allOptions.edges.smooth.type !== 'dynamic') { + type = allOptions.edges.smooth.type; + } + + this.optionsBackup.edges = { + smooth: allOptions.edges.smooth.enabled === undefined ? true : allOptions.edges.smooth.enabled, + type: allOptions.edges.smooth.type === undefined ? 'dynamic' : allOptions.edges.smooth.type, + roundness: allOptions.edges.smooth.roundness === undefined ? 0.5 : allOptions.edges.smooth.roundness + }; + allOptions.edges.smooth = { + enabled: allOptions.edges.smooth.enabled === undefined ? true : allOptions.edges.smooth.enabled, + type: type, + roundness: allOptions.edges.smooth.roundness === undefined ? 0.5 : allOptions.edges.smooth.roundness + }; + } + } + + // force all edges into static smooth curves. Only applies to edges that do not use the global options for smooth. + this.body.emitter.emit('_forceDisableDynamicCurves', type); } - this.temporaryUIFunctions = {}; + return allOptions; } }, { - key: '_unbindTemporaryEvents', - - /** - * Unbind the events created by _temporaryBindEvent - * @private - */ - value: function _unbindTemporaryEvents() { - for (var i = 0; i < this.temporaryEventFunctions.length; i++) { - var eventName = this.temporaryEventFunctions[i].event; - var boundFunction = this.temporaryEventFunctions[i].boundFunction; - this.body.emitter.off(eventName, boundFunction); + key: 'seededRandom', + value: function seededRandom() { + var x = Math.sin(this.randomSeed++) * 10000; + return x - Math.floor(x); + } + }, { + key: 'positionInitially', + value: function positionInitially(nodesArray) { + if (this.options.hierarchical.enabled !== true) { + this.randomSeed = this.initialRandomSeed; + for (var i = 0; i < nodesArray.length; i++) { + var node = nodesArray[i]; + if (!node.isFixed() && (node.x === undefined || node.y === undefined)) { + var radius = 10 * 0.1 * nodesArray.length + 10; + var angle = 2 * Math.PI * this.seededRandom(); + if (node.options.fixed.x === false) { + node.x = radius * Math.cos(angle); + } + if (node.options.fixed.x === false) { + node.y = radius * Math.sin(angle); + } + } + } } - this.temporaryEventFunctions = []; } }, { - key: '_bindHammerToDiv', - - /** - * Bind an hammer instance to a DOM element. - * @param domElement - * @param funct - */ - value: function _bindHammerToDiv(domElement, boundFunction) { - var hammer = new Hammer(domElement, {}); - hammerUtil.onTouch(hammer, boundFunction); - this.manipulationHammers.push(hammer); + key: 'getSeed', + value: function getSeed() { + return this.initialRandomSeed; } }, { - key: '_cleanupTemporaryNodesAndEdges', + key: 'setupHierarchicalLayout', /** - * Neatly clean up temporary edges and nodes + * This is the main function to layout the nodes in a hierarchical way. + * It checks if the node details are supplied correctly + * * @private */ - value: function _cleanupTemporaryNodesAndEdges() { - // _clean temporary edges - for (var i = 0; i < this.temporaryIds.edges.length; i++) { - this.body.edges[this.temporaryIds.edges[i]].disconnect(); - delete this.body.edges[this.temporaryIds.edges[i]]; - var indexTempEdge = this.body.edgeIndices.indexOf(this.temporaryIds.edges[i]); - if (indexTempEdge !== -1) { - this.body.edgeIndices.splice(indexTempEdge, 1); - } - } + value: function setupHierarchicalLayout() { + if (this.options.hierarchical.enabled === true && this.body.nodeIndices.length > 0) { + // get the size of the largest hubs and check if the user has defined a level for a node. + var node = undefined, + nodeId = undefined; + var definedLevel = false; + var undefinedLevel = false; + this.hierarchicalLevels = {}; + this.nodeSpacing = 100; - // _clean temporary nodes - for (var i = 0; i < this.temporaryIds.nodes.length; i++) { - delete this.body.nodes[this.temporaryIds.nodes[i]]; - var indexTempNode = this.body.nodeIndices.indexOf(this.temporaryIds.nodes[i]); - if (indexTempNode !== -1) { - this.body.nodeIndices.splice(indexTempNode, 1); + for (nodeId in this.body.nodes) { + if (this.body.nodes.hasOwnProperty(nodeId)) { + node = this.body.nodes[nodeId]; + if (node.options.level !== undefined) { + definedLevel = true; + this.hierarchicalLevels[nodeId] = node.options.level; + } else { + undefinedLevel = true; + } + } } - } - this.temporaryIds = { nodes: [], edges: [] }; - } - }, { - key: '_controlNodeTouch', + // if the user defined some levels but not all, alert and run without hierarchical layout + if (undefinedLevel === true && definedLevel === true) { + throw new Error('To use the hierarchical layout, nodes require either no predefined levels or levels have to be defined for all nodes.'); + return; + } else { + // setup the system to use hierarchical method. + //this._changeConstants(); - // ------------------------------------------ EDIT EDGE FUNCTIONS -----------------------------------------// + // define levels if undefined by the users. Based on hubsize + if (undefinedLevel === true) { + if (this.options.hierarchical.sortMethod === 'hubsize') { + this._determineLevelsByHubsize(); + } else if (this.options.hierarchical.sortMethod === 'directed' || 'direction') { + this._determineLevelsDirected(); + } + } - /** - * the touch is used to get the position of the initial click - * @param event - * @private - */ - value: function _controlNodeTouch(event) { - this.selectionHandler.unselectAll(); - this.lastTouch = this.body.functions.getPointer(event.center); - this.lastTouch.translation = util.extend({}, this.body.view.translation); // copy the object + // check the distribution of the nodes per level. + var distribution = this._getDistribution(); + + // place the nodes on the canvas. + this._placeNodesByHierarchy(distribution); + } + } } }, { - key: '_controlNodeDragStart', + key: '_placeNodesByHierarchy', /** - * the drag start is used to mark one of the control nodes as selected. - * @param event + * This function places the nodes on the canvas based on the hierarchial distribution. + * + * @param {Object} distribution | obtained by the function this._getDistribution() * @private */ - value: function _controlNodeDragStart(event) { - var pointer = this.lastTouch; - var pointerObj = this.selectionHandler._pointerToPositionObject(pointer); - var from = this.body.nodes[this.temporaryIds.nodes[0]]; - var to = this.body.nodes[this.temporaryIds.nodes[1]]; - var edge = this.body.edges[this.edgeBeingEditedId]; - this.selectedControlNode = undefined; + value: function _placeNodesByHierarchy(distribution) { + var nodeId = undefined, + node = undefined; + this.positionedNodes = {}; + // start placing all the level 0 nodes first. Then recursively position their branches. + for (var level in distribution) { + if (distribution.hasOwnProperty(level)) { + for (nodeId in distribution[level].nodes) { + if (distribution[level].nodes.hasOwnProperty(nodeId)) { - var fromSelect = from.isOverlappingWith(pointerObj); - var toSelect = to.isOverlappingWith(pointerObj); + node = distribution[level].nodes[nodeId]; - if (fromSelect === true) { - this.selectedControlNode = from; - edge.edgeType.from = from; - } else if (toSelect === true) { - this.selectedControlNode = to; - edge.edgeType.to = to; - } + if (this.options.hierarchical.direction === 'UD' || this.options.hierarchical.direction === 'DU') { + if (node.x === undefined) { + node.x = distribution[level].distance; + } + distribution[level].distance = node.x + this.nodeSpacing; + } else { + if (node.y === undefined) { + node.y = distribution[level].distance; + } + distribution[level].distance = node.y + this.nodeSpacing; + } - this.body.emitter.emit('_redraw'); + this.positionedNodes[nodeId] = true; + this._placeBranchNodes(node.edges, node.id, distribution, level); + } + } + } + } } }, { - key: '_controlNodeDrag', + key: '_getDistribution', /** - * dragging the control nodes or the canvas - * @param event + * This function get the distribution of levels based on hubsize + * + * @returns {Object} * @private */ - value: function _controlNodeDrag(event) { - this.body.emitter.emit('disablePhysics'); - var pointer = this.body.functions.getPointer(event.center); - var pos = this.canvas.DOMtoCanvas(pointer); + value: function _getDistribution() { + var distribution = {}; + var nodeId = undefined, + node = undefined; - if (this.selectedControlNode !== undefined) { - this.selectedControlNode.x = pos.x; - this.selectedControlNode.y = pos.y; - } else { - // if the drag was not started properly because the click started outside the network div, start it now. - var diffX = pointer.x - this.lastTouch.x; - var diffY = pointer.y - this.lastTouch.y; - this.body.view.translation = { x: this.lastTouch.translation.x + diffX, y: this.lastTouch.translation.y + diffY }; + // we fix Y because the hierarchy is vertical, we fix X so we do not give a node an x position for a second time. + // the fix of X is removed after the x value has been set. + for (nodeId in this.body.nodes) { + if (this.body.nodes.hasOwnProperty(nodeId)) { + node = this.body.nodes[nodeId]; + var level = this.hierarchicalLevels[nodeId] === undefined ? 0 : this.hierarchicalLevels[nodeId]; + if (this.options.hierarchical.direction === 'UD' || this.options.hierarchical.direction === 'DU') { + node.y = this.options.hierarchical.levelSeparation * level; + node.options.fixed.y = true; + } else { + node.x = this.options.hierarchical.levelSeparation * level; + node.options.fixed.x = true; + } + if (distribution[level] === undefined) { + distribution[level] = { amount: 0, nodes: {}, distance: 0 }; + } + distribution[level].amount += 1; + distribution[level].nodes[nodeId] = node; + } } - this.body.emitter.emit('_redraw'); + return distribution; } }, { - key: '_controlNodeDragEnd', + key: '_getHubSize', /** - * connecting or restoring the control nodes. - * @param event + * Get the hubsize from all remaining unlevelled nodes. + * + * @returns {number} * @private */ - value: function _controlNodeDragEnd(event) { - var pointer = this.body.functions.getPointer(event.center); - var pointerObj = this.selectionHandler._pointerToPositionObject(pointer); - var edge = this.body.edges[this.edgeBeingEditedId]; - - var overlappingNodeIds = this.selectionHandler._getAllNodesOverlappingWith(pointerObj); - var node = undefined; - for (var i = overlappingNodeIds.length - 1; i >= 0; i--) { - if (overlappingNodeIds[i] !== this.selectedControlNode.id) { - node = this.body.nodes[overlappingNodeIds[i]]; - break; - } - } - - // perform the connection - if (node !== undefined && this.selectedControlNode !== undefined) { - if (node.isCluster === true) { - alert(this.options.locales[this.options.locale]['createEdgeError'] || this.options.locales['en']['createEdgeError']); - } else { - var from = this.body.nodes[this.temporaryIds.nodes[0]]; - if (this.selectedControlNode.id === from.id) { - this._performEditEdge(node.id, edge.to.id); - } else { - this._performEditEdge(edge.from.id, node.id); + value: function _getHubSize() { + var hubSize = 0; + for (var nodeId in this.body.nodes) { + if (this.body.nodes.hasOwnProperty(nodeId)) { + var node = this.body.nodes[nodeId]; + if (this.hierarchicalLevels[nodeId] === undefined) { + hubSize = node.edges.length < hubSize ? hubSize : node.edges.length; } } - } else { - edge.updateEdgeType(); - this.body.emitter.emit('restorePhysics'); } - this.body.emitter.emit('_redraw'); + return hubSize; } }, { - key: '_handleConnect', - - // ------------------------------------ END OF EDIT EDGE FUNCTIONS -----------------------------------------// + key: '_determineLevelsByHubsize', - // ------------------------------------------- ADD EDGE FUNCTIONS -----------------------------------------// /** - * the function bound to the selection event. It checks if you want to connect a cluster and changes the description - * to walk the user through the process. + * this function allocates nodes in levels based on the recursive branching from the largest hubs. * + * @param hubsize * @private */ - value: function _handleConnect(event) { - // check to avoid double fireing of this function. - if (new Date().valueOf() - this.touchTime > 100) { - this.lastTouch = this.body.functions.getPointer(event.center); - this.lastTouch.translation = util.extend({}, this.body.view.translation); // copy the object - - var pointer = this.lastTouch; - var node = this.selectionHandler.getNodeAt(pointer); - - if (node !== undefined) { - if (node.isCluster === true) { - alert(this.options.locales[this.options.locale]['createEdgeError'] || this.options.locales['en']['createEdgeError']); - } else { - // create a node the temporary line can look at - var targetNode = this._getNewTargetNode(node.x, node.y); - this.body.nodes[targetNode.id] = targetNode; - this.body.nodeIndices.push(targetNode.id); + value: function _determineLevelsByHubsize() { + var nodeId = undefined, + node = undefined; + var hubSize = 1; - // create a temporary edge - var connectionEdge = this.body.functions.createEdge({ - id: 'connectionEdge' + util.randomUUID(), - from: node.id, - to: targetNode.id, - physics: false, - smooth: { - enabled: true, - type: 'continuous', - roundness: 0.5 - } - }); - this.body.edges[connectionEdge.id] = connectionEdge; - this.body.edgeIndices.push(connectionEdge.id); + while (hubSize > 0) { + // determine hubs + hubSize = this._getHubSize(); + if (hubSize === 0) break; - this.temporaryIds.nodes.push(targetNode.id); - this.temporaryIds.edges.push(connectionEdge.id); + for (nodeId in this.body.nodes) { + if (this.body.nodes.hasOwnProperty(nodeId)) { + node = this.body.nodes[nodeId]; + if (node.edges.length === hubSize) { + this._setLevelByHubsize(0, node); + } } } - this.touchTime = new Date().valueOf(); - } - } - }, { - key: '_dragControlNode', - value: function _dragControlNode(event) { - var pointer = this.body.functions.getPointer(event.center); - if (this.temporaryIds.nodes[0] !== undefined) { - var targetNode = this.body.nodes[this.temporaryIds.nodes[0]]; // there is only one temp node in the add edge mode. - targetNode.x = this.canvas._XconvertDOMtoCanvas(pointer.x); - targetNode.y = this.canvas._YconvertDOMtoCanvas(pointer.y); - this.body.emitter.emit('_redraw'); - } else { - var diffX = pointer.x - this.lastTouch.x; - var diffY = pointer.y - this.lastTouch.y; - this.body.view.translation = { x: this.lastTouch.translation.x + diffX, y: this.lastTouch.translation.y + diffY }; } } }, { - key: '_finishConnect', + key: '_setLevelByHubsize', /** - * Connect the new edge to the target if one exists, otherwise remove temp line - * @param event + * this function is called recursively to enumerate the barnches of the largest hubs and give each node a level. + * + * @param level + * @param edges + * @param parentId * @private */ - value: function _finishConnect(event) { - var pointer = this.body.functions.getPointer(event.center); - var pointerObj = this.selectionHandler._pointerToPositionObject(pointer); - - // remember the edge id - var connectFromId = undefined; - if (this.temporaryIds.edges[0] !== undefined) { - connectFromId = this.body.edges[this.temporaryIds.edges[0]].fromId; - } - - // get the overlapping node but NOT the temporary node; - var overlappingNodeIds = this.selectionHandler._getAllNodesOverlappingWith(pointerObj); - var node = undefined; - for (var i = overlappingNodeIds.length - 1; i >= 0; i--) { - // if the node id is NOT a temporary node, accept the node. - if (this.temporaryIds.nodes.indexOf(overlappingNodeIds[i]) === -1) { - node = this.body.nodes[overlappingNodeIds[i]]; - break; - } - } - - // clean temporary nodes and edges. - this._cleanupTemporaryNodesAndEdges(); + value: function _setLevelByHubsize(level, node) { + if (this.hierarchicalLevels[node.id] !== undefined) return; - // perform the connection - if (node !== undefined) { - if (node.isCluster === true) { - alert(this.options.locales[this.options.locale]['createEdgeError'] || this.options.locales['en']['createEdgeError']); + var childNode = undefined; + this.hierarchicalLevels[node.id] = level; + for (var i = 0; i < node.edges.length; i++) { + if (node.edges[i].toId === node.id) { + childNode = node.edges[i].from; } else { - if (this.body.nodes[connectFromId] !== undefined && this.body.nodes[node.id] !== undefined) { - this._performAddEdge(connectFromId, node.id); - } + childNode = node.edges[i].to; } + this._setLevelByHubsize(level + 1, childNode); } - this.body.emitter.emit('_redraw'); } }, { - key: '_performAddNode', - - // --------------------------------------- END OF ADD EDGE FUNCTIONS -------------------------------------// - - // ------------------------------ Performing all the actual data manipulation ------------------------// + key: '_determineLevelsDirected', /** - * Adds a node on the specified location + * this function allocates nodes in levels based on the direction of the edges + * + * @param hubsize + * @private */ - value: function _performAddNode(clickData) { - var _this5 = this; + value: function _determineLevelsDirected() { + var nodeId = undefined, + node = undefined; + var minLevel = 10000; - var defaultData = { - id: util.randomUUID(), - x: clickData.pointer.canvas.x, - y: clickData.pointer.canvas.y, - label: 'new' - }; + // set first node to source + for (nodeId in this.body.nodes) { + if (this.body.nodes.hasOwnProperty(nodeId)) { + node = this.body.nodes[nodeId]; + this._setLevelDirected(minLevel, node); + } + } - if (typeof this.options.addNode === 'function') { - if (this.options.addNode.length === 2) { - this.options.addNode(defaultData, function (finalizedData) { - if (finalizedData !== null && finalizedData !== undefined && _this5.inMode === 'addNode') { - // if for whatever reason the mode has changes (due to dataset change) disregard the callback - _this5.body.data.nodes.getDataSet().add(finalizedData); - _this5.showManipulatorToolbar(); - } - }); - } else { - throw new Error('The function for add does not support two arguments (data,callback)'); - this.showManipulatorToolbar(); + // get the minimum level + for (nodeId in this.body.nodes) { + if (this.body.nodes.hasOwnProperty(nodeId)) { + minLevel = this.hierarchicalLevels[nodeId] < minLevel ? this.hierarchicalLevels[nodeId] : minLevel; + } + } + + // subtract the minimum from the set so we have a range starting from 0 + for (nodeId in this.body.nodes) { + if (this.body.nodes.hasOwnProperty(nodeId)) { + this.hierarchicalLevels[nodeId] -= minLevel; } - } else { - this.body.data.nodes.getDataSet().add(defaultData); - this.showManipulatorToolbar(); } } }, { - key: '_performAddEdge', + key: '_setLevelDirected', /** - * connect two nodes with a new edge. + * this function is called recursively to enumerate the branched of the first node and give each node a level based on edge direction * + * @param level + * @param edges + * @param parentId * @private */ - value: function _performAddEdge(sourceNodeId, targetNodeId) { - var _this6 = this; + value: function _setLevelDirected(level, node) { + if (this.hierarchicalLevels[node.id] !== undefined) return; - var defaultData = { from: sourceNodeId, to: targetNodeId }; - if (typeof this.options.addEdge === 'function') { - if (this.options.addEdge.length === 2) { - this.options.addEdge(defaultData, function (finalizedData) { - if (finalizedData !== null && finalizedData !== undefined && _this6.inMode === 'addEdge') { - // if for whatever reason the mode has changes (due to dataset change) disregard the callback - _this6.body.data.edges.add(finalizedData); - _this6.selectionHandler.unselectAll(); - _this6.showManipulatorToolbar(); - } - }); + var childNode = undefined; + this.hierarchicalLevels[node.id] = level; + + for (var i = 0; i < node.edges.length; i++) { + if (node.edges[i].toId === node.id) { + childNode = node.edges[i].from; + this._setLevelDirected(level - 1, childNode); } else { - throw new Error('The function for connect does not support two arguments (data,callback)'); + childNode = node.edges[i].to; + this._setLevelDirected(level + 1, childNode); } - } else { - this.body.data.edges.add(defaultData); - this.selectionHandler.unselectAll(); - this.showManipulatorToolbar(); } } }, { - key: '_performEditEdge', + key: '_placeBranchNodes', /** - * connect two nodes with a new edge. + * This is a recursively called function to enumerate the branches from the largest hubs and place the nodes + * on a X position that ensures there will be no overlap. * + * @param edges + * @param parentId + * @param distribution + * @param parentLevel * @private */ - value: function _performEditEdge(sourceNodeId, targetNodeId) { - var _this7 = this; - - var defaultData = { id: this.edgeBeingEditedId, from: sourceNodeId, to: targetNodeId }; - if (typeof this.options.editEdge === 'function') { - if (this.options.editEdge.length === 2) { - this.options.editEdge(defaultData, function (finalizedData) { - if (finalizedData === null || finalizedData === undefined || _this7.inMode !== 'editEdge') { - // if for whatever reason the mode has changes (due to dataset change) disregard the callback) { - _this7.body.edges[defaultData.id].updateEdgeType(); - _this7.body.emitter.emit('_redraw'); + value: function _placeBranchNodes(edges, parentId, distribution, parentLevel) { + for (var i = 0; i < edges.length; i++) { + var childNode = undefined; + var parentNode = undefined; + if (edges[i].toId === parentId) { + childNode = edges[i].from; + parentNode = edges[i].to; + } else { + childNode = edges[i].to; + parentNode = edges[i].from; + } + var childNodeLevel = this.hierarchicalLevels[childNode.id]; + + if (this.positionedNodes[childNode.id] === undefined) { + // if a node is conneceted to another node on the same level (or higher (means lower level))!, this is not handled here. + if (childNodeLevel > parentLevel) { + if (this.options.hierarchical.direction === 'UD' || this.options.hierarchical.direction === 'DU') { + if (childNode.x === undefined) { + childNode.x = Math.max(distribution[childNodeLevel].distance, parentNode.x); + } + distribution[childNodeLevel].distance = childNode.x + this.nodeSpacing; + this.positionedNodes[childNode.id] = true; } else { - _this7.body.data.edges.update(finalizedData); - _this7.selectionHandler.unselectAll(); - _this7.showManipulatorToolbar(); + if (childNode.y === undefined) { + childNode.y = Math.max(distribution[childNodeLevel].distance, parentNode.y); + } + distribution[childNodeLevel].distance = childNode.y + this.nodeSpacing; } - }); - } else { - throw new Error('The function for edit does not support two arguments (data, callback)'); + this.positionedNodes[childNode.id] = true; + + if (childNode.edges.length > 1) { + this._placeBranchNodes(childNode.edges, childNode.id, distribution, childNodeLevel); + } + } } - } else { - this.body.data.edges.update(defaultData); - this.selectionHandler.unselectAll(); - this.showManipulatorToolbar(); } } }]); - return ManipulationSystem; + return LayoutEngine; })(); - exports['default'] = ManipulationSystem; + exports['default'] = LayoutEngine; module.exports = exports['default']; -/***/ }, -/* 108 */ -/***/ function(module, exports, __webpack_require__) { - - /** - * This object contains all possible options. It will check if the types are correct, if required if the option is one - * of the allowed values. - * - * __any__ means that the name of the property does not matter. - * __type__ is a required field for all objects and contains the allowed types of all objects - */ - 'use strict'; - - Object.defineProperty(exports, '__esModule', { - value: true - }); - var string = 'string'; - var boolean = 'boolean'; - var number = 'number'; - var array = 'array'; - var object = 'object'; // should only be in a __type__ property - var dom = 'dom'; - var any = 'any'; - - var allOptions = { - configure: { - enabled: { boolean: boolean }, - filter: { boolean: boolean, string: string, array: array, 'function': 'function' }, - container: { dom: dom }, - showButton: { boolean: boolean }, - __type__: { object: object, boolean: boolean, string: string, array: array, 'function': 'function' } - }, - edges: { - arrows: { - to: { enabled: { boolean: boolean }, scaleFactor: { number: number }, __type__: { object: object, boolean: boolean } }, - middle: { enabled: { boolean: boolean }, scaleFactor: { number: number }, __type__: { object: object, boolean: boolean } }, - from: { enabled: { boolean: boolean }, scaleFactor: { number: number }, __type__: { object: object, boolean: boolean } }, - __type__: { string: ['from', 'to', 'middle'], object: object } - }, - color: { - color: { string: string }, - highlight: { string: string }, - hover: { string: string }, - inherit: { string: ['from', 'to', 'both'], boolean: boolean }, - opacity: { number: number }, - __type__: { object: object, string: string } - }, - dashes: { boolean: boolean, array: array }, - font: { - color: { string: string }, - size: { number: number }, // px - face: { string: string }, - background: { string: string }, - strokeWidth: { number: number }, // px - strokeColor: { string: string }, - align: { string: ['horizontal', 'top', 'middle', 'bottom'] }, - __type__: { object: object, string: string } - }, - hidden: { boolean: boolean }, - hoverWidth: { 'function': 'function', number: number }, - label: { string: string, 'undefined': 'undefined' }, - length: { number: number, 'undefined': 'undefined' }, - physics: { boolean: boolean }, - scaling: { - min: { number: number }, - max: { number: number }, - label: { - enabled: { boolean: boolean }, - min: { number: number }, - max: { number: number }, - maxVisible: { number: number }, - drawThreshold: { number: number }, - __type__: { object: object, boolean: boolean } - }, - customScalingFunction: { 'function': 'function' }, - __type__: { object: object } - }, - selectionWidth: { 'function': 'function', number: number }, - selfReferenceSize: { number: number }, - shadow: { - enabled: { boolean: boolean }, - size: { number: number }, - x: { number: number }, - y: { number: number }, - __type__: { object: object, boolean: boolean } - }, - smooth: { - enabled: { boolean: boolean }, - type: { string: ['dynamic', 'continuous', 'discrete', 'diagonalCross', 'straightCross', 'horizontal', 'vertical', 'curvedCW', 'curvedCCW'] }, - roundness: { number: number }, - __type__: { object: object, boolean: boolean } - }, - title: { string: string, 'undefined': 'undefined' }, - width: { number: number }, - value: { number: number, 'undefined': 'undefined' }, - __type__: { object: object } - }, - groups: { - useDefaultGroups: { boolean: boolean }, - __any__: 'get from nodes, will be overwritten below', - __type__: { object: object } - }, - interaction: { - dragNodes: { boolean: boolean }, - dragView: { boolean: boolean }, - hideEdgesOnDrag: { boolean: boolean }, - hideNodesOnDrag: { boolean: boolean }, - hover: { boolean: boolean }, - keyboard: { - enabled: { boolean: boolean }, - speed: { x: { number: number }, y: { number: number }, zoom: { number: number }, __type__: { object: object } }, - bindToWindow: { boolean: boolean }, - __type__: { object: object, boolean: boolean } - }, - multiselect: { boolean: boolean }, - navigationButtons: { boolean: boolean }, - selectable: { boolean: boolean }, - selectConnectedEdges: { boolean: boolean }, - hoverConnectedEdges: { boolean: boolean }, - tooltipDelay: { number: number }, - zoomView: { boolean: boolean }, - __type__: { object: object } - }, - layout: { - randomSeed: { 'undefined': 'undefined', number: number }, - hierarchical: { - enabled: { boolean: boolean }, - levelSeparation: { number: number }, - direction: { string: ['UD', 'DU', 'LR', 'RL'] }, // UD, DU, LR, RL - sortMethod: { string: ['hubsize', 'directed'] }, // hubsize, directed - __type__: { object: object, boolean: boolean } - }, - __type__: { object: object } - }, - manipulation: { - enabled: { boolean: boolean }, - initiallyActive: { boolean: boolean }, - addNode: { boolean: boolean, 'function': 'function' }, - addEdge: { boolean: boolean, 'function': 'function' }, - editNode: { 'function': 'function' }, - editEdge: { boolean: boolean, 'function': 'function' }, - deleteNode: { boolean: boolean, 'function': 'function' }, - deleteEdge: { boolean: boolean, 'function': 'function' }, - controlNodeStyle: 'get from nodes, will be overwritten below', - __type__: { object: object, boolean: boolean } - }, - nodes: { - borderWidth: { number: number }, - borderWidthSelected: { number: number, 'undefined': 'undefined' }, - brokenImage: { string: string, 'undefined': 'undefined' }, - color: { - border: { string: string }, - background: { string: string }, - highlight: { - border: { string: string }, - background: { string: string }, - __type__: { object: object, string: string } - }, - hover: { - border: { string: string }, - background: { string: string }, - __type__: { object: object, string: string } - }, - __type__: { object: object, string: string } - }, - fixed: { - x: { boolean: boolean }, - y: { boolean: boolean }, - __type__: { object: object, boolean: boolean } - }, - font: { - color: { string: string }, - size: { number: number }, // px - face: { string: string }, - background: { string: string }, - strokeWidth: { number: number }, // px - strokeColor: { string: string }, - __type__: { object: object, string: string } - }, - group: { string: string, number: number, 'undefined': 'undefined' }, - hidden: { boolean: boolean }, - icon: { - face: { string: string }, - code: { string: string }, //'\uf007', - size: { number: number }, //50, - color: { string: string }, - __type__: { object: object } - }, - id: { string: string, number: number }, - image: { string: string, 'undefined': 'undefined' }, // --> URL - label: { string: string, 'undefined': 'undefined' }, - level: { number: number, 'undefined': 'undefined' }, - mass: { number: number }, - physics: { boolean: boolean }, - scaling: { - min: { number: number }, - max: { number: number }, - label: { - enabled: { boolean: boolean }, - min: { number: number }, - max: { number: number }, - maxVisible: { number: number }, - drawThreshold: { number: number }, - __type__: { object: object, boolean: boolean } - }, - customScalingFunction: { 'function': 'function' }, - __type__: { object: object } - }, - shadow: { - enabled: { boolean: boolean }, - size: { number: number }, - x: { number: number }, - y: { number: number }, - __type__: { object: object, boolean: boolean } - }, - shape: { string: ['ellipse', 'circle', 'database', 'box', 'text', 'image', 'circularImage', 'diamond', 'dot', 'star', 'triangle', 'triangleDown', 'square', 'icon'] }, - size: { number: number }, - title: { string: string, 'undefined': 'undefined' }, - value: { number: number, 'undefined': 'undefined' }, - x: { number: number }, - y: { number: number }, - __type__: { object: object } - }, - physics: { - barnesHut: { - gravitationalConstant: { number: number }, - centralGravity: { number: number }, - springLength: { number: number }, - springConstant: { number: number }, - damping: { number: number }, - avoidOverlap: { number: number }, - __type__: { object: object } - }, - forceAtlas2Based: { - gravitationalConstant: { number: number }, - centralGravity: { number: number }, - springLength: { number: number }, - springConstant: { number: number }, - damping: { number: number }, - avoidOverlap: { number: number }, - __type__: { object: object } - }, - repulsion: { - centralGravity: { number: number }, - springLength: { number: number }, - springConstant: { number: number }, - nodeDistance: { number: number }, - damping: { number: number }, - __type__: { object: object } - }, - hierarchicalRepulsion: { - centralGravity: { number: number }, - springLength: { number: number }, - springConstant: { number: number }, - nodeDistance: { number: number }, - damping: { number: number }, - __type__: { object: object } - }, - maxVelocity: { number: number }, - minVelocity: { number: number }, // px/s - solver: { string: ['barnesHut', 'repulsion', 'hierarchicalRepulsion', 'forceAtlas2Based'] }, - stabilization: { - enabled: { boolean: boolean }, - iterations: { number: number }, // maximum number of iteration to stabilize - updateInterval: { number: number }, - onlyDynamicEdges: { boolean: boolean }, - fit: { boolean: boolean }, - __type__: { object: object, boolean: boolean } - }, - timestep: { number: number }, - __type__: { object: object, boolean: boolean } - }, - - //globals : - autoResize: { boolean: boolean }, - clickToUse: { boolean: boolean }, - locale: { string: string }, - locales: { - __any__: { object: object }, - __type__: { object: object } - }, - height: { string: string }, - width: { string: string }, - __type__: { object: object } - }; - - allOptions.groups.__any__ = allOptions.nodes; - allOptions.manipulation.controlNodeStyle = allOptions.nodes; - - var configureOptions = { - nodes: { - borderWidth: [1, 0, 10, 1], - borderWidthSelected: [2, 0, 10, 1], - color: { - border: ['color', '#2B7CE9'], - background: ['color', '#97C2FC'], - highlight: { - border: ['color', '#2B7CE9'], - background: ['color', '#D2E5FF'] - }, - hover: { - border: ['color', '#2B7CE9'], - background: ['color', '#D2E5FF'] - } - }, - fixed: { - x: false, - y: false - }, - font: { - color: ['color', '#343434'], - size: [14, 0, 100, 1], // px - face: ['arial', 'verdana', 'tahoma'], - background: ['color', 'none'], - strokeWidth: [0, 0, 50, 1], // px - strokeColor: ['color', '#ffffff'] - }, - //group: 'string', - hidden: false, - //icon: { - // face: 'string', //'FontAwesome', - // code: 'string', //'\uf007', - // size: [50, 0, 200, 1], //50, - // color: ['color','#2B7CE9'] //'#aa00ff' - //}, - //image: 'string', // --> URL - physics: true, - scaling: { - min: [10, 0, 200, 1], - max: [30, 0, 200, 1], - label: { - enabled: false, - min: [14, 0, 200, 1], - max: [30, 0, 200, 1], - maxVisible: [30, 0, 200, 1], - drawThreshold: [5, 0, 20, 1] - } - }, - shadow: { - enabled: false, - size: [10, 0, 20, 1], - x: [5, -30, 30, 1], - y: [5, -30, 30, 1] - }, - shape: ['ellipse', 'box', 'circle', 'database', 'diamond', 'dot', 'square', 'star', 'text', 'triangle', 'triangleDown'], - size: [25, 0, 200, 1] - }, - edges: { - arrows: { - to: { enabled: false, scaleFactor: [1, 0, 3, 0.05] }, // boolean / {arrowScaleFactor:1} / {enabled: false, arrowScaleFactor:1} - middle: { enabled: false, scaleFactor: [1, 0, 3, 0.05] }, - from: { enabled: false, scaleFactor: [1, 0, 3, 0.05] } - }, - color: { - color: ['color', '#848484'], - highlight: ['color', '#848484'], - hover: ['color', '#848484'], - inherit: ['from', 'to', 'both', true, false], - opacity: [1, 0, 1, 0.05] - }, - dashes: false, - font: { - color: ['color', '#343434'], - size: [14, 0, 100, 1], // px - face: ['arial', 'verdana', 'tahoma'], - background: ['color', 'none'], - strokeWidth: [2, 0, 50, 1], // px - strokeColor: ['color', '#ffffff'], - align: ['horizontal', 'top', 'middle', 'bottom'] - }, - hidden: false, - hoverWidth: [1.5, 0, 5, 0.1], - physics: true, - scaling: { - min: [1, 0, 100, 1], - max: [15, 0, 100, 1], - label: { - enabled: true, - min: [14, 0, 200, 1], - max: [30, 0, 200, 1], - maxVisible: [30, 0, 200, 1], - drawThreshold: [5, 0, 20, 1] - } - }, - selectionWidth: [1.5, 0, 5, 0.1], - selfReferenceSize: [20, 0, 200, 1], - shadow: { - enabled: false, - size: [10, 0, 20, 1], - x: [5, -30, 30, 1], - y: [5, -30, 30, 1] - }, - smooth: { - enabled: true, - type: ['dynamic', 'continuous', 'discrete', 'diagonalCross', 'straightCross', 'horizontal', 'vertical', 'curvedCW', 'curvedCCW'], - roundness: [0.5, 0, 1, 0.05] - }, - width: [1, 0, 30, 1] - }, - layout: { - //randomSeed: [0, 0, 500, 1], - hierarchical: { - enabled: false, - levelSeparation: [150, 20, 500, 5], - direction: ['UD', 'DU', 'LR', 'RL'], // UD, DU, LR, RL - sortMethod: ['hubsize', 'directed'] // hubsize, directed - } - }, - interaction: { - dragNodes: true, - dragView: true, - hideEdgesOnDrag: false, - hideNodesOnDrag: false, - hover: false, - keyboard: { - enabled: false, - speed: { x: [10, 0, 40, 1], y: [10, 0, 40, 1], zoom: [0.02, 0, 0.1, 0.005] }, - bindToWindow: true - }, - multiselect: false, - navigationButtons: false, - selectable: true, - selectConnectedEdges: true, - hoverConnectedEdges: true, - tooltipDelay: [300, 0, 1000, 25], - zoomView: true - }, - manipulation: { - enabled: false, - initiallyActive: false - }, - physics: { - barnesHut: { - //theta: [0.5, 0.1, 1, 0.05], - gravitationalConstant: [-2000, -30000, 0, 50], - centralGravity: [0.3, 0, 10, 0.05], - springLength: [95, 0, 500, 5], - springConstant: [0.04, 0, 1.2, 0.005], - damping: [0.09, 0, 1, 0.01], - avoidOverlap: [0, 0, 1, 0.01] - }, - forceAtlas2Based: { - //theta: [0.5, 0.1, 1, 0.05], - gravitationalConstant: [-50, -500, 0, 1], - centralGravity: [0.01, 0, 1, 0.005], - springLength: [95, 0, 500, 5], - springConstant: [0.08, 0, 1.2, 0.005], - damping: [0.4, 0, 1, 0.01], - avoidOverlap: [0, 0, 1, 0.01] - }, - repulsion: { - centralGravity: [0.2, 0, 10, 0.05], - springLength: [200, 0, 500, 5], - springConstant: [0.05, 0, 1.2, 0.005], - nodeDistance: [100, 0, 500, 5], - damping: [0.09, 0, 1, 0.01] - }, - hierarchicalRepulsion: { - centralGravity: [0.2, 0, 10, 0.05], - springLength: [100, 0, 500, 5], - springConstant: [0.01, 0, 1.2, 0.005], - nodeDistance: [120, 0, 500, 5], - damping: [0.09, 0, 1, 0.01] - }, - maxVelocity: [50, 0, 150, 1], - minVelocity: [0.1, 0.01, 0.5, 0.01], - solver: ['barnesHut', 'forceAtlas2Based', 'repulsion', 'hierarchicalRepulsion'], - timestep: [0.5, 0.01, 1, 0.01] - }, - global: { - locale: ['en', 'nl'] - } - }; - - exports.allOptions = allOptions; - exports.configureOptions = configureOptions; - /***/ }, /* 109 */ /***/ function(module, exports, __webpack_require__) { diff --git a/docs/network/manipulation.html b/docs/network/manipulation.html index 0552655e..067ad3d8 100644 --- a/docs/network/manipulation.html +++ b/docs/network/manipulation.html @@ -108,14 +108,14 @@ network.setOptions(options); - + - - - - + + +
NameTypeDefaultDescription
enabled Boolean false Toggle the manipulation system on or off. This property is optional. If you define any of the options below and enabled is undefined, this will be set to true.
enabled Boolean false Toggle the manipulation system on or off. Even when false, the manipulation API through the methods will still work. This property is optional. If you define any of the options below and enabled is undefined, this will be set to true.
initiallyActive Boolean true Toggle whether the toolbar is visible initially or if only the edit button is visible initially.
addNode Boolean or Function true You can use these options to switch certain functionalities on or off of attach a handler function to them. These functions are called before the action is performed. If a node is going to be added through the manipulation system, the addNode function will be called first. With this, you can provide a gui for your users, abort the process or anything else you want to do. For all except the editNode functionality, these handler functions are optional.

- When you supply a boolean, you only toggle the adding of nodes. + When you supply a boolean, you only toggle the 'add node' button on the GUI of the manipulation system. The lack of handling function could effect the API when using the methods. When a function is supplied, it will be called when the user clicks the canvas in 'addNode' mode. This function will receive two variables: the properties of the node that can be created and a callback function. If you call the callback function with the properties of the new node, the node will be added.

Example:
 var options = {
@@ -128,7 +128,7 @@ var options = {
 }
 
This function changes the label of the new node into 'hello world'. If you do not want the node created, do not call the callback function or call the callback function null or no argument.
addEdge Boolean or Function true If boolean, toggle the adding of edges. +
addEdge Boolean or Function true If boolean, toggle the availability of the 'addEdge' button in the GUI, the API through the methods will still work except (obviously) there will be no handler function. When a function is supplied, it will be called when the user drags the new edge from one node to the next in 'addEdge' mode. This function will receive two variables: the properties of the edge that can be created and a callback function. If you call the callback function with the properties of the new edge, the edge will be added.

Example:
 var options = {
@@ -149,9 +149,9 @@ var options = {
 
This example code will show a popup if you connect a node to itself to ask you if that was what you wanted. If you do not want the edge created, do not call the callback function or call the callback function null or no argument.
editNode Function undefined Editing of nodes is only possible when a handling function is supplied. If this is not the case, editing of nodes will be disabled. The function will be called when a node is selected and the 'Edit Node' button on the toolbar is pressed. This function will be called like the addNode function with the node's data and a callback function.
editEdge Boolean or Function true If boolean, toggle the editing of edges. When a function is supplied, it will be called when an edge is selcted and the 'Edit Edge' button on the toolbar is pressed. This function will be called in the same way the addEdge function was called. If the callback is not performed, the edge will remain hanging where it was released. To cancel, call the callback function with null as argument or without arguments.
deleteNode Boolean or Function true If boolean, toggle the deletion of nodes. If function, it will be called when a node is selected and the 'Delete selected' button is pressed.
deleteEdge Boolean or Function true If boolean, toggle the deletion of edges. If function, it will be called when an edge is selected and the 'Delete selected' button is pressed.
editEdge Boolean or Function true If boolean, toggle the editing of edges in the GUI. When a function is supplied, it will be called when an edge is selcted and the 'Edit Edge' button on the toolbar is pressed. This function will be called in the same way the addEdge function was called. If the callback is not performed, the edge will remain hanging where it was released. To cancel, call the callback function with null as argument or without arguments.
deleteNode Boolean or Function true If boolean, toggle the deletion of nodes in the GUI. If function, it will be called when a node is selected and the 'Delete selected' button is pressed. When using a function, it will receive a callback and an object with an array of selected nodeIds and an array of selected edges Ids. These are the items that will be deleted if the callback is performed.
deleteEdge Boolean or Function true If boolean, toggle the deletion of edges in the GUI. If function, it will be called when an edge is selected and the 'Delete selected' button is pressed. When using a function, it will receive a callback and an object with an array of selected nodeIds (empty) and an array of selected edges Ids. These are the items that will be deleted if the callback is performed.
controlNodeStyle Object ObjectYou can supply any styling information you'd like here. All fields described in the nodes module are allowed except obviously for id, x, y and fixed.

Default:
 {
diff --git a/docs/network/physics.html b/docs/network/physics.html
index d4c6fe89..9edb3efe 100644
--- a/docs/network/physics.html
+++ b/docs/network/physics.html
@@ -82,6 +82,7 @@
 // these are all options in full.
 var options = {
   physics:{
+    enabled: true,
     barnesHut: {
       gravitationalConstant: -2000,
       centralGravity: 0.3,
@@ -141,6 +142,7 @@ network.setOptions(options);
     

All of the individual options are explained here:

+ diff --git a/lib/network/modules/PhysicsEngine.js b/lib/network/modules/PhysicsEngine.js index e82bdf86..eac5d0e2 100644 --- a/lib/network/modules/PhysicsEngine.js +++ b/lib/network/modules/PhysicsEngine.js @@ -31,6 +31,7 @@ class PhysicsEngine { // default options this.options = {}; this.defaultOptions = { + enabled: true, barnesHut: { theta: 0.5, gravitationalConstant: -2000, @@ -106,6 +107,7 @@ class PhysicsEngine { setOptions(options) { if (options !== undefined) { if (options === false) { + this.options.enabled = false; this.physicsEnabled = false; this.stopSimulation(); } @@ -113,6 +115,15 @@ class PhysicsEngine { this.physicsEnabled = true; util.selectiveNotDeepExtend(['stabilization'], this.options, options); util.mergeOptions(this.options, options, 'stabilization') + + if (options.enabled === undefined) { + this.options.enabled = true; + } + + if (this.options.enabled === false) { + this.physicsEnabled = false; + this.stopSimulation(); + } } } this.init(); @@ -150,7 +161,7 @@ class PhysicsEngine { } initPhysics() { - if (this.physicsEnabled === true) { + if (this.physicsEnabled === true && this.options.enabled === true) { if (this.options.stabilization.enabled === true) { this.stabilize(); } @@ -171,7 +182,7 @@ class PhysicsEngine { * Start the simulation */ startSimulation() { - if (this.physicsEnabled === true) { + if (this.physicsEnabled === true && this.options.enabled === true) { this.stabilized = false; // this sets the width of all nodes initially which could be required for the avoidOverlap diff --git a/lib/network/options.js b/lib/network/options.js index fcaf7fc5..0af3c1e1 100644 --- a/lib/network/options.js +++ b/lib/network/options.js @@ -214,6 +214,7 @@ let allOptions = { __type__: {object} }, physics: { + enabled:{boolean}, barnesHut: { gravitationalConstant: {number}, centralGravity: {number}, @@ -423,6 +424,7 @@ let configureOptions = { initiallyActive: false }, physics: { + enabled: true, barnesHut: { //theta: [0.5, 0.1, 1, 0.05], gravitationalConstant: [-2000, -30000, 0, 50], diff --git a/test/networkTest.html b/test/networkTest.html index 06562c81..f39244fd 100644 --- a/test/networkTest.html +++ b/test/networkTest.html @@ -18,58 +18,123 @@ + + + + + + + +
+
NameTypeDefaultDescription
enabled Boolean true Toggle the physics system on or off. This property is optional. If you define any of the options below and enabled is undefined, this will be set to true.
barnesHut Object Object BarnesHut is a quadtree based gravity model. This is the fastest, default and recommended solver for non-hierarchical layouts.