From 0d32566e39234b44e7b62dcd2a95ab7c192764ba Mon Sep 17 00:00:00 2001 From: Alex de Mulder Date: Fri, 15 May 2015 17:05:59 +0200 Subject: [PATCH] bugfixes, start of reorganisation of examples --- dist/vis.js | 23661 ++++++++-------- docs/network/interaction.html | 4 +- examples/network/04_shapes.html | 76 - examples/network/06_groups.html | 171 - .../{ => categories}/01_basic_usage.html | 0 .../{ => categories}/02_random_nodes.html | 0 .../{ => categories}/05_social_network.html | 0 .../{ => categories}/07_selections.html | 0 .../network/{ => categories}/09_scaling.html | 0 .../{ => categories}/10_multiline_text.html | 0 .../network/{ => categories}/11_styling.html | 0 .../network/{ => categories}/12_arrows.html | 0 .../{ => categories}/13_dashed_lines.html | 0 .../{ => categories}/14_dot_language.html | 0 .../15_dot_language_playground.html | 0 .../{ => categories}/16_dynamic_data.html | 0 .../{ => categories}/17_network_info.html | 0 .../18_fully_random_nodes_clustering.html | 0 .../19_scale_free_graph_clustering.html | 0 .../{ => categories}/20_navigation.html | 0 .../{ => categories}/22_les_miserables.html | 0 .../23_hierarchical_layout.html | 0 .../24_hierarchical_layout_userdefined.html | 0 .../25_physics_configuration.html | 0 .../26_staticSmoothCurves.html | 0 .../27_world_cup_network.html | 0 .../28_world_cup_network_performance.html | 0 .../29_neighbourhood_highlight.html | 0 .../30_importing_from_gephi.html | 0 .../31_manipulation_and_localization.html | 0 .../32_hierarchicaLayoutMethods.html | 0 .../{ => categories}/33_animation.html | 0 .../{ => categories}/35_label_stroke.html | 0 .../{ => categories}/37_label_alignment.html | 0 .../{ => categories}/39_newClustering.html | 0 .../network/categories/edgeStyles/smooth.html | 115 + .../network/categories/edgeStyles/styles.html | 55 + examples/network/{ => categories}/index.html | 0 .../nodeStyles/HTML_in_Nodes.html} | 12 +- .../nodeStyles/circular_images.html} | 11 +- .../network/categories/nodeStyles/colors.html | 57 + .../categories/nodeStyles/customGroups.html | 134 + .../network/categories/nodeStyles/groups.html | 119 + .../nodeStyles/icons.html} | 6 +- .../nodeStyles/images.html} | 28 +- .../network/categories/nodeStyles/shapes.html | 72 + lib/network/modules/InteractionHandler.js | 28 +- lib/network/modules/SelectionHandler.js | 47 +- lib/network/modules/View.js | 8 +- lib/network/options.js | 4 +- lib/timeline/component/GraphGroup.js | 8 +- lib/util.js | 3 +- 52 files changed, 12475 insertions(+), 12144 deletions(-) delete mode 100644 examples/network/04_shapes.html delete mode 100644 examples/network/06_groups.html rename examples/network/{ => categories}/01_basic_usage.html (100%) rename examples/network/{ => categories}/02_random_nodes.html (100%) rename examples/network/{ => categories}/05_social_network.html (100%) rename examples/network/{ => categories}/07_selections.html (100%) rename examples/network/{ => categories}/09_scaling.html (100%) rename examples/network/{ => categories}/10_multiline_text.html (100%) rename examples/network/{ => categories}/11_styling.html (100%) rename examples/network/{ => categories}/12_arrows.html (100%) rename examples/network/{ => categories}/13_dashed_lines.html (100%) rename examples/network/{ => categories}/14_dot_language.html (100%) rename examples/network/{ => categories}/15_dot_language_playground.html (100%) rename examples/network/{ => categories}/16_dynamic_data.html (100%) rename examples/network/{ => categories}/17_network_info.html (100%) rename examples/network/{ => categories}/18_fully_random_nodes_clustering.html (100%) rename examples/network/{ => categories}/19_scale_free_graph_clustering.html (100%) rename examples/network/{ => categories}/20_navigation.html (100%) rename examples/network/{ => categories}/22_les_miserables.html (100%) rename examples/network/{ => categories}/23_hierarchical_layout.html (100%) rename examples/network/{ => categories}/24_hierarchical_layout_userdefined.html (100%) rename examples/network/{ => categories}/25_physics_configuration.html (100%) rename examples/network/{ => categories}/26_staticSmoothCurves.html (100%) rename examples/network/{ => categories}/27_world_cup_network.html (100%) rename examples/network/{ => categories}/28_world_cup_network_performance.html (100%) rename examples/network/{ => categories}/29_neighbourhood_highlight.html (100%) rename examples/network/{ => categories}/30_importing_from_gephi.html (100%) rename examples/network/{ => categories}/31_manipulation_and_localization.html (100%) rename examples/network/{ => categories}/32_hierarchicaLayoutMethods.html (100%) rename examples/network/{ => categories}/33_animation.html (100%) rename examples/network/{ => categories}/35_label_stroke.html (100%) rename examples/network/{ => categories}/37_label_alignment.html (100%) rename examples/network/{ => categories}/39_newClustering.html (100%) create mode 100644 examples/network/categories/edgeStyles/smooth.html create mode 100644 examples/network/categories/edgeStyles/styles.html rename examples/network/{ => categories}/index.html (100%) rename examples/network/{36_HTML_in_Nodes.html => categories/nodeStyles/HTML_in_Nodes.html} (88%) rename examples/network/{34_circular_images.html => categories/nodeStyles/circular_images.html} (91%) create mode 100644 examples/network/categories/nodeStyles/colors.html create mode 100644 examples/network/categories/nodeStyles/customGroups.html create mode 100644 examples/network/categories/nodeStyles/groups.html rename examples/network/{38_node_as_icon.html => categories/nodeStyles/icons.html} (94%) rename examples/network/{03_images.html => categories/nodeStyles/images.html} (70%) create mode 100644 examples/network/categories/nodeStyles/shapes.html diff --git a/dist/vis.js b/dist/vis.js index afd94a94..42550bc4 100644 --- a/dist/vis.js +++ b/dist/vis.js @@ -5,7 +5,7 @@ * A dynamic, browser-based visualization library. * * @version 4.0.0-SNAPSHOT - * @date 2015-05-13 + * @date 2015-05-15 * * @license * Copyright (C) 2011-2014 Almende B.V, http://almende.com @@ -1052,8 +1052,11 @@ return /******/ (function(modules) { // webpackBootstrap exports.parseColor = function (color) { var c; if (exports.isString(color) === true) { + console.log(color, exports.isValidRGB(color)); if (exports.isValidRGB(color) === true) { - var rgb = color.substr(4).substr(0, color.length - 5).split(','); + 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]); } if (exports.isValidHex(color) === true) { @@ -15595,51 +15598,51 @@ return /******/ (function(modules) { // webpackBootstrap function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - var _modulesGroups = __webpack_require__(63); + var _modulesGroups = __webpack_require__(54); var _modulesGroups2 = _interopRequireDefault(_modulesGroups); - var _modulesNodesHandler = __webpack_require__(64); + var _modulesNodesHandler = __webpack_require__(55); var _modulesNodesHandler2 = _interopRequireDefault(_modulesNodesHandler); - var _modulesEdgesHandler = __webpack_require__(65); + var _modulesEdgesHandler = __webpack_require__(56); var _modulesEdgesHandler2 = _interopRequireDefault(_modulesEdgesHandler); - var _modulesPhysicsEngine = __webpack_require__(66); + var _modulesPhysicsEngine = __webpack_require__(57); var _modulesPhysicsEngine2 = _interopRequireDefault(_modulesPhysicsEngine); - var _modulesClustering = __webpack_require__(67); + var _modulesClustering = __webpack_require__(58); var _modulesClustering2 = _interopRequireDefault(_modulesClustering); - var _modulesCanvasRenderer = __webpack_require__(68); + var _modulesCanvasRenderer = __webpack_require__(59); var _modulesCanvasRenderer2 = _interopRequireDefault(_modulesCanvasRenderer); - var _modulesCanvas = __webpack_require__(69); + var _modulesCanvas = __webpack_require__(60); var _modulesCanvas2 = _interopRequireDefault(_modulesCanvas); - var _modulesView = __webpack_require__(70); + var _modulesView = __webpack_require__(61); var _modulesView2 = _interopRequireDefault(_modulesView); - var _modulesInteractionHandler = __webpack_require__(71); + var _modulesInteractionHandler = __webpack_require__(62); var _modulesInteractionHandler2 = _interopRequireDefault(_modulesInteractionHandler); - var _modulesSelectionHandler = __webpack_require__(72); + var _modulesSelectionHandler = __webpack_require__(63); var _modulesSelectionHandler2 = _interopRequireDefault(_modulesSelectionHandler); - var _modulesLayoutEngine = __webpack_require__(73); + var _modulesLayoutEngine = __webpack_require__(64); var _modulesLayoutEngine2 = _interopRequireDefault(_modulesLayoutEngine); - var _modulesManipulationSystem = __webpack_require__(74); + var _modulesManipulationSystem = __webpack_require__(65); var _modulesManipulationSystem2 = _interopRequireDefault(_modulesManipulationSystem); @@ -15651,10 +15654,10 @@ return /******/ (function(modules) { // webpackBootstrap var _sharedValidator2 = _interopRequireDefault(_sharedValidator); - var _optionsJs = __webpack_require__(75); + var _optionsJs = __webpack_require__(66); // Load custom shapes into CanvasRenderingContext2D - __webpack_require__(76); + __webpack_require__(67); var Emitter = __webpack_require__(43); var Hammer = __webpack_require__(41); @@ -15664,8 +15667,8 @@ return /******/ (function(modules) { // webpackBootstrap var dotparser = __webpack_require__(38); var gephiParser = __webpack_require__(39); var Images = __webpack_require__(37); - var Activator = __webpack_require__(57); - var locales = __webpack_require__(77); + var Activator = __webpack_require__(68); + var locales = __webpack_require__(69); /** * @constructor Network @@ -17145,7 +17148,7 @@ return /******/ (function(modules) { // webpackBootstrap // use this instance. Else, load via commonjs. 'use strict'; - module.exports = typeof window !== 'undefined' && window['moment'] || __webpack_require__(54); + module.exports = typeof window !== 'undefined' && window['moment'] || __webpack_require__(70); /***/ }, /* 41 */ @@ -17156,8 +17159,8 @@ return /******/ (function(modules) { // webpackBootstrap 'use strict'; if (typeof window !== 'undefined') { - var propagating = __webpack_require__(55); - var Hammer = window['Hammer'] || __webpack_require__(56); + var propagating = __webpack_require__(71); + var Hammer = window['Hammer'] || __webpack_require__(72); module.exports = propagating(Hammer, { preventDefault: 'mouse' }); @@ -17568,7 +17571,7 @@ return /******/ (function(modules) { // webpackBootstrap var Range = __webpack_require__(17); var ItemSet = __webpack_require__(32); var TimeAxis = __webpack_require__(35); - var Activator = __webpack_require__(57); + var Activator = __webpack_require__(68); var DateUtil = __webpack_require__(15); var CustomTime = __webpack_require__(27); @@ -18536,7 +18539,7 @@ return /******/ (function(modules) { // webpackBootstrap function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - var _networkModulesComponentsColorPicker = __webpack_require__(58); + var _networkModulesComponentsColorPicker = __webpack_require__(73); var _networkModulesComponentsColorPicker2 = _interopRequireDefault(_networkModulesComponentsColorPicker); @@ -20666,5859 +20669,7346 @@ return /******/ (function(modules) { // webpackBootstrap /* 54 */ /***/ function(module, exports, __webpack_require__) { - /* WEBPACK VAR INJECTION */(function(module) {//! moment.js - //! version : 2.10.2 - //! 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; + "use strict"; - function utils_hooks__hooks () { - return hookCallback.apply(null, arguments); - } + Object.defineProperty(exports, "__esModule", { + value: true + }); - // This is done to register the method called with moment() - // without creating circular dependencies. - function setHookCallback (callback) { - hookCallback = callback; - } + 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 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 _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - function isArray(input) { - return Object.prototype.toString.call(input) === '[object Array]'; - } + var util = __webpack_require__(1); - function isDate(input) { - return Object.prototype.toString.call(input) === '[object Date]' || input instanceof Date; - } + /** + * @class Groups + * This class can store groups and options specific for groups. + */ - function map(arr, fn) { - var res = [], i; - for (i = 0; i < arr.length; ++i) { - res.push(fn(arr[i], i)); - } - return res; - } + var Groups = (function () { + function Groups() { + _classCallCheck(this, Groups); - function hasOwnProp(a, b) { - return Object.prototype.hasOwnProperty.call(a, b); - } + this.clear(); + this.defaultIndex = 0; + this.groupsArray = []; + this.groupIndex = 0; - function extend(a, b) { - for (var i in b) { - if (hasOwnProp(b, i)) { - a[i] = b[i]; - } - } + this.defaultGroups = [{ border: "#2B7CE9", background: "#97C2FC", highlight: { border: "#2B7CE9", background: "#D2E5FF" }, hover: { border: "#2B7CE9", background: "#D2E5FF" } }, // 0: blue + { border: "#FFA500", background: "#FFFF00", highlight: { border: "#FFA500", background: "#FFFFA3" }, hover: { border: "#FFA500", background: "#FFFFA3" } }, // 1: yellow + { border: "#FA0A10", background: "#FB7E81", highlight: { border: "#FA0A10", background: "#FFAFB1" }, hover: { border: "#FA0A10", background: "#FFAFB1" } }, // 2: red + { border: "#41A906", background: "#7BE141", highlight: { border: "#41A906", background: "#A1EC76" }, hover: { border: "#41A906", background: "#A1EC76" } }, // 3: green + { border: "#E129F0", background: "#EB7DF4", highlight: { border: "#E129F0", background: "#F0B3F5" }, hover: { border: "#E129F0", background: "#F0B3F5" } }, // 4: magenta + { border: "#7C29F0", background: "#AD85E4", highlight: { border: "#7C29F0", background: "#D3BDF0" }, hover: { border: "#7C29F0", background: "#D3BDF0" } }, // 5: purple + { border: "#C37F00", background: "#FFA807", highlight: { border: "#C37F00", background: "#FFCA66" }, hover: { border: "#C37F00", background: "#FFCA66" } }, // 6: orange + { border: "#4220FB", background: "#6E6EFD", highlight: { border: "#4220FB", background: "#9B9BFD" }, hover: { border: "#4220FB", background: "#9B9BFD" } }, // 7: darkblue + { border: "#FD5A77", background: "#FFC0CB", highlight: { border: "#FD5A77", background: "#FFD1D9" }, hover: { border: "#FD5A77", background: "#FFD1D9" } }, // 8: pink + { border: "#4AD63A", background: "#C2FABC", highlight: { border: "#4AD63A", background: "#E6FFE3" }, hover: { border: "#4AD63A", background: "#E6FFE3" } }, // 9: mint - if (hasOwnProp(b, 'toString')) { - a.toString = b.toString; - } + { border: "#990000", background: "#EE0000", highlight: { border: "#BB0000", background: "#FF3333" }, hover: { border: "#BB0000", background: "#FF3333" } }, // 10:bright red - if (hasOwnProp(b, 'valueOf')) { - a.valueOf = b.valueOf; - } + { border: "#FF6000", background: "#FF6000", highlight: { border: "#FF6000", background: "#FF6000" }, hover: { border: "#FF6000", background: "#FF6000" } }, // 12: real orange + { border: "#97C2FC", background: "#2B7CE9", highlight: { border: "#D2E5FF", background: "#2B7CE9" }, hover: { border: "#D2E5FF", background: "#2B7CE9" } }, // 13: blue + { border: "#399605", background: "#255C03", highlight: { border: "#399605", background: "#255C03" }, hover: { border: "#399605", background: "#255C03" } }, // 14: green + { border: "#B70054", background: "#FF007E", highlight: { border: "#B70054", background: "#FF007E" }, hover: { border: "#B70054", background: "#FF007E" } }, // 15: magenta + { border: "#AD85E4", background: "#7C29F0", highlight: { border: "#D3BDF0", background: "#7C29F0" }, hover: { border: "#D3BDF0", background: "#7C29F0" } }, // 16: purple + { border: "#4557FA", background: "#000EA1", highlight: { border: "#6E6EFD", background: "#000EA1" }, hover: { border: "#6E6EFD", background: "#000EA1" } }, // 17: darkblue + { border: "#FFC0CB", background: "#FD5A77", highlight: { border: "#FFD1D9", background: "#FD5A77" }, hover: { border: "#FFD1D9", background: "#FD5A77" } }, // 18: pink + { border: "#C2FABC", background: "#74D66A", highlight: { border: "#E6FFE3", background: "#74D66A" }, hover: { border: "#E6FFE3", background: "#74D66A" } }, // 19: mint - return a; - } + { border: "#EE0000", background: "#990000", highlight: { border: "#FF3333", background: "#BB0000" }, hover: { border: "#FF3333", background: "#BB0000" } } // 20:bright red + ]; - function create_utc__createUTC (input, format, locale, strict) { - return createLocalOrUTC(input, format, locale, strict, true).utc(); - } + this.options = {}; + this.defaultOptions = { + useDefaultGroups: true + }; + util.extend(this.options, this.defaultOptions); + } - function valid__isValid(m) { - if (m._isValid == null) { - m._isValid = !isNaN(m._d.getTime()) && - m._pf.overflow < 0 && - !m._pf.empty && - !m._pf.invalidMonth && - !m._pf.nullInput && - !m._pf.invalidFormat && - !m._pf.userInvalidated; + _createClass(Groups, [{ + key: "setOptions", + value: function setOptions(options) { + var optionFields = ["useDefaultGroups"]; - if (m._strict) { - m._isValid = m._isValid && - m._pf.charsLeftOver === 0 && - m._pf.unusedTokens.length === 0 && - m._pf.bigHour === undefined; + if (options !== undefined) { + for (var groupName in options) { + if (options.hasOwnProperty(groupName)) { + if (optionFields.indexOf(groupName) === -1) { + var group = options[groupName]; + this.add(groupName, group); } + } } - return m._isValid; + } } + }, { + key: "clear", - function valid__createInvalid (flags) { - var m = create_utc__createUTC(NaN); - if (flags != null) { - extend(m._pf, flags); - } - else { - m._pf.userInvalidated = true; - } - - return m; + /** + * Clear all groups + */ + value: function clear() { + this.groups = {}; + this.groupsArray = []; } + }, { + key: "get", - 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 = from._pf; - } - 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; - } - } + /** + * get group options of a groupname. If groupname is not found, a new group + * is added. + * @param {*} groupname Can be a number, string, Date, etc. + * @return {Object} group The created group, containing all group options + */ + value: function get(groupname) { + var group = this.groups[groupname]; + if (group === undefined) { + if (this.options.useDefaultGroups === false && this.groupsArray.length > 0) { + // create new group + var index = this.groupIndex % this.groupsArray.length; + this.groupIndex++; + group = {}; + group.color = this.groups[this.groupsArray[index]]; + this.groups[groupname] = group; + } else { + // create new group + var index = this.defaultIndex % this.defaultGroups.length; + this.defaultIndex++; + group = {}; + group.color = this.defaultGroups[index]; + this.groups[groupname] = group; } + } - 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; - } + return group; } + }, { + key: "add", - function isMoment (obj) { - return obj instanceof Moment || (obj != null && hasOwnProp(obj, '_isAMomentObject')); + /** + * Add a custom group style + * @param {String} groupName + * @param {Object} style An object containing borderColor, + * backgroundColor, etc. + * @return {Object} group The created group object + */ + value: function add(groupName, style) { + this.groups[groupName] = style; + this.groupsArray.push(groupName); + return style; } + }]); - function toInt(argumentForCoercion) { - var coercedNumber = +argumentForCoercion, - value = 0; + return Groups; + })(); - if (coercedNumber !== 0 && isFinite(coercedNumber)) { - if (coercedNumber >= 0) { - value = Math.floor(coercedNumber); - } else { - value = Math.ceil(coercedNumber); - } - } + exports["default"] = Groups; + module.exports = exports["default"]; - return value; - } +/***/ }, +/* 55 */ +/***/ function(module, exports, __webpack_require__) { - 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; - } + 'use strict'; - function Locale() { - } + Object.defineProperty(exports, '__esModule', { + value: true + }); - var locales = {}; - var globalLocale; + 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 normalizeLocale(key) { - return key ? key.toLowerCase().replace('_', '-') : key; - } + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - // 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; + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - 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; - } + var _componentsNode = __webpack_require__(74); - 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]; - } + var _componentsNode2 = _interopRequireDefault(_componentsNode); - // 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); - } + var _componentsSharedLabel = __webpack_require__(75); - if (data) { - // moment.duration._locale = moment._locale = data; - globalLocale = data; - } - } + var _componentsSharedLabel2 = _interopRequireDefault(_componentsSharedLabel); - return globalLocale._abbr; - } + var util = __webpack_require__(1); + var DataSet = __webpack_require__(3); + var DataView = __webpack_require__(4); - function defineLocale (name, values) { - if (values !== null) { - values.abbr = name; - if (!locales[name]) { - locales[name] = new Locale(); - } - locales[name].set(values); + var NodesHandler = (function () { + function NodesHandler(body, images, groups, layoutEngine) { + var _this = this; - // backwards compat for now: also set the locale - locale_locales__getSetGlobalLocale(name); + _classCallCheck(this, NodesHandler); - return locales[name]; - } else { - // useful for testing - delete locales[name]; - return null; - } - } + this.body = body; + this.images = images; + this.groups = groups; + this.layoutEngine = layoutEngine; - // returns locale data - function locale_locales__getLocale (key) { - var locale; + // create the node API in the body container + this.body.functions.createNode = this.create.bind(this); - if (key && key._locale && key._locale._abbr) { - key = key._locale._abbr; - } + this.nodesListeners = { + add: function add(event, params) { + _this.add(params.items); + }, + update: function update(event, params) { + _this.update(params.items, params.data); + }, + remove: function remove(event, params) { + _this.remove(params.items); + } + }; - if (!key) { - return globalLocale; + this.options = {}; + this.defaultOptions = { + borderWidth: 1, + borderWidthSelected: undefined, + brokenImage: undefined, + color: { + border: '#2B7CE9', + background: '#97C2FC', + highlight: { + border: '#2B7CE9', + background: '#D2E5FF' + }, + hover: { + border: '#2B7CE9', + background: '#D2E5FF' } - - if (!isArray(key)) { - //short-circuit everything else - locale = loadLocale(key); - if (locale) { - return locale; - } - key = [key]; + }, + fixed: { + x: false, + y: false + }, + font: { + color: '#343434', + size: 14, // px + face: 'arial', + background: 'none', + strokeWidth: 0, // px + strokeColor: '#ffffff', + align: 'horizontal' + }, + group: undefined, + hidden: false, + icon: { + face: 'FontAwesome', //'FontAwesome', + code: undefined, //'\uf007', + size: 50, //50, + color: '#2B7CE9' //'#aa00ff' + }, + image: undefined, // --> URL + label: undefined, + level: undefined, + mass: 1, + physics: true, + scaling: { + min: 10, + max: 30, + label: { + enabled: false, + min: 14, + max: 30, + maxVisible: 30, + drawThreshold: 3 + }, + customScalingFunction: function customScalingFunction(min, max, total, value) { + if (max === min) { + return 0.5; + } else { + var scale = 1 / (max - min); + return Math.max(0, (value - min) * scale); + } } + }, + shadow: { + enabled: false, + size: 10, + x: 5, + y: 5 + }, + shape: 'ellipse', + size: 25, + title: undefined, + value: undefined, + x: undefined, + y: undefined + }; + util.extend(this.options, this.defaultOptions); - return chooseLocale(key); - } + this.bindEventListeners(); + } - var aliases = {}; + _createClass(NodesHandler, [{ + key: 'bindEventListeners', + value: function bindEventListeners() { + var _this2 = this; - 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; + // refresh the nodes. Used when reverting from hierarchical layout + this.body.emitter.on('refreshNodes', this.refresh.bind(this)); + this.body.emitter.on('refresh', this.refresh.bind(this)); + this.body.emitter.on('destroy', function () { + delete _this2.body.functions.createNode; + delete _this2.nodesListeners.add; + delete _this2.nodesListeners.update; + delete _this2.nodesListeners.remove; + delete _this2.nodesListeners; + }); } + }, { + key: 'setOptions', + value: function setOptions(options) { + if (options !== undefined) { + _componentsNode2['default'].parseOptions(this.options, options); - function normalizeObjectUnits(inputObject) { - var normalizedInput = {}, - normalizedProp, - prop; - - for (prop in inputObject) { - if (hasOwnProp(inputObject, prop)) { - normalizedProp = normalizeUnits(prop); - if (normalizedProp) { - normalizedInput[normalizedProp] = inputObject[prop]; - } + // update the shape in all nodes + if (options.shape !== undefined) { + for (var nodeId in this.body.nodes) { + if (this.body.nodes.hasOwnProperty(nodeId)) { + this.body.nodes[nodeId].updateShape(); } + } } - 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); + // update the shape size in all nodes + if (options.font !== undefined) { + _componentsSharedLabel2['default'].parseOptions(this.options.font, options); + for (var nodeId in this.body.nodes) { + if (this.body.nodes.hasOwnProperty(nodeId)) { + this.body.nodes[nodeId].updateLabelModule(); + this.body.nodes[nodeId]._reset(); } - }; - } - - 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); + // update the shape size in all nodes + if (options.size !== undefined) { + for (var nodeId in this.body.nodes) { + if (this.body.nodes.hasOwnProperty(nodeId)) { + this.body.nodes[nodeId]._reset(); } + } } - return this; - } - - function zeroFill(number, targetLength, forceSign) { - var output = '' + Math.abs(number), - sign = number >= 0; - while (output.length < targetLength) { - output = '0' + output; + // update the state of the letiables if needed + if (options.hidden !== undefined || options.physics !== undefined) { + this.body.emitter.emit('_dataChanged'); } - return (sign ? (forceSign ? '+' : '') : '-') + output; + } } + }, { + key: 'setData', - 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; + /** + * Set a data set with nodes for the network + * @param {Array | DataSet | DataView} nodes The data containing the nodes. + * @private + */ + value: function setData(nodes) { + var _this3 = this; - var localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g; + var doNotEmit = arguments[1] === undefined ? false : arguments[1]; - var formatFunctions = {}; + var oldNodesData = this.body.data.nodes; - var formatTokenFunctions = {}; + if (nodes instanceof DataSet || nodes instanceof DataView) { + this.body.data.nodes = nodes; + } else if (Array.isArray(nodes)) { + this.body.data.nodes = new DataSet(); + this.body.data.nodes.add(nodes); + } else if (!nodes) { + this.body.data.nodes = new DataSet(); + } else { + throw new TypeError('Array or DataSet expected'); + } - // 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); - }; - } - } + if (oldNodesData) { + // unsubscribe from old dataset + util.forEach(this.nodesListeners, function (callback, event) { + oldNodesData.off(event, callback); + }); + } - function removeFormattingTokens(input) { - if (input.match(/\[[\s\S]/)) { - return input.replace(/^\[|\]$/g, ''); - } - return input.replace(/\\/g, ''); - } + // remove drawn nodes + this.body.nodes = {}; - function makeFormatFunction(format) { - var array = format.match(formattingTokens), i, length; + if (this.body.data.nodes) { + (function () { + // subscribe to new dataset + var me = _this3; + util.forEach(_this3.nodesListeners, function (callback, event) { + me.body.data.nodes.on(event, callback); + }); - for (i = 0, length = array.length; i < length; i++) { - if (formatTokenFunctions[array[i]]) { - array[i] = formatTokenFunctions[array[i]]; - } else { - array[i] = removeFormattingTokens(array[i]); - } - } + // draw all new nodes + var ids = _this3.body.data.nodes.getIds(); + _this3.add(ids, true); + })(); + } - 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; - }; + if (doNotEmit === false) { + this.body.emitter.emit('_dataChanged'); + } } + }, { + key: 'add', - // format date using native date object - function formatMoment(m, format) { - if (!m.isValid()) { - return m.localeData().invalidDate(); - } + /** + * Add nodes + * @param {Number[] | String[]} ids + * @private + */ + value: function add(ids) { + var doNotEmit = arguments[1] === undefined ? false : arguments[1]; - format = expandFormat(format, m.localeData()); + var id = undefined; + var newNodes = []; + for (var i = 0; i < ids.length; i++) { + id = ids[i]; + var _properties = this.body.data.nodes.get(id); + var node = this.create(_properties); + newNodes.push(node); + this.body.nodes[id] = node; // note: this may replace an existing node + } - if (!formatFunctions[format]) { - formatFunctions[format] = makeFormatFunction(format); - } + this.layoutEngine.positionInitially(newNodes); - return formatFunctions[format](m); + if (doNotEmit === false) { + this.body.emitter.emit('_dataChanged'); + } } + }, { + key: 'update', - 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; + /** + * Update existing nodes, or create them when not yet existing + * @param {Number[] | String[]} ids + * @private + */ + value: function update(ids, changedData) { + var nodes = this.body.nodes; + var dataChanged = false; + for (var i = 0; i < ids.length; i++) { + var id = ids[i]; + var node = nodes[id]; + var data = changedData[i]; + if (node !== undefined) { + // update node + node.setOptions(data, this.constants); + } else { + dataChanged = true; + // create node + node = this.create(properties); + nodes[id] = node; } + } - return format; + if (dataChanged === true) { + this.body.emitter.emit('_dataChanged'); + } else { + this.body.emitter.emit('_dataUpdated'); + } } + }, { + key: 'remove', - 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; + /** + * Remove existing nodes. If nodes do not exist, the method will just ignore it. + * @param {Number[] | String[]} ids + * @private + */ + value: function remove(ids) { + var nodes = this.body.nodes; - var regexes = {}; + for (var i = 0; i < ids.length; i++) { + var id = ids[i]; + delete nodes[id]; + } - function addRegexToken (token, regex, strictRegex) { - regexes[token] = typeof regex === 'function' ? regex : function (isStrict) { - return (isStrict && strictRegex) ? strictRegex : regex; - }; + this.body.emitter.emit('_dataChanged'); } + }, { + key: 'create', - function getParseRegexForToken (token, config) { - if (!hasOwnProp(regexes, token)) { - return new RegExp(unescapeFormat(token)); - } + /** + * create a node + * @param properties + * @param constructorClass + */ + value: function create(properties) { + var constructorClass = arguments[1] === undefined ? _componentsNode2['default'] : arguments[1]; - return regexes[token](config._strict, config._locale); + return new constructorClass(properties, this.body, this.images, this.groups, this.options); } - - // 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, '\\$&'); + }, { + key: 'refresh', + value: function refresh() { + var nodes = this.body.nodes; + for (var nodeId in nodes) { + var node = undefined; + if (nodes.hasOwnProperty(nodeId)) { + node = nodes[nodeId]; + } + var data = this.body.data.nodes._data[nodeId]; + if (node !== undefined && data !== undefined) { + node.setOptions({ fixed: false }); + node.setOptions(data); + } + } } + }, { + key: 'getPositions', - var tokens = {}; - - function addParseToken (token, callback) { - var i, func = callback; - if (typeof token === 'string') { - token = [token]; + /** + * Returns the positions of the nodes. + * @param ids --> optional, can be array of nodeIds, can be string + * @returns {{}} + */ + value: function getPositions(ids) { + var dataArray = {}; + if (ids !== undefined) { + if (Array.isArray(ids) === true) { + for (var i = 0; i < ids.length; i++) { + if (this.body.nodes[ids[i]] !== undefined) { + var node = this.body.nodes[ids[i]]; + dataArray[ids[i]] = { x: Math.round(node.x), y: Math.round(node.y) }; + } + } + } else { + if (this.body.nodes[ids] !== undefined) { + var node = this.body.nodes[ids]; + dataArray[ids] = { x: Math.round(node.x), y: Math.round(node.y) }; + } } - if (typeof callback === 'number') { - func = function (input, array) { - array[callback] = toInt(input); - }; + } else { + for (var nodeId in this.body.nodes) { + if (this.body.nodes.hasOwnProperty(nodeId)) { + var node = this.body.nodes[nodeId]; + dataArray[nodeId] = { x: Math.round(node.x), y: Math.round(node.y) }; + } } - for (i = 0; i < token.length; i++) { - tokens[token[i]] = func; + } + return dataArray; + } + }, { + key: 'storePositions', + + /** + * Load the XY positions of the nodes into the dataset. + */ + value: function storePositions() { + // todo: add support for clusters and hierarchical. + var dataArray = []; + for (var nodeId in this.body.nodes) { + if (this.body.nodes.hasOwnProperty(nodeId)) { + var node = this.body.nodes[nodeId]; + if (this.body.data.nodes._data[nodeId].x != Math.round(node.x) || this.body.data.nodes._data[nodeId].y != Math.round(node.y)) { + dataArray.push({ id: nodeId, x: Math.round(node.x), y: Math.round(node.y) }); + } } + } + this.body.data.nodes.update(dataArray); } + }, { + key: 'getBoundingBox', - function addWeekParseToken (token, callback) { - addParseToken(token, function (input, array, config, token) { - config._w = config._w || {}; - callback(input, config._w, config, token); - }); + /** + * get the bounding box of a node. + * @param nodeId + * @returns {j|*} + */ + value: function getBoundingBox(nodeId) { + if (this.body.nodes[nodeId] !== undefined) { + return this.body.nodes[nodeId].shape.boundingBox; + } } + }, { + key: 'getConnectedNodes', - function addTimeToArrayFromToken(token, input, config) { - if (input != null && hasOwnProp(tokens, token)) { - tokens[token](input, config._a, config, token); + /** + * Get the Ids of nodes connected to this node. + * @param nodeId + * @returns {Array} + */ + value: function getConnectedNodes(nodeId) { + var nodeList = []; + if (this.body.nodes[nodeId] !== undefined) { + var node = this.body.nodes[nodeId]; + var nodeObj = {}; // used to quickly check if node already exists + for (var i = 0; i < node.edges.length; i++) { + var edge = node.edges[i]; + if (edge.toId === nodeId) { + if (nodeObj[edge.fromId] === undefined) { + nodeList.push(edge.fromId); + nodeObj[edge.fromId] = true; + } + } else if (edge.fromId === nodeId) { + if (nodeObj[edge.toId] === undefined) { + nodeList.push(edge.toId); + nodeObj[edge.toId] = true; + } + } } + } + return nodeList; } + }, { + key: 'getEdges', - 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(); + /** + * Get the ids of the edges connected to this node. + * @param nodeId + * @returns {*} + */ + value: function getEdges(nodeId) { + var edgeList = []; + if (this.body.nodes[nodeId] !== undefined) { + var node = this.body.nodes[nodeId]; + for (var i = 0; i < node.edges.length; i++) { + edgeList.push(node.edges[i].id); + } + } + return nodeList; } + }]); - // FORMATTING + return NodesHandler; + })(); - addFormatToken('M', ['MM', 2], 'Mo', function () { - return this.month() + 1; - }); + exports['default'] = NodesHandler; + module.exports = exports['default']; - addFormatToken('MMM', 0, 0, function (format) { - return this.localeData().monthsShort(this, format); - }); +/***/ }, +/* 56 */ +/***/ function(module, exports, __webpack_require__) { - addFormatToken('MMMM', 0, 0, function (format) { - return this.localeData().months(this, format); - }); + 'use strict'; - // ALIASES + Object.defineProperty(exports, '__esModule', { + value: true + }); - addUnitAlias('month', 'M'); + 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; }; })(); - // PARSING + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - addRegexToken('M', match1to2); - addRegexToken('MM', match1to2, match2); - addRegexToken('MMM', matchWord); - addRegexToken('MMMM', matchWord); + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - addParseToken(['M', 'MM'], function (input, array) { - array[MONTH] = toInt(input) - 1; - }); + var _componentsEdge = __webpack_require__(76); - 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 { - config._pf.invalidMonth = input; - } - }); + var _componentsEdge2 = _interopRequireDefault(_componentsEdge); - // LOCALES + var _componentsSharedLabel = __webpack_require__(75); - var defaultLocaleMonths = 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'); - function localeMonths (m) { - return this._months[m.month()]; - } + var _componentsSharedLabel2 = _interopRequireDefault(_componentsSharedLabel); - var defaultLocaleMonthsShort = 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'); - function localeMonthsShort (m) { - return this._monthsShort[m.month()]; - } + var util = __webpack_require__(1); + var DataSet = __webpack_require__(3); + var DataView = __webpack_require__(4); - function localeMonthsParse (monthName, format, strict) { - var i, mom, regex; + var EdgesHandler = (function () { + function EdgesHandler(body, images, groups) { + var _this = this; - if (!this._monthsParse) { - this._monthsParse = []; - this._longMonthsParse = []; - this._shortMonthsParse = []; - } + _classCallCheck(this, EdgesHandler); - 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; - } - } - } + this.body = body; + this.images = images; + this.groups = groups; - // MOMENTS + // create the edge API in the body container + this.body.functions.createEdge = this.create.bind(this); - function setMonth (mom, value) { - var dayOfMonth; + this.edgesListeners = { + add: function add(event, params) { + _this.add(params.items); + }, + update: function update(event, params) { + _this.update(params.items); + }, + remove: function remove(event, params) { + _this.remove(params.items); + } + }; - // 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; - } + this.options = {}; + this.defaultOptions = { + arrows: { + to: { enabled: false, scaleFactor: 1 }, // boolean / {arrowScaleFactor:1} / {enabled: false, arrowScaleFactor:1} + middle: { enabled: false, scaleFactor: 1 }, + from: { enabled: false, scaleFactor: 1 } + }, + color: { + color: '#848484', + highlight: '#848484', + hover: '#848484', + inherit: 'from', + opacity: 1 + }, + dashes: false, + font: { + color: '#343434', + size: 14, // px + face: 'arial', + background: 'none', + strokeWidth: 2, // px + strokeColor: '#ffffff', + align: 'horizontal' + }, + hidden: false, + hoverWidth: 1.5, + label: undefined, + length: undefined, + physics: true, + scaling: { + min: 1, + max: 15, + label: { + enabled: true, + min: 14, + max: 30, + maxVisible: 30, + drawThreshold: 3 + }, + customScalingFunction: function customScalingFunction(min, max, total, value) { + if (max === min) { + return 0.5; + } else { + var scale = 1 / (max - min); + return Math.max(0, (value - min) * scale); + } } + }, + selectionWidth: 1, + selfReferenceSize: 20, + shadow: { + enabled: false, + size: 10, + x: 5, + y: 5 + }, + smooth: { + enabled: true, + dynamic: true, + type: 'continuous', + roundness: 0.5 + }, + title: undefined, + width: 1, + value: undefined + }; - 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'); - } - } + util.extend(this.options, this.defaultOptions); - function getDaysInMonth () { - return daysInMonth(this.year(), this.month()); - } + this.bindEventListeners(); + } - function checkOverflow (m) { - var overflow; - var a = m._a; + _createClass(EdgesHandler, [{ + key: 'bindEventListeners', + value: function bindEventListeners() { + var _this2 = this; - if (a && m._pf.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; + // this allows external modules to force all dynamic curves to turn static. + this.body.emitter.on('_forceDisableDynamicCurves', function (type) { + var emitChange = false; + for (var edgeId in _this2.body.edges) { + if (_this2.body.edges.hasOwnProperty(edgeId)) { + var edge = _this2.body.edges[edgeId]; + var edgeData = _this2.body.data.edges._data[edgeId]; - if (m._pf._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) { - overflow = DATE; + // only forcilby remove the smooth curve if the data has been set of the edge has the smooth curves defined. + // this is because a change in the global would not affect these curves. + if (edgeData !== undefined) { + var edgeOptions = edgeData.smooth; + if (edgeOptions !== undefined) { + if (edgeOptions.enabled === true && edgeOptions.dynamic === true) { + if (type === undefined) { + edge.setOptions({ smooth: false }); + } else { + edge.setOptions({ smooth: { dynamic: false, type: type } }); + } + emitChange = true; + } + } } - - m._pf.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; - return extend(function () { - if (firstTime) { - warn(msg); - firstTime = false; - } - return fn.apply(this, arguments); - }, fn); - } - - var deprecations = {}; - - function deprecateSimple(name, msg) { - if (!deprecations[name]) { - warn(msg); - deprecations[name] = true; + if (emitChange === true) { + _this2.body.emitter.emit('_dataChanged'); } - } - - 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); + // this is called when options of EXISTING nodes or edges have changed. + this.body.emitter.on('_dataUpdated', function () { + _this2.reconnectEdges(); + _this2.markAllEdgesAsDirty(); + }); - if (match) { - config._pf.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; - } + // refresh the edges. Used when reverting from hierarchical layout + this.body.emitter.on('refreshEdges', this.refresh.bind(this)); + this.body.emitter.on('refresh', this.refresh.bind(this)); + this.body.emitter.on('destroy', function () { + delete _this2.body.functions.createEdge; + delete _this2.edgesListeners.add; + delete _this2.edgesListeners.update; + delete _this2.edgesListeners.remove; + delete _this2.edgesListeners; + }); } + }, { + key: 'setOptions', + value: function setOptions(options) { + if (options !== undefined) { + // use the parser from the Edge class to fill in all shorthand notations + _componentsEdge2['default'].parseOptions(this.options, options); - // 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); + // hanlde multiple input cases for color + if (options.color !== undefined) { + this.markAllEdgesAsDirty(); } - } - 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' : '')); + // update smooth settings in all edges + var dataChanged = false; + if (options.smooth !== undefined) { + for (var edgeId in this.body.edges) { + if (this.body.edges.hasOwnProperty(edgeId)) { + dataChanged = this.body.edges[edgeId].updateEdgeType() || dataChanged; + } + } } - ); - 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); + // update fonts in all edges + if (options.font !== undefined) { + // use the parser from the Label class to fill in all shorthand notations + _componentsSharedLabel2['default'].parseOptions(this.options, options); + for (var edgeId in this.body.edges) { + if (this.body.edges.hasOwnProperty(edgeId)) { + this.body.edges[edgeId].updateLabelModule(); + } + } } - return date; - } - function createUTCDate (y) { - var date = new Date(Date.UTC.apply(null, arguments)); - if (y < 1970) { - date.setUTCFullYear(y); + // update the state of the variables if needed + if (options.hidden !== undefined || options.physics !== undefined || dataChanged === true) { + this.body.emitter.emit('_dataChanged'); } - return date; + } } + }, { + key: 'setData', - addFormatToken(0, ['YY', 2], 0, function () { - return this.year() % 100; - }); + /** + * Load edges by reading the data table + * @param {Array | DataSet | DataView} edges The data containing the edges. + * @private + * @private + */ + value: function setData(edges) { + var _this3 = this; - addFormatToken(0, ['YYYY', 4], 0, 'year'); - addFormatToken(0, ['YYYYY', 5], 0, 'year'); - addFormatToken(0, ['YYYYYY', 6, true], 0, 'year'); + var doNotEmit = arguments[1] === undefined ? false : arguments[1]; - // ALIASES + var oldEdgesData = this.body.data.edges; - addUnitAlias('year', 'y'); + if (edges instanceof DataSet || edges instanceof DataView) { + this.body.data.edges = edges; + } else if (Array.isArray(edges)) { + this.body.data.edges = new DataSet(); + this.body.data.edges.add(edges); + } else if (!edges) { + this.body.data.edges = new DataSet(); + } else { + throw new TypeError('Array or DataSet expected'); + } - // PARSING + // TODO: is this null or undefined or false? + if (oldEdgesData) { + // unsubscribe from old dataset + util.forEach(this.edgesListeners, function (callback, event) { + oldEdgesData.off(event, callback); + }); + } - addRegexToken('Y', matchSigned); - addRegexToken('YY', match1to2, match2); - addRegexToken('YYYY', match1to4, match4); - addRegexToken('YYYYY', match1to6, match6); - addRegexToken('YYYYYY', match1to6, match6); + // remove drawn edges + this.body.edges = {}; - addParseToken(['YYYY', 'YYYYY', 'YYYYYY'], YEAR); - addParseToken('YY', function (input, array) { - array[YEAR] = utils_hooks__hooks.parseTwoDigitYear(input); - }); + // TODO: is this null or undefined or false? + if (this.body.data.edges) { + // subscribe to new dataset + util.forEach(this.edgesListeners, function (callback, event) { + _this3.body.data.edges.on(event, callback); + }); - // HELPERS + // draw all new nodes + var ids = this.body.data.edges.getIds(); + this.add(ids, true); + } - function daysInYear(year) { - return isLeapYear(year) ? 366 : 365; + if (doNotEmit === false) { + this.body.emitter.emit('_dataChanged'); + } } + }, { + key: 'add', - function isLeapYear(year) { - return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; - } + /** + * Add edges + * @param {Number[] | String[]} ids + * @private + */ + value: function add(ids) { + var doNotEmit = arguments[1] === undefined ? false : arguments[1]; - // HOOKS + var edges = this.body.edges; + var edgesData = this.body.data.edges; - utils_hooks__hooks.parseTwoDigitYear = function (input) { - return toInt(input) + (toInt(input) > 68 ? 1900 : 2000); - }; + for (var i = 0; i < ids.length; i++) { + var id = ids[i]; - // MOMENTS + var oldEdge = edges[id]; + if (oldEdge) { + oldEdge.disconnect(); + } - var getSetYear = makeGetSet('FullYear', false); + var data = edgesData.get(id, { 'showInternalIds': true }); + edges[id] = this.create(data); + } - function getIsLeapYear () { - return isLeapYear(this.year()); + if (doNotEmit === false) { + this.body.emitter.emit('_dataChanged'); + } } + }, { + key: 'update', - addFormatToken('w', ['ww', 2], 'wo', 'week'); - addFormatToken('W', ['WW', 2], 'Wo', 'isoWeek'); - - // ALIASES - - addUnitAlias('week', 'w'); - addUnitAlias('isoWeek', 'W'); - - // PARSING + /** + * Update existing edges, or create them when not yet existing + * @param {Number[] | String[]} ids + * @private + */ + value: function update(ids) { + var edges = this.body.edges; + var edgesData = this.body.data.edges; + var dataChanged = false; + for (var i = 0; i < ids.length; i++) { + var id = ids[i]; + var data = edgesData.get(id); + var edge = edges[id]; + if (edge === null) { + // update edge + edge.disconnect(); + dataChanged = edge.setOptions(data) || dataChanged; // if a support node is added, data can be changed. + edge.connect(); + } else { + // create edge + this.body.edges[id] = this.create(data); + dataChanged = true; + } + } - addRegexToken('w', match1to2); - addRegexToken('ww', match1to2, match2); - addRegexToken('W', match1to2); - addRegexToken('WW', match1to2, match2); + if (dataChanged === true) { + this.body.emitter.emit('_dataChanged'); + } else { + this.body.emitter.emit('_dataUpdated'); + } + } + }, { + key: 'remove', - addWeekParseToken(['w', 'ww', 'W', 'WW'], function (input, week, config, token) { - week[token.substr(0, 1)] = toInt(input); - }); - - // HELPERS + /** + * Remove existing edges. Non existing ids will be ignored + * @param {Number[] | String[]} ids + * @private + */ + value: function remove(ids) { + var edges = this.body.edges; + for (var i = 0; i < ids.length; i++) { + var id = ids[i]; + var edge = edges[id]; + if (edge !== undefined) { + if (edge.via != null) { + delete this.body.supportNodes[edge.via.id]; + } + edge.disconnect(); + delete edges[id]; + } + } - // firstDayOfWeek 0 = sun, 6 = sat - // the day of the week that starts the week - // (usually sunday or monday) - // firstDayOfWeekOfYear 0 = sun, 6 = sat - // the first week is the week that contains the first - // of this day of the week - // (eg. ISO weeks use thursday (4)) - function weekOfYear(mom, firstDayOfWeek, firstDayOfWeekOfYear) { - var end = firstDayOfWeekOfYear - firstDayOfWeek, - daysToDayOfWeek = firstDayOfWeekOfYear - mom.day(), - adjustedMoment; + this.body.emitter.emit('_dataChanged'); + } + }, { + key: 'refresh', + value: function refresh() { + var edges = this.body.edges; + for (var edgeId in edges) { + var edge = undefined; + if (edges.hasOwnProperty(edgeId)) { + edge = edges[edgeId]; + } + var data = this.body.data.edges._data[edgeId]; + if (edge !== undefined && data !== undefined) { + edge.setOptions(data); + } + } + } + }, { + key: 'create', + value: function create(properties) { + return new _componentsEdge2['default'](properties, this.body, this.options); + } + }, { + key: 'markAllEdgesAsDirty', + value: function markAllEdgesAsDirty() { + for (var edgeId in this.body.edges) { + this.body.edges[edgeId].edgeType.colorDirty = true; + } + } + }, { + key: 'reconnectEdges', + /** + * Reconnect all edges + * @private + */ + value: function reconnectEdges() { + var id; + var nodes = this.body.nodes; + var edges = this.body.edges; - if (daysToDayOfWeek > end) { - daysToDayOfWeek -= 7; + for (id in nodes) { + if (nodes.hasOwnProperty(id)) { + nodes[id].edges = []; } + } - if (daysToDayOfWeek < end - 7) { - daysToDayOfWeek += 7; + for (id in edges) { + if (edges.hasOwnProperty(id)) { + var edge = edges[id]; + edge.from = null; + edge.to = null; + edge.connect(); } - - adjustedMoment = local__createLocal(mom).add(daysToDayOfWeek, 'd'); - return { - week: Math.ceil(adjustedMoment.dayOfYear() / 7), - year: adjustedMoment.year() - }; + } } + }]); - // LOCALES + return EdgesHandler; + })(); - function localeWeek (mom) { - return weekOfYear(mom, this._week.dow, this._week.doy).week; - } + exports['default'] = EdgesHandler; + module.exports = exports['default']; - var defaultLocaleWeek = { - dow : 0, // Sunday is the first day of the week. - doy : 6 // The week that contains Jan 1st is the first week of the year. - }; +/***/ }, +/* 57 */ +/***/ function(module, exports, __webpack_require__) { - function localeFirstDayOfWeek () { - return this._week.dow; - } + 'use strict'; - function localeFirstDayOfYear () { - return this._week.doy; - } + Object.defineProperty(exports, '__esModule', { + value: true + }); - // MOMENTS + 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 getSetWeek (input) { - var week = this.localeData().week(this); - return input == null ? week : this.add((input - week) * 7, 'd'); - } + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - function getSetISOWeek (input) { - var week = weekOfYear(this, 1, 4).week; - return input == null ? week : this.add((input - week) * 7, 'd'); - } + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - addFormatToken('DDD', ['DDDD', 3], 'DDDo', 'dayOfYear'); + var _componentsPhysicsBarnesHutSolver = __webpack_require__(77); - // ALIASES + var _componentsPhysicsBarnesHutSolver2 = _interopRequireDefault(_componentsPhysicsBarnesHutSolver); - addUnitAlias('dayOfYear', 'DDD'); + var _componentsPhysicsRepulsionSolver = __webpack_require__(78); - // PARSING + var _componentsPhysicsRepulsionSolver2 = _interopRequireDefault(_componentsPhysicsRepulsionSolver); - addRegexToken('DDD', match1to3); - addRegexToken('DDDD', match3); - addParseToken(['DDD', 'DDDD'], function (input, array, config) { - config._dayOfYear = toInt(input); - }); + var _componentsPhysicsHierarchicalRepulsionSolver = __webpack_require__(79); - // HELPERS + var _componentsPhysicsHierarchicalRepulsionSolver2 = _interopRequireDefault(_componentsPhysicsHierarchicalRepulsionSolver); - //http://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday - function dayOfYearFromWeeks(year, week, weekday, firstDayOfWeekOfYear, firstDayOfWeek) { - var d = createUTCDate(year, 0, 1).getUTCDay(); - var daysToAdd; - var dayOfYear; + var _componentsPhysicsSpringSolver = __webpack_require__(80); - d = d === 0 ? 7 : d; - weekday = weekday != null ? weekday : firstDayOfWeek; - daysToAdd = firstDayOfWeek - d + (d > firstDayOfWeekOfYear ? 7 : 0) - (d < firstDayOfWeek ? 7 : 0); - dayOfYear = 7 * (week - 1) + (weekday - firstDayOfWeek) + daysToAdd + 1; + var _componentsPhysicsSpringSolver2 = _interopRequireDefault(_componentsPhysicsSpringSolver); - return { - year : dayOfYear > 0 ? year : year - 1, - dayOfYear : dayOfYear > 0 ? dayOfYear : daysInYear(year - 1) + dayOfYear - }; - } + var _componentsPhysicsHierarchicalSpringSolver = __webpack_require__(81); - // MOMENTS + var _componentsPhysicsHierarchicalSpringSolver2 = _interopRequireDefault(_componentsPhysicsHierarchicalSpringSolver); - function getSetDayOfYear (input) { - var dayOfYear = Math.round((this.clone().startOf('day') - this.clone().startOf('year')) / 864e5) + 1; - return input == null ? dayOfYear : this.add((input - dayOfYear), 'd'); - } + var _componentsPhysicsCentralGravitySolver = __webpack_require__(82); - // Pick the first defined of two or three arguments. - function defaults(a, b, c) { - if (a != null) { - return a; - } - if (b != null) { - return b; - } - return c; - } + var _componentsPhysicsCentralGravitySolver2 = _interopRequireDefault(_componentsPhysicsCentralGravitySolver); - function currentDateArray(config) { - var now = new Date(); - if (config._useUTC) { - return [now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()]; - } - return [now.getFullYear(), now.getMonth(), now.getDate()]; - } + var _componentsPhysicsFA2BasedRepulsionSolver = __webpack_require__(83); - // convert an array to a date. - // the array should mirror the parameters below - // note: all values past the year are optional and will default to the lowest possible value. - // [year, month, day , hour, minute, second, millisecond] - function configFromArray (config) { - var i, date, input = [], currentDate, yearToUse; + var _componentsPhysicsFA2BasedRepulsionSolver2 = _interopRequireDefault(_componentsPhysicsFA2BasedRepulsionSolver); - if (config._d) { - return; - } + var _componentsPhysicsFA2BasedCentralGravitySolver = __webpack_require__(84); - currentDate = currentDateArray(config); + var _componentsPhysicsFA2BasedCentralGravitySolver2 = _interopRequireDefault(_componentsPhysicsFA2BasedCentralGravitySolver); - //compute day of the year from weeks and weekdays - if (config._w && config._a[DATE] == null && config._a[MONTH] == null) { - dayOfYearFromWeekInfo(config); - } + var util = __webpack_require__(1); - //if the day of the year is set, figure out what it is - if (config._dayOfYear) { - yearToUse = defaults(config._a[YEAR], currentDate[YEAR]); + var PhysicsEngine = (function () { + function PhysicsEngine(body) { + _classCallCheck(this, PhysicsEngine); - if (config._dayOfYear > daysInYear(yearToUse)) { - config._pf._overflowDayOfYear = true; - } + this.body = body; + this.physicsBody = { physicsNodeIndices: [], physicsEdgeIndices: [], forces: {}, velocities: {} }; - date = createUTCDate(yearToUse, 0, config._dayOfYear); - config._a[MONTH] = date.getUTCMonth(); - config._a[DATE] = date.getUTCDate(); - } + this.physicsEnabled = true; + this.simulationInterval = 1000 / 60; + this.requiresTimeout = true; + this.previousStates = {}; + this.freezeCache = {}; + this.renderTimer = undefined; - // Default to current date. - // * if no year, month, day of month are given, default to today - // * if day of month is given, default month and year - // * if month is given, default only year - // * if year is given, don't default anything - for (i = 0; i < 3 && config._a[i] == null; ++i) { - config._a[i] = input[i] = currentDate[i]; - } + this.stabilized = false; + this.stabilizationIterations = 0; + this.ready = false; // will be set to true if the stabilize - // Zero out whatever was not defaulted, including time - for (; i < 7; i++) { - config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i]; - } + // default options + this.options = {}; + this.defaultOptions = { + barnesHut: { + theta: 0.5, + gravitationalConstant: -2000, + centralGravity: 0.3, + springLength: 95, + springConstant: 0.04, + damping: 0.09 + }, + forceAtlas2Based: { + theta: 0.5, + gravitationalConstant: -50, + centralGravity: 0.01, + springConstant: 0.08, + springLength: 100, + damping: 0.4 + }, + repulsion: { + centralGravity: 0.2, + springLength: 200, + springConstant: 0.05, + nodeDistance: 100, + damping: 0.09 + }, + 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: 100, + onlyDynamicEdges: false, + fit: true + }, + timestep: 0.5 + }; + util.extend(this.options, this.defaultOptions); - // Check for 24:00:00.000 - if (config._a[HOUR] === 24 && - config._a[MINUTE] === 0 && - config._a[SECOND] === 0 && - config._a[MILLISECOND] === 0) { - config._nextDay = true; - config._a[HOUR] = 0; - } + this.bindEventListeners(); + } - config._d = (config._useUTC ? createUTCDate : createDate).apply(null, input); - // Apply timezone offset from input. The actual utcOffset can be changed - // with parseZone. - if (config._tzm != null) { - config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm); - } + _createClass(PhysicsEngine, [{ + key: 'bindEventListeners', + value: function bindEventListeners() { + var _this = this; - if (config._nextDay) { - config._a[HOUR] = 24; + 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(); + }); } - - function dayOfYearFromWeekInfo(config) { - var w, weekYear, week, weekday, dow, doy, temp; - - w = config._w; - if (w.GG != null || w.W != null || w.E != null) { - dow = 1; - doy = 4; - - // TODO: We need to take the current isoWeekYear, but that depends on - // how we interpret now (local, utc, fixed offset). So create - // a now version of current config (take local/utc/offset flags, and - // create now). - weekYear = defaults(w.GG, config._a[YEAR], weekOfYear(local__createLocal(), 1, 4).year); - week = defaults(w.W, 1); - weekday = defaults(w.E, 1); + }, { + key: 'setOptions', + value: function setOptions(options) { + if (options !== undefined) { + if (options === false) { + this.physicsEnabled = false; + this.stopSimulation(); } else { - dow = config._locale._week.dow; - doy = config._locale._week.doy; - - weekYear = defaults(w.gg, config._a[YEAR], weekOfYear(local__createLocal(), dow, doy).year); - week = defaults(w.w, 1); + 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); + } - if (w.d != null) { - // weekday -- low day numbers are considered next week - weekday = w.d; - if (weekday < dow) { - ++week; - } - } else if (w.e != null) { - // local weekday -- counting starts from begining of week - weekday = w.e + dow; - } else { - // default to begining of week - weekday = dow; - } + 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(); } - temp = dayOfYearFromWeeks(weekYear, week, weekday, doy, dow); + } else { + this.ready = true; + this.body.emitter.emit('fit'); + } + } + }, { + key: 'startSimulation', - config._a[YEAR] = temp.year; - config._dayOfYear = temp.dayOfYear; + /** + * Start the simulation + */ + value: function startSimulation() { + if (this.physicsEnabled === true) { + this.stabilized = false; + 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', - utils_hooks__hooks.ISO_8601 = function () {}; + /** + * Stop the simulation, force stabilization. + */ + value: function stopSimulation() { + var emit = arguments[0] === undefined ? true : arguments[0]; - // date from string and format string - function configFromStringAndFormat(config) { - // TODO: Move this to another part of the creation flow to prevent circular deps - if (config._f === utils_hooks__hooks.ISO_8601) { - configFromISO(config); - return; + 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: 'simulationStep', - config._a = []; - config._pf.empty = true; + /** + * 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 array is used to make a Date, either with `new Date` or `Date.UTC` - var string = '' + config._i, - i, parsedInput, tokens, token, skipped, - stringLength = string.length, - totalParsedInputLength = 0; + // run double speed if it is a little graph + if ((physicsTime < 0.4 * this.simulationInterval || this.runDoubleSpeed === true) && this.stabilized === false) { + this.physicsTick(); - tokens = expandFormat(config._f, config._locale).match(formattingTokens) || []; + // this makes sure there is no jitter. The decision is taken once to run it at double speed. + this.runDoubleSpeed = true; + } - for (i = 0; i < tokens.length; i++) { - token = tokens[i]; - parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0]; - if (parsedInput) { - skipped = string.substr(0, string.indexOf(parsedInput)); - if (skipped.length > 0) { - config._pf.unusedInput.push(skipped); - } - string = string.slice(string.indexOf(parsedInput) + parsedInput.length); - totalParsedInputLength += parsedInput.length; - } - // don't parse if it's not a known token - if (formatTokenFunctions[token]) { - if (parsedInput) { - config._pf.empty = false; - } - else { - config._pf.unusedTokens.push(token); - } - addTimeToArrayFromToken(token, parsedInput, config); - } - else if (config._strict && !parsedInput) { - config._pf.unusedTokens.push(token); - } - } - - // add remaining unparsed input length to the string - config._pf.charsLeftOver = stringLength - totalParsedInputLength; - if (string.length > 0) { - config._pf.unusedInput.push(string); - } - - // clear _12h flag if hour is <= 12 - if (config._pf.bigHour === true && config._a[HOUR] <= 12) { - config._pf.bigHour = undefined; + 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.stabilizationIterations = 0; + this.startedStabilization = false; + this._emitStabilized(); + } else { + this.stabilizationIterations = 0; } - // handle meridiem - config._a[HOUR] = meridiemFixWrap(config._locale, config._a[HOUR], config._meridiem); - - configFromArray(config); - checkOverflow(config); + this.stopSimulation(); + } } + }, { + key: '_emitStabilized', + value: function _emitStabilized() { + var _this2 = this; + if (this.stabilizationIterations > 1) { + setTimeout(function () { + _this2.body.emitter.emit('stabilized', { iterations: _this2.stabilizationIterations }); + }, 0); + } + } + }, { + key: 'physicsTick', - function meridiemFixWrap (locale, hour, meridiem) { - var isPm; + /** + * A single simulation step (or 'tick') in the physics simulation + * + * @private + */ + value: function physicsTick() { + if (this.stabilized === false) { + this.calculateForces(); + this.stabilized = this.moveNodes(); - if (meridiem == null) { - // nothing to do - return hour; - } - if (locale.meridiemHour != null) { - return locale.meridiemHour(hour, meridiem); - } else if (locale.isPM != null) { - // Fallback - isPm = locale.isPM(meridiem); - if (isPm && hour < 12) { - hour += 12; - } - if (!isPm && hour === 12) { - hour = 0; - } - return hour; + // determine if the network has stabilzied + if (this.stabilized === true) { + this.revert(); } else { - // this is not supposed to happen - return hour; + // 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; + } } - } - function configFromStringAndArray(config) { - var tempConfig, - bestMoment, + this.stabilizationIterations++; + } + } + }, { + key: 'updatePhysicsIndices', - scoreToBeat, - i, - currentScore; + /** + * 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 updatePhysicsIndices() { + this.physicsBody.forces = {}; + this.physicsBody.physicsNodeIndices = []; + this.physicsBody.physicsEdgeIndices = []; + var nodes = this.body.nodes; + var edges = this.body.edges; - if (config._f.length === 0) { - config._pf.invalidFormat = true; - config._d = new Date(NaN); - return; + // 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); + } } + } - for (i = 0; i < config._f.length; i++) { - currentScore = 0; - tempConfig = copyConfig({}, config); - if (config._useUTC != null) { - tempConfig._useUTC = config._useUTC; - } - tempConfig._pf = defaultParsingFlags(); - tempConfig._f = config._f[i]; - configFromStringAndFormat(tempConfig); + // 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 (!valid__isValid(tempConfig)) { - continue; - } + // 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 }; - // if there is any input that was not parsed add a penalty for that format - currentScore += tempConfig._pf.charsLeftOver; + // 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 }; + } + } - //or tokens - currentScore += tempConfig._pf.unusedTokens.length * 10; + // 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', - tempConfig._pf.score = currentScore; + /** + * 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; - if (scoreToBeat == null || currentScore < scoreToBeat) { - scoreToBeat = currentScore; - bestMoment = tempConfig; - } + 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]; } - - extend(config, bestMoment || tempConfig); + } } + }, { + key: 'moveNodes', - function configFromObject(config) { - if (config._d) { - return; - } + /** + * 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); - var i = normalizeObjectUnits(config._i); - config._a = [i.year, i.month, i.day || i.date, i.hour, i.minute, i.second, i.millisecond]; + 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; + } - configFromArray(config); + if (nodesPresent === true) { + if (vminCorrected > 0.5 * this.options.maxVelocity) { + return false; + } else { + return stabilized; + } + } + return true; } + }, { + key: '_performStep', - function createFromConfig (config) { - var input = config._i, - format = config._f, - res; + /** + * 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; - config._locale = config._locale || locale_locales__getLocale(config._l); + // 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 (input === null || (format === undefined && input === '')) { - return valid__createInvalid({nullInput: true}); - } + 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 (typeof input === 'string') { - config._i = input = config._locale.preparse(input); - } + 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; + } - if (isMoment(input)) { - return new Moment(checkOverflow(input)); - } else if (isArray(format)) { - configFromStringAndArray(config); - } else if (format) { - configFromStringAndFormat(config); - } else { - configFromInput(config); - } + var totalVelocity = Math.sqrt(Math.pow(velocities[nodeId].x, 2) + Math.pow(velocities[nodeId].y, 2)); + return totalVelocity; + } + }, { + key: 'calculateForces', - res = new Moment(checkOverflow(config)); - if (res._nextDay) { - // Adding is smart enough around DST - res.add(1, 'd'); - res._nextDay = undefined; - } + /** + * calculate the forces for one physics iteration. + */ + value: function calculateForces() { + this.gravitySolver.solve(); + this.nodesSolver.solve(); + this.edgesSolver.solve(); + } + }, { + key: '_freezeNodes', - return res; + /** + * 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: '_restoreFrozenNodes', - function configFromInput(config) { - var input = config._i; - if (input === undefined) { - config._d = new Date(); - } else if (isDate(input)) { - config._d = new Date(+input); - } else if (typeof input === 'string') { - configFromString(config); - } else if (isArray(input)) { - config._a = map(input.slice(0), function (obj) { - return parseInt(obj, 10); - }); - configFromArray(config); - } else if (typeof(input) === 'object') { - configFromObject(config); - } else if (typeof(input) === 'number') { - // from milliseconds - config._d = new Date(input); - } else { - utils_hooks__hooks.createFromInputFallback(config); + /** + * Unfreezes the nodes that have been frozen by _freezeDefinedNodes. + * + * @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 = {}; } + }, { + key: 'stabilize', - function createLocalOrUTC (input, format, locale, strict, isUTC) { - var c = {}; + /** + * Find a stable position for all nodes + * @private + */ + value: function stabilize() { + var iterations = arguments[0] === undefined ? this.options.stabilization.iterations : arguments[0]; - if (typeof(locale) === 'boolean') { - strict = locale; - locale = undefined; - } - // object construction must be done this way. - // https://github.com/moment/moment/issues/1423 - c._isAMomentObject = true; - c._useUTC = c._isUTC = isUTC; - c._l = locale; - c._i = input; - c._f = format; - c._strict = strict; - c._pf = defaultParsingFlags(); + 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; + } - return createFromConfig(c); - } + // stop the render loop + this.stopSimulation(); - function local__createLocal (input, format, locale, strict) { - return createLocalOrUTC(input, format, locale, strict, false); - } + // set stabilze to false + this.stabilized = false; - var prototypeMin = deprecate( - 'moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548', - function () { - var other = local__createLocal.apply(null, arguments); - return other < this ? this : other; - } - ); + // block redraw requests + this.body.emitter.emit('_blockRedrawRequests'); + this.body.emitter.emit('startStabilizing'); + this.startedStabilization = true; + this.targetIterations = iterations; - var prototypeMax = deprecate( - 'moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548', - function () { - var other = local__createLocal.apply(null, arguments); - return other > this ? this : other; - } - ); + // start the stabilization + if (this.options.stabilization.onlyDynamicEdges === true) { + this._freezeNodes(); + } + this.stabilizationIterations = 0; - // Pick a moment m from moments so that m[fn](other) is true for all - // other. This relies on the function fn to be transitive. - // - // moments should either be an array of moment objects or an array, whose - // first element is an array of moment objects. - function pickBy(fn, moments) { - var res, i; - if (moments.length === 1 && isArray(moments[0])) { - moments = moments[0]; - } - if (!moments.length) { - return local__createLocal(); - } - res = moments[0]; - for (i = 1; i < moments.length; ++i) { - if (moments[i][fn](res)) { - res = moments[i]; - } - } - return res; + setTimeout(this._stabilizationBatch.bind(this), 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++; + } - // TODO: Use [].sort instead? - function min () { - var args = [].slice.call(arguments, 0); - - return pickBy('isBefore', args); + 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: '_finalizeStabilization', + value: function _finalizeStabilization() { + this.body.emitter.emit('_allowRedrawRequests'); + if (this.options.stabilization.fit === true) { + this.body.emitter.emit('fit'); + } - function max () { - var args = [].slice.call(arguments, 0); + if (this.options.stabilization.onlyDynamicEdges === true) { + this._restoreFrozenNodes(); + } - return pickBy('isAfter', args); + this.body.emitter.emit('stabilizationIterationsDone'); + this.body.emitter.emit('_requestRedraw'); + + this.ready = true; } + }]); - function Duration (duration) { - var normalizedInput = normalizeObjectUnits(duration), - years = normalizedInput.year || 0, - quarters = normalizedInput.quarter || 0, - months = normalizedInput.month || 0, - weeks = normalizedInput.week || 0, - days = normalizedInput.day || 0, - hours = normalizedInput.hour || 0, - minutes = normalizedInput.minute || 0, - seconds = normalizedInput.second || 0, - milliseconds = normalizedInput.millisecond || 0; + return PhysicsEngine; + })(); - // representation for dateAddRemove - this._milliseconds = +milliseconds + - seconds * 1e3 + // 1000 - minutes * 6e4 + // 1000 * 60 - hours * 36e5; // 1000 * 60 * 60 - // Because of dateAddRemove treats 24 hours as different from a - // day when working around DST, we need to store them separately - this._days = +days + - weeks * 7; - // It is impossible translate months into days without knowing - // which months you are are talking about, so we have to store - // it separately. - this._months = +months + - quarters * 3 + - years * 12; + exports['default'] = PhysicsEngine; + module.exports = exports['default']; - this._data = {}; +/***/ }, +/* 58 */ +/***/ function(module, exports, __webpack_require__) { - this._locale = locale_locales__getLocale(); + "use strict"; - this._bubble(); - } + Object.defineProperty(exports, "__esModule", { + value: true + }); - function isDuration (obj) { - return obj instanceof Duration; - } + 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 offset (token, separator) { - addFormatToken(token, 0, 0, function () { - var offset = this.utcOffset(); - var sign = '+'; - if (offset < 0) { - offset = -offset; - sign = '-'; - } - return sign + zeroFill(~~(offset / 60), 2) + separator + zeroFill(~~(offset) % 60, 2); - }); - } + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } - offset('Z', ':'); - offset('ZZ', ''); + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - // PARSING + var _componentsNodesCluster = __webpack_require__(85); - addRegexToken('Z', matchOffset); - addRegexToken('ZZ', matchOffset); - addParseToken(['Z', 'ZZ'], function (input, array, config) { - config._useUTC = true; - config._tzm = offsetFromString(input); - }); + var _componentsNodesCluster2 = _interopRequireDefault(_componentsNodesCluster); - // HELPERS + var util = __webpack_require__(1); - // timezone chunker - // '+10:00' > ['10', '00'] - // '-1530' > ['-15', '30'] - var chunkOffset = /([\+\-]|\d\d)/gi; + var ClusterEngine = (function () { + function ClusterEngine(body) { + _classCallCheck(this, ClusterEngine); - function offsetFromString(string) { - var matches = ((string || '').match(matchOffset) || []); - var chunk = matches[matches.length - 1] || []; - var parts = (chunk + '').match(chunkOffset) || ['-', 0, 0]; - var minutes = +(parts[1] * 60) + toInt(parts[2]); + this.body = body; + this.clusteredNodes = {}; - return parts[0] === '+' ? minutes : -minutes; - } + this.options = {}; + this.defaultOptions = {}; + util.extend(this.options, this.defaultOptions); + } - // Return a moment from input, that is local/utc/zone equivalent to model. - function cloneWithOffset(input, model) { - var res, diff; - if (model._isUTC) { - res = model.clone(); - diff = (isMoment(input) || isDate(input) ? +input : +local__createLocal(input)) - (+res); - // Use low-level api, because this fn is low-level api. - res._d.setTime(+res._d + diff); - utils_hooks__hooks.updateOffset(res, false); - return res; - } else { - return local__createLocal(input).local(); - } - return model._isUTC ? local__createLocal(input).zone(model._offset || 0) : local__createLocal(input).local(); + _createClass(ClusterEngine, [{ + key: "setOptions", + value: function setOptions(options) { + if (options !== undefined) {} } + }, { + key: "clusterByHubsize", - function getDateOffset (m) { - // On Firefox.24 Date#getTimezoneOffset returns a floating point. - // https://github.com/moment/moment/pull/1871 - return -Math.round(m._d.getTimezoneOffset() / 15) * 15; - } + /** + * + * @param hubsize + * @param options + */ + value: function clusterByHubsize(hubsize, options) { + if (hubsize === undefined) { + hubsize = this._getHubSize(); + } else if (tyepof(hubsize) === "object") { + options = this._checkOptions(hubsize); + hubsize = this._getHubSize(); + } - // HOOKS + var nodesToCluster = []; + for (var i = 0; i < this.body.nodeIndices.length; i++) { + var node = this.body.nodes[this.body.nodeIndices[i]]; + if (node.edges.length >= hubsize) { + nodesToCluster.push(node.id); + } + } - // This function will be called whenever a moment is mutated. - // It is intended to keep the offset in sync with the timezone. - utils_hooks__hooks.updateOffset = function () {}; + for (var i = 0; i < nodesToCluster.length; i++) { + var node = this.body.nodes[nodesToCluster[i]]; + this.clusterByConnection(node, options, false); + } + this.body.emitter.emit("_dataChanged"); + } + }, { + key: "cluster", - // MOMENTS + /** + * loop over all nodes, check if they adhere to the condition and cluster if needed. + * @param options + * @param refreshData + */ + value: function cluster() { + var options = arguments[0] === undefined ? {} : arguments[0]; + var refreshData = arguments[1] === undefined ? true : arguments[1]; - // keepLocalTime = true means only change the timezone, without - // affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]--> - // 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset - // +0200, so we adjust the time as needed, to be valid. - // - // Keeping the time actually adds/subtracts (one hour) - // from the actual represented time. That is why we call updateOffset - // a second time. In case it wants us to change the offset again - // _changeInProgress == true case, then we have to adjust, because - // there is no such time in the given timezone. - function getSetOffset (input, keepLocalTime) { - var offset = this._offset || 0, - localAdjust; - if (input != null) { - if (typeof input === 'string') { - input = offsetFromString(input); - } - if (Math.abs(input) < 16) { - input = input * 60; - } - if (!this._isUTC && keepLocalTime) { - localAdjust = getDateOffset(this); - } - this._offset = input; - this._isUTC = true; - if (localAdjust != null) { - this.add(localAdjust, 'm'); - } - if (offset !== input) { - if (!keepLocalTime || this._changeInProgress) { - add_subtract__addSubtract(this, create__createDuration(input - offset, 'm'), 1, false); - } else if (!this._changeInProgress) { - this._changeInProgress = true; - utils_hooks__hooks.updateOffset(this, true); - this._changeInProgress = null; - } - } - return this; - } else { - return this._isUTC ? offset : getDateOffset(this); - } - } + if (options.joinCondition === undefined) { + throw new Error("Cannot call clusterByNodeData without a joinCondition function in the options."); + } - function getSetZone (input, keepLocalTime) { - if (input != null) { - if (typeof input !== 'string') { - input = -input; - } + // check if the options object is fine, append if needed + options = this._checkOptions(options); - this.utcOffset(input, keepLocalTime); + var childNodesObj = {}; + var childEdgesObj = {}; - return this; - } else { - return -this.utcOffset(); + // collect the nodes that will be in the cluster + for (var i = 0; i < this.body.nodeIndices.length; i++) { + var nodeId = this.body.nodeIndices[i]; + var clonedOptions = this._cloneOptions(nodeId); + if (options.joinCondition(clonedOptions) === true) { + childNodesObj[nodeId] = this.body.nodes[nodeId]; } - } + } - function setOffsetToUTC (keepLocalTime) { - return this.utcOffset(0, keepLocalTime); + this._cluster(childNodesObj, childEdgesObj, options, refreshData); } + }, { + key: "clusterOutliers", - function setOffsetToLocal (keepLocalTime) { - if (this._isUTC) { - this.utcOffset(0, keepLocalTime); - this._isUTC = false; + /** + * Cluster all nodes in the network that have only 1 edge + * @param options + * @param refreshData + */ + value: function clusterOutliers(options) { + var refreshData = arguments[1] === undefined ? true : arguments[1]; - if (keepLocalTime) { - this.subtract(getDateOffset(this), 'm'); + options = this._checkOptions(options); + var clusters = []; + + // collect the nodes that will be in the cluster + for (var i = 0; i < this.body.nodeIndices.length; i++) { + var childNodesObj = {}; + var childEdgesObj = {}; + var nodeId = this.body.nodeIndices[i]; + if (this.body.nodes[nodeId].edges.length === 1) { + var edge = this.body.nodes[nodeId].edges[0]; + var childNodeId = this._getConnectedId(edge, nodeId); + if (childNodeId != nodeId) { + if (options.joinCondition === undefined) { + childNodesObj[nodeId] = this.body.nodes[nodeId]; + childNodesObj[childNodeId] = this.body.nodes[childNodeId]; + } else { + var clonedOptions = this._cloneOptions(nodeId); + if (options.joinCondition(clonedOptions) === true) { + childNodesObj[nodeId] = this.body.nodes[nodeId]; + } + clonedOptions = this._cloneOptions(childNodeId); + if (options.joinCondition(clonedOptions) === true) { + childNodesObj[childNodeId] = this.body.nodes[childNodeId]; + } } + clusters.push({ nodes: childNodesObj, edges: childEdgesObj }); + } } - return this; - } + } - function setOffsetToParsedOffset () { - if (this._tzm) { - this.utcOffset(this._tzm); - } else if (typeof this._i === 'string') { - this.utcOffset(offsetFromString(this._i)); - } - return this; + for (var i = 0; i < clusters.length; i++) { + this._cluster(clusters[i].nodes, clusters[i].edges, options, false); + } + + if (refreshData === true) { + this.body.emitter.emit("_dataChanged"); + } } + }, { + key: "clusterByConnection", - function hasAlignedHourOffset (input) { - if (!input) { - input = 0; - } - else { - input = local__createLocal(input).utcOffset(); - } + /** + * suck all connected nodes of a node into the node. + * @param nodeId + * @param options + * @param refreshData + */ + value: function clusterByConnection(nodeId, options) { + var refreshData = arguments[2] === undefined ? true : arguments[2]; - return (this.utcOffset() - input) % 60 === 0; - } + // kill conditions + if (nodeId === undefined) { + throw new Error("No nodeId supplied to clusterByConnection!"); + } + if (this.body.nodes[nodeId] === undefined) { + throw new Error("The nodeId given to clusterByConnection does not exist!"); + } - function isDaylightSavingTime () { - return ( - this.utcOffset() > this.clone().month(0).utcOffset() || - this.utcOffset() > this.clone().month(5).utcOffset() - ); - } + var node = this.body.nodes[nodeId]; + options = this._checkOptions(options, node); + if (options.clusterNodeProperties.x === undefined) { + options.clusterNodeProperties.x = node.x; + } + if (options.clusterNodeProperties.y === undefined) { + options.clusterNodeProperties.y = node.y; + } + if (options.clusterNodeProperties.fixed === undefined) { + options.clusterNodeProperties.fixed = {}; + options.clusterNodeProperties.fixed.x = node.options.fixed.x; + options.clusterNodeProperties.fixed.y = node.options.fixed.y; + } - function isDaylightSavingTimeShifted () { - if (this._a) { - var other = this._isUTC ? create_utc__createUTC(this._a) : local__createLocal(this._a); - return this.isValid() && compareArrays(this._a, other.toArray()) > 0; - } + var childNodesObj = {}; + var childEdgesObj = {}; + var parentNodeId = node.id; + var parentClonedOptions = this._cloneOptions(parentNodeId); + childNodesObj[parentNodeId] = node; - return false; - } + // collect the nodes that will be in the cluster + for (var i = 0; i < node.edges.length; i++) { + var edge = node.edges[i]; + var childNodeId = this._getConnectedId(edge, parentNodeId); - function isLocal () { - return !this._isUTC; - } + if (childNodeId !== parentNodeId) { + if (options.joinCondition === undefined) { + childEdgesObj[edge.id] = edge; + childNodesObj[childNodeId] = this.body.nodes[childNodeId]; + } else { + // clone the options and insert some additional parameters that could be interesting. + var childClonedOptions = this._cloneOptions(childNodeId); + if (options.joinCondition(parentClonedOptions, childClonedOptions) === true) { + childEdgesObj[edge.id] = edge; + childNodesObj[childNodeId] = this.body.nodes[childNodeId]; + } + } + } else { + childEdgesObj[edge.id] = edge; + } + } - function isUtcOffset () { - return this._isUTC; + this._cluster(childNodesObj, childEdgesObj, options, refreshData); } + }, { + key: "_cloneOptions", - function isUtc () { - return this._isUTC && this._offset === 0; + /** + * This returns a clone of the options or options of the edge or node to be used for construction of new edges or check functions for new nodes. + * @param objId + * @param type + * @returns {{}} + * @private + */ + value: function _cloneOptions(objId, type) { + var clonedOptions = {}; + if (type === undefined || type === "node") { + util.deepExtend(clonedOptions, this.body.nodes[objId].options, true); + clonedOptions.x = this.body.nodes[objId].x; + clonedOptions.y = this.body.nodes[objId].y; + clonedOptions.amountOfConnections = this.body.nodes[objId].edges.length; + } else { + util.deepExtend(clonedOptions, this.body.edges[objId].options, true); + } + return clonedOptions; } + }, { + key: "_createClusterEdges", - var aspNetRegex = /(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/; + /** + * This function creates the edges that will be attached to the cluster. + * + * @param childNodesObj + * @param childEdgesObj + * @param newEdges + * @param options + * @private + */ + value: function _createClusterEdges(childNodesObj, childEdgesObj, newEdges, options) { + var edge = undefined, + childNodeId = undefined, + childNode = undefined; - // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html - // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere - var create__isoRegex = /^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/; + var childKeys = Object.keys(childNodesObj); + for (var i = 0; i < childKeys.length; i++) { + childNodeId = childKeys[i]; + childNode = childNodesObj[childNodeId]; - function create__createDuration (input, key) { - var duration = input, - // matching against regexp is expensive, do it on demand - match = null, - sign, - ret, - diffRes; + // mark all edges for removal from global and construct new edges from the cluster to others + for (var j = 0; j < childNode.edges.length; j++) { + edge = childNode.edges[j]; + childEdgesObj[edge.id] = edge; - if (isDuration(input)) { - duration = { - ms : input._milliseconds, - d : input._days, - M : input._months - }; - } else if (typeof input === 'number') { - duration = {}; - if (key) { - duration[key] = input; + var otherNodeId = edge.toId; + var otherOnTo = true; + if (edge.toId != childNodeId) { + otherNodeId = edge.toId; + otherOnTo = true; + } else if (edge.fromId != childNodeId) { + otherNodeId = edge.fromId; + otherOnTo = false; + } + + if (childNodesObj[otherNodeId] === undefined) { + var clonedOptions = this._cloneOptions(edge.id, "edge"); + util.deepExtend(clonedOptions, options.clusterEdgeProperties); + if (otherOnTo === true) { + clonedOptions.from = options.clusterNodeProperties.id; + clonedOptions.to = otherNodeId; } else { - duration.milliseconds = input; + clonedOptions.from = otherNodeId; + clonedOptions.to = options.clusterNodeProperties.id; } - } else if (!!(match = aspNetRegex.exec(input))) { - sign = (match[1] === '-') ? -1 : 1; - duration = { - y : 0, - d : toInt(match[DATE]) * sign, - h : toInt(match[HOUR]) * sign, - m : toInt(match[MINUTE]) * sign, - s : toInt(match[SECOND]) * sign, - ms : toInt(match[MILLISECOND]) * sign - }; - } else if (!!(match = create__isoRegex.exec(input))) { - sign = (match[1] === '-') ? -1 : 1; - duration = { - y : parseIso(match[2], sign), - M : parseIso(match[3], sign), - d : parseIso(match[4], sign), - h : parseIso(match[5], sign), - m : parseIso(match[6], sign), - s : parseIso(match[7], sign), - w : parseIso(match[8], sign) - }; - } else if (duration == null) {// checks for null or undefined - duration = {}; - } else if (typeof duration === 'object' && ('from' in duration || 'to' in duration)) { - diffRes = momentsDifference(local__createLocal(duration.from), local__createLocal(duration.to)); - - duration = {}; - duration.ms = diffRes.milliseconds; - duration.M = diffRes.months; + clonedOptions.id = "clusterEdge:" + util.randomUUID(); + newEdges.push(this.body.functions.createEdge(clonedOptions)); + } } + } + } + }, { + key: "_checkOptions", - ret = new Duration(duration); + /** + * This function checks the options that can be supplied to the different cluster functions + * for certain fields and inserts defaults if needed + * @param options + * @returns {*} + * @private + */ + value: function _checkOptions() { + var options = arguments[0] === undefined ? {} : arguments[0]; - if (isDuration(input) && hasOwnProp(input, '_locale')) { - ret._locale = input._locale; - } + if (options.clusterEdgeProperties === undefined) { + options.clusterEdgeProperties = {}; + } + if (options.clusterNodeProperties === undefined) { + options.clusterNodeProperties = {}; + } - return ret; + return options; } + }, { + key: "_cluster", - create__createDuration.fn = Duration.prototype; + /** + * + * @param {Object} childNodesObj | object with node objects, id as keys, same as childNodes except it also contains a source node + * @param {Object} childEdgesObj | object with edge objects, id as keys + * @param {Array} options | object with {clusterNodeProperties, clusterEdgeProperties, processProperties} + * @param {Boolean} refreshData | when true, do not wrap up + * @private + */ + value: function _cluster(childNodesObj, childEdgesObj, options) { + var refreshData = arguments[3] === undefined ? true : arguments[3]; - function parseIso (inp, sign) { - // We'd normally use ~~inp for this, but unfortunately it also - // converts floats to ints. - // inp may be undefined, so careful calling replace on it. - var res = inp && parseFloat(inp.replace(',', '.')); - // apply sign while we're at it - return (isNaN(res) ? 0 : res) * sign; - } + // kill condition: no children so cant cluster + if (Object.keys(childNodesObj).length === 0) { + return; + } - function positiveMomentsDifference(base, other) { - var res = {milliseconds: 0, months: 0}; + // check if we have an unique id; + if (options.clusterNodeProperties.id === undefined) { + options.clusterNodeProperties.id = "cluster:" + util.randomUUID(); + } + var clusterId = options.clusterNodeProperties.id; - res.months = other.month() - base.month() + - (other.year() - base.year()) * 12; - if (base.clone().add(res.months, 'M').isAfter(other)) { - --res.months; + // construct the clusterNodeProperties + var clusterNodeProperties = options.clusterNodeProperties; + if (options.processProperties !== undefined) { + // get the childNode options + var childNodesOptions = []; + for (var nodeId in childNodesObj) { + var clonedOptions = this._cloneOptions(nodeId); + childNodesOptions.push(clonedOptions); } - res.milliseconds = +other - +(base.clone().add(res.months, 'M')); - - return res; - } - - function momentsDifference(base, other) { - var res; - other = cloneWithOffset(other, base); - if (base.isBefore(other)) { - res = positiveMomentsDifference(base, other); - } else { - res = positiveMomentsDifference(other, base); - res.milliseconds = -res.milliseconds; - res.months = -res.months; + // get clusterproperties based on childNodes + var childEdgesOptions = []; + for (var edgeId in childEdgesObj) { + var clonedOptions = this._cloneOptions(edgeId, "edge"); + childEdgesOptions.push(clonedOptions); } - return res; - } + clusterNodeProperties = options.processProperties(clusterNodeProperties, childNodesOptions, childEdgesOptions); + if (!clusterNodeProperties) { + throw new Error("The processClusterProperties function does not return properties!"); + } + } + if (clusterNodeProperties.label === undefined) { + clusterNodeProperties.label = "cluster"; + } - function createAdder(direction, name) { - return function (val, period) { - var dur, tmp; - //invert the arguments, but complain about it - if (period !== null && !isNaN(+period)) { - deprecateSimple(name, 'moment().' + name + '(period, number) is deprecated. Please use moment().' + name + '(number, period).'); - tmp = val; val = period; period = tmp; - } + // give the clusterNode a postion if it does not have one. + var pos = undefined; + if (clusterNodeProperties.x === undefined) { + pos = this._getClusterPosition(childNodesObj); + clusterNodeProperties.x = pos.x; + } + if (clusterNodeProperties.y === undefined) { + if (pos === undefined) { + pos = this._getClusterPosition(childNodesObj); + } + clusterNodeProperties.y = pos.y; + } - val = typeof val === 'string' ? +val : val; - dur = create__createDuration(val, period); - add_subtract__addSubtract(this, dur, direction); - return this; - }; - } + // force the ID to remain the same + clusterNodeProperties.id = clusterId; - function add_subtract__addSubtract (mom, duration, isAdding, updateOffset) { - var milliseconds = duration._milliseconds, - days = duration._days, - months = duration._months; - updateOffset = updateOffset == null ? true : updateOffset; + // create the clusterNode + var clusterNode = this.body.functions.createNode(clusterNodeProperties, _componentsNodesCluster2["default"]); + clusterNode.isCluster = true; + clusterNode.containedNodes = childNodesObj; + clusterNode.containedEdges = childEdgesObj; - if (milliseconds) { - mom._d.setTime(+mom._d + milliseconds * isAdding); - } - if (days) { - get_set__set(mom, 'Date', get_set__get(mom, 'Date') + days * isAdding); - } - if (months) { - setMonth(mom, get_set__get(mom, 'Month') + months * isAdding); + // finally put the cluster node into global + this.body.nodes[clusterNodeProperties.id] = clusterNode; + + // create the new edges that will connect to the cluster + var newEdges = []; + this._createClusterEdges(childNodesObj, childEdgesObj, newEdges, options); + + // disable the childEdges + for (var edgeId in childEdgesObj) { + if (childEdgesObj.hasOwnProperty(edgeId)) { + if (this.body.edges[edgeId] !== undefined) { + var edge = this.body.edges[edgeId]; + edge.togglePhysics(false); + edge.options.hidden = true; + } } - if (updateOffset) { - utils_hooks__hooks.updateOffset(mom, days || months); + } + + // disable the childNodes + for (var nodeId in childNodesObj) { + if (childNodesObj.hasOwnProperty(nodeId)) { + this.clusteredNodes[nodeId] = { clusterId: clusterNodeProperties.id, node: this.body.nodes[nodeId] }; + this.body.nodes[nodeId].togglePhysics(false); + this.body.nodes[nodeId].options.hidden = true; } - } + } - var add_subtract__add = createAdder(1, 'add'); - var add_subtract__subtract = createAdder(-1, 'subtract'); + // push new edges to global + for (var i = 0; i < newEdges.length; i++) { + this.body.edges[newEdges[i].id] = newEdges[i]; + this.body.edges[newEdges[i].id].connect(); + } - function moment_calendar__calendar (time) { - // We want to compare the start of today, vs this. - // Getting start-of-today depends on whether we're local/utc/offset or not. - var now = time || local__createLocal(), - sod = cloneWithOffset(now, this).startOf('day'), - diff = this.diff(sod, 'days', true), - format = diff < -6 ? 'sameElse' : - diff < -1 ? 'lastWeek' : - diff < 0 ? 'lastDay' : - diff < 1 ? 'sameDay' : - diff < 2 ? 'nextDay' : - diff < 7 ? 'nextWeek' : 'sameElse'; - return this.format(this.localeData().calendar(format, this, local__createLocal(now))); - } + // set ID to undefined so no duplicates arise + clusterNodeProperties.id = undefined; - function clone () { - return new Moment(this); + // wrap up + if (refreshData === true) { + this.body.emitter.emit("_dataChanged"); + } } + }, { + key: "isCluster", - function isAfter (input, units) { - var inputMs; - units = normalizeUnits(typeof units !== 'undefined' ? units : 'millisecond'); - if (units === 'millisecond') { - input = isMoment(input) ? input : local__createLocal(input); - return +this > +input; - } else { - inputMs = isMoment(input) ? +input : +local__createLocal(input); - return inputMs < +this.clone().startOf(units); - } + /** + * Check if a node is a cluster. + * @param nodeId + * @returns {*} + */ + value: function isCluster(nodeId) { + if (this.body.nodes[nodeId] !== undefined) { + return this.body.nodes[nodeId].isCluster === true; + } else { + console.log("Node does not exist."); + return false; + } } + }, { + key: "_getClusterPosition", - function isBefore (input, units) { - var inputMs; - units = normalizeUnits(typeof units !== 'undefined' ? units : 'millisecond'); - if (units === 'millisecond') { - input = isMoment(input) ? input : local__createLocal(input); - return +this < +input; - } else { - inputMs = isMoment(input) ? +input : +local__createLocal(input); - return +this.clone().endOf(units) < inputMs; - } + /** + * get the position of the cluster node based on what's inside + * @param {object} childNodesObj | object with node objects, id as keys + * @returns {{x: number, y: number}} + * @private + */ + value: function _getClusterPosition(childNodesObj) { + var childKeys = Object.keys(childNodesObj); + var minX = childNodesObj[childKeys[0]].x; + var maxX = childNodesObj[childKeys[0]].x; + var minY = childNodesObj[childKeys[0]].y; + var maxY = childNodesObj[childKeys[0]].y; + var node = undefined; + for (var i = 0; i < childKeys.lenght; i++) { + node = childNodesObj[childKeys[0]]; + minX = node.x < minX ? node.x : minX; + maxX = node.x > maxX ? node.x : maxX; + minY = node.y < minY ? node.y : minY; + maxY = node.y > maxY ? node.y : maxY; + } + return { x: 0.5 * (minX + maxX), y: 0.5 * (minY + maxY) }; } + }, { + key: "openCluster", - function isBetween (from, to, units) { - return this.isAfter(from, units) && this.isBefore(to, units); - } + /** + * Open a cluster by calling this function. + * @param {String} clusterNodeId | the ID of the cluster node + * @param {Boolean} refreshData | wrap up afterwards if not true + */ + value: function openCluster(clusterNodeId) { + var refreshData = arguments[1] === undefined ? true : arguments[1]; - function isSame (input, units) { - var inputMs; - units = normalizeUnits(units || 'millisecond'); - if (units === 'millisecond') { - input = isMoment(input) ? input : local__createLocal(input); - return +this === +input; - } else { - inputMs = +local__createLocal(input); - return +(this.clone().startOf(units)) <= inputMs && inputMs <= +(this.clone().endOf(units)); - } - } + // kill conditions + if (clusterNodeId === undefined) { + throw new Error("No clusterNodeId supplied to openCluster."); + } + if (this.body.nodes[clusterNodeId] === undefined) { + throw new Error("The clusterNodeId supplied to openCluster does not exist."); + } + if (this.body.nodes[clusterNodeId].containedNodes === undefined) { + console.log("The node:" + clusterNodeId + " is not a cluster."); + return; + } + var clusterNode = this.body.nodes[clusterNodeId]; + var containedNodes = clusterNode.containedNodes; + var containedEdges = clusterNode.containedEdges; - function absFloor (number) { - if (number < 0) { - return Math.ceil(number); - } else { - return Math.floor(number); - } - } + // release nodes + for (var nodeId in containedNodes) { + if (containedNodes.hasOwnProperty(nodeId)) { + var containedNode = this.body.nodes[nodeId]; + containedNode = containedNodes[nodeId]; + // inherit position + containedNode.x = clusterNode.x; + containedNode.y = clusterNode.y; - function diff (input, units, asFloat) { - var that = cloneWithOffset(input, this), - zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4, - delta, output; + // inherit speed + containedNode.vx = clusterNode.vx; + containedNode.vy = clusterNode.vy; - units = normalizeUnits(units); + containedNode.options.hidden = false; + containedNode.togglePhysics(true); - if (units === 'year' || units === 'month' || units === 'quarter') { - output = monthDiff(this, that); - if (units === 'quarter') { - output = output / 3; - } else if (units === 'year') { - output = output / 12; - } - } else { - delta = this - that; - output = units === 'second' ? delta / 1e3 : // 1000 - units === 'minute' ? delta / 6e4 : // 1000 * 60 - units === 'hour' ? delta / 36e5 : // 1000 * 60 * 60 - units === 'day' ? (delta - zoneDelta) / 864e5 : // 1000 * 60 * 60 * 24, negate dst - units === 'week' ? (delta - zoneDelta) / 6048e5 : // 1000 * 60 * 60 * 24 * 7, negate dst - delta; + delete this.clusteredNodes[nodeId]; } - return asFloat ? output : absFloor(output); - } - - function monthDiff (a, b) { - // difference in months - var wholeMonthDiff = ((b.year() - a.year()) * 12) + (b.month() - a.month()), - // b is in (anchor - 1 month, anchor + 1 month) - anchor = a.clone().add(wholeMonthDiff, 'months'), - anchor2, adjust; + } - if (b - anchor < 0) { - anchor2 = a.clone().add(wholeMonthDiff - 1, 'months'); - // linear across the month - adjust = (b - anchor) / (anchor - anchor2); - } else { - anchor2 = a.clone().add(wholeMonthDiff + 1, 'months'); - // linear across the month - adjust = (b - anchor) / (anchor2 - anchor); + // release edges + for (var edgeId in containedEdges) { + if (containedEdges.hasOwnProperty(edgeId)) { + var edge = this.body.edges[edgeId]; + edge.options.hidden = false; + edge.togglePhysics(true); } + } - return -(wholeMonthDiff + adjust); - } + // 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]; + } - utils_hooks__hooks.defaultFormat = 'YYYY-MM-DDTHH:mm:ssZ'; + // remove clusterNode + delete this.body.nodes[clusterNodeId]; - function toString () { - return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ'); + if (refreshData === true) { + this.body.emitter.emit("_dataChanged"); + } } + }, { + key: "_connectEdge", - function moment_format__toISOString () { - var m = this.clone().utc(); - if (0 < m.year() && m.year() <= 9999) { - if ('function' === typeof Date.prototype.toISOString) { - // native implementation is ~50x faster, use it when we can - return this.toDate().toISOString(); - } else { - return formatMoment(m, 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); - } - } else { - return formatMoment(m, 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); - } + /** + * Connect an edge that was previously contained from cluster A to cluster B if the node that it was originally connected to + * is currently residing in cluster B + * @param edge + * @param nodeId + * @param from + * @private + */ + value: function _connectEdge(edge, nodeId, from) { + var clusterStack = this.findNode(nodeId); + if (from === true) { + edge.from = clusterStack[clusterStack.length - 1]; + edge.fromId = clusterStack[clusterStack.length - 1].id; + clusterStack.pop(); + edge.fromArray = clusterStack; + } else { + edge.to = clusterStack[clusterStack.length - 1]; + edge.toId = clusterStack[clusterStack.length - 1].id; + clusterStack.pop(); + edge.toArray = clusterStack; + } + edge.connect(); } + }, { + key: "findNode", - function format (inputString) { - var output = formatMoment(this, inputString || utils_hooks__hooks.defaultFormat); - return this.localeData().postformat(output); - } + /** + * 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; - function from (time, withoutSuffix) { - return create__createDuration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix); + 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", - function fromNow (withoutSuffix) { - return this.from(local__createLocal(), withoutSuffix); + /** + * 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", - function locale (key) { - var newLocaleData; + /** + * 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; - if (key === undefined) { - return this._locale._abbr; - } else { - newLocaleData = locale_locales__getLocale(key); - if (newLocaleData != null) { - this._locale = newLocaleData; - } - return this; + 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 lang = deprecate( - 'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.', - function (key) { - if (key === undefined) { - return this.localeData(); - } else { - return this.locale(key); - } - } - ); + var letiance = averageSquared - Math.pow(average, 2); + var standardDeviation = Math.sqrt(letiance); - function localeData () { - return this._locale; + var hubThreshold = Math.floor(average + 2 * standardDeviation); + + // always have at least one to cluster + if (hubThreshold > largestHub) { + hubThreshold = largestHub; + } + + return hubThreshold; } + }]); - function startOf (units) { - units = normalizeUnits(units); - // the following switch intentionally omits break keywords - // to utilize falling through the cases. - switch (units) { - case 'year': - this.month(0); - /* falls through */ - case 'quarter': - case 'month': - this.date(1); - /* falls through */ - case 'week': - case 'isoWeek': - case 'day': - this.hours(0); - /* falls through */ - case 'hour': - this.minutes(0); - /* falls through */ - case 'minute': - this.seconds(0); - /* falls through */ - case 'second': - this.milliseconds(0); - } + return ClusterEngine; + })(); - // weeks are a special case - if (units === 'week') { - this.weekday(0); - } - if (units === 'isoWeek') { - this.isoWeekday(1); - } + exports["default"] = ClusterEngine; + module.exports = exports["default"]; - // quarters are also special - if (units === 'quarter') { - this.month(Math.floor(this.month() / 3) * 3); - } +/***/ }, +/* 59 */ +/***/ function(module, exports, __webpack_require__) { - return this; - } + 'use strict'; - function endOf (units) { - units = normalizeUnits(units); - if (units === undefined || units === 'millisecond') { - return this; - } - return this.startOf(units).add(1, (units === 'isoWeek' ? 'week' : units)).subtract(1, 'ms'); - } + Object.defineProperty(exports, '__esModule', { + value: true + }); - function to_type__valueOf () { - return +this._d - ((this._offset || 0) * 60000); - } + 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 unix () { - return Math.floor(+this / 1000); - } + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - function toDate () { - return this._offset ? new Date(+this) : this._d; - } + if (typeof window !== 'undefined') { + window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame; + } - function toArray () { - var m = this; - return [m.year(), m.month(), m.date(), m.hour(), m.minute(), m.second(), m.millisecond()]; - } - - function moment_valid__isValid () { - return valid__isValid(this); - } - - function parsingFlags () { - return extend({}, this._pf); - } - - function invalidAt () { - return this._pf.overflow; - } - - addFormatToken(0, ['gg', 2], 0, function () { - return this.weekYear() % 100; - }); - - addFormatToken(0, ['GG', 2], 0, function () { - return this.isoWeekYear() % 100; - }); - - function addWeekYearFormatToken (token, getter) { - addFormatToken(0, [token, token.length], 0, getter); - } - - addWeekYearFormatToken('gggg', 'weekYear'); - addWeekYearFormatToken('ggggg', 'weekYear'); - addWeekYearFormatToken('GGGG', 'isoWeekYear'); - addWeekYearFormatToken('GGGGG', 'isoWeekYear'); - - // ALIASES - - addUnitAlias('weekYear', 'gg'); - addUnitAlias('isoWeekYear', 'GG'); - - // PARSING - - addRegexToken('G', matchSigned); - addRegexToken('g', matchSigned); - addRegexToken('GG', match1to2, match2); - addRegexToken('gg', match1to2, match2); - addRegexToken('GGGG', match1to4, match4); - addRegexToken('gggg', match1to4, match4); - addRegexToken('GGGGG', match1to6, match6); - addRegexToken('ggggg', match1to6, match6); + var util = __webpack_require__(1); - addWeekParseToken(['gggg', 'ggggg', 'GGGG', 'GGGGG'], function (input, week, config, token) { - week[token.substr(0, 2)] = toInt(input); - }); + var CanvasRenderer = (function () { + function CanvasRenderer(body, canvas) { + _classCallCheck(this, CanvasRenderer); - addWeekParseToken(['gg', 'GG'], function (input, week, config, token) { - week[token] = utils_hooks__hooks.parseTwoDigitYear(input); - }); + this.body = body; + this.canvas = canvas; - // HELPERS + this.redrawRequested = false; + this.renderTimer = undefined; + this.requiresTimeout = true; + this.renderingActive = false; + this.renderRequests = 0; + this.pixelRatio = undefined; + this.allowRedrawRequests = true; - function weeksInYear(year, dow, doy) { - return weekOfYear(local__createLocal([year, 11, 31 + dow - doy]), dow, doy).week; - } + this.dragging = false; + this.options = {}; + this.defaultOptions = { + hideEdgesOnDrag: false, + hideNodesOnDrag: false + }; + util.extend(this.options, this.defaultOptions); - // MOMENTS + this._determineBrowserMethod(); + this.bindEventListeners(); + } - function getSetWeekYear (input) { - var year = weekOfYear(this, this.localeData()._week.dow, this.localeData()._week.doy).year; - return input == null ? year : this.add((input - year), 'y'); - } + _createClass(CanvasRenderer, [{ + key: 'bindEventListeners', + value: function bindEventListeners() { + var _this = this; - function getSetISOWeekYear (input) { - var year = weekOfYear(this, 1, 4).year; - return input == null ? year : this.add((input - year), 'y'); + this.body.emitter.on('dragStart', function () { + _this.dragging = true; + }); + this.body.emitter.on('dragEnd', function () { + return _this.dragging = false; + }); + 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 () { + _this.renderRequests = 0; + _this.renderingActive = false; + if (_this.requiresTimeout === true) { + clearTimeout(_this.renderTimer); + } else { + cancelAnimationFrame(_this.renderTimer); + } + _this.body.emitter.off(); + }); } - - function getISOWeeksInYear () { - return weeksInYear(this.year(), 1, 4); + }, { + key: 'setOptions', + value: function setOptions(options) { + if (options !== undefined) { + var fields = ['hideEdgesOnDrag', 'hideNodesOnDrag']; + util.selectiveDeepExtend(fields, this.options, options); + } } - - function getWeeksInYear () { - var weekInfo = this.localeData()._week; - return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy); + }, { + 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: '_renderStep', + value: function _renderStep() { + if (this.renderingActive === true) { + // reset the renderTimer so a new scheduled animation step can be set + this.renderTimer = undefined; - addFormatToken('Q', 0, 0, 'quarter'); - - // ALIASES - - addUnitAlias('quarter', 'Q'); + if (this.requiresTimeout === true) { + // this schedules a new simulation step + this._startRendering(); + } - // PARSING + this._redraw(); - addRegexToken('Q', match1); - addParseToken('Q', function (input, array) { - array[MONTH] = (toInt(input) - 1) * 3; - }); + if (this.requiresTimeout === false) { + // this schedules a new simulation step + this._startRendering(); + } + } + } + }, { + key: 'redraw', - // MOMENTS + /** + * Redraw the network with the current data + * chart will be resized too. + */ + value: function redraw() { + this.body.emitter.emit('setSize'); + this._redraw(); + } + }, { + key: '_requestRedraw', - function getSetQuarter (input) { - return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3); + /** + * 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 _requestRedraw() { + if (this.redrawRequested !== true && this.renderingActive === false && this.allowRedrawRequests === true) { + this.redrawRequested = true; + if (this.requiresTimeout === true) { + window.setTimeout(this._redraw.bind(this, false), 0); + } else { + window.requestAnimationFrame(this._redraw.bind(this, false)); + } + } } + }, { + key: '_redraw', + value: function _redraw() { + var hidden = arguments[0] === undefined ? false : arguments[0]; - addFormatToken('D', ['DD', 2], 'Do', 'date'); + this.body.emitter.emit('initRedraw'); - // ALIASES + this.redrawRequested = false; + var ctx = this.canvas.frame.canvas.getContext('2d'); - addUnitAlias('date', 'D'); + // 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(); + } - // PARSING + if (this.pixelRatio === undefined) { + this.pixelRatio = (window.devicePixelRatio || 1) / (ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1); + } - addRegexToken('D', match1to2); - addRegexToken('DD', match1to2, match2); - addRegexToken('Do', function (isStrict, locale) { - return isStrict ? locale._ordinalParse : locale._ordinalParseLenient; - }); + ctx.setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0); - addParseToken(['D', 'DD'], DATE); - addParseToken('Do', function (input, array) { - array[DATE] = toInt(input.match(match1to2)[0], 10); - }); + // clear the canvas + var w = this.canvas.frame.canvas.clientWidth; + var h = this.canvas.frame.canvas.clientHeight; + ctx.clearRect(0, 0, w, h); - // MOMENTS + this.body.emitter.emit('beforeDrawing', ctx); - var getSetDayOfMonth = makeGetSet('Date', 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); - addFormatToken('d', 0, 'do', 'day'); + if (hidden === false) { + if (this.dragging === false || this.dragging === true && this.options.hideEdgesOnDrag === false) { + this._drawEdges(ctx); + } + } - addFormatToken('dd', 0, 0, function (format) { - return this.localeData().weekdaysMin(this, format); - }); + if (this.dragging === false || this.dragging === true && this.options.hideNodesOnDrag === false) { + this._drawNodes(ctx, hidden); + } - addFormatToken('ddd', 0, 0, function (format) { - return this.localeData().weekdaysShort(this, format); - }); + if (this.controlNodesActive === true) { + this._drawControlNodes(ctx); + } - addFormatToken('dddd', 0, 0, function (format) { - return this.localeData().weekdays(this, format); - }); + //this.physics.nodesSolver._debug(ctx,"#F00F0F"); - addFormatToken('e', 0, 0, 'weekday'); - addFormatToken('E', 0, 0, 'isoWeekday'); + this.body.emitter.emit('afterDrawing', ctx); - // ALIASES + // restore original scaling and translation + ctx.restore(); - addUnitAlias('day', 'd'); - addUnitAlias('weekday', 'e'); - addUnitAlias('isoWeekday', 'E'); + if (hidden === true) { + ctx.clearRect(0, 0, w, h); + } + } + }, { + key: '_drawNodes', - // PARSING + /** + * 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]; - addRegexToken('d', match1to2); - addRegexToken('e', match1to2); - addRegexToken('E', match1to2); - addRegexToken('dd', matchWord); - addRegexToken('ddd', matchWord); - addRegexToken('dddd', matchWord); + 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 }; - addWeekParseToken(['dd', 'ddd', 'dddd'], function (input, week, config) { - var weekday = config._locale.weekdaysParse(input); - // if we didn't get a weekday name, mark the date as invalid - if (weekday != null) { - week.d = weekday; + // 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 { - config._pf.invalidWeekday = input; + if (alwaysShow === true) { + node.draw(ctx); + } else if (node.isBoundingBoxOverlappingWith(viewableArea) === true) { + node.draw(ctx); + } else { + node.updateBoundingBox(ctx); + } } - }); - - addWeekParseToken(['d', 'e', 'E'], function (input, week, config, token) { - week[token] = toInt(input); - }); - - // HELPERS + } - function parseWeekday(input, locale) { - if (typeof input === 'string') { - if (!isNaN(input)) { - input = parseInt(input, 10); - } - else { - input = locale.weekdaysParse(input); - if (typeof input !== 'number') { - return null; - } - } - } - return input; + // draw the selected nodes on top + for (var i = 0; i < selected.length; i++) { + node = nodes[selected[i]]; + node.draw(ctx); + } } + }, { + key: '_drawEdges', - // LOCALES + /** + * Redraw all edges + * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d'); + * @param {CanvasRenderingContext2D} ctx + * @private + */ + value: function _drawEdges(ctx) { + var edges = this.body.edges; + var edgeIndices = this.body.edgeIndices; + var edge = undefined; - var defaultLocaleWeekdays = 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'); - function localeWeekdays (m) { - return this._weekdays[m.day()]; + for (var i = 0; i < edgeIndices.length; i++) { + edge = edges[edgeIndices[i]]; + if (edge.connected === true) { + edge.draw(ctx); + } + } } + }, { + key: '_drawControlNodes', - var defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'); - function localeWeekdaysShort (m) { - return this._weekdaysShort[m.day()]; - } + /** + * Redraw all edges + * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d'); + * @param {CanvasRenderingContext2D} ctx + * @private + */ + value: function _drawControlNodes(ctx) { + var edges = this.body.edges; + var edgeIndices = this.body.edgeIndices; + var edge = undefined; - var defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'); - function localeWeekdaysMin (m) { - return this._weekdaysMin[m.day()]; + for (var i = 0; i < edgeIndices.length; i++) { + edge = edges[edgeIndices[i]]; + edge._drawControlNodes(ctx); + } } + }, { + key: '_determineBrowserMethod', - function localeWeekdaysParse (weekdayName) { - var i, mom, regex; - - if (!this._weekdaysParse) { - this._weekdaysParse = []; - } - - for (i = 0; i < 7; i++) { - // make the regex if we don't have it already - if (!this._weekdaysParse[i]) { - mom = local__createLocal([2000, 1]).day(i); - regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, ''); - this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i'); - } - // test the regex - if (this._weekdaysParse[i].test(weekdayName)) { - return i; - } + /** + * 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 _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; + } } + } else { + this.requiresTimeout = true; + } } + }]); - // MOMENTS + return CanvasRenderer; + })(); - function getSetDayOfWeek (input) { - var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay(); - if (input != null) { - input = parseWeekday(input, this.localeData()); - return this.add(input - day, 'd'); - } else { - return day; - } - } + exports['default'] = CanvasRenderer; + module.exports = exports['default']; - function getSetLocaleDayOfWeek (input) { - var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7; - return input == null ? weekday : this.add(input - weekday, 'd'); - } +/***/ }, +/* 60 */ +/***/ function(module, exports, __webpack_require__) { - function getSetISODayOfWeek (input) { - // behaves the same as moment#day except - // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6) - // as a setter, sunday should belong to the previous week. - return input == null ? this.day() || 7 : this.day(this.day() % 7 ? input : input - 7); - } + 'use strict'; - addFormatToken('H', ['HH', 2], 0, 'hour'); - addFormatToken('h', ['hh', 2], 0, function () { - return this.hours() % 12 || 12; - }); + Object.defineProperty(exports, '__esModule', { + value: true + }); - function meridiem (token, lowercase) { - addFormatToken(token, 0, 0, function () { - return this.localeData().meridiem(this.hours(), this.minutes(), lowercase); - }); - } + 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; }; })(); - meridiem('a', true); - meridiem('A', false); + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - // ALIASES + var Hammer = __webpack_require__(41); + var hammerUtil = __webpack_require__(49); - addUnitAlias('hour', 'h'); + var util = __webpack_require__(1); - // PARSING + /** + * 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 + */ - function matchMeridiem (isStrict, locale) { - return locale._meridiemParse; - } + var Canvas = (function () { + function Canvas(body) { + _classCallCheck(this, Canvas); - addRegexToken('a', matchMeridiem); - addRegexToken('A', matchMeridiem); - addRegexToken('H', match1to2); - addRegexToken('h', match1to2); - addRegexToken('HH', match1to2, match2); - addRegexToken('hh', match1to2, match2); + this.body = body; + this.pixelRatio = 1; + this.resizeTimer = undefined; + this.resizeFunction = this._onResize.bind(this); - addParseToken(['H', 'HH'], HOUR); - addParseToken(['a', 'A'], function (input, array, config) { - config._isPm = config._locale.isPM(input); - config._meridiem = input; - }); - addParseToken(['h', 'hh'], function (input, array, config) { - array[HOUR] = toInt(input); - config._pf.bigHour = true; - }); + this.options = {}; + this.defaultOptions = { + autoResize: true, + height: '100%', + width: '100%' + }; + util.extend(this.options, this.defaultOptions); - // LOCALES + this.bindEventListeners(); + } - function localeIsPM (input) { - // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays - // Using charAt should be more compatible. - return ((input + '').toLowerCase().charAt(0) === 'p'); + _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; - var defaultLocaleMeridiemParse = /[ap]\.?m?\.?/i; - function localeMeridiem (hours, minutes, isLower) { - if (hours > 11) { - return isLower ? 'pm' : 'PM'; - } else { - return isLower ? 'am' : 'AM'; + if (options !== undefined) { + var fields = ['width', 'height', 'autoResize']; + util.selectiveDeepExtend(fields, this.options, options); + } + + if (this.options.autoResize === true) { + // automatically adapt to a changing size of the browser. + this._cleanUp(); + this.resizeTimer = setInterval(function () { + _this2.setSize();_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: '_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); + } - // MOMENTS + 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 - // Setting the hour should keep the time, because the user explicitly - // specified which hour he wants. So trying to maintain the same hour (in - // a new timezone) makes sense. Adding/subtracting hours does not follow - // this rule. - var getSetHour = makeGetSet('Hours', true); + ////////////////////////////////////////////////////////////////// - addFormatToken('m', ['mm', 2], 0, 'minute'); + this.frame.canvas = document.createElement('canvas'); + this.frame.canvas.style.position = 'relative'; + this.frame.appendChild(this.frame.canvas); - // ALIASES + 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); - addUnitAlias('minute', 'm'); + this.frame.canvas.getContext('2d').setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0); + } - // PARSING + // add the frame to the container element + this.body.container.appendChild(this.frame); - addRegexToken('m', match1to2); - addRegexToken('mm', match1to2, match2); - addParseToken(['m', 'mm'], MINUTE); + this.body.view.scale = 1; + this.body.view.translation = { x: 0.5 * this.frame.canvas.clientWidth, y: 0.5 * this.frame.canvas.clientHeight }; - // MOMENTS + this._bindHammer(); + } + }, { + key: '_bindHammer', - var getSetMinute = makeGetSet('Minutes', false); + /** + * This function binds hammer, it can be repeated over and over due to the uniqueness check. + * @private + */ + value: function _bindHammer() { + var _this3 = this; - addFormatToken('s', ['ss', 2], 0, 'second'); + if (this.hammer !== undefined) { + this.hammer.destroy(); + } + this.drag = {}; + this.pinch = {}; - // ALIASES + // 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}); - addUnitAlias('second', 's'); + 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); + }); - // PARSING + // 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); + }); - addRegexToken('s', match1to2); - addRegexToken('ss', match1to2, match2); - addParseToken(['s', 'ss'], SECOND); + this.frame.canvas.addEventListener('mousemove', function (event) { + _this3.body.eventListeners.onMouseMove(event); + }); + this.frame.canvas.addEventListener('contextmenu', function (event) { + _this3.body.eventListeners.onContext(event); + }); - // MOMENTS + this.hammerFrame = new Hammer(this.frame); + hammerUtil.onRelease(this.hammerFrame, function (event) { + _this3.body.eventListeners.onRelease(event); + }); + } + }, { + key: 'setSize', - var getSetSecond = makeGetSet('Seconds', false); + /** + * 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]; - addFormatToken('S', 0, 0, function () { - return ~~(this.millisecond() / 100); - }); + width = this._prepareValue(width); + height = this._prepareValue(height); - addFormatToken(0, ['SS', 2], 0, function () { - return ~~(this.millisecond() / 10); - }); + var emitEvent = false; + var oldWidth = this.frame.canvas.width; + var oldHeight = this.frame.canvas.height; - function millisecond__milliseconds (token) { - addFormatToken(0, [token, 3], 0, 'millisecond'); - } + 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; - millisecond__milliseconds('SSS'); - millisecond__milliseconds('SSSS'); + this.frame.canvas.style.width = '100%'; + this.frame.canvas.style.height = '100%'; - // ALIASES + this.frame.canvas.width = this.frame.canvas.clientWidth * this.pixelRatio; + this.frame.canvas.height = this.frame.canvas.clientHeight * this.pixelRatio; - addUnitAlias('millisecond', 'ms'); + this.options.width = width; + this.options.height = height; - // PARSING + emitEvent = true; + } else { + // this would adapt the width of the canvas to the width from 100% if and only if + // there is a change. - addRegexToken('S', match1to3, match1); - addRegexToken('SS', match1to3, match2); - addRegexToken('SSS', match1to3, match3); - addRegexToken('SSSS', matchUnsigned); - addParseToken(['S', 'SS', 'SSS', 'SSSS'], function (input, array) { - array[MILLISECOND] = toInt(('0.' + input) * 1000); - }); + if (this.frame.canvas.width != this.frame.canvas.clientWidth * this.pixelRatio) { + this.frame.canvas.width = this.frame.canvas.clientWidth * this.pixelRatio; + emitEvent = true; + } + if (this.frame.canvas.height != this.frame.canvas.clientHeight * this.pixelRatio) { + this.frame.canvas.height = this.frame.canvas.clientHeight * this.pixelRatio; + emitEvent = true; + } + } - // MOMENTS + if (emitEvent === true) { + this.body.emitter.emit('resize', { width: this.frame.canvas.width / this.pixelRatio, height: this.frame.canvas.height / this.pixelRatio, oldWidth: oldWidth / this.pixelRatio, oldHeight: oldHeight / this.pixelRatio }); + } + } + }, { + key: '_XconvertDOMtoCanvas', - var getSetMillisecond = makeGetSet('Milliseconds', false); + /** + * 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', - addFormatToken('z', 0, 0, 'zoneAbbr'); - addFormatToken('zz', 0, 0, 'zoneName'); + /** + * 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', - // MOMENTS + /** + * 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', - function getZoneAbbr () { - return this._isUTC ? 'UTC' : ''; + /** + * 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', - function getZoneName () { - return this._isUTC ? 'Coordinated Universal Time' : ''; + /** + * + * @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', - var momentPrototype__proto = Moment.prototype; + /** + * + * @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) }; + } + }]); - momentPrototype__proto.add = add_subtract__add; - momentPrototype__proto.calendar = moment_calendar__calendar; - momentPrototype__proto.clone = clone; - momentPrototype__proto.diff = diff; - momentPrototype__proto.endOf = endOf; - momentPrototype__proto.format = format; - momentPrototype__proto.from = from; - momentPrototype__proto.fromNow = fromNow; - momentPrototype__proto.get = getSet; - momentPrototype__proto.invalidAt = invalidAt; - momentPrototype__proto.isAfter = isAfter; - momentPrototype__proto.isBefore = isBefore; - momentPrototype__proto.isBetween = isBetween; - momentPrototype__proto.isSame = isSame; - momentPrototype__proto.isValid = moment_valid__isValid; - momentPrototype__proto.lang = lang; - momentPrototype__proto.locale = locale; - momentPrototype__proto.localeData = localeData; - momentPrototype__proto.max = prototypeMax; - momentPrototype__proto.min = prototypeMin; - momentPrototype__proto.parsingFlags = parsingFlags; - momentPrototype__proto.set = getSet; - momentPrototype__proto.startOf = startOf; - momentPrototype__proto.subtract = add_subtract__subtract; - momentPrototype__proto.toArray = toArray; - momentPrototype__proto.toDate = toDate; - momentPrototype__proto.toISOString = moment_format__toISOString; - momentPrototype__proto.toJSON = moment_format__toISOString; - momentPrototype__proto.toString = toString; - momentPrototype__proto.unix = unix; - momentPrototype__proto.valueOf = to_type__valueOf; + return Canvas; + })(); - // Year - momentPrototype__proto.year = getSetYear; - momentPrototype__proto.isLeapYear = getIsLeapYear; + exports['default'] = Canvas; + module.exports = exports['default']; - // Week Year - momentPrototype__proto.weekYear = getSetWeekYear; - momentPrototype__proto.isoWeekYear = getSetISOWeekYear; +/***/ }, +/* 61 */ +/***/ function(module, exports, __webpack_require__) { - // Quarter - momentPrototype__proto.quarter = momentPrototype__proto.quarters = getSetQuarter; + "use strict"; - // Month - momentPrototype__proto.month = getSetMonth; - momentPrototype__proto.daysInMonth = getDaysInMonth; + Object.defineProperty(exports, "__esModule", { + value: true + }); - // Week - momentPrototype__proto.week = momentPrototype__proto.weeks = getSetWeek; - momentPrototype__proto.isoWeek = momentPrototype__proto.isoWeeks = getSetISOWeek; - momentPrototype__proto.weeksInYear = getWeeksInYear; - momentPrototype__proto.isoWeeksInYear = getISOWeeksInYear; + 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; }; })(); - // Day - momentPrototype__proto.date = getSetDayOfMonth; - momentPrototype__proto.day = momentPrototype__proto.days = getSetDayOfWeek; - momentPrototype__proto.weekday = getSetLocaleDayOfWeek; - momentPrototype__proto.isoWeekday = getSetISODayOfWeek; - momentPrototype__proto.dayOfYear = getSetDayOfYear; + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - // Hour - momentPrototype__proto.hour = momentPrototype__proto.hours = getSetHour; + var util = __webpack_require__(1); - // Minute - momentPrototype__proto.minute = momentPrototype__proto.minutes = getSetMinute; + var View = (function () { + function View(body, canvas) { + var _this = this; - // Second - momentPrototype__proto.second = momentPrototype__proto.seconds = getSetSecond; + _classCallCheck(this, View); - // Millisecond - momentPrototype__proto.millisecond = momentPrototype__proto.milliseconds = getSetMillisecond; + this.body = body; + this.canvas = canvas; - // Offset - momentPrototype__proto.utcOffset = getSetOffset; - momentPrototype__proto.utc = setOffsetToUTC; - momentPrototype__proto.local = setOffsetToLocal; - momentPrototype__proto.parseZone = setOffsetToParsedOffset; - momentPrototype__proto.hasAlignedHourOffset = hasAlignedHourOffset; - momentPrototype__proto.isDST = isDaylightSavingTime; - momentPrototype__proto.isDSTShifted = isDaylightSavingTimeShifted; - momentPrototype__proto.isLocal = isLocal; - momentPrototype__proto.isUtcOffset = isUtcOffset; - momentPrototype__proto.isUtc = isUtc; - momentPrototype__proto.isUTC = isUtc; + 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; - // Timezone - momentPrototype__proto.zoneAbbr = getZoneAbbr; - momentPrototype__proto.zoneName = getZoneName; + this.viewFunction = undefined; - // Deprecations - momentPrototype__proto.dates = deprecate('dates accessor is deprecated. Use date instead.', getSetDayOfMonth); - momentPrototype__proto.months = deprecate('months accessor is deprecated. Use month instead', getSetMonth); - momentPrototype__proto.years = deprecate('years accessor is deprecated. Use year instead', getSetYear); - momentPrototype__proto.zone = deprecate('moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779', getSetZone); + 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)); + } - var momentPrototype = momentPrototype__proto; + _createClass(View, [{ + key: "setOptions", + value: function setOptions() { + var options = arguments[0] === undefined ? {} : arguments[0]; - function moment__createUnix (input) { - return local__createLocal(input * 1000); + this.options = options; } + }, { + key: "_getRange", - function moment__createInZone () { - return local__createLocal.apply(null, arguments).parseZone(); - } + /** + * Find the center position of the network + * @private + */ + value: function _getRange() { + var specificNodes = arguments[0] === undefined ? [] : arguments[0]; - var defaultCalendar = { - sameDay : '[Today at] LT', - nextDay : '[Tomorrow at] LT', - nextWeek : 'dddd [at] LT', - lastDay : '[Yesterday at] LT', - lastWeek : '[Last] dddd [at] LT', - sameElse : 'L' - }; + 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 + } + } + } - function locale_calendar__calendar (key, mom, now) { - var output = this._calendar[key]; - return typeof output === 'function' ? output.call(mom, now) : output; + 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", - var defaultLongDateFormat = { - LTS : 'h:mm:ss A', - LT : 'h:mm A', - L : 'MM/DD/YYYY', - LL : 'MMMM D, YYYY', - LLL : 'MMMM D, YYYY LT', - LLLL : 'dddd, MMMM D, YYYY LT' - }; - - function longDateFormat (key) { - var output = this._longDateFormat[key]; - if (!output && this._longDateFormat[key.toUpperCase()]) { - output = this._longDateFormat[key.toUpperCase()].replace(/MMMM|MM|DD|dddd/g, function (val) { - return val.slice(1); - }); - this._longDateFormat[key] = output; - } - return output; + /** + * @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", - var defaultInvalidDate = 'Invalid date'; + /** + * 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]; - function invalidDate () { - return this._invalidDate; - } + var range; + var zoomLevel; - var defaultOrdinal = '%d'; - var defaultOrdinalParse = /\d{1,2}/; + 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; + } - function ordinal (number) { - return this._ordinal.replace('%d', number); - } + range = this._getRange(options.nodes); - function preParsePostFormat (string) { - return string; - } + 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. - var defaultRelativeTime = { - future : 'in %s', - past : '%s ago', - s : 'a few seconds', - m : 'a minute', - mm : '%d minutes', - h : 'an hour', - hh : '%d hours', - d : 'a day', - dd : '%d days', - M : 'a month', - MM : '%d months', - y : 'a year', - yy : '%d years' - }; + // 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("_redraw", true); + 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; - function relative__relativeTime (number, withoutSuffix, string, isFuture) { - var output = this._relativeTime[string]; - return (typeof output === 'function') ? - output(number, withoutSuffix, string, isFuture) : - output.replace(/%d/i, number); - } + var xZoomLevel = this.canvas.frame.canvas.clientWidth / xDistance; + var yZoomLevel = this.canvas.frame.canvas.clientHeight / yDistance; - function pastFuture (diff, output) { - var format = this._relativeTime[diff > 0 ? 'future' : 'past']; - return typeof format === 'function' ? format(output) : format.replace(/%s/i, output); - } + zoomLevel = xZoomLevel <= yZoomLevel ? xZoomLevel : yZoomLevel; + } - function locale_set__set (config) { - var prop, i; - for (i in config) { - prop = config[i]; - if (typeof prop === 'function') { - this[i] = prop; - } else { - this['_' + i] = prop; - } - } - // Lenient ordinal parsing accepts just a number in addition to - // number + (possibly) stuff coming from _ordinalParseLenient. - this._ordinalParseLenient = new RegExp(this._ordinalParse.source + '|' + /\d{1,2}/.source); - } + if (zoomLevel > 1) { + zoomLevel = 1; + } else if (zoomLevel === 0) { + zoomLevel = 1; + } - var prototype__proto = Locale.prototype; + var center = this._findCenter(range); + var animationOptions = { position: center, scale: zoomLevel, animation: options.animation }; + this.moveTo(animationOptions); + } + }, { + key: "focus", - prototype__proto._calendar = defaultCalendar; - prototype__proto.calendar = locale_calendar__calendar; - prototype__proto._longDateFormat = defaultLongDateFormat; - prototype__proto.longDateFormat = longDateFormat; - prototype__proto._invalidDate = defaultInvalidDate; - prototype__proto.invalidDate = invalidDate; - prototype__proto._ordinal = defaultOrdinal; - prototype__proto.ordinal = ordinal; - prototype__proto._ordinalParse = defaultOrdinalParse; - prototype__proto.preparse = preParsePostFormat; - prototype__proto.postformat = preParsePostFormat; - prototype__proto._relativeTime = defaultRelativeTime; - prototype__proto.relativeTime = relative__relativeTime; - prototype__proto.pastFuture = pastFuture; - prototype__proto.set = locale_set__set; + // animation - // Month - prototype__proto.months = localeMonths; - prototype__proto._months = defaultLocaleMonths; - prototype__proto.monthsShort = localeMonthsShort; - prototype__proto._monthsShort = defaultLocaleMonthsShort; - prototype__proto.monthsParse = localeMonthsParse; + /** + * Center a node in view. + * + * @param {Number} nodeId + * @param {Number} [options] + */ + value: function focus(nodeId) { + var options = arguments[1] === undefined ? {} : arguments[1]; - // Week - prototype__proto.week = localeWeek; - prototype__proto._week = defaultLocaleWeek; - prototype__proto.firstDayOfYear = localeFirstDayOfYear; - prototype__proto.firstDayOfWeek = localeFirstDayOfWeek; + 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; - // Day of Week - prototype__proto.weekdays = localeWeekdays; - prototype__proto._weekdays = defaultLocaleWeekdays; - prototype__proto.weekdaysMin = localeWeekdaysMin; - prototype__proto._weekdaysMin = defaultLocaleWeekdaysMin; - prototype__proto.weekdaysShort = localeWeekdaysShort; - prototype__proto._weekdaysShort = defaultLocaleWeekdaysShort; - prototype__proto.weekdaysParse = localeWeekdaysParse; + this.moveTo(options); + } else { + console.log("Node: " + nodeId + " cannot be found."); + } + } + }, { + key: "moveTo", - // Hours - prototype__proto.isPM = localeIsPM; - prototype__proto._meridiemParse = defaultLocaleMeridiemParse; - prototype__proto.meridiem = localeMeridiem; + /** + * + * @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.body.view.translation; + } + 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 - function lists__get (format, index, field, setter) { - var locale = locale_locales__getLocale(); - var utc = create_utc__createUTC().set(setter, index); - return locale[field](utc, format); + this.animateView(options); } + }, { + key: "animateView", - function list (format, index, field, count, setter) { - if (typeof format === 'number') { - index = format; - format = undefined; - } + /** + * + * @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; + } - format = format || ''; + // 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 (index != null) { - return lists__get(format, index, field, setter); - } + this.sourceScale = this.body.view.scale; + this.sourceTranslation = this.body.view.translation; + this.targetScale = options.scale; - var i; - var out = []; - for (i = 0; i < count; i++) { - out[i] = lists__get(format, i, field, setter); + // 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"); } - return out; - } + } 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; - function lists__listMonths (format, index) { - return list(format, index, 'months', 12, 'month'); + this.viewFunction = this._transitionRedraw.bind(this); + this.body.emitter.on("initRedraw", this.viewFunction); + this.body.emitter.emit("_startRendering"); + } } + }, { + key: "_lockedRedraw", - function lists__listMonthsShort (format, index) { - return list(format, index, 'monthsShort', 12, 'month'); - } + /** + * 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 + }; - function lists__listWeekdays (format, index) { - return list(format, index, 'weekdays', 7, 'day'); + this.body.view.translation = targetTranslation; } - - function lists__listWeekdaysShort (format, index) { - return list(format, index, 'weekdaysShort', 7, 'day'); + }, { + 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: "_transitionRedraw", - function lists__listWeekdaysMin (format, index) { - return list(format, index, 'weekdaysMin', 7, 'day'); - } + /** + * + * @param easingTime + * @private + */ + value: function _transitionRedraw() { + var finished = arguments[0] === undefined ? false : arguments[0]; - locale_locales__getSetGlobalLocale('en', { - ordinalParse: /\d{1,2}(th|st|nd|rd)/, - ordinal : function (number) { - var b = number % 10, - output = (toInt(number % 100 / 10) === 1) ? 'th' : - (b === 1) ? 'st' : - (b === 2) ? 'nd' : - (b === 3) ? 'rd' : 'th'; - return number + output; - } - }); + this.easingTime += this.animationSpeed; + this.easingTime = finished === true ? 1 : this.easingTime; - // Side effect imports - utils_hooks__hooks.lang = deprecate('moment.lang is deprecated. Use moment.locale instead.', locale_locales__getSetGlobalLocale); - utils_hooks__hooks.langData = deprecate('moment.langData is deprecated. Use moment.localeData instead.', locale_locales__getLocale); + var progress = util.easingFunctions[this.animationEasingFunction](this.easingTime); - var mathAbs = Math.abs; + 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 + }; - function duration_abs__abs () { - var data = this._data; + // 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: "getScale", + value: function getScale() { + return this.body.view.scale; + } + }, { + key: "getPosition", + value: function getPosition() { + return { x: this.body.view.translation.x, y: this.body.view.translation.y }; + } + }]); - this._milliseconds = mathAbs(this._milliseconds); - this._days = mathAbs(this._days); - this._months = mathAbs(this._months); + return View; + })(); - data.milliseconds = mathAbs(data.milliseconds); - data.seconds = mathAbs(data.seconds); - data.minutes = mathAbs(data.minutes); - data.hours = mathAbs(data.hours); - data.months = mathAbs(data.months); - data.years = mathAbs(data.years); + exports["default"] = View; + module.exports = exports["default"]; - return this; - } +/***/ }, +/* 62 */ +/***/ function(module, exports, __webpack_require__) { - function duration_add_subtract__addSubtract (duration, input, value, direction) { - var other = create__createDuration(input, value); + 'use strict'; - duration._milliseconds += direction * other._milliseconds; - duration._days += direction * other._days; - duration._months += direction * other._months; + Object.defineProperty(exports, '__esModule', { + value: true + }); - return duration._bubble(); - } + 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; }; })(); - // supports only 2.0-style add(1, 's') or add(duration) - function duration_add_subtract__add (input, value) { - return duration_add_subtract__addSubtract(this, input, value, 1); - } + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - // supports only 2.0-style subtract(1, 's') or subtract(duration) - function duration_add_subtract__subtract (input, value) { - return duration_add_subtract__addSubtract(this, input, value, -1); - } + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - function bubble () { - var milliseconds = this._milliseconds; - var days = this._days; - var months = this._months; - var data = this._data; - var seconds, minutes, hours, years = 0; + var _componentsNavigationHandler = __webpack_require__(86); - // The following code bubbles up values, see the tests for - // examples of what that means. - data.milliseconds = milliseconds % 1000; + var _componentsNavigationHandler2 = _interopRequireDefault(_componentsNavigationHandler); - seconds = absFloor(milliseconds / 1000); - data.seconds = seconds % 60; + var _componentsPopup = __webpack_require__(87); - minutes = absFloor(seconds / 60); - data.minutes = minutes % 60; + var _componentsPopup2 = _interopRequireDefault(_componentsPopup); - hours = absFloor(minutes / 60); - data.hours = hours % 24; + var util = __webpack_require__(1); - days += absFloor(hours / 24); + var InteractionHandler = (function () { + function InteractionHandler(body, canvas, selectionHandler) { + _classCallCheck(this, InteractionHandler); - // Accurately convert days to years, assume start from year 0. - years = absFloor(daysToYears(days)); - days -= absFloor(yearsToDays(years)); + this.body = body; + this.canvas = canvas; + this.selectionHandler = selectionHandler; + this.navigationHandler = new _componentsNavigationHandler2['default'](body, canvas); - // 30 days to a month - // TODO (iskren): Use anchor date (like 1st Jan) to compute this. - months += absFloor(days / 30); - days %= 30; + // 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); - // 12 months -> 1 year - years += absFloor(months / 12); - months %= 12; + this.touchTime = 0; + this.drag = {}; + this.pinch = {}; + this.popup = undefined; + this.popupObj = undefined; + this.popupTimer = undefined; - data.days = days; - data.months = months; - data.years = years; + this.body.functions.getPointer = this.getPointer.bind(this); - return this; - } + 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 + }; + util.extend(this.options, this.defaultOptions); - function daysToYears (days) { - // 400 years have 146097 days (taking into account leap year rules) - return days * 400 / 146097; - } + this.bindEventListeners(); + } - function yearsToDays (years) { - // years * 365 + absFloor(years / 4) - - // absFloor(years / 100) + absFloor(years / 400); - return years * 146097 / 400; - } + _createClass(InteractionHandler, [{ + key: 'bindEventListeners', + value: function bindEventListeners() { + var _this = this; - function as (units) { - var days; - var months; - var milliseconds = this._milliseconds; + this.body.emitter.on('destroy', function () { + clearTimeout(_this.popupTimer); + delete _this.body.functions.getPointer; + }); + } + }, { + 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); - units = normalizeUnits(units); + // merge the keyboard options in. + util.mergeOptions(this.options, options, 'keyboard'); - if (units === 'month' || units === 'year') { - days = this._days + milliseconds / 864e5; - months = this._months + daysToYears(days) * 12; - return units === 'month' ? months : months / 12; - } else { - // handle milliseconds separately because of floating point math errors (issue #1867) - days = this._days + Math.round(yearsToDays(this._months / 12)); - switch (units) { - case 'week' : return days / 7 + milliseconds / 6048e5; - case 'day' : return days + milliseconds / 864e5; - case 'hour' : return days * 24 + milliseconds / 36e5; - case 'minute' : return days * 24 * 60 + milliseconds / 6e4; - case 'second' : return days * 24 * 60 * 60 + milliseconds / 1000; - // Math.floor prevents floating point math errors here - case 'millisecond': return Math.floor(days * 24 * 60 * 60 * 1000) + milliseconds; - default: throw new Error('Unknown unit ' + units); - } + if (options.tooltip) { + util.extend(this.options.tooltip, options.tooltip); + if (options.tooltip.color) { + this.options.tooltip.color = util.parseColor(options.tooltip.color); + } } - } - - // TODO: Use this.as('ms')? - function duration_as__valueOf () { - return ( - this._milliseconds + - this._days * 864e5 + - (this._months % 12) * 2592e6 + - toInt(this._months / 12) * 31536e6 - ); - } + } - function makeAs (alias) { - return function () { - return this.as(alias); - }; + this.navigationHandler.setOptions(this.options); } + }, { + key: 'getPointer', - var asMilliseconds = makeAs('ms'); - var asSeconds = makeAs('s'); - var asMinutes = makeAs('m'); - var asHours = makeAs('h'); - var asDays = makeAs('d'); - var asWeeks = makeAs('w'); - var asMonths = makeAs('M'); - var asYears = makeAs('y'); - - function duration_get__get (units) { - units = normalizeUnits(units); - return this[units + 's'](); + /** + * 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', - function makeGetter(name) { - return function () { - return this._data[name]; - }; + /** + * 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', - var duration_get__milliseconds = makeGetter('milliseconds'); - var seconds = makeGetter('seconds'); - var minutes = makeGetter('minutes'); - var hours = makeGetter('hours'); - var days = makeGetter('days'); - var months = makeGetter('months'); - var years = makeGetter('years'); + /** + * 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; - function weeks () { - return absFloor(this.days() / 7); + this.checkSelectionChanges(pointer, event, multiselect); + this.selectionHandler._generateClickEvent('click', event, pointer); } + }, { + key: 'onDoubleTap', - var round = Math.round; - var thresholds = { - s: 45, // seconds to minute - m: 45, // minutes to hour - h: 22, // hours to day - d: 26, // days to month - M: 11 // months to year - }; - - // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize - function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) { - return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture); + /** + * handle doubletap event + * @private + */ + value: function onDoubleTap(event) { + var pointer = this.getPointer(event.center); + this.selectionHandler._generateClickEvent('doubleClick', event, pointer); } + }, { + key: 'onHold', - function duration_humanize__relativeTime (posNegDuration, withoutSuffix, locale) { - var duration = create__createDuration(posNegDuration).abs(); - var seconds = round(duration.as('s')); - var minutes = round(duration.as('m')); - var hours = round(duration.as('h')); - var days = round(duration.as('d')); - var months = round(duration.as('M')); - var years = round(duration.as('y')); + /** + * handle long tap event: multi select nodes + * @private + */ + value: function onHold(event) { + var pointer = this.getPointer(event.center); + var multiselect = this.selectionHandler.options.multiselect; - var a = seconds < thresholds.s && ['s', seconds] || - minutes === 1 && ['m'] || - minutes < thresholds.m && ['mm', minutes] || - hours === 1 && ['h'] || - hours < thresholds.h && ['hh', hours] || - days === 1 && ['d'] || - days < thresholds.d && ['dd', days] || - months === 1 && ['M'] || - months < thresholds.M && ['MM', months] || - years === 1 && ['y'] || ['yy', years]; + this.checkSelectionChanges(pointer, event, multiselect); - a[2] = withoutSuffix; - a[3] = +posNegDuration > 0; - a[4] = locale; - return substituteTimeAgo.apply(null, a); + this.selectionHandler._generateClickEvent('click', event, pointer); + this.selectionHandler._generateClickEvent('hold', event, pointer); } + }, { + key: 'onRelease', - // This function allows you to set a threshold for relative time strings - function duration_humanize__getSetRelativeTimeThreshold (threshold, limit) { - if (thresholds[threshold] === undefined) { - return false; - } - if (limit === undefined) { - return thresholds[threshold]; - } - thresholds[threshold] = limit; - return true; + /** + * 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(); + } } - - function humanize (withSuffix) { - var locale = this.localeData(); - var output = duration_humanize__relativeTime(this, !withSuffix, locale); - - if (withSuffix) { - output = locale.pastFuture(+this, output); - } - - return locale.postformat(output); + }, { + key: 'onContext', + value: function onContext(event) { + var pointer = this.getPointer({ x: event.pageX, y: event.pageY }); + this.selectionHandler._generateClickEvent('oncontext', event, pointer); } + }, { + key: 'checkSelectionChanges', - var iso_string__abs = Math.abs; + /** + * + * @param pointer + * @param add + */ + value: function checkSelectionChanges(pointer, event) { + var add = arguments[2] === undefined ? false : arguments[2]; - function iso_string__toISOString() { - // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js - var Y = iso_string__abs(this.years()); - var M = iso_string__abs(this.months()); - var D = iso_string__abs(this.days()); - var h = iso_string__abs(this.hours()); - var m = iso_string__abs(this.minutes()); - var s = iso_string__abs(this.seconds() + this.milliseconds() / 1000); - var total = this.asSeconds(); + 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 selectedEdges = this.selectionHandler._getSelectedEdgeCount(); + var selectedNodes = this.selectionHandler._getSelectedNodeCount(); - if (!total) { - // this is the same as C#'s (Noda) and python (isodate)... - // but not other JS (goog.date) - return 'P0D'; - } + if (selectedNodes - previouslySelectedNodeCount > 0) { + // node was selected + this.selectionHandler._generateClickEvent('selectNode', event, pointer); + selected = true; + } else if (selectedNodes - previouslySelectedNodeCount < 0) { + // node was deselected + this.selectionHandler._generateClickEvent('deselectNode', event, pointer, previousSelection); + selected = true; + } - return (total < 0 ? '-' : '') + - 'P' + - (Y ? Y + 'Y' : '') + - (M ? M + 'M' : '') + - (D ? D + 'D' : '') + - ((h || m || s) ? 'T' : '') + - (h ? h + 'H' : '') + - (m ? m + 'M' : '') + - (s ? s + 'S' : ''); + if (selectedEdges - previouslySelectedEdgeCount > 0) { + // node was selected + this.selectionHandler._generateClickEvent('selectEdge', event, pointer); + selected = true; + } else if (selectedEdges - previouslySelectedEdgeCount < 0) { + // node was deselected + this.selectionHandler._generateClickEvent('deselectEdge', event, pointer, previousSelection); + selected = true; + } + + if (selected === true) { + // select or unselect + this.selectionHandler._generateClickEvent('select', event, pointer); + } } + }, { + key: 'onDragStart', - var duration_prototype__proto = Duration.prototype; + /** + * This function is called by onDragStart. + * It is separated out because we can then overload it for the datamanipulation system. + * + * @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); + } - duration_prototype__proto.abs = duration_abs__abs; - duration_prototype__proto.add = duration_add_subtract__add; - duration_prototype__proto.subtract = duration_add_subtract__subtract; - duration_prototype__proto.as = as; - duration_prototype__proto.asMilliseconds = asMilliseconds; - duration_prototype__proto.asSeconds = asSeconds; - duration_prototype__proto.asMinutes = asMinutes; - duration_prototype__proto.asHours = asHours; - duration_prototype__proto.asDays = asDays; - duration_prototype__proto.asWeeks = asWeeks; - duration_prototype__proto.asMonths = asMonths; - duration_prototype__proto.asYears = asYears; - duration_prototype__proto.valueOf = duration_as__valueOf; - duration_prototype__proto._bubble = bubble; - duration_prototype__proto.get = duration_get__get; - duration_prototype__proto.milliseconds = duration_get__milliseconds; - duration_prototype__proto.seconds = seconds; - duration_prototype__proto.minutes = minutes; - duration_prototype__proto.hours = hours; - duration_prototype__proto.days = days; - duration_prototype__proto.weeks = weeks; - duration_prototype__proto.months = months; - duration_prototype__proto.years = years; - duration_prototype__proto.humanize = humanize; - duration_prototype__proto.toISOString = iso_string__toISOString; - duration_prototype__proto.toString = iso_string__toISOString; - duration_prototype__proto.toJSON = iso_string__toISOString; - duration_prototype__proto.locale = locale; - duration_prototype__proto.localeData = localeData; + // note: drag.pointer is set in onTouch to get the initial touch location + var node = this.selectionHandler.getNodeAt(this.drag.pointer); - // Deprecations - duration_prototype__proto.toIsoString = deprecate('toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)', iso_string__toISOString); - duration_prototype__proto.lang = lang; + this.drag.dragging = true; + this.drag.selection = []; + this.drag.translation = util.extend({}, this.body.view.translation); // copy the object + this.drag.nodeId = undefined; - // Side effect imports + this.selectionHandler._generateClickEvent('dragStart', event, this.drag.pointer); - addFormatToken('X', 0, 0, 'unix'); - addFormatToken('x', 0, 0, 'valueOf'); + 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); + } - // PARSING + 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, - addRegexToken('x', matchSigned); - addRegexToken('X', matchTimestamp); - addParseToken('X', function (input, array, config) { - config._d = new Date(parseFloat(input, 10) * 1000); - }); - addParseToken('x', function (input, array, config) { - config._d = new Date(toInt(input)); - }); + // 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 + }; - // Side effect imports + object.options.fixed.x = true; + object.options.fixed.y = true; + this.drag.selection.push(s); + } + } + } + } + }, { + key: 'onDrag', - utils_hooks__hooks.version = '2.10.2'; + /** + * handle drag event + * @private + */ + value: function onDrag(event) { + var _this2 = this; - setHookCallback(local__createLocal); + if (this.drag.pinched === true) { + return; + } - utils_hooks__hooks.fn = momentPrototype; - utils_hooks__hooks.min = min; - utils_hooks__hooks.max = max; - utils_hooks__hooks.utc = create_utc__createUTC; - utils_hooks__hooks.unix = moment__createUnix; - utils_hooks__hooks.months = lists__listMonths; - utils_hooks__hooks.isDate = isDate; - utils_hooks__hooks.locale = locale_locales__getSetGlobalLocale; - utils_hooks__hooks.invalid = valid__createInvalid; - utils_hooks__hooks.duration = create__createDuration; - utils_hooks__hooks.isMoment = isMoment; - utils_hooks__hooks.weekdays = lists__listWeekdays; - utils_hooks__hooks.parseZone = moment__createInZone; - utils_hooks__hooks.localeData = locale_locales__getLocale; - utils_hooks__hooks.isDuration = isDuration; - utils_hooks__hooks.monthsShort = lists__listMonthsShort; - utils_hooks__hooks.weekdaysMin = lists__listWeekdaysMin; - utils_hooks__hooks.defineLocale = defineLocale; - utils_hooks__hooks.weekdaysShort = lists__listWeekdaysShort; - utils_hooks__hooks.normalizeUnits = normalizeUnits; - utils_hooks__hooks.relativeTimeThreshold = duration_humanize__getSetRelativeTimeThreshold; + // remove the focus on node if it is focussed on by the focusOnNode + this.body.emitter.emit('unlockNode'); - var _moment = utils_hooks__hooks; + var pointer = this.getPointer(event.center); - return _moment; + this.selectionHandler._generateClickEvent('dragging', event, pointer); - })); - /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(60)(module))) + 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; -/***/ }, -/* 55 */ -/***/ function(module, exports, __webpack_require__) { + // 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 __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;'use strict'; + // 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; - (function (factory) { - if (true) { - // AMD. Register as an anonymous module. - !(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); - } else if (typeof exports === 'object') { - // Node. Does not work with strict CommonJS, but - // only CommonJS-like environments that support module.exports, - // like Node. - module.exports = factory(); - } else { - // Browser globals (root is window) - window.propagating = factory(); - } - }(function () { - var _firstTarget = null; // singleton, will contain the target element where the touch event started - var _processing = false; // singleton, true when a touch event is being handled + this.body.view.translation = { x: this.drag.translation.x + diffX, y: this.drag.translation.y + diffY }; + this.body.emitter.emit('_redraw'); + } + } + } + }, { + key: 'onDragEnd', - /** - * Extend an Hammer.js instance with event propagation. - * - * Features: - * - Events emitted by hammer will propagate in order from child to parent - * elements. - * - Events are extended with a function `event.stopPropagation()` to stop - * propagation to parent elements. - * - An option `preventDefault` to stop all default browser behavior. - * - * Usage: - * var hammer = propagatingHammer(new Hammer(element)); - * var hammer = propagatingHammer(new Hammer(element), {preventDefault: true}); - * - * @param {Hammer.Manager} hammer An hammer instance. - * @param {Object} [options] Available options: - * - `preventDefault: true | 'mouse' | 'touch' | 'pen'`. - * Enforce preventing the default browser behavior. - * Cannot be set to `false`. - * @return {Hammer.Manager} Returns the same hammer instance with extended - * functionality - */ - return function propagating(hammer, options) { - if (options && options.preventDefault === false) { - throw new Error('Only supports preventDefault == true'); + /** + * handle drag start event + * @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'); + } + this.selectionHandler._generateClickEvent('dragEnd', event, this.getPointer(event.center)); } - var _options = options || { - preventDefault: false - }; + }, { + key: 'onPinch', - if (hammer.Manager) { - // This looks like the Hammer constructor. - // Overload the constructors with our own. - var Hammer = hammer; + /** + * Handle pinch event + * @param event + * @private + */ + value: function onPinch(event) { + var pointer = this.getPointer(event.center); - var PropagatingHammer = function(element, options) { - return propagating(new Hammer(element, options), _options); - }; - Hammer.extend(PropagatingHammer, Hammer); - PropagatingHammer.Manager = function (element, options) { - return propagating(new Hammer.Manager(element, options), _options); - }; + this.drag.pinched = true; + if (this.pinch['scale'] === undefined) { + this.pinch.scale = 1; + } - return PropagatingHammer; + // TODO: enabled moving while pinching? + var scale = this.pinch.scale * event.scale; + this.zoom(scale, pointer); } + }, { + key: 'zoom', - // attach to DOM element - var element = hammer.element; - element.hammer = hammer; + /** + * 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 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; + } - // move the original functions that we will wrap - hammer._on = hammer.on; - hammer._off = hammer.off; - hammer._emit = hammer.emit; - hammer._destroy = hammer.destroy; + 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; - /** @type {Object.>} */ - hammer._handlers = {}; + var scaleFrac = scale / scaleOld; + var tx = (1 - scaleFrac) * pointer.x + translation.x * scaleFrac; + var ty = (1 - scaleFrac) * pointer.y + translation.y * scaleFrac; - // register an event to catch the start of a gesture and store the - // target in a singleton - hammer._on('hammer.input', function (event) { - if (_options.preventDefault === true || (_options.preventDefault === event.pointerType)) { - event.preventDefault(); - } - if (event.isFirst) { - _firstTarget = event.target; - _processing = true; - } - if (event.isFinal) { - _processing = false; + 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: '+' }); + } else { + this.body.emitter.emit('zoom', { direction: '-' }); + } } - }); + } + }, { + key: 'onMouseWheel', /** - * Register a handler for one or multiple events - * @param {String} events A space separated string with events - * @param {function} handler A callback function, called as handler(event) - * @returns {Hammer.Manager} Returns the hammer instance + * 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 */ - hammer.on = function (events, handler) { - // register the handler - split(events).forEach(function (event) { - var _handlers = hammer._handlers[event]; - if (!_handlers) { - hammer._handlers[event] = _handlers = []; + 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; + } - // register the static, propagated handler - hammer._on(event, propagatedHandler); + // 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); } - _handlers.push(handler); - }); + scale *= 1 + zoom; - return hammer; - }; + // calculate the pointer location + var pointer = this.getPointer({ x: event.pageX, y: event.pageY }); + + // apply the new scale + this.zoom(scale, pointer); + } + + // Prevent default actions caused by mouse wheel. + event.preventDefault(); + } + }, { + key: 'onMouseMove', /** - * Unregister a handler for one or multiple events - * @param {String} events A space separated string with events - * @param {function} [handler] Optional. The registered handler. If not - * provided, all handlers for given events - * are removed. - * @returns {Hammer.Manager} Returns the hammer instance + * Mouse move handler for checking whether the title moves over a node with a title. + * @param {Event} event + * @private */ - hammer.off = function (events, handler) { - // unregister the handler - split(events).forEach(function (event) { - var _handlers = hammer._handlers[event]; - if (_handlers) { - _handlers = handler ? _handlers.filter(function (h) { - return h !== handler; - }) : []; + value: function onMouseMove(event) { + var _this3 = this; - if (_handlers.length > 0) { - hammer._handlers[event] = _handlers; - } - else { - // remove static, propagated handler - hammer._off(event, propagatedHandler); - delete hammer._handlers[event]; - } - } - }); + var pointer = this.getPointer({ x: event.pageX, y: event.pageY }); + var popupVisible = false; - return hammer; - }; + // check if the previously selected node is still selected + if (this.popup !== undefined) { + if (this.popup.hidden === false) { + this._checkHidePopup(pointer); + } - /** - * Emit to the event listeners - * @param {string} eventType - * @param {Event} event - */ - hammer.emit = function(eventType, event) { - if (!_processing) { - _firstTarget = event.target; + // 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(); + } } - hammer._emit(eventType, event); - }; - - hammer.destroy = function () { - // Detach from DOM element - var element = hammer.element; - delete element.hammer; - // clear all handlers - hammer._handlers = {}; + // 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(); + } - // call original hammer destroy - hammer._destroy(); - }; + // 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); + } + } - // split a string with space separated words - function split(events) { - return events.match(/[^ ]+/g); + /** + * 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: '_checkShowPopup', /** - * A static event handler, applying event propagation. - * @param {Object} event + * 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 */ - function propagatedHandler(event) { - // let only a single hammer instance handle this event - if (event.type !== 'hammer.input') { - // it is possible that the same srcEvent is used with multiple hammer events, - // we keep track on which events are handled in an object _handled - if (!event.srcEvent._handled) { - event.srcEvent._handled = {}; + 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 (event.srcEvent._handled[event.type]) { - return; + 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; } - else { - event.srcEvent._handled[event.type] = 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'; } } - // attach a stopPropagation function to the event - var stopped = false; - event.stopPropagation = function () { - stopped = true; - }; + 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); + } - // attach firstTarget property to the event - event.firstTarget = _firstTarget; + this.popup.popupTargetType = popupType; + this.popup.popupTargetId = this.popupObj.id; - // propagate over all elements (until stopped) - var elem = _firstTarget; - while (elem && !stopped) { - var _handlers = elem.hammer && elem.hammer._handlers[event.type]; - if (_handlers) { - for (var i = 0; i < _handlers.length && !stopped; i++) { - _handlers[i](event); + // 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'); + } + } + } + }, { + key: '_checkHidePopup', + + /** + * 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 _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); } } + } - elem = elem.parentNode; + if (stillOnObj === false) { + this.popupObj = undefined; + this.popup.hide(); + this.body.emitter.emit('hidePopup'); } } + }]); - return hammer; - }; - })); + return InteractionHandler; + })(); + exports['default'] = InteractionHandler; + module.exports = exports['default']; /***/ }, -/* 56 */ +/* 63 */ /***/ function(module, exports, __webpack_require__) { - var __WEBPACK_AMD_DEFINE_RESULT__;/*! Hammer.JS - v2.0.4 - 2014-09-28 - * http://hammerjs.github.io/ - * - * Copyright (c) 2014 Jorik Tangelder; - * Licensed under the MIT license */ - (function(window, document, exportName, undefined) { - 'use strict'; + "use strict"; - var VENDOR_PREFIXES = ['', 'webkit', 'moz', 'MS', 'ms', 'o']; - var TEST_ELEMENT = document.createElement('div'); + Object.defineProperty(exports, "__esModule", { + value: true + }); - var TYPE_FUNCTION = 'function'; + var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); - var round = Math.round; - var abs = Math.abs; - var now = Date.now; + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - /** - * set a timeout with a given scope - * @param {Function} fn - * @param {Number} timeout - * @param {Object} context - * @returns {number} - */ - function setTimeoutContext(fn, timeout, context) { - return setTimeout(bindFn(fn, context), timeout); - } + var Node = __webpack_require__(74); + var Edge = __webpack_require__(76); + var util = __webpack_require__(1); - /** - * if the argument is an array, we want to execute the fn on each entry - * if it aint an array we don't want to do a thing. - * this is used by all the methods that accept a single and array argument. - * @param {*|Array} arg - * @param {String} fn - * @param {Object} [context] - * @returns {Boolean} - */ - function invokeArrayArg(arg, fn, context) { - if (Array.isArray(arg)) { - each(arg, context[fn], context); - return true; - } - return false; - } + var SelectionHandler = (function () { + function SelectionHandler(body, canvas) { + var _this = this; - /** - * walk objects and arrays - * @param {Object} obj - * @param {Function} iterator - * @param {Object} context - */ - function each(obj, iterator, context) { - var i; + _classCallCheck(this, SelectionHandler); - if (!obj) { - return; - } + this.body = body; + this.canvas = canvas; + this.selectionObj = { nodes: [], edges: [] }; + this.hoverObj = { nodes: {}, edges: {} }; - if (obj.forEach) { - obj.forEach(iterator, context); - } else if (obj.length !== undefined) { - i = 0; - while (i < obj.length) { - iterator.call(context, obj[i], i, obj); - i++; - } - } else { - for (i in obj) { - obj.hasOwnProperty(i) && iterator.call(context, obj[i], i, obj); - } + this.options = {}; + this.defaultOptions = { + multiselect: false, + selectable: true, + selectConnectedEdges: 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", "selectable", "selectConnectedEdges"]; + util.selectiveDeepExtend(fields, this.options, options); + } } - } + }, { + key: "selectOnPoint", - /** - * extend object. - * means that properties in dest will be overwritten by the ones in src. - * @param {Object} dest - * @param {Object} src - * @param {Boolean} [merge] - * @returns {Object} dest - */ - function extend(dest, src, merge) { - var keys = Object.keys(src); - var i = 0; - while (i < keys.length) { - if (!merge || (merge && dest[keys[i]] === undefined)) { - dest[keys[i]] = src[keys[i]]; + /** + * handles the selection part of the tap; + * + * @param {Object} pointer + * @private + */ + value: function selectOnPoint(pointer) { + var selected = false; + if (this.options.selectable === true) { + this.unselectAll(); + var obj = this.getNodeAt(pointer) || this.getEdgeAt(pointer); + if (obj !== undefined) { + selected = this.selectObject(obj); } - i++; + this.body.emitter.emit("_requestRedraw"); + } + return selected; } - return dest; - } - - /** - * merge the values from src in the dest. - * means that properties that exist in dest will not be overwritten by src - * @param {Object} dest - * @param {Object} src - * @returns {Object} dest - */ - function merge(dest, src) { - return extend(dest, src, true); - } - - /** - * simple class inheritance - * @param {Function} child - * @param {Function} base - * @param {Object} [properties] - */ - function inherit(child, base, properties) { - var baseP = base.prototype, - childP; + }, { + key: "selectAdditionalOnPoint", + value: function selectAdditionalOnPoint(pointer) { + var selectionChanged = false; + if (this.options.selectable === true) { + var obj = this.getNodeAt(pointer) || this.getEdgeAt(pointer); - childP = child.prototype = Object.create(baseP); - childP.constructor = child; - childP._super = baseP; + if (obj !== undefined) { + selectionChanged = true; + if (obj.isSelected() === true) { + this.deselectObject(obj); + } else { + this.selectObject(obj); + } - if (properties) { - extend(childP, properties); + this.body.emitter.emit("_requestRedraw"); + } + } + return selectionChanged; } - } - - /** - * simple function bind - * @param {Function} fn - * @param {Object} context - * @returns {Function} - */ - function bindFn(fn, context) { - return function boundFn() { - return fn.apply(context, arguments); - }; - } + }, { + 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; - /** - * let a boolean value also be a function that must return a boolean - * this first item in args will be used as the context - * @param {Boolean|Function} val - * @param {Array} [args] - * @returns {Boolean} - */ - function boolOrFn(val, args) { - if (typeof val == TYPE_FUNCTION) { - return val.apply(args ? args[0] || undefined : undefined, args); + if (oldSelection !== undefined) { + properties["previousSelection"] = oldSelection; + } + this.body.emitter.emit(eventType, properties); } - return val; - } - - /** - * use the val2 when val1 is undefined - * @param {*} val1 - * @param {*} val2 - * @returns {*} - */ - function ifUndefined(val1, val2) { - return (val1 === undefined) ? val2 : val1; - } - - /** - * addEventListener with multiple events at once - * @param {EventTarget} target - * @param {String} types - * @param {Function} handler - */ - function addEventListeners(target, types, handler) { - each(splitStr(types), function(type) { - target.addEventListener(type, handler, false); - }); - } - - /** - * removeEventListener with multiple events at once - * @param {EventTarget} target - * @param {String} types - * @param {Function} handler - */ - function removeEventListeners(target, types, handler) { - each(splitStr(types), function(type) { - target.removeEventListener(type, handler, false); - }); - } + }, { + key: "selectObject", + value: function selectObject(obj) { + var highlightEdges = arguments[1] === undefined ? this.options.selectConnectedEdges : arguments[1]; - /** - * find if a node is in the given parent - * @method hasParent - * @param {HTMLElement} node - * @param {HTMLElement} parent - * @return {Boolean} found - */ - function hasParent(node, parent) { - while (node) { - if (node == parent) { - return true; + if (obj !== undefined) { + if (obj instanceof Node) { + if (highlightEdges === true) { + this._selectConnectedEdges(obj); + } } - node = node.parentNode; + obj.select(); + this._addToSelection(obj); + return true; + } + return false; } - return false; - } - - /** - * small indexOf wrapper - * @param {String} str - * @param {String} find - * @returns {Boolean} found - */ - function inStr(str, find) { - return str.indexOf(find) > -1; - } - - /** - * split string on whitespace - * @param {String} str - * @returns {Array} words - */ - function splitStr(str) { - return str.trim().split(/\s+/g); - } + }, { + key: "deselectObject", + value: function deselectObject(obj) { + if (obj.isSelected() === true) { + obj.selected = false; + this._removeFromSelection(obj); + } + } + }, { + key: "_getAllNodesOverlappingWith", - /** - * find if a array contains the object using indexOf or a simple polyFill - * @param {Array} src - * @param {String} find - * @param {String} [findByKey] - * @return {Boolean|Number} false when not found, or the index - */ - function inArray(src, find, findByKey) { - if (src.indexOf && !findByKey) { - return src.indexOf(find); - } else { - var i = 0; - while (i < src.length) { - if ((findByKey && src[i][findByKey] == find) || (!findByKey && src[i] === find)) { - return i; - } - i++; + /** + * 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 -1; + } + return overlappingNodes; } - } + }, { + key: "_pointerToPositionObject", - /** - * convert array-like objects to real arrays - * @param {Object} obj - * @returns {Array} - */ - function toArray(obj) { - return Array.prototype.slice.call(obj, 0); - } + /** + * Return a position object in canvasspace from a single point in screenspace + * + * @param pointer + * @returns {{left: number, top: number, right: number, bottom: number}} + * @private + */ + 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: "getNodeAt", - /** - * unique array with objects based on a key (like 'id') or just by the array's value - * @param {Array} src [{id:1},{id:2},{id:1}] - * @param {String} [key] - * @param {Boolean} [sort=False] - * @returns {Array} [{id:1},{id:2}] - */ - function uniqueArray(src, key, sort) { - var results = []; - var values = []; - var i = 0; + /** + * Get the top node at the a specific point (like a click) + * + * @param {{x: Number, y: Number}} pointer + * @return {Node | undefined} node + * @private + */ + value: function getNodeAt(pointer) { + var returnNode = arguments[1] === undefined ? true : arguments[1]; - while (i < src.length) { - var val = key ? src[i][key] : src[i]; - if (inArray(values, val) < 0) { - results.push(src[i]); + // 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]; } - values[i] = val; - i++; + } else { + return undefined; + } } + }, { + key: "_getEdgesOverlappingWith", - if (sort) { - if (!key) { - results = results.sort(); - } else { - results = results.sort(function sortUniqueArray(a, b) { - return a[key] > b[key]; - }); + /** + * 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 _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: "_getAllEdgesOverlappingWith", - return results; - } + /** + * 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 _getAllEdgesOverlappingWith(object) { + var overlappingEdges = []; + this._getEdgesOverlappingWith(object, overlappingEdges); + return overlappingEdges; + } + }, { + key: "getEdgeAt", - /** - * get the prefixed property - * @param {Object} obj - * @param {String} property - * @returns {String|Undefined} prefixed - */ - function prefixed(obj, property) { - var prefix, prop; - var camelProp = property[0].toUpperCase() + property.slice(1); + /** + * 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} + * @private + */ + value: function getEdgeAt(pointer) { + var returnEdge = arguments[1] === undefined ? true : arguments[1]; - var i = 0; - while (i < VENDOR_PREFIXES.length) { - prefix = VENDOR_PREFIXES[i]; - prop = (prefix) ? prefix + camelProp : property; + var positionObject = this._pointerToPositionObject(pointer); + var overlappingEdges = this._getAllEdgesOverlappingWith(positionObject); - if (prop in obj) { - return prop; + if (overlappingEdges.length > 0) { + if (returnEdge === true) { + return this.body.edges[overlappingEdges[overlappingEdges.length - 1]]; + } else { + return overlappingEdges[overlappingEdges.length - 1]; } - i++; + } else { + return undefined; + } } - return undefined; - } - - /** - * get a unique id - * @returns {number} uniqueId - */ - var _uniqueId = 1; - function uniqueId() { - return _uniqueId++; - } - - /** - * get the window object of an element - * @param {HTMLElement} element - * @returns {DocumentView|Window} - */ - function getWindowForElement(element) { - var doc = element.ownerDocument; - return (doc.defaultView || doc.parentWindow); - } + }, { + key: "_addToSelection", - var MOBILE_REGEX = /mobile|tablet|ip(ad|hone|od)|android/i; + /** + * 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", - var SUPPORT_TOUCH = ('ontouchstart' in window); - var SUPPORT_POINTER_EVENTS = prefixed(window, 'PointerEvent') !== undefined; - var SUPPORT_ONLY_TOUCH = SUPPORT_TOUCH && MOBILE_REGEX.test(navigator.userAgent); + /** + * Add object to the selection array. + * + * @param obj + * @private + */ + value: function _addToHover(obj) { + if (obj instanceof Node) { + this.hoverObj.nodes[obj.id] = obj; + } else { + this.hoverObj.edges[obj.id] = obj; + } + } + }, { + key: "_removeFromSelection", - var INPUT_TYPE_TOUCH = 'touch'; - var INPUT_TYPE_PEN = 'pen'; - var INPUT_TYPE_MOUSE = 'mouse'; - var INPUT_TYPE_KINECT = 'kinect'; - - var COMPUTE_INTERVAL = 25; - - var INPUT_START = 1; - var INPUT_MOVE = 2; - var INPUT_END = 4; - var INPUT_CANCEL = 8; - - var DIRECTION_NONE = 1; - var DIRECTION_LEFT = 2; - var DIRECTION_RIGHT = 4; - var DIRECTION_UP = 8; - var DIRECTION_DOWN = 16; - - var DIRECTION_HORIZONTAL = DIRECTION_LEFT | DIRECTION_RIGHT; - var DIRECTION_VERTICAL = DIRECTION_UP | DIRECTION_DOWN; - var DIRECTION_ALL = DIRECTION_HORIZONTAL | DIRECTION_VERTICAL; - - var PROPS_XY = ['x', 'y']; - var PROPS_CLIENT_XY = ['clientX', 'clientY']; - - /** - * create new input type manager - * @param {Manager} manager - * @param {Function} callback - * @returns {Input} - * @constructor - */ - function Input(manager, callback) { - var self = this; - this.manager = manager; - this.callback = callback; - this.element = manager.element; - this.target = manager.options.inputTarget; + /** + * Remove a single option from selection. + * + * @param {Object} obj + * @private + */ + value: function _removeFromSelection(obj) { + if (obj instanceof Node) { + delete this.selectionObj.nodes[obj.id]; + } else { + delete this.selectionObj.edges[obj.id]; + } + } + }, { + key: "unselectAll", - // smaller wrapper around the handler, for the scope and the enabled state of the manager, - // so when disabled the input events are completely bypassed. - this.domHandler = function(ev) { - if (boolOrFn(manager.options.enable, [manager])) { - self.handler(ev); + /** + * 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(); } - }; + } + for (var edgeId in this.selectionObj.edges) { + if (this.selectionObj.edges.hasOwnProperty(edgeId)) { + this.selectionObj.edges[edgeId].unselect(); + } + } - this.init(); + this.selectionObj = { nodes: {}, edges: {} }; + } + }, { + key: "_getSelectedNodeCount", - } + /** + * return the number of selected nodes + * + * @returns {number} + * @private + */ + value: function _getSelectedNodeCount() { + var count = 0; + for (var nodeId in this.selectionObj.nodes) { + if (this.selectionObj.nodes.hasOwnProperty(nodeId)) { + count += 1; + } + } + return count; + } + }, { + key: "_getSelectedNode", - Input.prototype = { /** - * should handle the inputEvent data and trigger the callback - * @virtual + * return the selected node + * + * @returns {number} + * @private */ - handler: function() { }, + value: function _getSelectedNode() { + for (var nodeId in this.selectionObj.nodes) { + if (this.selectionObj.nodes.hasOwnProperty(nodeId)) { + return this.selectionObj.nodes[nodeId]; + } + } + return undefined; + } + }, { + key: "_getSelectedEdge", /** - * bind the events + * return the selected edge + * + * @returns {number} + * @private */ - init: function() { - this.evEl && addEventListeners(this.element, this.evEl, this.domHandler); - this.evTarget && addEventListeners(this.target, this.evTarget, this.domHandler); - this.evWin && addEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler); - }, + value: function _getSelectedEdge() { + for (var edgeId in this.selectionObj.edges) { + if (this.selectionObj.edges.hasOwnProperty(edgeId)) { + return this.selectionObj.edges[edgeId]; + } + } + return undefined; + } + }, { + key: "_getSelectedEdgeCount", /** - * unbind the events + * return the number of selected edges + * + * @returns {number} + * @private */ - destroy: function() { - this.evEl && removeEventListeners(this.element, this.evEl, this.domHandler); - this.evTarget && removeEventListeners(this.target, this.evTarget, this.domHandler); - this.evWin && removeEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler); + 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", - /** - * create new input type manager - * called by the Manager constructor - * @param {Hammer} manager - * @returns {Input} - */ - function createInputInstance(manager) { - var Type; - var inputClass = manager.options.inputClass; + /** + * 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; + } + } + for (var edgeId in this.selectionObj.edges) { + if (this.selectionObj.edges.hasOwnProperty(edgeId)) { + count += 1; + } + } + return count; + } + }, { + key: "_selectionIsEmpty", - if (inputClass) { - Type = inputClass; - } else if (SUPPORT_POINTER_EVENTS) { - Type = PointerEventInput; - } else if (SUPPORT_ONLY_TOUCH) { - Type = TouchInput; - } else if (!SUPPORT_TOUCH) { - Type = MouseInput; - } else { - Type = TouchMouseInput; + /** + * Check if anything is selected + * + * @returns {boolean} + * @private + */ + value: function _selectionIsEmpty() { + for (var nodeId in this.selectionObj.nodes) { + if (this.selectionObj.nodes.hasOwnProperty(nodeId)) { + return false; + } + } + for (var edgeId in this.selectionObj.edges) { + if (this.selectionObj.edges.hasOwnProperty(edgeId)) { + return false; + } + } + return true; } - return new (Type)(manager, inputHandler); - } + }, { + key: "_clusterInSelection", - /** - * handle input events - * @param {Manager} manager - * @param {String} eventType - * @param {Object} input - */ - function inputHandler(manager, eventType, input) { - var pointersLen = input.pointers.length; - var changedPointersLen = input.changedPointers.length; - var isFirst = (eventType & INPUT_START && (pointersLen - changedPointersLen === 0)); - var isFinal = (eventType & (INPUT_END | INPUT_CANCEL) && (pointersLen - changedPointersLen === 0)); + /** + * check if one of the selected nodes is a cluster. + * + * @returns {boolean} + * @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; + } + } + } + return false; + } + }, { + key: "_selectConnectedEdges", - input.isFirst = !!isFirst; - input.isFinal = !!isFinal; + /** + * select the edges connected to the node that is being selected + * + * @param {Node} node + * @private + */ + value: function _selectConnectedEdges(node) { + for (var i = 0; i < node.edges.length; i++) { + var edge = node.edges[i]; + edge.select(); + this._addToSelection(edge); + } + } + }, { + key: "_hoverConnectedEdges", - if (isFirst) { - manager.session = {}; + /** + * 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); + } } + }, { + key: "_unselectConnectedEdges", - // source event is the normalized value of the domEvents - // like 'touchstart, mouseup, pointerdown' - input.eventType = eventType; + /** + * 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", - // compute scale, rotation etc - computeInputData(manager, input); + /** + * 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: "hoverObject", - // emit secret event - manager.emit('hammer.input', input); + /** + * 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 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]; + } + } + } - manager.recognize(input); - manager.session.prevInput = input; - } + //// 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]; + } + } - /** - * extend the data with some usable properties like scale, rotate, velocity etc - * @param {Object} manager - * @param {Object} input - */ - function computeInputData(manager, input) { - var session = manager.session; - var pointers = input.pointers; - var pointersLength = pointers.length; + 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.selectConnectedEdges === true) { + this._hoverConnectedEdges(object); + } + } - // store the first input to calculate the distance and direction - if (!session.firstInput) { - session.firstInput = simpleCloneInputData(input); + if (hoverChanged === true) { + this.body.emitter.emit("_requestRedraw"); + } } + }, { + key: "getSelection", - // to compute scale and rotation we need to store the multiple touches - if (pointersLength > 1 && !session.firstMultiple) { - session.firstMultiple = simpleCloneInputData(input); - } else if (pointersLength === 1) { - session.firstMultiple = false; + /** + * + * 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: "getSelectedNodes", - var firstInput = session.firstInput; - var firstMultiple = session.firstMultiple; - var offsetCenter = firstMultiple ? firstMultiple.center : firstInput.center; - - var center = input.center = getCenter(pointers); - input.timeStamp = now(); - input.deltaTime = input.timeStamp - firstInput.timeStamp; - - input.angle = getAngle(offsetCenter, center); - input.distance = getDistance(offsetCenter, center); + /** + * + * retrieve the currently selected nodes + * @return {String[]} selection An array with the ids of the + * selected nodes. + */ + 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); + } + } + } + return idArray; + } + }, { + key: "getSelectedEdges", - computeDeltaXY(session, input); - input.offsetDirection = getDirection(input.deltaX, input.deltaY); + /** + * + * retrieve the currently selected edges + * @return {Array} selection An array with the ids of the + * selected nodes. + */ + 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: "selectNodes", - input.scale = firstMultiple ? getScale(firstMultiple.pointers, pointers) : 1; - input.rotation = firstMultiple ? getRotation(firstMultiple.pointers, pointers) : 0; + /** + * 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]; - computeIntervalInputData(session, input); + var i = undefined, + id = undefined; - // find the correct target - var target = manager.element; - if (hasParent(input.srcEvent.target, target)) { - target = input.srcEvent.target; - } - input.target = target; - } + if (!selection || selection.length === undefined) throw "Selection must be an array with ids"; - function computeDeltaXY(session, input) { - var center = input.center; - var offset = session.offsetDelta || {}; - var prevDelta = session.prevDelta || {}; - var prevInput = session.prevInput || {}; + // first unselect any selected node + this.unselectAll(); - if (input.eventType === INPUT_START || prevInput.eventType === INPUT_END) { - prevDelta = session.prevDelta = { - x: prevInput.deltaX || 0, - y: prevInput.deltaY || 0 - }; + for (i = 0; i < selection.length; i++) { + id = selection[i]; - offset = session.offsetDelta = { - x: center.x, - y: center.y - }; + 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: "selectEdges", - input.deltaX = prevDelta.x + (center.x - offset.x); - input.deltaY = prevDelta.y + (center.y - offset.y); - } + /** + * select zero or more edges + * @param {Number[] | String[]} selection An array with the ids of the + * selected nodes. + */ + value: function selectEdges(selection) { + var i = undefined, + id = undefined; - /** - * velocity is calculated every x ms - * @param {Object} session - * @param {Object} input - */ - function computeIntervalInputData(session, input) { - var last = session.lastInterval || input, - deltaTime = input.timeStamp - last.timeStamp, - velocity, velocityX, velocityY, direction; + if (!selection || selection.length === undefined) throw "Selection must be an array with ids"; - if (input.eventType != INPUT_CANCEL && (deltaTime > COMPUTE_INTERVAL || last.velocity === undefined)) { - var deltaX = last.deltaX - input.deltaX; - var deltaY = last.deltaY - input.deltaY; + // first unselect any selected objects + this.unselectAll(); - var v = getVelocity(deltaTime, deltaX, deltaY); - velocityX = v.x; - velocityY = v.y; - velocity = (abs(v.x) > abs(v.y)) ? v.x : v.y; - direction = getDirection(deltaX, deltaY); + for (i = 0; i < selection.length; i++) { + id = selection[i]; - session.lastInterval = input; - } else { - // use latest velocity info if it doesn't overtake a minimum period - velocity = last.velocity; - velocityX = last.velocityX; - velocityY = last.velocityY; - direction = last.direction; + 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", - input.velocity = velocity; - input.velocityX = velocityX; - input.velocityY = velocityY; - input.direction = direction; - } - - /** - * create a simple clone from the input used for storage of firstInput and firstMultiple - * @param {Object} input - * @returns {Object} clonedInputData - */ - function simpleCloneInputData(input) { - // make a simple copy of the pointers because we will get a reference if we don't - // we only need clientXY for the calculations - var pointers = []; - var i = 0; - while (i < input.pointers.length) { - pointers[i] = { - clientX: round(input.pointers[i].clientX), - clientY: round(input.pointers[i].clientY) - }; - i++; + /** + * 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]; + } + } + } } + }]); - return { - timeStamp: now(), - pointers: pointers, - center: getCenter(pointers), - deltaX: input.deltaX, - deltaY: input.deltaY - }; - } - - /** - * get the center of all the pointers - * @param {Array} pointers - * @return {Object} center contains `x` and `y` properties - */ - function getCenter(pointers) { - var pointersLength = pointers.length; + return SelectionHandler; + })(); - // no need to loop when only one touch - if (pointersLength === 1) { - return { - x: round(pointers[0].clientX), - y: round(pointers[0].clientY) - }; - } + exports["default"] = SelectionHandler; + module.exports = exports["default"]; - var x = 0, y = 0, i = 0; - while (i < pointersLength) { - x += pointers[i].clientX; - y += pointers[i].clientY; - i++; - } +/***/ }, +/* 64 */ +/***/ function(module, exports, __webpack_require__) { - return { - x: round(x / pointersLength), - y: round(y / pointersLength) - }; - } + 'use strict'; - /** - * calculate the velocity between two points. unit is in px per ms. - * @param {Number} deltaTime - * @param {Number} x - * @param {Number} y - * @return {Object} velocity `x` and `y` - */ - function getVelocity(deltaTime, x, y) { - return { - x: x / deltaTime || 0, - y: y / deltaTime || 0 - }; - } + Object.defineProperty(exports, '__esModule', { + value: true + }); - /** - * get the direction between two points - * @param {Number} x - * @param {Number} y - * @return {Number} direction - */ - function getDirection(x, y) { - if (x === y) { - return DIRECTION_NONE; - } + var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); - if (abs(x) >= abs(y)) { - return x > 0 ? DIRECTION_LEFT : DIRECTION_RIGHT; - } - return y > 0 ? DIRECTION_UP : DIRECTION_DOWN; - } + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - /** - * calculate the absolute distance between two points - * @param {Object} p1 {x, y} - * @param {Object} p2 {x, y} - * @param {Array} [props] containing x and y keys - * @return {Number} distance - */ - function getDistance(p1, p2, props) { - if (!props) { - props = PROPS_XY; - } - var x = p2[props[0]] - p1[props[0]], - y = p2[props[1]] - p1[props[1]]; + var util = __webpack_require__(1); - return Math.sqrt((x * x) + (y * y)); - } + var LayoutEngine = (function () { + function LayoutEngine(body) { + _classCallCheck(this, LayoutEngine); - /** - * calculate the angle between two coordinates - * @param {Object} p1 - * @param {Object} p2 - * @param {Array} [props] containing x and y keys - * @return {Number} angle - */ - function getAngle(p1, p2, props) { - if (!props) { - props = PROPS_XY; - } - var x = p2[props[0]] - p1[props[0]], - y = p2[props[1]] - p1[props[1]]; - return Math.atan2(y, x) * 180 / Math.PI; - } + this.body = body; - /** - * calculate the rotation degrees between two pointersets - * @param {Array} start array of pointers - * @param {Array} end array of pointers - * @return {Number} rotation - */ - function getRotation(start, end) { - return getAngle(end[1], end[0], PROPS_CLIENT_XY) - getAngle(start[1], start[0], PROPS_CLIENT_XY); - } + this.initialRandomSeed = Math.round(Math.random() * 1000000); + this.randomSeed = this.initialRandomSeed; + this.options = {}; + this.optionsBackup = {}; - /** - * calculate the scale factor between two pointersets - * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out - * @param {Array} start array of pointers - * @param {Array} end array of pointers - * @return {Number} scale - */ - function getScale(start, end) { - return getDistance(end[0], end[1], PROPS_CLIENT_XY) / getDistance(start[0], start[1], PROPS_CLIENT_XY); - } + 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); - var MOUSE_INPUT_MAP = { - mousedown: INPUT_START, - mousemove: INPUT_MOVE, - mouseup: INPUT_END - }; + this.hierarchicalLevels = {}; - var MOUSE_ELEMENT_EVENTS = 'mousedown'; - var MOUSE_WINDOW_EVENTS = 'mousemove mouseup'; + this.bindEventListeners(); + } - /** - * Mouse events input - * @constructor - * @extends Input - */ - function MouseInput() { - this.evEl = MOUSE_ELEMENT_EVENTS; - this.evWin = MOUSE_WINDOW_EVENTS; + _createClass(LayoutEngine, [{ + key: 'bindEventListeners', + value: function bindEventListeners() { + var _this = this; - this.allow = true; // used by Input.TouchMouse to disable mouse events - this.pressed = false; // mousedown state + this.body.emitter.on('_dataChanged', function () { + _this.setupHierarchicalLayout(); + }); + this.body.emitter.on('_resetHierarchicalLayout', function () { + _this.setupHierarchicalLayout(); + }); + } + }, { + key: 'setOptions', + value: function setOptions(options, allOptions) { + if (options !== undefined) { + var prevHierarchicalState = this.options.hierarchical.enabled; - Input.apply(this, arguments); - } + util.mergeOptions(this.options, options, 'hierarchical'); + if (options.randomSeed !== undefined) { + this.randomSeed = options.randomSeed; + } - inherit(MouseInput, Input, { - /** - * handle mouse events - * @param {Object} ev - */ - handler: function MEhandler(ev) { - var eventType = MOUSE_INPUT_MAP[ev.type]; + 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; + } + } - // on start we want to have the left mouse button down - if (eventType & INPUT_START && ev.button === 0) { - this.pressed = true; + 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); + } } - - if (eventType & INPUT_MOVE && ev.which !== 1) { - eventType = INPUT_END; + } + 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 options.physics === 'object') { + this.optionsBackup.physics = { solver: 'barnesHut' }; + if (options.physics.solver !== undefined) { + this.optionsBackup.physics = { solver: options.physics.solver }; + } + allOptions.physics['solver'] = 'hierarchicalRepulsion'; + } else if (options.physics !== false) { + this.optionsBackup.physics = { solver: 'barnesHut' }; + allOptions.physics['solver'] = 'hierarchicalRepulsion'; } - // mouse must be down, and mouse events are allowed (see the TouchMouse input) - if (!this.pressed || !this.allow) { - return; + // 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'; } - if (eventType & INPUT_END) { - this.pressed = false; + // 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: true, dynamic: true }; + allOptions.edges = { smooth: false }; + } else if (allOptions.edges.smooth === undefined) { + this.optionsBackup.edges = { smooth: true, dynamic: true }; + allOptions.edges.smooth = false; + } else { + if (typeof allOptions.edges.smooth === 'boolean') { + this.optionsBackup.edges = { smooth: allOptions.edges.smooth, dynamic: true }; + allOptions.edges.smooth = { enabled: allOptions.edges.smooth, dynamic: false, type: type }; + } else { + this.optionsBackup.edges = { smooth: allOptions.edges.smooth.enabled === undefined ? true : allOptions.edges.smooth.enabled, dynamic: true }; + allOptions.edges.smooth = { enabled: allOptions.edges.smooth.enabled === undefined ? true : allOptions.edges.smooth.enabled, dynamic: false, type: type }; + } } - this.callback(this.manager, eventType, { - pointers: [ev], - changedPointers: [ev], - pointerType: INPUT_TYPE_MOUSE, - srcEvent: ev - }); + // 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) { + 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(); - var POINTER_INPUT_MAP = { - pointerdown: INPUT_START, - pointermove: INPUT_MOVE, - pointerup: INPUT_END, - pointercancel: INPUT_CANCEL, - pointerout: INPUT_CANCEL - }; + if (node.options.fixed.x === false) { + node.x = radius * Math.cos(angle); + } + if (node.options.fixed.x === false) { + node.y = radius * Math.sin(angle); + } + } + } + } + } + }, { + key: 'getSeed', + value: function getSeed() { + return this.initialRandomSeed; + } + }, { + key: 'setupHierarchicalLayout', - // in IE10 the pointer types is defined as an enum - var IE10_POINTER_TYPE_ENUM = { - 2: INPUT_TYPE_TOUCH, - 3: INPUT_TYPE_PEN, - 4: INPUT_TYPE_MOUSE, - 5: INPUT_TYPE_KINECT // see https://twitter.com/jacobrossi/status/480596438489890816 - }; + /** + * 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 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; - var POINTER_ELEMENT_EVENTS = 'pointerdown'; - var POINTER_WINDOW_EVENTS = 'pointermove pointerup pointercancel'; + 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; + } + } + } - // IE10 has prefixed support, and case-sensitive - if (window.MSPointerEvent) { - POINTER_ELEMENT_EVENTS = 'MSPointerDown'; - POINTER_WINDOW_EVENTS = 'MSPointerMove MSPointerUp MSPointerCancel'; - } + // 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(); - /** - * Pointer events input - * @constructor - * @extends Input - */ - function PointerEventInput() { - this.evEl = POINTER_ELEMENT_EVENTS; - this.evWin = POINTER_WINDOW_EVENTS; + // 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(); + } + } - Input.apply(this, arguments); + // check the distribution of the nodes per level. + var distribution = this._getDistribution(); - this.store = (this.manager.session.pointerEvents = []); - } + // place the nodes on the canvas. + this._placeNodesByHierarchy(distribution); + } + } + } + }, { + key: '_placeNodesByHierarchy', - inherit(PointerEventInput, Input, { /** - * handle mouse events - * @param {Object} ev + * This function places the nodes on the canvas based on the hierarchial distribution. + * + * @param {Object} distribution | obtained by the function this._getDistribution() + * @private */ - handler: function PEhandler(ev) { - var store = this.store; - var removePointer = false; - - var eventTypeNormalized = ev.type.toLowerCase().replace('ms', ''); - var eventType = POINTER_INPUT_MAP[eventTypeNormalized]; - var pointerType = IE10_POINTER_TYPE_ENUM[ev.pointerType] || ev.pointerType; + 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 isTouch = (pointerType == INPUT_TYPE_TOUCH); + node = distribution[level].nodes[nodeId]; - // get index of the event in the store - var storeIndex = inArray(store, ev.pointerId, 'pointerId'); + 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; + } - // start and mouse must be down - if (eventType & INPUT_START && (ev.button === 0 || isTouch)) { - if (storeIndex < 0) { - store.push(ev); - storeIndex = store.length - 1; + this.positionedNodes[nodeId] = true; + this._placeBranchNodes(node.edges, node.id, distribution, level); } - } else if (eventType & (INPUT_END | INPUT_CANCEL)) { - removePointer = true; - } - - // it not found, so the pointer hasn't been down (so it's probably a hover) - if (storeIndex < 0) { - return; - } - - // update the event in the store - store[storeIndex] = ev; - - this.callback(this.manager, eventType, { - pointers: store, - changedPointers: [ev], - pointerType: pointerType, - srcEvent: ev - }); - - if (removePointer) { - // remove from the store - store.splice(storeIndex, 1); + } } + } } - }); - - var SINGLE_TOUCH_INPUT_MAP = { - touchstart: INPUT_START, - touchmove: INPUT_MOVE, - touchend: INPUT_END, - touchcancel: INPUT_CANCEL - }; + }, { + key: '_getDistribution', - var SINGLE_TOUCH_TARGET_EVENTS = 'touchstart'; - var SINGLE_TOUCH_WINDOW_EVENTS = 'touchstart touchmove touchend touchcancel'; - - /** - * Touch events input - * @constructor - * @extends Input - */ - function SingleTouchInput() { - this.evTarget = SINGLE_TOUCH_TARGET_EVENTS; - this.evWin = SINGLE_TOUCH_WINDOW_EVENTS; - this.started = false; - - Input.apply(this, arguments); - } - - inherit(SingleTouchInput, Input, { - handler: function TEhandler(ev) { - var type = SINGLE_TOUCH_INPUT_MAP[ev.type]; + /** + * This function get the distribution of levels based on hubsize + * + * @returns {Object} + * @private + */ + value: function _getDistribution() { + var distribution = {}; + var nodeId = undefined, + node = undefined; - // should we handle the touch events? - if (type === INPUT_START) { - this.started = true; + // 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; } + } + return distribution; + } + }, { + key: '_getHubSize', - if (!this.started) { - return; + /** + * Get the hubsize from all remaining unlevelled nodes. + * + * @returns {number} + * @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; + } } + } + return hubSize; + } + }, { + key: '_determineLevelsByHubsize', - var touches = normalizeSingleTouches.call(this, ev, type); + /** + * this function allocates nodes in levels based on the recursive branching from the largest hubs. + * + * @param hubsize + * @private + */ + value: function _determineLevelsByHubsize() { + var nodeId = undefined, + node = undefined; + var hubSize = 1; - // when done, reset the started state - if (type & (INPUT_END | INPUT_CANCEL) && touches[0].length - touches[1].length === 0) { - this.started = false; - } + while (hubSize > 0) { + // determine hubs + hubSize = this._getHubSize(); + if (hubSize === 0) break; - this.callback(this.manager, type, { - pointers: touches[0], - changedPointers: touches[1], - pointerType: INPUT_TYPE_TOUCH, - srcEvent: ev - }); + for (nodeId in this.body.nodes) { + if (this.body.nodes.hasOwnProperty(nodeId)) { + node = this.body.nodes[nodeId]; + if (node.edges.length === hubSize) { + this._setLevel(0, node); + } + } + } + } } - }); + }, { + key: '_setLevel', - /** - * @this {TouchInput} - * @param {Object} ev - * @param {Number} type flag - * @returns {undefined|Array} [all, changed] - */ - function normalizeSingleTouches(ev, type) { - var all = toArray(ev.touches); - var changed = toArray(ev.changedTouches); + /** + * 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 _setLevel(level, node) { + if (this.hierarchicalLevels[node.id] !== undefined) return; - if (type & (INPUT_END | INPUT_CANCEL)) { - all = uniqueArray(all.concat(changed), 'identifier', true); + 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 { + childNode = node.edges[i].to; + } + this._setLevel(level + 1, childNode); + } } + }, { + key: '_determineLevelsDirected', - return [all, changed]; - } - - var TOUCH_INPUT_MAP = { - touchstart: INPUT_START, - touchmove: INPUT_MOVE, - touchend: INPUT_END, - touchcancel: INPUT_CANCEL - }; - - var TOUCH_TARGET_EVENTS = 'touchstart touchmove touchend touchcancel'; - - /** - * Multi-user touch events input - * @constructor - * @extends Input - */ - function TouchInput() { - this.evTarget = TOUCH_TARGET_EVENTS; - this.targetIds = {}; - - Input.apply(this, arguments); - } + /** + * this function allocates nodes in levels based on the direction of the edges + * + * @param hubsize + * @private + */ + value: function _determineLevelsDirected() { + var nodeId = undefined, + node = undefined; + var minLevel = 10000; - inherit(TouchInput, Input, { - handler: function MTEhandler(ev) { - var type = TOUCH_INPUT_MAP[ev.type]; - var touches = getTouches.call(this, ev, type); - if (!touches) { - return; + // 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); } + } - this.callback(this.manager, type, { - pointers: touches[0], - changedPointers: touches[1], - pointerType: INPUT_TYPE_TOUCH, - srcEvent: ev - }); - } - }); - - /** - * @this {TouchInput} - * @param {Object} ev - * @param {Number} type flag - * @returns {undefined|Array} [all, changed] - */ - function getTouches(ev, type) { - var allTouches = toArray(ev.touches); - var targetIds = this.targetIds; + // 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; + } + } - // when there is only one touch, the process can be simplified - if (type & (INPUT_START | INPUT_MOVE) && allTouches.length === 1) { - targetIds[allTouches[0].identifier] = true; - return [allTouches, allTouches]; + // 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; + } + } } + }, { + key: '_setLevelDirected', - var i, - targetTouches, - changedTouches = toArray(ev.changedTouches), - changedTargetTouches = [], - target = this.target; + /** + * 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 _setLevelDirected(level, node) { + if (this.hierarchicalLevels[node.id] !== undefined) return; - // get target touches from touches - targetTouches = allTouches.filter(function(touch) { - return hasParent(touch.target, target); - }); + var childNode = undefined; + this.hierarchicalLevels[node.id] = level; - // collect touches - if (type === INPUT_START) { - i = 0; - while (i < targetTouches.length) { - targetIds[targetTouches[i].identifier] = true; - i++; + 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); } + } } + }, { + key: '_placeBranchNodes', - // filter changed touches to only contain touches that exist in the collected target ids - i = 0; - while (i < changedTouches.length) { - if (targetIds[changedTouches[i].identifier]) { - changedTargetTouches.push(changedTouches[i]); + /** + * 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 _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]; - // cleanup removed touches - if (type & (INPUT_END | INPUT_CANCEL)) { - delete targetIds[changedTouches[i].identifier]; + 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; + + if (childNode.edges.length > 1) { + this._placeBranchNodes(childNode.edges, childNode.id, distribution, childNodeLevel); + } + } } - i++; + } } + }]); - if (!changedTargetTouches.length) { - return; - } + return LayoutEngine; + })(); - return [ - // merge targetTouches with changedTargetTouches so it contains ALL touches, including 'end' and 'cancel' - uniqueArray(targetTouches.concat(changedTargetTouches), 'identifier', true), - changedTargetTouches - ]; - } + exports['default'] = LayoutEngine; + module.exports = exports['default']; + +/***/ }, +/* 65 */ +/***/ 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__(1); + var Hammer = __webpack_require__(41); + var hammerUtil = __webpack_require__(49); /** - * Combined touch and mouse input - * - * Touch has a higher priority then mouse, and while touching no mouse events are allowed. - * This because touch devices also emit mouse events while doing a touch. + * clears the toolbar div element of children * - * @constructor - * @extends Input + * @private */ - function TouchMouseInput() { - Input.apply(this, arguments); - var handler = bindFn(this.handler, this); - this.touch = new TouchInput(this.manager, handler); - this.mouse = new MouseInput(this.manager, handler); - } + var ManipulationSystem = (function () { + function ManipulationSystem(body, canvas, selectionHandler) { + var _this = this; - inherit(TouchMouseInput, Input, { - /** - * handle mouse and touch events - * @param {Hammer} manager - * @param {String} inputEvent - * @param {Object} inputData - */ - handler: function TMEhandler(manager, inputEvent, inputData) { - var isTouch = (inputData.pointerType == INPUT_TYPE_TOUCH), - isMouse = (inputData.pointerType == INPUT_TYPE_MOUSE); + _classCallCheck(this, ManipulationSystem); - // when we're in a touch event, so block all upcoming mouse events - // most mobile browser also emit mouseevents, right after touchstart - if (isTouch) { - this.mouse.allow = false; - } else if (isMouse && !this.mouse.allow) { - return; - } + this.body = body; + this.canvas = canvas; + this.selectionHandler = selectionHandler; - // reset the allowMouse when we're done - if (inputEvent & (INPUT_END | INPUT_CANCEL)) { - this.mouse.allow = true; - } + this.editMode = false; + this.manipulationDiv = undefined; + this.editModeDiv = undefined; + this.closeDiv = undefined; - this.callback(manager, inputEvent, inputData); - }, + this.manipulationHammers = []; + this.temporaryUIFunctions = {}; + this.temporaryEventFunctions = []; - /** - * remove the event listeners - */ - destroy: function destroy() { - this.touch.destroy(); - this.mouse.destroy(); - } - }); + this.touchTime = 0; + this.temporaryIds = { nodes: [], edges: [] }; + this.guiEnabled = false; + this.inMode = false; + this.selectedControlNode = undefined; - var PREFIXED_TOUCH_ACTION = prefixed(TEST_ELEMENT.style, 'touchAction'); - var NATIVE_TOUCH_ACTION = PREFIXED_TOUCH_ACTION !== 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); - // magical touchAction value - var TOUCH_ACTION_COMPUTE = 'compute'; - var TOUCH_ACTION_AUTO = 'auto'; - var TOUCH_ACTION_MANIPULATION = 'manipulation'; // not implemented - var TOUCH_ACTION_NONE = 'none'; - var TOUCH_ACTION_PAN_X = 'pan-x'; - var TOUCH_ACTION_PAN_Y = 'pan-y'; + 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)); + } - /** - * Touch Action - * sets the touchAction property or uses the js alternative - * @param {Manager} manager - * @param {String} value - * @constructor - */ - function TouchAction(manager, value) { - this.manager = manager; - this.set(value); - } + _createClass(ManipulationSystem, [{ + key: '_restore', - TouchAction.prototype = { /** - * set the touchAction value on the element or enable the polyfill - * @param {String} value + * If something changes in the data during editing, switch back to the initial datamanipulation state and close all edit modes. + * @private */ - set: function(value) { - // find out the touch-action by the event handlers - if (value == TOUCH_ACTION_COMPUTE) { - value = this.compute(); + value: function _restore() { + if (this.inMode !== false) { + if (this.options.initiallyActive === true) { + this.enableEditMode(); + } else { + this.disableEditMode(); } + } + } + }, { + key: 'setOptions', - if (NATIVE_TOUCH_ACTION) { - this.manager.element.style[PREFIXED_TOUCH_ACTION] = value; + /** + * Set the Options + * @param options + */ + value: function setOptions(options, allOptions) { + if (allOptions !== undefined) { + if (allOptions.locale !== undefined) { + this.options.locale = allOptions.locale; + }; + if (allOptions.locales !== undefined) { + this.options.locales = allOptions.locales; + }; + } + + if (options !== undefined) { + if (typeof options === 'boolean') { + this.options.enabled = options; + } else { + this.options.enabled = true; + util.deepExtend(this.options, options); } - this.actions = value.toLowerCase().trim(); - }, + if (this.options.initiallyActive === true) { + this.editMode = true; + } + this._setup(); + } + } + }, { + key: 'toggleEditMode', /** - * just re-set the touchAction value + * Enable or disable edit-mode. Draws the DOM required and cleans up after itself. + * + * @private */ - update: function() { - this.set(this.manager.options.touchAction); - }, + value: function toggleEditMode() { + if (this.editMode === true) { + this.disableEditMode(); + } else { + this.enableEditMode(); + } + } + }, { + key: 'enableEditMode', + value: function enableEditMode() { + this.editMode = true; - /** - * compute the value for the touchAction property based on the recognizer's settings - * @returns {String} value - */ - compute: function() { - var actions = []; - each(this.manager.recognizers, function(recognizer) { - if (boolOrFn(recognizer.options.enable, [recognizer])) { - actions = actions.concat(recognizer.getTouchAction()); - } - }); - return cleanTouchActions(actions.join(' ')); - }, + 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; + + this._clean(); + if (this.guiEnabled === true) { + this.manipulationDiv.style.display = 'none'; + this.closeDiv.style.display = 'none'; + this.editModeDiv.style.display = 'block'; + this._createEditButton(); + } + } + }, { + key: 'showManipulatorToolbar', /** - * this method is called on each input cycle and provides the preventing of the browser behavior - * @param {Object} input + * Creates the main toolbar. Removes functions bound to the select event. Binds all the buttons of the toolbar. + * + * @private */ - preventDefaults: function(input) { - // not needed with native support for the touchAction property - if (NATIVE_TOUCH_ACTION) { - return; - } + value: function showManipulatorToolbar() { + // restore the state of any bound functions or events, remove control nodes, restore physics + this._clean(); - var srcEvent = input.srcEvent; - var direction = input.offsetDirection; + // reset global letiables + this.manipulationDOM = {}; - // if the touch action did prevented once this session - if (this.manager.session.prevented) { - srcEvent.preventDefault(); - return; - } + // 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 actions = this.actions; - var hasNone = inStr(actions, TOUCH_ACTION_NONE); - var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y); - var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X); + 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 (hasNone || - (hasPanY && direction & DIRECTION_HORIZONTAL) || - (hasPanX && direction & DIRECTION_VERTICAL)) { - return this.preventSrc(srcEvent); + 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); } - }, - - /** - * call preventDefault to prevent the browser's default behavior (scrolling in most cases) - * @param {Object} srcEvent - */ - preventSrc: function(srcEvent) { - this.manager.session.prevented = true; - srcEvent.preventDefault(); - } - }; - /** - * when the touchActions are collected they are not a valid value, so we need to clean things up. * - * @param {String} actions - * @returns {*} - */ - function cleanTouchActions(actions) { - // none - if (inStr(actions, TOUCH_ACTION_NONE)) { - return TOUCH_ACTION_NONE; - } + 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); + } - var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X); - var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y); + // 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); + } + } - // pan-x and pan-y can be combined - if (hasPanX && hasPanY) { - return TOUCH_ACTION_PAN_X + ' ' + TOUCH_ACTION_PAN_Y; - } + // bind the close button + this._bindHammerToDiv(this.closeDiv, this.toggleEditMode.bind(this)); - // pan-x OR pan-y - if (hasPanX || hasPanY) { - return hasPanX ? TOUCH_ACTION_PAN_X : TOUCH_ACTION_PAN_Y; - } + // refresh this bar based on what has been selected + this._temporaryBindEvent('select', this.showManipulatorToolbar.bind(this)); + } - // manipulation - if (inStr(actions, TOUCH_ACTION_MANIPULATION)) { - return TOUCH_ACTION_MANIPULATION; + // redraw to show any possible changes + this.body.emitter.emit('_redraw'); } + }, { + key: 'addNodeMode', - return TOUCH_ACTION_AUTO; - } - - /** - * Recognizer flow explained; * - * All recognizers have the initial state of POSSIBLE when a input session starts. - * The definition of a input session is from the first input until the last input, with all it's movement in it. * - * Example session for mouse-input: mousedown -> mousemove -> mouseup - * - * On each recognizing cycle (see Manager.recognize) the .recognize() method is executed - * which determines with state it should be. - * - * If the recognizer has the state FAILED, CANCELLED or RECOGNIZED (equals ENDED), it is reset to - * POSSIBLE to give it another change on the next cycle. - * - * Possible - * | - * +-----+---------------+ - * | | - * +-----+-----+ | - * | | | - * Failed Cancelled | - * +-------+------+ - * | | - * Recognized Began - * | - * Changed - * | - * Ended/Recognized - */ - var STATE_POSSIBLE = 1; - var STATE_BEGAN = 2; - var STATE_CHANGED = 4; - var STATE_ENDED = 8; - var STATE_RECOGNIZED = STATE_ENDED; - var STATE_CANCELLED = 16; - var STATE_FAILED = 32; - - /** - * Recognizer - * Every recognizer needs to extend from this class. - * @constructor - * @param {Object} options - */ - function Recognizer(options) { - this.id = uniqueId(); + /** + * 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(); + } - this.manager = null; - this.options = merge(options || {}, this.defaults); + // restore the state of any bound functions or events, remove control nodes, restore physics + this._clean(); - // default is enable true - this.options.enable = ifUndefined(this.options.enable, true); + 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']); - this.state = STATE_POSSIBLE; + // bind the close button + this._bindHammerToDiv(this.closeDiv, this.toggleEditMode.bind(this)); + } - this.simultaneous = {}; - this.requireFail = []; - } + this._temporaryBindEvent('click', this._performAddNode.bind(this)); + } + }, { + key: 'editNodeMode', - Recognizer.prototype = { /** - * @virtual - * @type {Object} + * call the bound function to handle the editing of the node. The node has to be selected. + * + * @private */ - defaults: {}, + value: function editNodeMode() { + var _this2 = this; - /** - * set options - * @param {Object} options - * @return {Recognizer} - */ - set: function(options) { - extend(this.options, options); + // when using the gui, enable edit mode if it wasnt already. + if (this.editMode !== true) { + this.enableEditMode(); + } - // also update the touchAction, in case something changed about the directions/enabled state - this.manager && this.manager.touchAction.update(); - return this; - }, + // restore the state of any bound functions or events, remove control nodes, restore physics + this._clean(); - /** - * recognize simultaneous with an other recognizer. - * @param {Recognizer} otherRecognizer - * @returns {Recognizer} this - */ - recognizeWith: function(otherRecognizer) { - if (invokeArrayArg(otherRecognizer, 'recognizeWith', this)) { - return this; - } + this.inMode = 'editNode'; + if (typeof this.options.editNode === 'function') { + var node = this.selectionHandler._getSelectedNode(); + if (node.isCluster !== true) { + var data = util.deepExtend({}, node.options, true); + data.x = node.x; + data.y = node.y; - var simultaneous = this.simultaneous; - otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this); - if (!simultaneous[otherRecognizer.id]) { - simultaneous[otherRecognizer.id] = otherRecognizer; - otherRecognizer.recognizeWith(this); + if (this.options.editNode.length === 2) { + this.options.editNode(data, function (finalizedData) { + if (finalizedData !== null && finalizedData !== undefined && _this2.inMode === 'delete') { + // if for whatever reason the mode has changes (due to dataset change) disregard the callback) { + _this2.body.data.nodes.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']); } - return this; - }, + } else { + throw new Error('No function has been configured to handle the editing of nodes.'); + } + } + }, { + key: 'addEdgeMode', /** - * drop the simultaneous link. it doesnt remove the link on the other recognizer. - * @param {Recognizer} otherRecognizer - * @returns {Recognizer} this + * create the toolbar to connect nodes + * + * @private */ - dropRecognizeWith: function(otherRecognizer) { - if (invokeArrayArg(otherRecognizer, 'dropRecognizeWith', this)) { - return this; - } + value: function addEdgeMode() { + // when using the gui, enable edit mode if it wasnt already. + if (this.editMode !== true) { + this.enableEditMode(); + } - otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this); - delete this.simultaneous[otherRecognizer.id]; - return this; - }, + // restore the state of any bound functions or events, remove control nodes, restore physics + this._clean(); - /** - * recognizer can only run when an other is failing - * @param {Recognizer} otherRecognizer - * @returns {Recognizer} this - */ - requireFailure: function(otherRecognizer) { - if (invokeArrayArg(otherRecognizer, 'requireFailure', this)) { - return this; - } + 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']); - var requireFail = this.requireFail; - otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this); - if (inArray(requireFail, otherRecognizer) === -1) { - requireFail.push(otherRecognizer); - otherRecognizer.requireFailure(this); - } - return this; - }, + // bind the close button + this._bindHammerToDiv(this.closeDiv, this.toggleEditMode.bind(this)); + } - /** - * drop the requireFailure link. it does not remove the link on the other recognizer. - * @param {Recognizer} otherRecognizer - * @returns {Recognizer} this - */ - dropRequireFailure: function(otherRecognizer) { - if (invokeArrayArg(otherRecognizer, 'dropRequireFailure', this)) { - return this; - } + // 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)); - otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this); - var index = inArray(this.requireFail, otherRecognizer); - if (index > -1) { - this.requireFail.splice(index, 1); - } - return this; - }, + this._temporaryBindUI('onDragStart', function () {}); + this._temporaryBindUI('onHold', function () {}); + } + }, { + key: 'editEdgeMode', /** - * has require failures boolean - * @returns {boolean} + * create the toolbar to edit edges + * + * @private */ - hasRequireFailures: function() { - return this.requireFail.length > 0; - }, + value: function editEdgeMode() { + // when using the gui, enable edit mode if it wasnt already. + if (this.editMode !== true) { + this.enableEditMode(); + } - /** - * if the recognizer can recognize simultaneous with an other recognizer - * @param {Recognizer} otherRecognizer - * @returns {Boolean} - */ - canRecognizeWith: function(otherRecognizer) { - return !!this.simultaneous[otherRecognizer.id]; - }, + // restore the state of any bound functions or events, remove control nodes, restore physics + this._clean(); - /** - * You should use `tryEmit` instead of `emit` directly to check - * that all the needed recognizers has failed before emitting. - * @param {Object} input - */ - emit: function(input) { - var self = this; - var state = this.state; + 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']); - function emit(withState) { - self.manager.emit(self.options.event + (withState ? stateStr(state) : ''), input); - } + // bind the close button + this._bindHammerToDiv(this.closeDiv, this.toggleEditMode.bind(this)); + } - // 'panstart' and 'panmove' - if (state < STATE_ENDED) { - emit(true); - } + this.edgeBeingEditedId = this.selectionHandler.getSelectedEdges()[0]; + var edge = this.body.edges[this.edgeBeingEditedId]; - emit(); // simple 'eventName' events + // create control nodes + var controlNodeFrom = this._getNewTargetNode(edge.from.x, edge.from.y); + var controlNodeTo = this._getNewTargetNode(edge.to.x, edge.to.y); - // panend and pancancel - if (state >= STATE_ENDED) { - emit(true); - } - }, + this.temporaryIds.nodes.push(controlNodeFrom.id); + this.temporaryIds.nodes.push(controlNodeTo.id); - /** - * Check that all the require failure recognizers has failed, - * if true, it emits a gesture event, - * otherwise, setup the state to FAILED. - * @param {Object} input - */ - tryEmit: function(input) { - if (this.canEmit()) { - return this.emit(input); + this.body.nodes[controlNodeFrom.id] = controlNodeFrom; + this.body.nodeIndices.push(controlNodeFrom.id); + this.body.nodes[controlNodeTo.id] = controlNodeTo; + this.body.nodeIndices.push(controlNodeTo.id); + + // temporarily overload UI functions, cleaned up automatically because of _temporaryBindUI + this._temporaryBindUI('onTouch', this._controlNodeTouch.bind(this)); // used to get the position + this._temporaryBindUI('onTap', function () {}); // disabled + this._temporaryBindUI('onHold', function () {}); // disabled + this._temporaryBindUI('onDragStart', this._controlNodeDragStart.bind(this)); // used to select control node + this._temporaryBindUI('onDrag', this._controlNodeDrag.bind(this)); // used to drag control node + this._temporaryBindUI('onDragEnd', this._controlNodeDragEnd.bind(this)); // used to connect or revert control nodes + this._temporaryBindUI('onMouseMove', function () {}); // disabled + + // create function to position control nodes correctly on movement + // automatically cleaned up because we use the temporary bind + this._temporaryBindEvent('beforeDrawing', function (ctx) { + var positions = edge.edgeType.findBorderPositions(ctx); + if (controlNodeFrom.selected === false) { + controlNodeFrom.x = positions.from.x; + controlNodeFrom.y = positions.from.y; } - // it's failing anyway - this.state = STATE_FAILED; - }, + if (controlNodeTo.selected === false) { + controlNodeTo.x = positions.to.x; + controlNodeTo.y = positions.to.y; + } + }); + + this.body.emitter.emit('_redraw'); + } + }, { + key: 'deleteSelected', /** - * can we emit? - * @returns {boolean} + * delete everything in the selection + * + * @private */ - canEmit: function() { - var i = 0; - while (i < this.requireFail.length) { - if (!(this.requireFail[i].state & (STATE_FAILED | STATE_POSSIBLE))) { - return false; - } - i++; - } - return true; - }, + value: function deleteSelected() { + var _this3 = this; - /** - * update the recognizer - * @param {Object} inputData - */ - recognize: function(inputData) { - // make a new copy of the inputData - // so we can change the inputData without messing up the other recognizers - var inputDataClone = extend({}, inputData); + // when using the gui, enable edit mode if it wasnt already. + if (this.editMode !== true) { + this.enableEditMode(); + } - // is is enabled and allow recognizing? - if (!boolOrFn(this.options.enable, [this, inputDataClone])) { - this.reset(); - this.state = STATE_FAILED; + // restore the state of any bound functions or events, remove control nodes, restore physics + this._clean(); + + 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; + } } - // reset when we've reached the end - if (this.state & (STATE_RECOGNIZED | STATE_CANCELLED | STATE_FAILED)) { - this.state = STATE_POSSIBLE; + 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; + } + } - this.state = this.process(inputDataClone); - - // the recognizer has recognized a gesture - // so trigger an event - if (this.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED | STATE_CANCELLED)) { - this.tryEmit(inputDataClone); + if (typeof deleteFunction === 'function') { + var data = { nodes: selectedNodes, edges: selectedEdges }; + if (deleteFunction.length === 2) { + deleteFunction(data, function (finalizedData) { + if (finalizedData !== null && finalizedData !== undefined && _this3.inMode === 'delete') { + // if for whatever reason the mode has changes (due to dataset change) disregard the callback) { + _this3.body.data.edges.remove(finalizedData.edges); + _this3.body.data.nodes.remove(finalizedData.nodes); + _this3.body.emitter.emit('startSimulation'); + } + }); + } else { + throw new Error('The function for delete does not support two arguments (data, callback)'); } - }, + } else { + this.body.data.edges.remove(selectedEdges); + this.body.data.nodes.remove(selectedNodes); + this.body.emitter.emit('startSimulation'); + } + } + }, { + key: '_setup', - /** - * return the state of the recognizer - * the actual recognizing happens in this method - * @virtual - * @param {Object} inputData - * @returns {Const} STATE - */ - process: function(inputData) { }, // jshint ignore:line + //********************************************** PRIVATE ***************************************// /** - * return the preferred touch-action - * @virtual - * @returns {Array} + * draw or remove the DOM + * @private */ - getTouchAction: function() { }, + value: function _setup() { + if (this.options.enabled === true) { + // Enable the GUI + this.guiEnabled = true; - /** - * called when the gesture isn't allowed to recognize - * like when another is being recognized or it is disabled - * @virtual - */ - reset: function() { } - }; + this._createWrappers(); + if (this.editMode === false) { + this._createEditButton(); + } else { + this.showManipulatorToolbar(); + } + } else { + this._removeManipulationDOM(); - /** - * get a usable string, used as event postfix - * @param {Const} state - * @returns {String} state - */ - function stateStr(state) { - if (state & STATE_CANCELLED) { - return 'cancel'; - } else if (state & STATE_ENDED) { - return 'end'; - } else if (state & STATE_CHANGED) { - return 'move'; - } else if (state & STATE_BEGAN) { - return 'start'; + // disable the gui + this.guiEnabled = false; + } } - return ''; - } + }, { + key: '_createWrappers', - /** - * direction cons to string - * @param {Const} direction - * @returns {String} - */ - function directionStr(direction) { - if (direction == DIRECTION_DOWN) { - return 'down'; - } else if (direction == DIRECTION_UP) { - return 'up'; - } else if (direction == DIRECTION_LEFT) { - return 'left'; - } else if (direction == DIRECTION_RIGHT) { - return 'right'; - } - return ''; - } + /** + * 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 a recognizer by name if it is bound to a manager - * @param {Recognizer|String} otherRecognizer - * @param {Recognizer} recognizer - * @returns {Recognizer} - */ - function getRecognizerByNameIfManager(otherRecognizer, recognizer) { - var manager = recognizer.manager; - if (manager) { - return manager.get(otherRecognizer); - } - return otherRecognizer; - } + // 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); + } - /** - * This recognizer is just used as a base for the simple attribute recognizers. - * @constructor - * @extends Recognizer - */ - function AttrRecognizer() { - Recognizer.apply(this, arguments); - } + // 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', - inherit(AttrRecognizer, Recognizer, { /** - * @namespace - * @memberof AttrRecognizer + * generate a new target node. Used for creating new edges and editing edges + * @param x + * @param y + * @returns {*} + * @private */ - defaults: { - /** - * @type {Number} - * @default 1 - */ - pointers: 1 - }, + value: function _getNewTargetNode(x, y) { + var controlNodeStyle = util.deepExtend({}, this.options.controlNodeStyle); - /** - * Used to check if it the recognizer receives valid input, like input.distance > 10. - * @memberof AttrRecognizer - * @param {Object} input - * @returns {Boolean} recognized - */ - attrTest: function(input) { - var optionPointers = this.options.pointers; - return optionPointers === 0 || input.pointers.length === optionPointers; - }, + controlNodeStyle.id = 'targetNode' + util.randomUUID(); + controlNodeStyle.hidden = false; + controlNodeStyle.physics = false; + controlNodeStyle.x = x; + controlNodeStyle.y = y; + + return this.body.functions.createNode(controlNodeStyle); + } + }, { + key: '_createEditButton', /** - * Process the input and return the state for the recognizer - * @memberof AttrRecognizer - * @param {Object} input - * @returns {*} State + * Create the edit button */ - process: function(input) { - var state = this.state; - var eventType = input.eventType; + value: function _createEditButton() { + // restore everything to it's original state (if applicable) + this._clean(); - var isRecognized = state & (STATE_BEGAN | STATE_CHANGED); - var isValid = this.attrTest(input); + // reset the manipulationDOM + this.manipulationDOM = {}; - // on cancel input and we've recognized before, return STATE_CANCELLED - if (isRecognized && (eventType & INPUT_CANCEL || !isValid)) { - return state | STATE_CANCELLED; - } else if (isRecognized || isValid) { - if (eventType & INPUT_END) { - return state | STATE_ENDED; - } else if (!(state & STATE_BEGAN)) { - return STATE_BEGAN; - } - return state | STATE_CHANGED; - } - return STATE_FAILED; - } - }); + // empty the editModeDiv + util.recursiveDOMDelete(this.editModeDiv); - /** - * Pan - * Recognized when the pointer is down and moved in the allowed direction. - * @constructor - * @extends AttrRecognizer - */ - function PanRecognizer() { - AttrRecognizer.apply(this, arguments); + // 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); - this.pX = null; - this.pY = null; - } + // bind a hammer listener to the button, calling the function toggleEditMode. + this._bindHammerToDiv(button, this.toggleEditMode.bind(this)); + } + }, { + key: '_clean', - inherit(PanRecognizer, AttrRecognizer, { /** - * @namespace - * @memberof PanRecognizer + * this function cleans up after everything this module does. Temporary elements, functions and events are removed, physics restored, hammers removed. + * @private */ - defaults: { - event: 'pan', - threshold: 10, - pointers: 1, - direction: DIRECTION_ALL - }, - - getTouchAction: function() { - var direction = this.options.direction; - var actions = []; - if (direction & DIRECTION_HORIZONTAL) { - actions.push(TOUCH_ACTION_PAN_Y); - } - if (direction & DIRECTION_VERTICAL) { - actions.push(TOUCH_ACTION_PAN_X); - } - return actions; - }, + value: function _clean() { + // not in mode + this.inMode = false; - directionTest: function(input) { - var options = this.options; - var hasMoved = true; - var distance = input.distance; - var direction = input.direction; - var x = input.deltaX; - var y = input.deltaY; + // _clean the divs + if (this.guiEnabled === true) { + util.recursiveDOMDelete(this.editModeDiv); + util.recursiveDOMDelete(this.manipulationDiv); - // lock to axis? - if (!(direction & options.direction)) { - if (options.direction & DIRECTION_HORIZONTAL) { - direction = (x === 0) ? DIRECTION_NONE : (x < 0) ? DIRECTION_LEFT : DIRECTION_RIGHT; - hasMoved = x != this.pX; - distance = Math.abs(input.deltaX); - } else { - direction = (y === 0) ? DIRECTION_NONE : (y < 0) ? DIRECTION_UP : DIRECTION_DOWN; - hasMoved = y != this.pY; - distance = Math.abs(input.deltaY); - } - } - input.direction = direction; - return hasMoved && distance > options.threshold && direction & options.direction; - }, + // removes all the bindings and overloads + this._cleanManipulatorHammers(); + } - attrTest: function(input) { - return AttrRecognizer.prototype.attrTest.call(this, input) && - (this.state & STATE_BEGAN || (!(this.state & STATE_BEGAN) && this.directionTest(input))); - }, + // remove temporary nodes and edges + this._cleanupTemporaryNodesAndEdges(); - emit: function(input) { - this.pX = input.deltaX; - this.pY = input.deltaY; + // restore overloaded UI functions + this._unbindTemporaryUIs(); - var direction = directionStr(input.direction); - if (direction) { - this.manager.emit(this.options.event + direction, input); - } + // remove the temporaryEventFunctions + this._unbindTemporaryEvents(); - this._super.emit.call(this, input); + // restore the physics if required + this.body.emitter.emit('restorePhysics'); } - }); - - /** - * Pinch - * Recognized when two or more pointers are moving toward (zoom-in) or away from each other (zoom-out). - * @constructor - * @extends AttrRecognizer - */ - function PinchRecognizer() { - AttrRecognizer.apply(this, arguments); - } + }, { + key: '_cleanManipulatorHammers', - inherit(PinchRecognizer, AttrRecognizer, { /** - * @namespace - * @memberof PinchRecognizer + * Each dom element has it's own hammer. They are stored in this.manipulationHammers. This cleans them up. + * @private */ - defaults: { - event: 'pinch', - threshold: 0, - pointers: 2 - }, - - getTouchAction: function() { - return [TOUCH_ACTION_NONE]; - }, - - attrTest: function(input) { - return this._super.attrTest.call(this, input) && - (Math.abs(input.scale - 1) > this.options.threshold || this.state & STATE_BEGAN); - }, - - emit: function(input) { - this._super.emit.call(this, input); - if (input.scale !== 1) { - var inOut = input.scale < 1 ? 'in' : 'out'; - this.manager.emit(this.options.event + inOut, input); + 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 = []; + } } - }); - - /** - * Press - * Recognized when the pointer is down for x ms without any movement. - * @constructor - * @extends Recognizer - */ - function PressRecognizer() { - Recognizer.apply(this, arguments); - - this._timer = null; - this._input = null; - } + }, { + key: '_removeManipulationDOM', - inherit(PressRecognizer, Recognizer, { /** - * @namespace - * @memberof PressRecognizer + * Remove all DOM elements created by this module. + * @private */ - defaults: { - event: 'press', - pointers: 1, - time: 500, // minimal time of the pointer to be pressed - threshold: 5 // a minimal movement is ok, but keep it low - }, + value: function _removeManipulationDOM() { + // removes all the bindings and overloads + this._clean(); - getTouchAction: function() { - return [TOUCH_ACTION_AUTO]; - }, + // empty the manipulation divs + util.recursiveDOMDelete(this.manipulationDiv); + util.recursiveDOMDelete(this.editModeDiv); + util.recursiveDOMDelete(this.closeDiv); - process: function(input) { - var options = this.options; - var validPointers = input.pointers.length === options.pointers; - var validMovement = input.distance < options.threshold; - var validTime = input.deltaTime > options.time; + // remove the manipulation divs + this.canvas.frame.removeChild(this.manipulationDiv); + this.canvas.frame.removeChild(this.editModeDiv); + this.canvas.frame.removeChild(this.closeDiv); - this._input = input; + // set the references to undefined + this.manipulationDiv = undefined; + this.editModeDiv = undefined; + this.closeDiv = undefined; + } + }, { + key: '_createSeperator', - // we only allow little movement - // and we've reached an end event, so a tap is possible - if (!validMovement || !validPointers || (input.eventType & (INPUT_END | INPUT_CANCEL) && !validTime)) { - this.reset(); - } else if (input.eventType & INPUT_START) { - this.reset(); - this._timer = setTimeoutContext(function() { - this.state = STATE_RECOGNIZED; - this.tryEmit(); - }, options.time, this); - } else if (input.eventType & INPUT_END) { - return STATE_RECOGNIZED; - } - return STATE_FAILED; - }, + /** + * 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]; - reset: function() { - clearTimeout(this._timer); - }, + this.manipulationDOM['seperatorLineDiv' + index] = document.createElement('div'); + this.manipulationDOM['seperatorLineDiv' + index].className = 'vis-separator-line'; + this.manipulationDiv.appendChild(this.manipulationDOM['seperatorLineDiv' + index]); + } + }, { + key: '_createAddNodeButton', - emit: function(input) { - if (this.state !== STATE_RECOGNIZED) { - return; - } + // ---------------------- DOM functions for buttons --------------------------// - if (input && (input.eventType & INPUT_END)) { - this.manager.emit(this.options.event + 'up', input); - } else { - this._input.timeStamp = now(); - this.manager.emit(this.options.event, this._input); - } + 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)); } - }); - - /** - * Rotate - * Recognized when two or more pointer are moving in a circular motion. - * @constructor - * @extends AttrRecognizer - */ - function RotateRecognizer() { - AttrRecognizer.apply(this, arguments); - } + }, { + 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('editNodeMode', 'vis-button vis-edit', locale['editNode'] || this.options.locales['en']['editNode']); + this.manipulationDiv.appendChild(button); + this._bindHammerToDiv(button, this.editNodeMode.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]; - inherit(RotateRecognizer, AttrRecognizer, { - /** - * @namespace - * @memberof RotateRecognizer - */ - defaults: { - event: 'rotate', - threshold: 0, - pointers: 2 - }, + 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', - getTouchAction: function() { - return [TOUCH_ACTION_NONE]; - }, + // -------------------------- End of DOM functions for buttons ------------------------------// - attrTest: function(input) { - return this._super.attrTest.call(this, input) && - (Math.abs(input.rotation) > this.options.threshold || this.state & STATE_BEGAN); + /** + * 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', - /** - * Swipe - * Recognized when the pointer is moving fast (velocity), with enough distance in the allowed direction. - * @constructor - * @extends AttrRecognizer - */ - function SwipeRecognizer() { - AttrRecognizer.apply(this, arguments); - } + /** + * 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', - inherit(SwipeRecognizer, AttrRecognizer, { /** - * @namespace - * @memberof SwipeRecognizer + * Restore the overridden UI functions to their original state. + * + * @private */ - defaults: { - event: 'swipe', - threshold: 10, - velocity: 0.65, - direction: DIRECTION_HORIZONTAL | DIRECTION_VERTICAL, - pointers: 1 - }, + 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]; + } + } + this.temporaryUIFunctions = {}; + } + }, { + key: '_unbindTemporaryEvents', - getTouchAction: function() { - return PanRecognizer.prototype.getTouchAction.call(this); - }, + /** + * 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', - attrTest: function(input) { - var direction = this.options.direction; - var velocity; + /** + * 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', - if (direction & (DIRECTION_HORIZONTAL | DIRECTION_VERTICAL)) { - velocity = input.velocity; - } else if (direction & DIRECTION_HORIZONTAL) { - velocity = input.velocityX; - } else if (direction & DIRECTION_VERTICAL) { - velocity = input.velocityY; + /** + * 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); } + } - return this._super.attrTest.call(this, input) && - direction & input.direction && - input.distance > this.options.threshold && - abs(velocity) > this.options.velocity && input.eventType & INPUT_END; - }, - - emit: function(input) { - var direction = directionStr(input.direction); - if (direction) { - this.manager.emit(this.options.event + direction, input); + // _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); } + } - this.manager.emit(this.options.event, input); + this.temporaryIds = { nodes: [], edges: [] }; } - }); - - /** - * A tap is ecognized when the pointer is doing a small tap/click. Multiple taps are recognized if they occur - * between the given interval and position. The delay option can be used to recognize multi-taps without firing - * a single tap. - * - * The eventData from the emitted event contains the property `tapCount`, which contains the amount of - * multi-taps being recognized. - * @constructor - * @extends Recognizer - */ - function TapRecognizer() { - Recognizer.apply(this, arguments); + }, { + key: '_controlNodeTouch', - // previous time and center, - // used for tap counting - this.pTime = false; - this.pCenter = false; + // ------------------------------------------ EDIT EDGE FUNCTIONS -----------------------------------------// - this._timer = null; - this._input = null; - this.count = 0; - } + /** + * 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', - inherit(TapRecognizer, Recognizer, { /** - * @namespace - * @memberof PinchRecognizer + * the drag start is used to mark one of the control nodes as selected. + * @param event + * @private */ - defaults: { - event: 'tap', - pointers: 1, - taps: 1, - interval: 300, // max time between the multi-tap taps - time: 250, // max time of the pointer to be down (like finger on the screen) - threshold: 2, // a minimal movement is ok, but keep it low - posThreshold: 10 // a multi-tap can be a bit off the initial position - }, + 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; - getTouchAction: function() { - return [TOUCH_ACTION_MANIPULATION]; - }, + var fromSelect = from.isOverlappingWith(pointerObj); + var toSelect = to.isOverlappingWith(pointerObj); - process: function(input) { - var options = this.options; + if (fromSelect === true) { + this.selectedControlNode = from; + edge.edgeType.from = from; + } else if (toSelect === true) { + this.selectedControlNode = to; + edge.edgeType.to = to; + } - var validPointers = input.pointers.length === options.pointers; - var validMovement = input.distance < options.threshold; - var validTouchTime = input.deltaTime < options.time; + this.body.emitter.emit('_redraw'); + } + }, { + key: '_controlNodeDrag', - this.reset(); + /** + * 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); - if ((input.eventType & INPUT_START) && (this.count === 0)) { - return this.failTimeout(); - } + 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', - // we only allow little movement - // and we've reached an end event, so a tap is possible - if (validMovement && validTouchTime && validPointers) { - if (input.eventType != INPUT_END) { - return this.failTimeout(); - } + /** + * 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]; - var validInterval = this.pTime ? (input.timeStamp - this.pTime < options.interval) : true; - var validMultiTap = !this.pCenter || getDistance(this.pCenter, input.center) < options.posThreshold; + 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; + } + } - this.pTime = input.timeStamp; - this.pCenter = input.center; + // 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'); + } + }, { + key: '_handleConnect', - if (!validMultiTap || !validInterval) { - this.count = 1; - } else { - this.count += 1; - } + // ------------------------------------ END OF EDIT EDGE FUNCTIONS -----------------------------------------// - this._input = input; + // ------------------------------------------- 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 - // if tap count matches we have recognized it, - // else it has began recognizing... - var tapCount = this.count % options.taps; - if (tapCount === 0) { - // no failing requirements, immediately trigger the tap event - // or wait as long as the multitap interval to trigger - if (!this.hasRequireFailures()) { - return STATE_RECOGNIZED; - } else { - this._timer = setTimeoutContext(function() { - this.state = STATE_RECOGNIZED; - this.tryEmit(); - }, options.interval, this); - return STATE_BEGAN; - } - } - } - return STATE_FAILED; - }, + var pointer = this.lastTouch; + var node = this.selectionHandler.getNodeAt(pointer); - failTimeout: function() { - this._timer = setTimeoutContext(function() { - this.state = STATE_FAILED; - }, this.options.interval, this); - return STATE_FAILED; - }, + 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); - reset: function() { - clearTimeout(this._timer); - }, + // 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, + dynamic: false, + type: 'continuous', + roundness: 0.5 + } + }); + this.body.edges[connectionEdge.id] = connectionEdge; + this.body.edgeIndices.push(connectionEdge.id); - emit: function() { - if (this.state == STATE_RECOGNIZED ) { - this._input.tapCount = this.count; - this.manager.emit(this.options.event, this._input); + this.temporaryIds.nodes.push(targetNode.id); + this.temporaryIds.edges.push(connectionEdge.id); + } } + this.touchTime = new Date().valueOf(); + } } - }); - - /** - * Simple way to create an manager with a default set of recognizers. - * @param {HTMLElement} element - * @param {Object} [options] - * @constructor - */ - function Hammer(element, options) { - options = options || {}; - options.recognizers = ifUndefined(options.recognizers, Hammer.defaults.preset); - return new Manager(element, options); - } - - /** - * @const {string} - */ - Hammer.VERSION = '2.0.4'; + }, { + 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', - /** - * default settings - * @namespace - */ - Hammer.defaults = { /** - * set if DOM events are being triggered. - * But this is slower and unused by simple implementations, so disabled by default. - * @type {Boolean} - * @default false + * Connect the new edge to the target if one exists, otherwise remove temp line + * @param event + * @private */ - domEvents: false, + value: function _finishConnect(event) { + var pointer = this.body.functions.getPointer(event.center); + var pointerObj = this.selectionHandler._pointerToPositionObject(pointer); - /** - * The value for the touchAction property/fallback. - * When set to `compute` it will magically set the correct value based on the added recognizers. - * @type {String} - * @default compute - */ - touchAction: TOUCH_ACTION_COMPUTE, + // remember the edge id + var connectFromId = undefined; + if (this.temporaryIds.edges[0] !== undefined) { + connectFromId = this.body.edges[this.temporaryIds.edges[0]].fromId; + } - /** - * @type {Boolean} - * @default true - */ - enable: true, - - /** - * EXPERIMENTAL FEATURE -- can be removed/changed - * Change the parent input target element. - * If Null, then it is being set the to main element. - * @type {Null|EventTarget} - * @default null - */ - inputTarget: null, - - /** - * force an input class - * @type {Null|Function} - * @default null - */ - inputClass: null, - - /** - * Default recognizer setup when calling `Hammer()` - * When creating a new Manager these will be skipped. - * @type {Array} - */ - preset: [ - // RecognizerClass, options, [recognizeWith, ...], [requireFailure, ...] - [RotateRecognizer, { enable: false }], - [PinchRecognizer, { enable: false }, ['rotate']], - [SwipeRecognizer,{ direction: DIRECTION_HORIZONTAL }], - [PanRecognizer, { direction: DIRECTION_HORIZONTAL }, ['swipe']], - [TapRecognizer], - [TapRecognizer, { event: 'doubletap', taps: 2 }, ['tap']], - [PressRecognizer] - ], - - /** - * Some CSS properties can be used to improve the working of Hammer. - * Add them to this method and they will be set when creating a new Manager. - * @namespace - */ - cssProps: { - /** - * Disables text selection to improve the dragging gesture. Mainly for desktop browsers. - * @type {String} - * @default 'none' - */ - userSelect: 'none', - - /** - * Disable the Windows Phone grippers when pressing an element. - * @type {String} - * @default 'none' - */ - touchSelect: 'none', - - /** - * Disables the default callout shown when you touch and hold a touch target. - * On iOS, when you touch and hold a touch target such as a link, Safari displays - * a callout containing information about the link. This property allows you to disable that callout. - * @type {String} - * @default 'none' - */ - touchCallout: 'none', - - /** - * Specifies whether zooming is enabled. Used by IE10> - * @type {String} - * @default 'none' - */ - contentZooming: 'none', + // 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; + } + } - /** - * Specifies that an entire element should be draggable instead of its contents. Mainly for desktop browsers. - * @type {String} - * @default 'none' - */ - userDrag: 'none', + // clean temporary nodes and edges. + this._cleanupTemporaryNodesAndEdges(); - /** - * Overrides the highlight color shown when the user taps a link or a JavaScript - * clickable element in iOS. This property obeys the alpha value, if specified. - * @type {String} - * @default 'rgba(0,0,0,0)' - */ - tapHighlightColor: 'rgba(0,0,0,0)' + // 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'); } - }; - - var STOP = 1; - var FORCED_STOP = 2; - - /** - * Manager - * @param {HTMLElement} element - * @param {Object} [options] - * @constructor - */ - function Manager(element, options) { - options = options || {}; - - this.options = merge(options, Hammer.defaults); - this.options.inputTarget = this.options.inputTarget || element; - - this.handlers = {}; - this.session = {}; - this.recognizers = []; - - this.element = element; - this.input = createInputInstance(this); - this.touchAction = new TouchAction(this, this.options.touchAction); - - toggleCssProps(this, true); - - each(options.recognizers, function(item) { - var recognizer = this.add(new (item[0])(item[1])); - item[2] && recognizer.recognizeWith(item[2]); - item[3] && recognizer.requireFailure(item[3]); - }, this); - } - - Manager.prototype = { - /** - * set options - * @param {Object} options - * @returns {Manager} - */ - set: function(options) { - extend(this.options, options); + }, { + key: '_performAddNode', - // Options that need a little more setup - if (options.touchAction) { - this.touchAction.update(); - } - if (options.inputTarget) { - // Clean up existing event listeners and reinitialize - this.input.destroy(); - this.input.target = options.inputTarget; - this.input.init(); - } - return this; - }, + // --------------------------------------- END OF ADD EDGE FUNCTIONS -------------------------------------// - /** - * stop recognizing for this session. - * This session will be discarded, when a new [input]start event is fired. - * When forced, the recognizer cycle is stopped immediately. - * @param {Boolean} [force] - */ - stop: function(force) { - this.session.stopped = force ? FORCED_STOP : STOP; - }, + // ------------------------------ Performing all the actual data manipulation ------------------------// /** - * run the recognizers! - * called by the inputHandler function on every movement of the pointers (touches) - * it walks through all the recognizers and tries to detect the gesture that is being made - * @param {Object} inputData + * Adds a node on the specified location */ - recognize: function(inputData) { - var session = this.session; - if (session.stopped) { - return; - } - - // run the touch-action polyfill - this.touchAction.preventDefaults(inputData); - - var recognizer; - var recognizers = this.recognizers; - - // this holds the recognizer that is being recognized. - // so the recognizer's state needs to be BEGAN, CHANGED, ENDED or RECOGNIZED - // if no recognizer is detecting a thing, it is set to `null` - var curRecognizer = session.curRecognizer; - - // reset when the last recognizer is recognized - // or when we're in a new session - if (!curRecognizer || (curRecognizer && curRecognizer.state & STATE_RECOGNIZED)) { - curRecognizer = session.curRecognizer = null; - } - - var i = 0; - while (i < recognizers.length) { - recognizer = recognizers[i]; + value: function _performAddNode(clickData) { + var _this4 = this; - // find out if we are allowed try to recognize the input for this one. - // 1. allow if the session is NOT forced stopped (see the .stop() method) - // 2. allow if we still haven't recognized a gesture in this session, or the this recognizer is the one - // that is being recognized. - // 3. allow if the recognizer is allowed to run simultaneous with the current recognized recognizer. - // this can be setup with the `recognizeWith()` method on the recognizer. - if (session.stopped !== FORCED_STOP && ( // 1 - !curRecognizer || recognizer == curRecognizer || // 2 - recognizer.canRecognizeWith(curRecognizer))) { // 3 - recognizer.recognize(inputData); - } else { - recognizer.reset(); - } + var defaultData = { + id: util.randomUUID(), + x: clickData.pointer.canvas.x, + y: clickData.pointer.canvas.y, + label: 'new' + }; - // if the recognizer has been recognizing the input as a valid gesture, we want to store this one as the - // current active recognizer. but only if we don't already have an active recognizer - if (!curRecognizer && recognizer.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED)) { - curRecognizer = session.curRecognizer = recognizer; + if (typeof this.options.addNode === 'function') { + if (this.options.addNode.length === 2) { + this.options.addNode(defaultData, function (finalizedData) { + if (finalizedData !== null && finalizedData !== undefined && _this4.inMode === 'addNode') { + // if for whatever reason the mode has changes (due to dataset change) disregard the callback + _this4.body.data.nodes.add(finalizedData); + _this4.showManipulatorToolbar(); } - i++; + }); + } else { + throw new Error('The function for add does not support two arguments (data,callback)'); + this.showManipulatorToolbar(); } - }, + } else { + this.body.data.nodes.add(defaultData); + this.showManipulatorToolbar(); + } + } + }, { + key: '_performAddEdge', /** - * get a recognizer by its event name. - * @param {Recognizer|String} recognizer - * @returns {Recognizer|Null} + * connect two nodes with a new edge. + * + * @private */ - get: function(recognizer) { - if (recognizer instanceof Recognizer) { - return recognizer; - } + value: function _performAddEdge(sourceNodeId, targetNodeId) { + var _this5 = this; - var recognizers = this.recognizers; - for (var i = 0; i < recognizers.length; i++) { - if (recognizers[i].options.event == recognizer) { - return recognizers[i]; + 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 && _this5.inMode === 'addEdge') { + // if for whatever reason the mode has changes (due to dataset change) disregard the callback + _this5.body.data.edges.add(finalizedData); + _this5.selectionHandler.unselectAll(); + _this5.showManipulatorToolbar(); } + }); + } else { + throw new Error('The function for connect does not support two arguments (data,callback)'); } - return null; - }, - - /** - * add a recognizer to the manager - * existing recognizers with the same event name will be removed - * @param {Recognizer} recognizer - * @returns {Recognizer|Manager} - */ - add: function(recognizer) { - if (invokeArrayArg(recognizer, 'add', this)) { - return this; - } - - // remove existing - var existing = this.get(recognizer.options.event); - if (existing) { - this.remove(existing); - } - - this.recognizers.push(recognizer); - recognizer.manager = this; - - this.touchAction.update(); - return recognizer; - }, - - /** - * remove a recognizer by name or instance - * @param {Recognizer|String} recognizer - * @returns {Manager} - */ - remove: function(recognizer) { - if (invokeArrayArg(recognizer, 'remove', this)) { - return this; - } - - var recognizers = this.recognizers; - recognizer = this.get(recognizer); - recognizers.splice(inArray(recognizers, recognizer), 1); - - this.touchAction.update(); - return this; - }, + } else { + this.body.data.edges.add(defaultData); + this.selectionHandler.unselectAll(); + this.showManipulatorToolbar(); + } + } + }, { + key: '_performEditEdge', /** - * bind event - * @param {String} events - * @param {Function} handler - * @returns {EventEmitter} this + * connect two nodes with a new edge. + * + * @private */ - on: function(events, handler) { - var handlers = this.handlers; - each(splitStr(events), function(event) { - handlers[event] = handlers[event] || []; - handlers[event].push(handler); - }); - return this; - }, + value: function _performEditEdge(sourceNodeId, targetNodeId) { + var _this6 = this; - /** - * unbind event, leave emit blank to remove all handlers - * @param {String} events - * @param {Function} [handler] - * @returns {EventEmitter} this - */ - off: function(events, handler) { - var handlers = this.handlers; - each(splitStr(events), function(event) { - if (!handler) { - delete handlers[event]; + 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 || _this6.inMode !== 'editEdge') { + // if for whatever reason the mode has changes (due to dataset change) disregard the callback) { + _this6.body.edges[defaultData.id].updateEdgeType(); + _this6.body.emitter.emit('_redraw'); } else { - handlers[event].splice(inArray(handlers[event], handler), 1); + _this6.body.data.edges.update(finalizedData); + _this6.selectionHandler.unselectAll(); + _this6.showManipulatorToolbar(); } - }); - return this; - }, - - /** - * emit event to the listeners - * @param {String} event - * @param {Object} data - */ - emit: function(event, data) { - // we also want to trigger dom events - if (this.options.domEvents) { - triggerDomEvent(event, data); - } - - // no handlers, so skip it all - var handlers = this.handlers[event] && this.handlers[event].slice(); - if (!handlers || !handlers.length) { - return; - } - - data.type = event; - data.preventDefault = function() { - data.srcEvent.preventDefault(); - }; - - var i = 0; - while (i < handlers.length) { - handlers[i](data); - i++; + }); + } else { + throw new Error('The function for edit does not support two arguments (data, callback)'); } - }, - - /** - * destroy the manager and unbinds all events - * it doesn't unbind dom events, that is the user own responsibility - */ - destroy: function() { - this.element && toggleCssProps(this, false); - - this.handlers = {}; - this.session = {}; - this.input.destroy(); - this.element = null; + } else { + this.body.data.edges.update(defaultData); + this.selectionHandler.unselectAll(); + this.showManipulatorToolbar(); + } } - }; - - /** - * add/remove the css properties as defined in manager.options.cssProps - * @param {Manager} manager - * @param {Boolean} add - */ - function toggleCssProps(manager, add) { - var element = manager.element; - each(manager.options.cssProps, function(value, name) { - element.style[prefixed(element.style, name)] = add ? value : ''; - }); - } - - /** - * trigger dom event - * @param {String} event - * @param {Object} data - */ - function triggerDomEvent(event, data) { - var gestureEvent = document.createEvent('Event'); - gestureEvent.initEvent(event, true, true); - gestureEvent.gesture = data; - data.target.dispatchEvent(gestureEvent); - } - - extend(Hammer, { - INPUT_START: INPUT_START, - INPUT_MOVE: INPUT_MOVE, - INPUT_END: INPUT_END, - INPUT_CANCEL: INPUT_CANCEL, - - STATE_POSSIBLE: STATE_POSSIBLE, - STATE_BEGAN: STATE_BEGAN, - STATE_CHANGED: STATE_CHANGED, - STATE_ENDED: STATE_ENDED, - STATE_RECOGNIZED: STATE_RECOGNIZED, - STATE_CANCELLED: STATE_CANCELLED, - STATE_FAILED: STATE_FAILED, - - DIRECTION_NONE: DIRECTION_NONE, - DIRECTION_LEFT: DIRECTION_LEFT, - DIRECTION_RIGHT: DIRECTION_RIGHT, - DIRECTION_UP: DIRECTION_UP, - DIRECTION_DOWN: DIRECTION_DOWN, - DIRECTION_HORIZONTAL: DIRECTION_HORIZONTAL, - DIRECTION_VERTICAL: DIRECTION_VERTICAL, - DIRECTION_ALL: DIRECTION_ALL, - - Manager: Manager, - Input: Input, - TouchAction: TouchAction, - - TouchInput: TouchInput, - MouseInput: MouseInput, - PointerEventInput: PointerEventInput, - TouchMouseInput: TouchMouseInput, - SingleTouchInput: SingleTouchInput, - - Recognizer: Recognizer, - AttrRecognizer: AttrRecognizer, - Tap: TapRecognizer, - Pan: PanRecognizer, - Swipe: SwipeRecognizer, - Pinch: PinchRecognizer, - Rotate: RotateRecognizer, - Press: PressRecognizer, - - on: addEventListeners, - off: removeEventListeners, - each: each, - merge: merge, - extend: extend, - inherit: inherit, - bindFn: bindFn, - prefixed: prefixed - }); - - if ("function" == TYPE_FUNCTION && __webpack_require__(61)) { - !(__WEBPACK_AMD_DEFINE_RESULT__ = function() { - return Hammer; - }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); - } else if (typeof module != 'undefined' && module.exports) { - module.exports = Hammer; - } else { - window[exportName] = Hammer; - } + }]); - })(window, document, 'Hammer'); + return ManipulationSystem; + })(); + exports['default'] = ManipulationSystem; + module.exports = exports['default']; /***/ }, -/* 57 */ +/* 66 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - var keycharm = __webpack_require__(62); - var Emitter = __webpack_require__(43); - var Hammer = __webpack_require__(41); - var util = __webpack_require__(1); - + Object.defineProperty(exports, '__esModule', { + value: true + }); /** - * Turn an element into an clickToUse element. - * When not active, the element has a transparent overlay. When the overlay is - * clicked, the mode is changed to active. - * When active, the element is displayed with a blue border around it, and - * the interactive contents of the element can be used. When clicked outside - * the element, the elements mode is changed to inactive. - * @param {Element} container - * @constructor + * 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 */ - function Activator(container) { - this.active = false; - - this.dom = { - container: container - }; - - this.dom.overlay = document.createElement('div'); - this.dom.overlay.className = 'vis-overlay'; + 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 fn = 'function'; + var undef = 'undefined'; + var any = 'any'; - this.dom.container.appendChild(this.dom.overlay); + var allOptions = { + configure: { + enabled: { boolean: boolean }, + filter: { boolean: boolean, string: string, array: array, fn: fn }, + container: { dom: dom }, + __type__: { object: object, boolean: boolean, string: string, array: array, fn: fn } + }, + 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: { fn: fn, number: number }, + label: { string: string, undef: undef }, + length: { number: number, undef: undef }, + 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: { fn: fn }, + __type__: { object: object } + }, + selectionWidth: { fn: fn, 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 }, + dynamic: { boolean: boolean }, + type: { string: string }, + roundness: { number: number }, + __type__: { object: object, boolean: boolean } + }, + title: { string: string, undef: undef }, + width: { number: number }, + value: { number: number, undef: undef }, + __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 }, + tooltipDelay: { number: number }, + zoomView: { boolean: boolean }, + __type__: { object: object } + }, + layout: { + randomSeed: { undef: undef, 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, fn: fn }, + addEdge: { boolean: boolean, fn: fn }, + editNode: { fn: fn }, + editEdge: { boolean: boolean, fn: fn }, + deleteNode: { boolean: boolean, fn: fn }, + deleteEdge: { boolean: boolean, fn: fn }, + controlNodeStyle: 'get from nodes, will be overwritten below', + __type__: { object: object, boolean: boolean } + }, + nodes: { + borderWidth: { number: number }, + borderWidthSelected: { number: number, undef: undef }, + brokenImage: { string: string, undef: undef }, + 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 }, + stroke: { number: number }, // px + strokeColor: { string: string }, + __type__: { object: object, string: string } + }, + group: { string: string, number: number, undef: undef }, + 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, undef: undef }, // --> URL + label: { string: string, undef: undef }, + level: { number: number, undef: undef }, + 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: { fn: fn }, + __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, undef: undef }, + value: { number: number, undef: undef }, + 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 }, + __type__: { object: object } + }, + forceAtlas2Based: { + gravitationalConstant: { number: number }, + centralGravity: { number: number }, + springLength: { number: number }, + springConstant: { number: number }, + damping: { 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 } + }, - this.hammer = Hammer(this.dom.overlay); - this.hammer.on('tap', this._onTapOverlay.bind(this)); + //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 } + }; - // block all touch events (except tap) - var me = this; - var events = ['tap', 'doubletap', 'press', 'pinch', 'pan', 'panstart', 'panmove', 'panend']; - events.forEach(function (event) { - me.hammer.on(event, function (event) { - event.stopPropagation(); - }); - }); + allOptions.groups.__any__ = allOptions.nodes; + allOptions.manipulation.controlNodeStyle = allOptions.nodes; - // attach a tap event to the window, in order to deactivate when clicking outside the timeline - this.bodyHammer = Hammer(document && document.body, { prevent_default: false }); - this.bodyHammer.on('tap', function (event) { - // deactivate when clicked outside the container - if (!_hasParent(event.target, container)) { - me.deactivate(); + 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: true, + min: [14, 0, 200, 1], + max: [30, 0, 200, 1], + maxVisible: [30, 0, 200, 1], + drawThreshold: [3, 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: [1, 0, 50, 1], // px + strokeColor: ['color', '#ffffff'], + align: ['horizontal', 'top', 'middle', 'bottom'] + }, + hidden: false, + hoverWidth: [2, 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: [3, 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, + dynamic: true, + type: ['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 } - }); - - if (this.keycharm !== undefined) { - this.keycharm.destroy(); + }, + 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, + 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, 5, 0.005], + damping: [0.09, 0, 1, 0.01] + }, + forceAtlas2Based: { + //theta: [0.5, 0.1, 1, 0.05], + gravitationalConstant: [-800, -3000, 0, 1], + centralGravity: [0.01, 0, 1, 0.005], + springLength: [95, 0, 500, 5], + springConstant: [0.08, 0, 5, 0.005], + damping: [0.4, 0, 1, 0.01] + }, + repulsion: { + centralGravity: [0.2, 0, 10, 0.05], + springLength: [200, 0, 500, 5], + springConstant: [0.05, 0, 5, 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, 5, 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, 1, 0.05] + }, + global: { + locale: ['en', 'nl'] } - this.keycharm = keycharm(); - - // keycharm listener only bounded when active) - this.escListener = this.deactivate.bind(this); - } + }; - // turn into an event emitter - Emitter(Activator.prototype); + exports.allOptions = allOptions; + exports.configureOptions = configureOptions; - // The currently active activator - Activator.current = null; +/***/ }, +/* 67 */ +/***/ function(module, exports, __webpack_require__) { /** - * Destroy the activator. Cleans up all created DOM and event listeners + * Canvas shapes used by Network + */ + 'use strict'; + + if (typeof CanvasRenderingContext2D !== 'undefined') { + + /** + * Draw a circle shape + */ + CanvasRenderingContext2D.prototype.circle = function (x, y, r) { + this.beginPath(); + this.arc(x, y, r, 0, 2 * Math.PI, false); + }; + + /** + * Draw a square shape + * @param {Number} x horizontal center + * @param {Number} y vertical center + * @param {Number} r size, width and height of the square + */ + CanvasRenderingContext2D.prototype.square = function (x, y, r) { + this.beginPath(); + this.rect(x - r, y - r, r * 2, r * 2); + }; + + /** + * Draw a triangle shape + * @param {Number} x horizontal center + * @param {Number} y vertical center + * @param {Number} r radius, half the length of the sides of the triangle + */ + CanvasRenderingContext2D.prototype.triangle = function (x, y, r) { + // http://en.wikipedia.org/wiki/Equilateral_triangle + this.beginPath(); + + // the change in radius and the offset is here to center the shape + r *= 1.15; + y += 0.275 * r; + + var s = r * 2; + var s2 = s / 2; + var ir = Math.sqrt(3) / 6 * s; // radius of inner circle + var h = Math.sqrt(s * s - s2 * s2); // height + + this.moveTo(x, y - (h - ir)); + this.lineTo(x + s2, y + ir); + this.lineTo(x - s2, y + ir); + this.lineTo(x, y - (h - ir)); + this.closePath(); + }; + + /** + * Draw a triangle shape in downward orientation + * @param {Number} x horizontal center + * @param {Number} y vertical center + * @param {Number} r radius + */ + CanvasRenderingContext2D.prototype.triangleDown = function (x, y, r) { + // http://en.wikipedia.org/wiki/Equilateral_triangle + this.beginPath(); + + // the change in radius and the offset is here to center the shape + r *= 1.15; + y -= 0.275 * r; + + var s = r * 2; + var s2 = s / 2; + var ir = Math.sqrt(3) / 6 * s; // radius of inner circle + var h = Math.sqrt(s * s - s2 * s2); // height + + this.moveTo(x, y + (h - ir)); + this.lineTo(x + s2, y - ir); + this.lineTo(x - s2, y - ir); + this.lineTo(x, y + (h - ir)); + this.closePath(); + }; + + /** + * Draw a star shape, a star with 5 points + * @param {Number} x horizontal center + * @param {Number} y vertical center + * @param {Number} r radius, half the length of the sides of the triangle + */ + CanvasRenderingContext2D.prototype.star = function (x, y, r) { + // http://www.html5canvastutorials.com/labs/html5-canvas-star-spinner/ + this.beginPath(); + + // the change in radius and the offset is here to center the shape + r *= 0.82; + y += 0.1 * r; + + for (var n = 0; n < 10; n++) { + var radius = n % 2 === 0 ? r * 1.3 : r * 0.5; + this.lineTo(x + radius * Math.sin(n * 2 * Math.PI / 10), y - radius * Math.cos(n * 2 * Math.PI / 10)); + } + + this.closePath(); + }; + + /** + * Draw a Diamond shape + * @param {Number} x horizontal center + * @param {Number} y vertical center + * @param {Number} r radius, half the length of the sides of the triangle + */ + CanvasRenderingContext2D.prototype.diamond = function (x, y, r) { + // http://www.html5canvastutorials.com/labs/html5-canvas-star-spinner/ + this.beginPath(); + + this.lineTo(x, y + r); + this.lineTo(x + r, y); + this.lineTo(x, y - r); + this.lineTo(x - r, y); + + this.closePath(); + }; + + /** + * http://stackoverflow.com/questions/1255512/how-to-draw-a-rounded-rectangle-on-html-canvas + */ + CanvasRenderingContext2D.prototype.roundRect = function (x, y, w, h, r) { + var r2d = Math.PI / 180; + if (w - 2 * r < 0) { + r = w / 2; + } //ensure that the radius isn't too large for x + if (h - 2 * r < 0) { + r = h / 2; + } //ensure that the radius isn't too large for y + this.beginPath(); + this.moveTo(x + r, y); + this.lineTo(x + w - r, y); + this.arc(x + w - r, y + r, r, r2d * 270, r2d * 360, false); + this.lineTo(x + w, y + h - r); + this.arc(x + w - r, y + h - r, r, 0, r2d * 90, false); + this.lineTo(x + r, y + h); + this.arc(x + r, y + h - r, r, r2d * 90, r2d * 180, false); + this.lineTo(x, y + r); + this.arc(x + r, y + r, r, r2d * 180, r2d * 270, false); + }; + + /** + * http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + */ + CanvasRenderingContext2D.prototype.ellipse = function (x, y, w, h) { + var kappa = 0.5522848, + ox = w / 2 * kappa, + // control point offset horizontal + oy = h / 2 * kappa, + // control point offset vertical + xe = x + w, + // x-end + ye = y + h, + // y-end + xm = x + w / 2, + // x-middle + ym = y + h / 2; // y-middle + + this.beginPath(); + this.moveTo(x, ym); + this.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + this.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + this.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + this.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + }; + + /** + * http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + */ + CanvasRenderingContext2D.prototype.database = function (x, y, w, h) { + var f = 1 / 3; + var wEllipse = w; + var hEllipse = h * f; + + var kappa = 0.5522848, + ox = wEllipse / 2 * kappa, + // control point offset horizontal + oy = hEllipse / 2 * kappa, + // control point offset vertical + xe = x + wEllipse, + // x-end + ye = y + hEllipse, + // y-end + xm = x + wEllipse / 2, + // x-middle + ym = y + hEllipse / 2, + // y-middle + ymb = y + (h - hEllipse / 2), + // y-midlle, bottom ellipse + yeb = y + h; // y-end, bottom ellipse + + this.beginPath(); + this.moveTo(xe, ym); + + this.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + this.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + this.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + this.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + + this.lineTo(xe, ymb); + + this.bezierCurveTo(xe, ymb + oy, xm + ox, yeb, xm, yeb); + this.bezierCurveTo(xm - ox, yeb, x, ymb + oy, x, ymb); + + this.lineTo(x, ym); + }; + + /** + * Draw an arrow point (no line) + */ + CanvasRenderingContext2D.prototype.arrow = function (x, y, angle, length) { + // tail + var xt = x - length * Math.cos(angle); + var yt = y - length * Math.sin(angle); + + // inner tail + // TODO: allow to customize different shapes + var xi = x - length * 0.9 * Math.cos(angle); + var yi = y - length * 0.9 * Math.sin(angle); + + // left + var xl = xt + length / 3 * Math.cos(angle + 0.5 * Math.PI); + var yl = yt + length / 3 * Math.sin(angle + 0.5 * Math.PI); + + // right + var xr = xt + length / 3 * Math.cos(angle - 0.5 * Math.PI); + var yr = yt + length / 3 * Math.sin(angle - 0.5 * Math.PI); + + this.beginPath(); + this.moveTo(x, y); + this.lineTo(xl, yl); + this.lineTo(xi, yi); + this.lineTo(xr, yr); + this.closePath(); + }; + + /** + * Sets up the dashedLine functionality for drawing + * Original code came from http://stackoverflow.com/questions/4576724/dotted-stroke-in-canvas + * @author David Jordan + * @date 2012-08-08 + */ + CanvasRenderingContext2D.prototype.dashedLine = function (x, y, x2, y2, pattern) { + this.beginPath(); + this.moveTo(x, y); + + var patternLength = pattern.length; + var dx = x2 - x; + var dy = y2 - y; + var slope = dy / dx; + var distRemaining = Math.sqrt(dx * dx + dy * dy); + var patternIndex = 0; + var draw = true; + var xStep = 0; + var dashLength = pattern[0]; + + while (distRemaining >= 0.1) { + dashLength = pattern[patternIndex++ % patternLength]; + if (dashLength > distRemaining) { + dashLength = distRemaining; + } + + xStep = Math.sqrt(dashLength * dashLength / (1 + slope * slope)); + xStep = dx < 0 ? -xStep : xStep; + x += xStep; + y += slope * xStep; + + if (draw === true) { + this.lineTo(x, y); + } else { + this.moveTo(x, y); + } + + distRemaining -= dashLength; + draw = !draw; + } + }; + } + +/***/ }, +/* 68 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var keycharm = __webpack_require__(88); + var Emitter = __webpack_require__(43); + var Hammer = __webpack_require__(41); + var util = __webpack_require__(1); + + /** + * Turn an element into an clickToUse element. + * When not active, the element has a transparent overlay. When the overlay is + * clicked, the mode is changed to active. + * When active, the element is displayed with a blue border around it, and + * the interactive contents of the element can be used. When clicked outside + * the element, the elements mode is changed to inactive. + * @param {Element} container + * @constructor + */ + function Activator(container) { + this.active = false; + + this.dom = { + container: container + }; + + this.dom.overlay = document.createElement('div'); + this.dom.overlay.className = 'vis-overlay'; + + this.dom.container.appendChild(this.dom.overlay); + + this.hammer = Hammer(this.dom.overlay); + this.hammer.on('tap', this._onTapOverlay.bind(this)); + + // block all touch events (except tap) + var me = this; + var events = ['tap', 'doubletap', 'press', 'pinch', 'pan', 'panstart', 'panmove', 'panend']; + events.forEach(function (event) { + me.hammer.on(event, function (event) { + event.stopPropagation(); + }); + }); + + // attach a tap event to the window, in order to deactivate when clicking outside the timeline + this.bodyHammer = Hammer(document && document.body, { prevent_default: false }); + this.bodyHammer.on('tap', function (event) { + // deactivate when clicked outside the container + if (!_hasParent(event.target, container)) { + me.deactivate(); + } + }); + + if (this.keycharm !== undefined) { + this.keycharm.destroy(); + } + this.keycharm = keycharm(); + + // keycharm listener only bounded when active) + this.escListener = this.deactivate.bind(this); + } + + // turn into an event emitter + Emitter(Activator.prototype); + + // The currently active activator + Activator.current = null; + + /** + * Destroy the activator. Cleans up all created DOM and event listeners */ Activator.prototype.destroy = function () { this.deactivate(); @@ -26602,8129 +28092,6417 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = Activator; /***/ }, -/* 58 */ +/* 69 */ /***/ function(module, exports, __webpack_require__) { + // English '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; }; })(); + exports['en'] = { + edit: 'Edit', + del: 'Delete selected', + back: 'Back', + addNode: 'Add Node', + addEdge: 'Add Edge', + editNode: 'Edit Node', + editEdge: 'Edit Edge', + addDescription: 'Click in an empty space to place a new node.', + edgeDescription: 'Click on a node and drag the edge to another node to connect them.', + editEdgeDescription: 'Click on the control points and drag them to a node to connect to it.', + createEdgeError: 'Cannot link edges to a cluster.', + deleteClusterError: 'Clusters cannot be deleted.', + editClusterError: 'Clusters cannot be edited.' + }; + exports['en_EN'] = exports['en']; + exports['en_US'] = exports['en']; - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + // Dutch + exports['nl'] = { + edit: 'Wijzigen', + del: 'Selectie verwijderen', + back: 'Terug', + addNode: 'Node toevoegen', + addEdge: 'Link toevoegen', + editNode: 'Node wijzigen', + editEdge: 'Link wijzigen', + addDescription: 'Klik op een leeg gebied om een nieuwe node te maken.', + edgeDescription: 'Klik op een node en sleep de link naar een andere node om ze te verbinden.', + editEdgeDescription: 'Klik op de verbindingspunten en sleep ze naar een node om daarmee te verbinden.', + createEdgeError: 'Kan geen link maken naar een cluster.', + deleteClusterError: 'Clusters kunnen niet worden verwijderd.', + editClusterError: 'Clusters kunnen niet worden aangepast.' + }; + exports['nl_NL'] = exports['nl']; + exports['nl_BE'] = exports['nl']; - var Hammer = __webpack_require__(41); - var hammerUtil = __webpack_require__(49); - var util = __webpack_require__(1); +/***/ }, +/* 70 */ +/***/ function(module, exports, __webpack_require__) { - var ColorPicker = (function () { - function ColorPicker() { - var pixelRatio = arguments[0] === undefined ? 1 : arguments[0]; + /* WEBPACK VAR INJECTION */(function(module) {//! moment.js + //! version : 2.10.2 + //! authors : Tim Wood, Iskren Chernev, Moment.js contributors + //! license : MIT + //! momentjs.com - _classCallCheck(this, ColorPicker); + (function (global, factory) { + true ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + global.moment = factory() + }(this, function () { 'use strict'; - this.pixelRatio = pixelRatio; - this.generated = false; - this.centerCoordinates = { x: 289 / 2, y: 289 / 2 }; - this.r = 289 * 0.49; - this.color = { r: 255, g: 255, b: 255, a: 1 }; - this.hueCircle = undefined; - this.initialColor = { r: 255, g: 255, b: 255, a: 1 }; - this.previousColor = undefined; - this.applied = false; + var hookCallback; - // bound by - this.updateCallback = function () {}; + function utils_hooks__hooks () { + return hookCallback.apply(null, arguments); + } - // create all DOM elements - this._create(); - } + // This is done to register the method called with moment() + // without creating circular dependencies. + function setHookCallback (callback) { + hookCallback = callback; + } - _createClass(ColorPicker, [{ - key: 'insertTo', + 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 + }; + } - /** - * this inserts the colorPicker into a div from the DOM - * @param container - */ - value: function insertTo(container) { - if (this.hammer !== undefined) { - this.hammer.destroy(); - this.hammer = undefined; - } - this.container = container; - this.container.appendChild(this.frame); - this._bindHammer(); + function isArray(input) { + return Object.prototype.toString.call(input) === '[object Array]'; + } - this._setSize(); + function isDate(input) { + return Object.prototype.toString.call(input) === '[object Date]' || input instanceof Date; } - }, { - key: 'setCallback', - /** - * the callback is executed on apply and save. Bind it to the application - * @param callback - */ - value: function setCallback(callback) { - if (typeof callback === 'function') { - this.updateCallback = callback; - } else { - throw new Error('Function attempted to set as colorPicker callback is not a function.'); - } + function map(arr, fn) { + var res = [], i; + for (i = 0; i < arr.length; ++i) { + res.push(fn(arr[i], i)); + } + return res; } - }, { - key: '_isColorString', - value: function _isColorString(color) { - var htmlColors = { black: '#000000', navy: '#000080', darkblue: '#00008B', mediumblue: '#0000CD', blue: '#0000FF', darkgreen: '#006400', green: '#008000', teal: '#008080', darkcyan: '#008B8B', deepskyblue: '#00BFFF', darkturquoise: '#00CED1', mediumspringgreen: '#00FA9A', lime: '#00FF00', springgreen: '#00FF7F', aqua: '#00FFFF', cyan: '#00FFFF', midnightblue: '#191970', dodgerblue: '#1E90FF', lightseagreen: '#20B2AA', forestgreen: '#228B22', seagreen: '#2E8B57', darkslategray: '#2F4F4F', limegreen: '#32CD32', mediumseagreen: '#3CB371', turquoise: '#40E0D0', royalblue: '#4169E1', steelblue: '#4682B4', darkslateblue: '#483D8B', mediumturquoise: '#48D1CC', indigo: '#4B0082', darkolivegreen: '#556B2F', cadetblue: '#5F9EA0', cornflowerblue: '#6495ED', mediumaquamarine: '#66CDAA', dimgray: '#696969', slateblue: '#6A5ACD', olivedrab: '#6B8E23', slategray: '#708090', lightslategray: '#778899', mediumslateblue: '#7B68EE', lawngreen: '#7CFC00', chartreuse: '#7FFF00', aquamarine: '#7FFFD4', maroon: '#800000', purple: '#800080', olive: '#808000', gray: '#808080', skyblue: '#87CEEB', lightskyblue: '#87CEFA', blueviolet: '#8A2BE2', darkred: '#8B0000', darkmagenta: '#8B008B', saddlebrown: '#8B4513', darkseagreen: '#8FBC8F', lightgreen: '#90EE90', mediumpurple: '#9370D8', darkviolet: '#9400D3', palegreen: '#98FB98', darkorchid: '#9932CC', yellowgreen: '#9ACD32', sienna: '#A0522D', brown: '#A52A2A', darkgray: '#A9A9A9', lightblue: '#ADD8E6', greenyellow: '#ADFF2F', paleturquoise: '#AFEEEE', lightsteelblue: '#B0C4DE', powderblue: '#B0E0E6', firebrick: '#B22222', darkgoldenrod: '#B8860B', mediumorchid: '#BA55D3', rosybrown: '#BC8F8F', darkkhaki: '#BDB76B', silver: '#C0C0C0', mediumvioletred: '#C71585', indianred: '#CD5C5C', peru: '#CD853F', chocolate: '#D2691E', tan: '#D2B48C', lightgrey: '#D3D3D3', palevioletred: '#D87093', thistle: '#D8BFD8', orchid: '#DA70D6', goldenrod: '#DAA520', crimson: '#DC143C', gainsboro: '#DCDCDC', plum: '#DDA0DD', burlywood: '#DEB887', lightcyan: '#E0FFFF', lavender: '#E6E6FA', darksalmon: '#E9967A', violet: '#EE82EE', palegoldenrod: '#EEE8AA', lightcoral: '#F08080', khaki: '#F0E68C', aliceblue: '#F0F8FF', honeydew: '#F0FFF0', azure: '#F0FFFF', sandybrown: '#F4A460', wheat: '#F5DEB3', beige: '#F5F5DC', whitesmoke: '#F5F5F5', mintcream: '#F5FFFA', ghostwhite: '#F8F8FF', salmon: '#FA8072', antiquewhite: '#FAEBD7', linen: '#FAF0E6', lightgoldenrodyellow: '#FAFAD2', oldlace: '#FDF5E6', red: '#FF0000', fuchsia: '#FF00FF', magenta: '#FF00FF', deeppink: '#FF1493', orangered: '#FF4500', tomato: '#FF6347', hotpink: '#FF69B4', coral: '#FF7F50', darkorange: '#FF8C00', lightsalmon: '#FFA07A', orange: '#FFA500', lightpink: '#FFB6C1', pink: '#FFC0CB', gold: '#FFD700', peachpuff: '#FFDAB9', navajowhite: '#FFDEAD', moccasin: '#FFE4B5', bisque: '#FFE4C4', mistyrose: '#FFE4E1', blanchedalmond: '#FFEBCD', papayawhip: '#FFEFD5', lavenderblush: '#FFF0F5', seashell: '#FFF5EE', cornsilk: '#FFF8DC', lemonchiffon: '#FFFACD', floralwhite: '#FFFAF0', snow: '#FFFAFA', yellow: '#FFFF00', lightyellow: '#FFFFE0', ivory: '#FFFFF0', white: '#FFFFFF' }; - if (typeof color === 'string') { - return htmlColors[color]; - } + + function hasOwnProp(a, b) { + return Object.prototype.hasOwnProperty.call(a, b); } - }, { - key: 'setColor', - /** - * Set the color of the colorPicker - * Supported formats: - * 'red' --> HTML color string - * '#ffffff' --> hex string - * 'rbg(255,255,255)' --> rgb string - * 'rgba(255,255,255,1.0)' --> rgba string - * {r:255,g:255,b:255} --> rgb object - * {r:255,g:255,b:255,a:1.0} --> rgba object - * @param color - * @param setInitial - */ - value: function setColor(color) { - var setInitial = arguments[1] === undefined ? true : arguments[1]; - - if (color === 'none') { - return; - } - - var rgba = undefined; - - // if a html color shorthand is used, convert to hex - var htmlColor = this._isColorString(color); - if (htmlColor !== undefined) { - color = htmlColor; - } + function extend(a, b) { + for (var i in b) { + if (hasOwnProp(b, i)) { + a[i] = b[i]; + } + } - // check format - if (util.isString(color) === true) { - if (util.isValidRGB(color) === true) { - var rgbaArray = color.substr(4).substr(0, color.length - 5).split(','); - rgba = { r: rgbaArray[0], g: rgbaArray[1], b: rgbaArray[2], a: 1 }; - } else if (util.isValidRGBA(color) === true) { - var rgbaArray = color.substr(5).substr(0, color.length - 6).split(','); - rgba = { r: rgbaArray[0], g: rgbaArray[1], b: rgbaArray[2], a: rgbaArray[3] }; - } else if (util.isValidHex(color) === true) { - var rgbObj = util.hexToRGB(color); - rgba = { r: rgbObj.r, g: rgbObj.g, b: rgbObj.b, a: 1 }; + if (hasOwnProp(b, 'toString')) { + a.toString = b.toString; } - } else { - if (color instanceof Object) { - if (color.r !== undefined && color.g !== undefined && color.b !== undefined) { - var alpha = color.a !== undefined ? color.a : '1.0'; - rgba = { r: color.r, g: color.g, b: color.b, a: alpha }; - } + + if (hasOwnProp(b, 'valueOf')) { + a.valueOf = b.valueOf; } - } - // set color - if (rgba === undefined) { - throw new Error('Unknown color passed to the colorPicker. Supported are strings: rgb, hex, rgba. Object: rgb ({r:r,g:g,b:b,[a:a]}). Supplied: ' + JSON.stringify(color)); - } else { - this._setColor(rgba, setInitial); - } + return a; } - }, { - key: 'show', - /** - * this shows the color picker at a location. The hue circle is constructed once and stored. - * @param x - * @param y - */ - value: function show(x, y) { - this.applied = false; - this.frame.style.display = 'block'; - this.frame.style.top = y + 'px'; - this.frame.style.left = x + 'px'; - this._generateHueCircle(); + function create_utc__createUTC (input, format, locale, strict) { + return createLocalOrUTC(input, format, locale, strict, true).utc(); } - }, { - key: '_hide', - - // ------------------------------------------ PRIVATE ----------------------------- // - - /** - * Hide the picker. Is called by the cancel button. - * Optional boolean to store the previous color for easy access later on. - * @param storePrevious - * @private - */ - value: function _hide() { - var storePrevious = arguments[0] === undefined ? true : arguments[0]; - - // store the previous color for next time; - if (storePrevious === true) { - this.previousColor = util.extend({}, this.color); - } - if (this.applied === true) { - this.updateCallback(this.initialColor); - } + function valid__isValid(m) { + if (m._isValid == null) { + m._isValid = !isNaN(m._d.getTime()) && + m._pf.overflow < 0 && + !m._pf.empty && + !m._pf.invalidMonth && + !m._pf.nullInput && + !m._pf.invalidFormat && + !m._pf.userInvalidated; - this.frame.style.display = 'none'; + if (m._strict) { + m._isValid = m._isValid && + m._pf.charsLeftOver === 0 && + m._pf.unusedTokens.length === 0 && + m._pf.bigHour === undefined; + } + } + return m._isValid; } - }, { - key: '_save', - /** - * bound to the save button. Saves and hides. - * @private - */ - value: function _save() { - this.updateCallback(this.color); - this.applied = false; - this._hide(); - } - }, { - key: '_apply', + function valid__createInvalid (flags) { + var m = create_utc__createUTC(NaN); + if (flags != null) { + extend(m._pf, flags); + } + else { + m._pf.userInvalidated = true; + } - /** - * Bound to apply button. Saves but does not close. Is undone by the cancel button. - * @private - */ - value: function _apply() { - this.applied = true; - this.updateCallback(this.color); - this._updatePicker(this.color); + return m; } - }, { - key: '_loadLast', - /** - * load the color from the previous session. - * @private - */ - value: function _loadLast() { - if (this.previousColor !== undefined) { - this.setColor(this.previousColor, false); - } else { - alert('There is no last color to load...'); - } - } - }, { - key: '_setColor', + var momentProperties = utils_hooks__hooks.momentProperties = []; - /** - * set the color, place the picker - * @param rgba - * @param setInitial - * @private - */ - value: function _setColor(rgba) { - var setInitial = arguments[1] === undefined ? true : arguments[1]; + function copyConfig(to, from) { + var i, prop, val; - // store the initial color - if (setInitial === true) { - this.initialColor = util.extend({}, rgba); - } + 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 = from._pf; + } + if (typeof from._locale !== 'undefined') { + to._locale = from._locale; + } - this.color = rgba; - var hsv = util.RGBToHSV(rgba.r, rgba.g, rgba.b); + if (momentProperties.length > 0) { + for (i in momentProperties) { + prop = momentProperties[i]; + val = from[prop]; + if (typeof val !== 'undefined') { + to[prop] = val; + } + } + } - var angleConvert = 2 * Math.PI; - var radius = this.r * hsv.s; - var x = this.centerCoordinates.x + radius * Math.sin(angleConvert * hsv.h); - var y = this.centerCoordinates.y + radius * Math.cos(angleConvert * hsv.h); + return to; + } - this.colorPickerSelector.style.left = x - 0.5 * this.colorPickerSelector.clientWidth + 'px'; - this.colorPickerSelector.style.top = y - 0.5 * this.colorPickerSelector.clientHeight + 'px'; + var updateInProgress = false; - this._updatePicker(rgba); + // 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; + } } - }, { - key: '_setOpacity', - /** - * bound to opacity control - * @param value - * @private - */ - value: function _setOpacity(value) { - this.color.a = value / 100; - this._updatePicker(this.color); + function isMoment (obj) { + return obj instanceof Moment || (obj != null && hasOwnProp(obj, '_isAMomentObject')); } - }, { - key: '_setBrightness', - /** - * bound to brightness control - * @param value - * @private - */ - value: function _setBrightness(value) { - var hsv = util.RGBToHSV(this.color.r, this.color.g, this.color.b); - hsv.v = value / 100; - var rgba = util.HSVToRGB(hsv.h, hsv.s, hsv.v); - rgba['a'] = this.color.a; - this.color = rgba; - this._updatePicker(); - } - }, { - key: '_updatePicker', + function toInt(argumentForCoercion) { + var coercedNumber = +argumentForCoercion, + value = 0; - /** - * update the colorpicker. A black circle overlays the hue circle to mimic the brightness decreasing. - * @param rgba - * @private - */ - value: function _updatePicker() { - var rgba = arguments[0] === undefined ? this.color : arguments[0]; + if (coercedNumber !== 0 && isFinite(coercedNumber)) { + if (coercedNumber >= 0) { + value = Math.floor(coercedNumber); + } else { + value = Math.ceil(coercedNumber); + } + } - var hsv = util.RGBToHSV(rgba.r, rgba.g, rgba.b); - var ctx = this.colorPickerCanvas.getContext('2d'); - if (this.pixelRation === 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); + return value; + } - // clear the canvas - var w = this.colorPickerCanvas.clientWidth; - var h = this.colorPickerCanvas.clientHeight; - ctx.clearRect(0, 0, w, h); + 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; + } - ctx.putImageData(this.hueCircle, 0, 0); - ctx.fillStyle = 'rgba(0,0,0,' + (1 - hsv.v) + ')'; - ctx.circle(this.centerCoordinates.x, this.centerCoordinates.y, this.r); - ctx.fill(); + function Locale() { + } - this.brightnessRange.value = 100 * hsv.v; - this.opacityRange.value = 100 * rgba.a; + var locales = {}; + var globalLocale; - this.initialColorDiv.style.backgroundColor = 'rgba(' + this.initialColor.r + ',' + this.initialColor.g + ',' + this.initialColor.b + ',' + this.initialColor.a + ')'; - this.newColorDiv.style.backgroundColor = 'rgba(' + this.color.r + ',' + this.color.g + ',' + this.color.b + ',' + this.color.a + ')'; + function normalizeLocale(key) { + return key ? key.toLowerCase().replace('_', '-') : key; } - }, { - key: '_setSize', - /** - * used by create to set the size of the canvas. - * @private - */ - value: function _setSize() { - this.colorPickerCanvas.style.width = '100%'; - this.colorPickerCanvas.style.height = '100%'; + // 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; - this.colorPickerCanvas.width = 289 * this.pixelRatio; - this.colorPickerCanvas.height = 289 * this.pixelRatio; + 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; } - }, { - key: '_create', - /** - * create all dom elements - * TODO: cleanup, lots of similar dom elements - * @private - */ - value: function _create() { - this.frame = document.createElement('div'); - this.frame.className = 'vis-color-picker'; + 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.colorPickerDiv = document.createElement('div'); - this.colorPickerSelector = document.createElement('div'); - this.colorPickerSelector.className = 'vis-selector'; - this.colorPickerDiv.appendChild(this.colorPickerSelector); + // 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); + } - this.colorPickerCanvas = document.createElement('canvas'); - this.colorPickerDiv.appendChild(this.colorPickerCanvas); + if (data) { + // moment.duration._locale = moment._locale = data; + globalLocale = data; + } + } - if (!this.colorPickerCanvas.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.colorPickerCanvas.appendChild(noCanvas); - } else { - var ctx = this.colorPickerCanvas.getContext('2d'); - this.pixelRatio = (window.devicePixelRatio || 1) / (ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1); + return globalLocale._abbr; + } - this.colorPickerCanvas.getContext('2d').setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0); - } + function defineLocale (name, values) { + if (values !== null) { + values.abbr = name; + if (!locales[name]) { + locales[name] = new Locale(); + } + locales[name].set(values); - this.colorPickerDiv.className = 'vis-color'; + // backwards compat for now: also set the locale + locale_locales__getSetGlobalLocale(name); - this.opacityDiv = document.createElement('div'); - this.opacityDiv.className = 'vis-opacity'; + return locales[name]; + } else { + // useful for testing + delete locales[name]; + return null; + } + } - this.brightnessDiv = document.createElement('div'); - this.brightnessDiv.className = 'vis-brightness'; + // returns locale data + function locale_locales__getLocale (key) { + var locale; - this.arrowDiv = document.createElement('div'); - this.arrowDiv.className = 'vis-arrow'; + if (key && key._locale && key._locale._abbr) { + key = key._locale._abbr; + } - this.opacityRange = document.createElement('input'); - this.opacityRange.type = 'range'; - this.opacityRange.min = '0'; - this.opacityRange.max = '100'; - this.opacityRange.value = '100'; - this.opacityRange.className = 'vis-range'; + if (!key) { + return globalLocale; + } - this.brightnessRange = document.createElement('input'); - this.brightnessRange.type = 'range'; - this.brightnessRange.min = '0'; - this.brightnessRange.max = '100'; - this.brightnessRange.value = '100'; - this.brightnessRange.className = 'vis-range'; + if (!isArray(key)) { + //short-circuit everything else + locale = loadLocale(key); + if (locale) { + return locale; + } + key = [key]; + } - this.opacityDiv.appendChild(this.opacityRange); - this.brightnessDiv.appendChild(this.brightnessRange); + return chooseLocale(key); + } - var me = this; - this.opacityRange.onchange = function () { - me._setOpacity(this.value); - }; - this.opacityRange.oninput = function () { - me._setOpacity(this.value); - }; - this.brightnessRange.onchange = function () { - me._setBrightness(this.value); - }; - this.brightnessRange.oninput = function () { - me._setBrightness(this.value); - }; + var aliases = {}; - this.brightnessLabel = document.createElement('div'); - this.brightnessLabel.className = 'vis-label vis-brightness'; - this.brightnessLabel.innerHTML = 'brightness:'; + function addUnitAlias (unit, shorthand) { + var lowerCase = unit.toLowerCase(); + aliases[lowerCase] = aliases[lowerCase + 's'] = aliases[shorthand] = unit; + } - this.opacityLabel = document.createElement('div'); - this.opacityLabel.className = 'vis-label vis-opacity'; - this.opacityLabel.innerHTML = 'opacity:'; + function normalizeUnits(units) { + return typeof units === 'string' ? aliases[units] || aliases[units.toLowerCase()] : undefined; + } - this.newColorDiv = document.createElement('div'); - this.newColorDiv.className = 'vis-new-color'; - this.newColorDiv.innerHTML = 'new'; + function normalizeObjectUnits(inputObject) { + var normalizedInput = {}, + normalizedProp, + prop; - this.initialColorDiv = document.createElement('div'); - this.initialColorDiv.className = 'vis-initial-color'; - this.initialColorDiv.innerHTML = 'initial'; + for (prop in inputObject) { + if (hasOwnProp(inputObject, prop)) { + normalizedProp = normalizeUnits(prop); + if (normalizedProp) { + normalizedInput[normalizedProp] = inputObject[prop]; + } + } + } - this.cancelButton = document.createElement('div'); - this.cancelButton.className = 'vis-button vis-cancel'; - this.cancelButton.innerHTML = 'cancel'; - this.cancelButton.onclick = this._hide.bind(this, false); + return normalizedInput; + } - this.applyButton = document.createElement('div'); - this.applyButton.className = 'vis-button vis-apply'; - this.applyButton.innerHTML = 'apply'; - this.applyButton.onclick = this._apply.bind(this); + 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); + } + }; + } - this.saveButton = document.createElement('div'); - this.saveButton.className = 'vis-button vis-save'; - this.saveButton.innerHTML = 'save'; - this.saveButton.onclick = this._save.bind(this); + function get_set__get (mom, unit) { + return mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit](); + } - this.loadButton = document.createElement('div'); - this.loadButton.className = 'vis-button vis-load'; - this.loadButton.innerHTML = 'load last'; - this.loadButton.onclick = this._loadLast.bind(this); + function get_set__set (mom, unit, value) { + return mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value); + } - this.frame.appendChild(this.colorPickerDiv); - this.frame.appendChild(this.arrowDiv); - this.frame.appendChild(this.brightnessLabel); - this.frame.appendChild(this.brightnessDiv); - this.frame.appendChild(this.opacityLabel); - this.frame.appendChild(this.opacityDiv); - this.frame.appendChild(this.newColorDiv); - this.frame.appendChild(this.initialColorDiv); + // MOMENTS - this.frame.appendChild(this.cancelButton); - this.frame.appendChild(this.applyButton); - this.frame.appendChild(this.saveButton); - this.frame.appendChild(this.loadButton); + 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; } - }, { - key: '_bindHammer', - /** - * bind hammer to the color picker - * @private - */ - value: function _bindHammer() { - var _this = this; + function zeroFill(number, targetLength, forceSign) { + var output = '' + Math.abs(number), + sign = number >= 0; - this.drag = {}; - this.pinch = {}; - this.hammer = new Hammer(this.colorPickerCanvas); - this.hammer.get('pinch').set({ enable: true }); + while (output.length < targetLength) { + output = '0' + output; + } + return (sign ? (forceSign ? '+' : '') : '-') + output; + } - hammerUtil.onTouch(this.hammer, function (event) { - _this._moveSelector(event); - }); - this.hammer.on('tap', function (event) { - _this._moveSelector(event); - }); - this.hammer.on('panstart', function (event) { - _this._moveSelector(event); - }); - this.hammer.on('panmove', function (event) { - _this._moveSelector(event); - }); - this.hammer.on('panend', function (event) { - _this._moveSelector(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 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); + }; + } } - }, { - key: '_generateHueCircle', - /** - * generate the hue circle. This is relatively heavy (200ms) and is done only once on the first time it is shown. - * @private - */ - value: function _generateHueCircle() { - if (this.generated === false) { - var ctx = this.colorPickerCanvas.getContext('2d'); - if (this.pixelRation === undefined) { - this.pixelRatio = (window.devicePixelRatio || 1) / (ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1); + function removeFormattingTokens(input) { + if (input.match(/\[[\s\S]/)) { + return input.replace(/^\[|\]$/g, ''); } - ctx.setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0); + return input.replace(/\\/g, ''); + } - // clear the canvas - var w = this.colorPickerCanvas.clientWidth; - var h = this.colorPickerCanvas.clientHeight; - ctx.clearRect(0, 0, w, h); + function makeFormatFunction(format) { + var array = format.match(formattingTokens), i, length; - // draw hue circle - var x = undefined, - y = undefined, - hue = undefined, - sat = undefined; - this.centerCoordinates = { x: w * 0.5, y: h * 0.5 }; - this.r = 0.49 * w; - var angleConvert = 2 * Math.PI / 360; - var hfac = 1 / 360; - var sfac = 1 / this.r; - var rgb = undefined; - for (hue = 0; hue < 360; hue++) { - for (sat = 0; sat < this.r; sat++) { - x = this.centerCoordinates.x + sat * Math.sin(angleConvert * hue); - y = this.centerCoordinates.y + sat * Math.cos(angleConvert * hue); - rgb = util.HSVToRGB(hue * hfac, sat * sfac, 1); - ctx.fillStyle = 'rgb(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ')'; - ctx.fillRect(x - 0.5, y - 0.5, 2, 2); - } + for (i = 0, length = array.length; i < length; i++) { + if (formatTokenFunctions[array[i]]) { + array[i] = formatTokenFunctions[array[i]]; + } else { + array[i] = removeFormattingTokens(array[i]); + } } - ctx.strokeStyle = 'rgba(0,0,0,1)'; - ctx.circle(this.centerCoordinates.x, this.centerCoordinates.y, this.r); - ctx.stroke(); - this.hueCircle = ctx.getImageData(0, 0, w, h); - } - this.generated = true; + 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; + }; } - }, { - key: '_moveSelector', - /** - * move the selector. This is called by hammer functions. - * - * @param event - * @private - */ - value: function _moveSelector(event) { - var rect = this.colorPickerDiv.getBoundingClientRect(); - var left = event.center.x - rect.left; - var top = event.center.y - rect.top; + // format date using native date object + function formatMoment(m, format) { + if (!m.isValid()) { + return m.localeData().invalidDate(); + } - var centerY = 0.5 * this.colorPickerDiv.clientHeight; - var centerX = 0.5 * this.colorPickerDiv.clientWidth; + format = expandFormat(format, m.localeData()); - var x = left - centerX; - var y = top - centerY; + if (!formatFunctions[format]) { + formatFunctions[format] = makeFormatFunction(format); + } - var angle = Math.atan2(x, y); - var radius = 0.98 * Math.min(Math.sqrt(x * x + y * y), centerX); + return formatFunctions[format](m); + } - var newTop = Math.cos(angle) * radius + centerY; - var newLeft = Math.sin(angle) * radius + centerX; + function expandFormat(format, locale) { + var i = 5; - this.colorPickerSelector.style.top = newTop - 0.5 * this.colorPickerSelector.clientHeight + 'px'; - this.colorPickerSelector.style.left = newLeft - 0.5 * this.colorPickerSelector.clientWidth + 'px'; + function replaceLongDateFormatTokens(input) { + return locale.longDateFormat(input) || input; + } - // set color - var h = angle / (2 * Math.PI); - h = h < 0 ? h + 1 : h; - var s = radius / this.r; - var hsv = util.RGBToHSV(this.color.r, this.color.g, this.color.b); - hsv.h = h; - hsv.s = s; - var rgba = util.HSVToRGB(hsv.h, hsv.s, hsv.v); - rgba['a'] = this.color.a; - this.color = rgba; + localFormattingTokens.lastIndex = 0; + while (i >= 0 && localFormattingTokens.test(format)) { + format = format.replace(localFormattingTokens, replaceLongDateFormatTokens); + localFormattingTokens.lastIndex = 0; + i -= 1; + } - // update previews - this.initialColorDiv.style.backgroundColor = 'rgba(' + this.initialColor.r + ',' + this.initialColor.g + ',' + this.initialColor.b + ',' + this.initialColor.a + ')'; - this.newColorDiv.style.backgroundColor = 'rgba(' + this.color.r + ',' + this.color.g + ',' + this.color.b + ',' + this.color.a + ')'; + return format; } - }]); - return ColorPicker; - })(); + 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 - exports['default'] = ColorPicker; - module.exports = exports['default']; + var matchUnsigned = /\d+/; // 0 - inf + var matchSigned = /[+-]?\d+/; // -inf - inf -/***/ }, -/* 59 */ -/***/ function(module, exports, __webpack_require__) { + var matchOffset = /Z|[+-]\d\d:?\d\d/gi; // +00:00 -00:00 +0000 -0000 or Z - function webpackContext(req) { - throw new Error("Cannot find module '" + req + "'."); - } - webpackContext.keys = function() { return []; }; - webpackContext.resolve = webpackContext; - module.exports = webpackContext; - webpackContext.id = 59; + 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; -/***/ }, -/* 60 */ -/***/ function(module, exports, __webpack_require__) { + var regexes = {}; - module.exports = function(module) { - if(!module.webpackPolyfill) { - module.deprecate = function() {}; - module.paths = []; - // module.parent = undefined by default - module.children = []; - module.webpackPolyfill = 1; - } - return module; - } + 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)); + } -/***/ }, -/* 61 */ -/***/ function(module, exports, __webpack_require__) { + return regexes[token](config._strict, config._locale); + } - /* WEBPACK VAR INJECTION */(function(__webpack_amd_options__) {module.exports = __webpack_amd_options__; + // 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, '\\$&'); + } - /* WEBPACK VAR INJECTION */}.call(exports, {})) + var tokens = {}; -/***/ }, -/* 62 */ -/***/ function(module, exports, __webpack_require__) { + 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; + } + } - var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;"use strict"; - /** - * Created by Alex on 11/6/2014. - */ + function addWeekParseToken (token, callback) { + addParseToken(token, function (input, array, config, token) { + config._w = config._w || {}; + callback(input, config._w, config, token); + }); + } - // https://github.com/umdjs/umd/blob/master/returnExports.js#L40-L60 - // if the module has no dependencies, the above pattern can be simplified to - (function (root, factory) { - if (true) { - // AMD. Register as an anonymous module. - !(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); - } else if (typeof exports === 'object') { - // Node. Does not work with strict CommonJS, but - // only CommonJS-like environments that support module.exports, - // like Node. - module.exports = factory(); - } else { - // Browser globals (root is window) - root.keycharm = factory(); - } - }(this, function () { + function addTimeToArrayFromToken(token, input, config) { + if (input != null && hasOwnProp(tokens, token)) { + tokens[token](input, config._a, config, token); + } + } - function keycharm(options) { - var preventDefault = options && options.preventDefault || false; + var YEAR = 0; + var MONTH = 1; + var DATE = 2; + var HOUR = 3; + var MINUTE = 4; + var SECOND = 5; + var MILLISECOND = 6; - var container = options && options.container || window; - var _exportFunctions = {}; - var _bound = {keydown:{}, keyup:{}}; - var _keys = {}; - var i; + function daysInMonth(year, month) { + return new Date(Date.UTC(year, month + 1, 0)).getUTCDate(); + } - // a - z - for (i = 97; i <= 122; i++) {_keys[String.fromCharCode(i)] = {code:65 + (i - 97), shift: false};} - // A - Z - for (i = 65; i <= 90; i++) {_keys[String.fromCharCode(i)] = {code:i, shift: true};} - // 0 - 9 - for (i = 0; i <= 9; i++) {_keys['' + i] = {code:48 + i, shift: false};} - // F1 - F12 - for (i = 1; i <= 12; i++) {_keys['F' + i] = {code:111 + i, shift: false};} - // num0 - num9 - for (i = 0; i <= 9; i++) {_keys['num' + i] = {code:96 + i, shift: false};} + // FORMATTING - // numpad misc - _keys['num*'] = {code:106, shift: false}; - _keys['num+'] = {code:107, shift: false}; - _keys['num-'] = {code:109, shift: false}; - _keys['num/'] = {code:111, shift: false}; - _keys['num.'] = {code:110, shift: false}; - // arrows - _keys['left'] = {code:37, shift: false}; - _keys['up'] = {code:38, shift: false}; - _keys['right'] = {code:39, shift: false}; - _keys['down'] = {code:40, shift: false}; - // extra keys - _keys['space'] = {code:32, shift: false}; - _keys['enter'] = {code:13, shift: false}; - _keys['shift'] = {code:16, shift: undefined}; - _keys['esc'] = {code:27, shift: false}; - _keys['backspace'] = {code:8, shift: false}; - _keys['tab'] = {code:9, shift: false}; - _keys['ctrl'] = {code:17, shift: false}; - _keys['alt'] = {code:18, shift: false}; - _keys['delete'] = {code:46, shift: false}; - _keys['pageup'] = {code:33, shift: false}; - _keys['pagedown'] = {code:34, shift: false}; - // symbols - _keys['='] = {code:187, shift: false}; - _keys['-'] = {code:189, shift: false}; - _keys[']'] = {code:221, shift: false}; - _keys['['] = {code:219, shift: false}; + 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); + }); - var down = function(event) {handleEvent(event,'keydown');}; - var up = function(event) {handleEvent(event,'keyup');}; + // ALIASES - // handle the actualy bound key with the event - var handleEvent = function(event,type) { - if (_bound[type][event.keyCode] !== undefined) { - var bound = _bound[type][event.keyCode]; - for (var i = 0; i < bound.length; i++) { - if (bound[i].shift === undefined) { - bound[i].fn(event); - } - else if (bound[i].shift == true && event.shiftKey == true) { - bound[i].fn(event); - } - else if (bound[i].shift == false && event.shiftKey == false) { - bound[i].fn(event); - } - } + addUnitAlias('month', 'M'); - if (preventDefault == true) { - event.preventDefault(); - } - } - }; + // PARSING - // bind a key to a callback - _exportFunctions.bind = function(key, callback, type) { - if (type === undefined) { - type = 'keydown'; - } - if (_keys[key] === undefined) { - throw new Error("unsupported key: " + key); - } - if (_bound[type][_keys[key].code] === undefined) { - _bound[type][_keys[key].code] = []; - } - _bound[type][_keys[key].code].push({fn:callback, shift:_keys[key].shift}); - }; + addRegexToken('M', match1to2); + addRegexToken('MM', match1to2, match2); + addRegexToken('MMM', matchWord); + addRegexToken('MMMM', matchWord); + addParseToken(['M', 'MM'], function (input, array) { + array[MONTH] = toInt(input) - 1; + }); - // bind all keys to a call back (demo purposes) - _exportFunctions.bindAll = function(callback, type) { - if (type === undefined) { - type = 'keydown'; - } - for (var key in _keys) { - if (_keys.hasOwnProperty(key)) { - _exportFunctions.bind(key,callback,type); + 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 { + config._pf.invalidMonth = input; } - } - }; + }); - // get the key label from an event - _exportFunctions.getKey = function(event) { - for (var key in _keys) { - if (_keys.hasOwnProperty(key)) { - if (event.shiftKey == true && _keys[key].shift == true && event.keyCode == _keys[key].code) { - return key; - } - else if (event.shiftKey == false && _keys[key].shift == false && event.keyCode == _keys[key].code) { - return key; - } - else if (event.keyCode == _keys[key].code && key == 'shift') { - return key; - } - } - } - return "unknown key, currently not supported"; - }; + // LOCALES - // unbind either a specific callback from a key or all of them (by leaving callback undefined) - _exportFunctions.unbind = function(key, callback, type) { - if (type === undefined) { - type = 'keydown'; - } - if (_keys[key] === undefined) { - throw new Error("unsupported key: " + key); - } - if (callback !== undefined) { - var newBindings = []; - var bound = _bound[type][_keys[key].code]; - if (bound !== undefined) { - for (var i = 0; i < bound.length; i++) { - if (!(bound[i].fn == callback && bound[i].shift == _keys[key].shift)) { - newBindings.push(_bound[type][_keys[key].code][i]); - } - } - } - _bound[type][_keys[key].code] = newBindings; - } - else { - _bound[type][_keys[key].code] = []; - } - }; + var defaultLocaleMonths = 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'); + function localeMonths (m) { + return this._months[m.month()]; + } - // reset all bound variables. - _exportFunctions.reset = function() { - _bound = {keydown:{}, keyup:{}}; - }; + var defaultLocaleMonthsShort = 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'); + function localeMonthsShort (m) { + return this._monthsShort[m.month()]; + } - // unbind all listeners and reset all variables. - _exportFunctions.destroy = function() { - _bound = {keydown:{}, keyup:{}}; - container.removeEventListener('keydown', down, true); - container.removeEventListener('keyup', up, true); - }; + function localeMonthsParse (monthName, format, strict) { + var i, mom, regex; - // create listeners. - container.addEventListener('keydown',down,true); - container.addEventListener('keyup',up,true); + if (!this._monthsParse) { + this._monthsParse = []; + this._longMonthsParse = []; + this._shortMonthsParse = []; + } - // return the public functions. - return _exportFunctions; - } + 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; + } + } + } - return keycharm; - })); + // 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; + } -/***/ }, -/* 63 */ -/***/ function(module, exports, __webpack_require__) { + function getSetMonth (value) { + if (value != null) { + setMonth(this, value); + utils_hooks__hooks.updateOffset(this, true); + return this; + } else { + return get_set__get(this, 'Month'); + } + } - "use strict"; + function getDaysInMonth () { + return daysInMonth(this.year(), this.month()); + } - Object.defineProperty(exports, "__esModule", { - value: true - }); + function checkOverflow (m) { + var overflow; + var a = m._a; - var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + if (a && m._pf.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; - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + if (m._pf._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) { + overflow = DATE; + } - var util = __webpack_require__(1); + m._pf.overflow = overflow; + } - /** - * @class Groups - * This class can store groups and options specific for groups. - */ + return m; + } - var Groups = (function () { - function Groups() { - _classCallCheck(this, Groups); + function warn(msg) { + if (utils_hooks__hooks.suppressDeprecationWarnings === false && typeof console !== 'undefined' && console.warn) { + console.warn('Deprecation warning: ' + msg); + } + } - this.clear(); - this.defaultIndex = 0; - this.groupsArray = []; - this.groupIndex = 0; + function deprecate(msg, fn) { + var firstTime = true; + return extend(function () { + if (firstTime) { + warn(msg); + firstTime = false; + } + return fn.apply(this, arguments); + }, fn); + } - this.defaultGroups = [{ border: "#2B7CE9", background: "#97C2FC", highlight: { border: "#2B7CE9", background: "#D2E5FF" }, hover: { border: "#2B7CE9", background: "#D2E5FF" } }, // 0: blue - { border: "#FFA500", background: "#FFFF00", highlight: { border: "#FFA500", background: "#FFFFA3" }, hover: { border: "#FFA500", background: "#FFFFA3" } }, // 1: yellow - { border: "#FA0A10", background: "#FB7E81", highlight: { border: "#FA0A10", background: "#FFAFB1" }, hover: { border: "#FA0A10", background: "#FFAFB1" } }, // 2: red - { border: "#41A906", background: "#7BE141", highlight: { border: "#41A906", background: "#A1EC76" }, hover: { border: "#41A906", background: "#A1EC76" } }, // 3: green - { border: "#E129F0", background: "#EB7DF4", highlight: { border: "#E129F0", background: "#F0B3F5" }, hover: { border: "#E129F0", background: "#F0B3F5" } }, // 4: magenta - { border: "#7C29F0", background: "#AD85E4", highlight: { border: "#7C29F0", background: "#D3BDF0" }, hover: { border: "#7C29F0", background: "#D3BDF0" } }, // 5: purple - { border: "#C37F00", background: "#FFA807", highlight: { border: "#C37F00", background: "#FFCA66" }, hover: { border: "#C37F00", background: "#FFCA66" } }, // 6: orange - { border: "#4220FB", background: "#6E6EFD", highlight: { border: "#4220FB", background: "#9B9BFD" }, hover: { border: "#4220FB", background: "#9B9BFD" } }, // 7: darkblue - { border: "#FD5A77", background: "#FFC0CB", highlight: { border: "#FD5A77", background: "#FFD1D9" }, hover: { border: "#FD5A77", background: "#FFD1D9" } }, // 8: pink - { border: "#4AD63A", background: "#C2FABC", highlight: { border: "#4AD63A", background: "#E6FFE3" }, hover: { border: "#4AD63A", background: "#E6FFE3" } }, // 9: mint + var deprecations = {}; - { border: "#990000", background: "#EE0000", highlight: { border: "#BB0000", background: "#FF3333" }, hover: { border: "#BB0000", background: "#FF3333" } }, // 10:bright red + function deprecateSimple(name, msg) { + if (!deprecations[name]) { + warn(msg); + deprecations[name] = true; + } + } - { border: "#FF6000", background: "#FF6000", highlight: { border: "#FF6000", background: "#FF6000" }, hover: { border: "#FF6000", background: "#FF6000" } }, // 12: real orange - { border: "#97C2FC", background: "#2B7CE9", highlight: { border: "#D2E5FF", background: "#2B7CE9" }, hover: { border: "#D2E5FF", background: "#2B7CE9" } }, // 13: blue - { border: "#399605", background: "#255C03", highlight: { border: "#399605", background: "#255C03" }, hover: { border: "#399605", background: "#255C03" } }, // 14: green - { border: "#B70054", background: "#FF007E", highlight: { border: "#B70054", background: "#FF007E" }, hover: { border: "#B70054", background: "#FF007E" } }, // 15: magenta - { border: "#AD85E4", background: "#7C29F0", highlight: { border: "#D3BDF0", background: "#7C29F0" }, hover: { border: "#D3BDF0", background: "#7C29F0" } }, // 16: purple - { border: "#4557FA", background: "#000EA1", highlight: { border: "#6E6EFD", background: "#000EA1" }, hover: { border: "#6E6EFD", background: "#000EA1" } }, // 17: darkblue - { border: "#FFC0CB", background: "#FD5A77", highlight: { border: "#FFD1D9", background: "#FD5A77" }, hover: { border: "#FFD1D9", background: "#FD5A77" } }, // 18: pink - { border: "#C2FABC", background: "#74D66A", highlight: { border: "#E6FFE3", background: "#74D66A" }, hover: { border: "#E6FFE3", background: "#74D66A" } }, // 19: mint + utils_hooks__hooks.suppressDeprecationWarnings = false; - { border: "#EE0000", background: "#990000", highlight: { border: "#FF3333", background: "#BB0000" }, hover: { border: "#FF3333", background: "#BB0000" } } // 20:bright red + 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}/] ]; - this.options = {}; - this.defaultOptions = { - useDefaultGroups: true - }; - util.extend(this.options, this.defaultOptions); - } + // 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/] + ]; - _createClass(Groups, [{ - key: "setOptions", - value: function setOptions(options) { - var optionFields = ["useDefaultGroups"]; + var aspNetJsonRegex = /^\/?Date\((\-?\d+)/i; - if (options !== undefined) { - for (var groupName in options) { - if (options.hasOwnProperty(groupName)) { - if (optionFields.indexOf(groupName) === -1) { - var group = options[groupName]; - this.add(groupName, group); + // date from iso format + function configFromISO(config) { + var i, l, + string = config._i, + match = from_string__isoRegex.exec(string); + + if (match) { + config._pf.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; } - } } - }, { - key: "clear", - /** - * Clear all groups - */ - value: function clear() { - this.groups = {}; - this.groupsArray = []; + // 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); + } } - }, { - key: "get", - /** - * get group options of a groupname. If groupname is not found, a new group - * is added. - * @param {*} groupname Can be a number, string, Date, etc. - * @return {Object} group The created group, containing all group options - */ - value: function get(groupname) { - var group = this.groups[groupname]; - if (group === undefined) { - if (this.options.useDefaultGroups === false && this.groupsArray.length > 0) { - // create new group - var index = this.groupIndex % this.groupsArray.length; - this.groupIndex++; - group = {}; - group.color = this.groups[this.groupsArray[index]]; - this.groups[groupname] = group; - } else { - // create new group - var index = this.defaultIndex % this.defaultGroups.length; - this.defaultIndex++; - group = {}; - group.color = this.defaultGroups[index]; - this.groups[groupname] = group; + 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' : '')); } - } + ); - return group; + 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; } - }, { - key: "add", - /** - * Add a custom group style - * @param {String} groupName - * @param {Object} style An object containing borderColor, - * backgroundColor, etc. - * @return {Object} group The created group object - */ - value: function add(groupName, style) { - this.groups[groupName] = style; - this.groupsArray.push(groupName); - return style; + function createUTCDate (y) { + var date = new Date(Date.UTC.apply(null, arguments)); + if (y < 1970) { + date.setUTCFullYear(y); + } + return date; } - }]); - return Groups; - })(); + addFormatToken(0, ['YY', 2], 0, function () { + return this.year() % 100; + }); - exports["default"] = Groups; - module.exports = exports["default"]; + addFormatToken(0, ['YYYY', 4], 0, 'year'); + addFormatToken(0, ['YYYYY', 5], 0, 'year'); + addFormatToken(0, ['YYYYYY', 6, true], 0, 'year'); -/***/ }, -/* 64 */ -/***/ function(module, exports, __webpack_require__) { + // ALIASES - 'use strict'; + addUnitAlias('year', 'y'); - Object.defineProperty(exports, '__esModule', { - value: true - }); + // PARSING - 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; }; })(); + addRegexToken('Y', matchSigned); + addRegexToken('YY', match1to2, match2); + addRegexToken('YYYY', match1to4, match4); + addRegexToken('YYYYY', match1to6, match6); + addRegexToken('YYYYYY', match1to6, match6); - function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + addParseToken(['YYYY', 'YYYYY', 'YYYYYY'], YEAR); + addParseToken('YY', function (input, array) { + array[YEAR] = utils_hooks__hooks.parseTwoDigitYear(input); + }); - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + // HELPERS - var _componentsNode = __webpack_require__(78); + function daysInYear(year) { + return isLeapYear(year) ? 366 : 365; + } - var _componentsNode2 = _interopRequireDefault(_componentsNode); + function isLeapYear(year) { + return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; + } - var _componentsSharedLabel = __webpack_require__(79); + // HOOKS - var _componentsSharedLabel2 = _interopRequireDefault(_componentsSharedLabel); + utils_hooks__hooks.parseTwoDigitYear = function (input) { + return toInt(input) + (toInt(input) > 68 ? 1900 : 2000); + }; - var util = __webpack_require__(1); - var DataSet = __webpack_require__(3); - var DataView = __webpack_require__(4); + // MOMENTS - var NodesHandler = (function () { - function NodesHandler(body, images, groups, layoutEngine) { - var _this = this; + var getSetYear = makeGetSet('FullYear', false); - _classCallCheck(this, NodesHandler); + function getIsLeapYear () { + return isLeapYear(this.year()); + } - this.body = body; - this.images = images; - this.groups = groups; - this.layoutEngine = layoutEngine; + addFormatToken('w', ['ww', 2], 'wo', 'week'); + addFormatToken('W', ['WW', 2], 'Wo', 'isoWeek'); - // create the node API in the body container - this.body.functions.createNode = this.create.bind(this); + // ALIASES - this.nodesListeners = { - add: function add(event, params) { - _this.add(params.items); - }, - update: function update(event, params) { - _this.update(params.items, params.data); - }, - remove: function remove(event, params) { - _this.remove(params.items); - } - }; + addUnitAlias('week', 'w'); + addUnitAlias('isoWeek', 'W'); - this.options = {}; - this.defaultOptions = { - borderWidth: 1, - borderWidthSelected: undefined, - brokenImage: undefined, - color: { - border: '#2B7CE9', - background: '#97C2FC', - highlight: { - border: '#2B7CE9', - background: '#D2E5FF' - }, - hover: { - border: '#2B7CE9', - background: '#D2E5FF' - } - }, - fixed: { - x: false, - y: false - }, - font: { - color: '#343434', - size: 14, // px - face: 'arial', - background: 'none', - strokeWidth: 0, // px - strokeColor: '#ffffff', - align: 'horizontal' - }, - group: undefined, - hidden: false, - icon: { - face: 'FontAwesome', //'FontAwesome', - code: undefined, //'\uf007', - size: 50, //50, - color: '#2B7CE9' //'#aa00ff' - }, - image: undefined, // --> URL - label: undefined, - level: undefined, - mass: 1, - physics: true, - scaling: { - min: 10, - max: 30, - label: { - enabled: false, - min: 14, - max: 30, - maxVisible: 30, - drawThreshold: 3 - }, - customScalingFunction: function customScalingFunction(min, max, total, value) { - if (max === min) { - return 0.5; - } else { - var scale = 1 / (max - min); - return Math.max(0, (value - min) * scale); - } - } - }, - shadow: { - enabled: false, - size: 10, - x: 5, - y: 5 - }, - shape: 'ellipse', - size: 25, - title: undefined, - value: undefined, - x: undefined, - y: undefined - }; - util.extend(this.options, this.defaultOptions); + // PARSING - this.bindEventListeners(); - } + addRegexToken('w', match1to2); + addRegexToken('ww', match1to2, match2); + addRegexToken('W', match1to2); + addRegexToken('WW', match1to2, match2); - _createClass(NodesHandler, [{ - key: 'bindEventListeners', - value: function bindEventListeners() { - var _this2 = this; + addWeekParseToken(['w', 'ww', 'W', 'WW'], function (input, week, config, token) { + week[token.substr(0, 1)] = toInt(input); + }); - // refresh the nodes. Used when reverting from hierarchical layout - this.body.emitter.on('refreshNodes', this.refresh.bind(this)); - this.body.emitter.on('refresh', this.refresh.bind(this)); - this.body.emitter.on('destroy', function () { - delete _this2.body.functions.createNode; - delete _this2.nodesListeners.add; - delete _this2.nodesListeners.update; - delete _this2.nodesListeners.remove; - delete _this2.nodesListeners; - }); - } - }, { - key: 'setOptions', - value: function setOptions(options) { - if (options !== undefined) { - _componentsNode2['default'].parseOptions(this.options, options); + // HELPERS - // update the shape in all nodes - if (options.shape !== undefined) { - for (var nodeId in this.body.nodes) { - if (this.body.nodes.hasOwnProperty(nodeId)) { - this.body.nodes[nodeId].updateShape(); - } - } - } + // firstDayOfWeek 0 = sun, 6 = sat + // the day of the week that starts the week + // (usually sunday or monday) + // firstDayOfWeekOfYear 0 = sun, 6 = sat + // the first week is the week that contains the first + // of this day of the week + // (eg. ISO weeks use thursday (4)) + function weekOfYear(mom, firstDayOfWeek, firstDayOfWeekOfYear) { + var end = firstDayOfWeekOfYear - firstDayOfWeek, + daysToDayOfWeek = firstDayOfWeekOfYear - mom.day(), + adjustedMoment; - // update the shape size in all nodes - if (options.font !== undefined) { - _componentsSharedLabel2['default'].parseOptions(this.options.font, options); - for (var nodeId in this.body.nodes) { - if (this.body.nodes.hasOwnProperty(nodeId)) { - this.body.nodes[nodeId].updateLabelModule(); - this.body.nodes[nodeId]._reset(); - } - } - } - // update the shape size in all nodes - if (options.size !== undefined) { - for (var nodeId in this.body.nodes) { - if (this.body.nodes.hasOwnProperty(nodeId)) { - this.body.nodes[nodeId]._reset(); - } - } + if (daysToDayOfWeek > end) { + daysToDayOfWeek -= 7; } - // update the state of the letiables if needed - if (options.hidden !== undefined || options.physics !== undefined) { - this.body.emitter.emit('_dataChanged'); + if (daysToDayOfWeek < end - 7) { + daysToDayOfWeek += 7; } - } - } - }, { - key: 'setData', - /** - * Set a data set with nodes for the network - * @param {Array | DataSet | DataView} nodes The data containing the nodes. - * @private - */ - value: function setData(nodes) { - var _this3 = this; + adjustedMoment = local__createLocal(mom).add(daysToDayOfWeek, 'd'); + return { + week: Math.ceil(adjustedMoment.dayOfYear() / 7), + year: adjustedMoment.year() + }; + } - var doNotEmit = arguments[1] === undefined ? false : arguments[1]; + // LOCALES - var oldNodesData = this.body.data.nodes; + function localeWeek (mom) { + return weekOfYear(mom, this._week.dow, this._week.doy).week; + } - if (nodes instanceof DataSet || nodes instanceof DataView) { - this.body.data.nodes = nodes; - } else if (Array.isArray(nodes)) { - this.body.data.nodes = new DataSet(); - this.body.data.nodes.add(nodes); - } else if (!nodes) { - this.body.data.nodes = new DataSet(); - } else { - throw new TypeError('Array or DataSet expected'); - } + var defaultLocaleWeek = { + dow : 0, // Sunday is the first day of the week. + doy : 6 // The week that contains Jan 1st is the first week of the year. + }; - if (oldNodesData) { - // unsubscribe from old dataset - util.forEach(this.nodesListeners, function (callback, event) { - oldNodesData.off(event, callback); - }); - } + function localeFirstDayOfWeek () { + return this._week.dow; + } - // remove drawn nodes - this.body.nodes = {}; + function localeFirstDayOfYear () { + return this._week.doy; + } - if (this.body.data.nodes) { - (function () { - // subscribe to new dataset - var me = _this3; - util.forEach(_this3.nodesListeners, function (callback, event) { - me.body.data.nodes.on(event, callback); - }); + // MOMENTS - // draw all new nodes - var ids = _this3.body.data.nodes.getIds(); - _this3.add(ids, true); - })(); - } + function getSetWeek (input) { + var week = this.localeData().week(this); + return input == null ? week : this.add((input - week) * 7, 'd'); + } - if (doNotEmit === false) { - this.body.emitter.emit('_dataChanged'); - } + function getSetISOWeek (input) { + var week = weekOfYear(this, 1, 4).week; + return input == null ? week : this.add((input - week) * 7, 'd'); } - }, { - key: 'add', - /** - * Add nodes - * @param {Number[] | String[]} ids - * @private - */ - value: function add(ids) { - var doNotEmit = arguments[1] === undefined ? false : arguments[1]; + addFormatToken('DDD', ['DDDD', 3], 'DDDo', 'dayOfYear'); - var id = undefined; - var newNodes = []; - for (var i = 0; i < ids.length; i++) { - id = ids[i]; - var _properties = this.body.data.nodes.get(id); - var node = this.create(_properties); - newNodes.push(node); - this.body.nodes[id] = node; // note: this may replace an existing node - } + // ALIASES - this.layoutEngine.positionInitially(newNodes); + addUnitAlias('dayOfYear', 'DDD'); - if (doNotEmit === false) { - this.body.emitter.emit('_dataChanged'); - } - } - }, { - key: 'update', + // PARSING - /** - * Update existing nodes, or create them when not yet existing - * @param {Number[] | String[]} ids - * @private - */ - value: function update(ids, changedData) { - var nodes = this.body.nodes; - var dataChanged = false; - for (var i = 0; i < ids.length; i++) { - var id = ids[i]; - var node = nodes[id]; - var data = changedData[i]; - if (node !== undefined) { - // update node - node.setOptions(data, this.constants); - } else { - dataChanged = true; - // create node - node = this.create(properties); - nodes[id] = node; - } - } + addRegexToken('DDD', match1to3); + addRegexToken('DDDD', match3); + addParseToken(['DDD', 'DDDD'], function (input, array, config) { + config._dayOfYear = toInt(input); + }); - if (dataChanged === true) { - this.body.emitter.emit('_dataChanged'); - } else { - this.body.emitter.emit('_dataUpdated'); - } - } - }, { - key: 'remove', + // HELPERS - /** - * Remove existing nodes. If nodes do not exist, the method will just ignore it. - * @param {Number[] | String[]} ids - * @private - */ - value: function remove(ids) { - var nodes = this.body.nodes; + //http://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday + function dayOfYearFromWeeks(year, week, weekday, firstDayOfWeekOfYear, firstDayOfWeek) { + var d = createUTCDate(year, 0, 1).getUTCDay(); + var daysToAdd; + var dayOfYear; - for (var i = 0; i < ids.length; i++) { - var id = ids[i]; - delete nodes[id]; - } + d = d === 0 ? 7 : d; + weekday = weekday != null ? weekday : firstDayOfWeek; + daysToAdd = firstDayOfWeek - d + (d > firstDayOfWeekOfYear ? 7 : 0) - (d < firstDayOfWeek ? 7 : 0); + dayOfYear = 7 * (week - 1) + (weekday - firstDayOfWeek) + daysToAdd + 1; - this.body.emitter.emit('_dataChanged'); + return { + year : dayOfYear > 0 ? year : year - 1, + dayOfYear : dayOfYear > 0 ? dayOfYear : daysInYear(year - 1) + dayOfYear + }; } - }, { - key: 'create', - /** - * create a node - * @param properties - * @param constructorClass - */ - value: function create(properties) { - var constructorClass = arguments[1] === undefined ? _componentsNode2['default'] : arguments[1]; + // MOMENTS - return new constructorClass(properties, this.body, this.images, this.groups, this.options); - } - }, { - key: 'refresh', - value: function refresh() { - var nodes = this.body.nodes; - for (var nodeId in nodes) { - var node = undefined; - if (nodes.hasOwnProperty(nodeId)) { - node = nodes[nodeId]; - } - var data = this.body.data.nodes._data[nodeId]; - if (node !== undefined && data !== undefined) { - node.setOptions({ fixed: false }); - node.setOptions(data); - } - } + function getSetDayOfYear (input) { + var dayOfYear = Math.round((this.clone().startOf('day') - this.clone().startOf('year')) / 864e5) + 1; + return input == null ? dayOfYear : this.add((input - dayOfYear), 'd'); } - }, { - key: 'getPositions', - /** - * Returns the positions of the nodes. - * @param ids --> optional, can be array of nodeIds, can be string - * @returns {{}} - */ - value: function getPositions(ids) { - var dataArray = {}; - if (ids !== undefined) { - if (Array.isArray(ids) === true) { - for (var i = 0; i < ids.length; i++) { - if (this.body.nodes[ids[i]] !== undefined) { - var node = this.body.nodes[ids[i]]; - dataArray[ids[i]] = { x: Math.round(node.x), y: Math.round(node.y) }; - } - } - } else { - if (this.body.nodes[ids] !== undefined) { - var node = this.body.nodes[ids]; - dataArray[ids] = { x: Math.round(node.x), y: Math.round(node.y) }; - } + // Pick the first defined of two or three arguments. + function defaults(a, b, c) { + if (a != null) { + return a; } - } else { - for (var nodeId in this.body.nodes) { - if (this.body.nodes.hasOwnProperty(nodeId)) { - var node = this.body.nodes[nodeId]; - dataArray[nodeId] = { x: Math.round(node.x), y: Math.round(node.y) }; - } + if (b != null) { + return b; } - } - return dataArray; + return c; } - }, { - key: 'storePositions', - /** - * Load the XY positions of the nodes into the dataset. - */ - value: function storePositions() { - // todo: add support for clusters and hierarchical. - var dataArray = []; - for (var nodeId in this.body.nodes) { - if (this.body.nodes.hasOwnProperty(nodeId)) { - var node = this.body.nodes[nodeId]; - if (this.body.data.nodes._data[nodeId].x != Math.round(node.x) || this.body.data.nodes._data[nodeId].y != Math.round(node.y)) { - dataArray.push({ id: nodeId, x: Math.round(node.x), y: Math.round(node.y) }); - } + function currentDateArray(config) { + var now = new Date(); + if (config._useUTC) { + return [now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()]; } - } - this.body.data.nodes.update(dataArray); + return [now.getFullYear(), now.getMonth(), now.getDate()]; } - }, { - key: 'getBoundingBox', - /** - * get the bounding box of a node. - * @param nodeId - * @returns {j|*} - */ - value: function getBoundingBox(nodeId) { - if (this.body.nodes[nodeId] !== undefined) { - return this.body.nodes[nodeId].shape.boundingBox; - } - } - }, { - key: 'getConnectedNodes', + // convert an array to a date. + // the array should mirror the parameters below + // note: all values past the year are optional and will default to the lowest possible value. + // [year, month, day , hour, minute, second, millisecond] + function configFromArray (config) { + var i, date, input = [], currentDate, yearToUse; - /** - * Get the Ids of nodes connected to this node. - * @param nodeId - * @returns {Array} - */ - value: function getConnectedNodes(nodeId) { - var nodeList = []; - if (this.body.nodes[nodeId] !== undefined) { - var node = this.body.nodes[nodeId]; - var nodeObj = {}; // used to quickly check if node already exists - for (var i = 0; i < node.edges.length; i++) { - var edge = node.edges[i]; - if (edge.toId === nodeId) { - if (nodeObj[edge.fromId] === undefined) { - nodeList.push(edge.fromId); - nodeObj[edge.fromId] = true; - } - } else if (edge.fromId === nodeId) { - if (nodeObj[edge.toId] === undefined) { - nodeList.push(edge.toId); - nodeObj[edge.toId] = true; - } - } + if (config._d) { + return; } - } - return nodeList; - } - }, { - key: 'getEdges', - /** - * Get the ids of the edges connected to this node. - * @param nodeId - * @returns {*} - */ - value: function getEdges(nodeId) { - var edgeList = []; - if (this.body.nodes[nodeId] !== undefined) { - var node = this.body.nodes[nodeId]; - for (var i = 0; i < node.edges.length; i++) { - edgeList.push(node.edges[i].id); + currentDate = currentDateArray(config); + + //compute day of the year from weeks and weekdays + if (config._w && config._a[DATE] == null && config._a[MONTH] == null) { + dayOfYearFromWeekInfo(config); } - } - return nodeList; - } - }]); - return NodesHandler; - })(); + //if the day of the year is set, figure out what it is + if (config._dayOfYear) { + yearToUse = defaults(config._a[YEAR], currentDate[YEAR]); - exports['default'] = NodesHandler; - module.exports = exports['default']; + if (config._dayOfYear > daysInYear(yearToUse)) { + config._pf._overflowDayOfYear = true; + } -/***/ }, -/* 65 */ -/***/ function(module, exports, __webpack_require__) { + date = createUTCDate(yearToUse, 0, config._dayOfYear); + config._a[MONTH] = date.getUTCMonth(); + config._a[DATE] = date.getUTCDate(); + } - 'use strict'; + // Default to current date. + // * if no year, month, day of month are given, default to today + // * if day of month is given, default month and year + // * if month is given, default only year + // * if year is given, don't default anything + for (i = 0; i < 3 && config._a[i] == null; ++i) { + config._a[i] = input[i] = currentDate[i]; + } - Object.defineProperty(exports, '__esModule', { - value: true - }); + // Zero out whatever was not defaulted, including time + for (; i < 7; i++) { + config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i]; + } - 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; }; })(); + // Check for 24:00:00.000 + if (config._a[HOUR] === 24 && + config._a[MINUTE] === 0 && + config._a[SECOND] === 0 && + config._a[MILLISECOND] === 0) { + config._nextDay = true; + config._a[HOUR] = 0; + } - function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + config._d = (config._useUTC ? createUTCDate : createDate).apply(null, input); + // Apply timezone offset from input. The actual utcOffset can be changed + // with parseZone. + if (config._tzm != null) { + config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm); + } - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + if (config._nextDay) { + config._a[HOUR] = 24; + } + } - var _componentsEdge = __webpack_require__(80); + function dayOfYearFromWeekInfo(config) { + var w, weekYear, week, weekday, dow, doy, temp; - var _componentsEdge2 = _interopRequireDefault(_componentsEdge); + w = config._w; + if (w.GG != null || w.W != null || w.E != null) { + dow = 1; + doy = 4; - var _componentsSharedLabel = __webpack_require__(79); + // TODO: We need to take the current isoWeekYear, but that depends on + // how we interpret now (local, utc, fixed offset). So create + // a now version of current config (take local/utc/offset flags, and + // create now). + weekYear = defaults(w.GG, config._a[YEAR], weekOfYear(local__createLocal(), 1, 4).year); + week = defaults(w.W, 1); + weekday = defaults(w.E, 1); + } else { + dow = config._locale._week.dow; + doy = config._locale._week.doy; - var _componentsSharedLabel2 = _interopRequireDefault(_componentsSharedLabel); + weekYear = defaults(w.gg, config._a[YEAR], weekOfYear(local__createLocal(), dow, doy).year); + week = defaults(w.w, 1); - var util = __webpack_require__(1); - var DataSet = __webpack_require__(3); - var DataView = __webpack_require__(4); + if (w.d != null) { + // weekday -- low day numbers are considered next week + weekday = w.d; + if (weekday < dow) { + ++week; + } + } else if (w.e != null) { + // local weekday -- counting starts from begining of week + weekday = w.e + dow; + } else { + // default to begining of week + weekday = dow; + } + } + temp = dayOfYearFromWeeks(weekYear, week, weekday, doy, dow); - var EdgesHandler = (function () { - function EdgesHandler(body, images, groups) { - var _this = this; + config._a[YEAR] = temp.year; + config._dayOfYear = temp.dayOfYear; + } - _classCallCheck(this, EdgesHandler); + utils_hooks__hooks.ISO_8601 = function () {}; - this.body = body; - this.images = images; - this.groups = groups; + // date from string and format string + function configFromStringAndFormat(config) { + // TODO: Move this to another part of the creation flow to prevent circular deps + if (config._f === utils_hooks__hooks.ISO_8601) { + configFromISO(config); + return; + } - // create the edge API in the body container - this.body.functions.createEdge = this.create.bind(this); + config._a = []; + config._pf.empty = true; - this.edgesListeners = { - add: function add(event, params) { - _this.add(params.items); - }, - update: function update(event, params) { - _this.update(params.items); - }, - remove: function remove(event, params) { - _this.remove(params.items); - } - }; + // This array is used to make a Date, either with `new Date` or `Date.UTC` + var string = '' + config._i, + i, parsedInput, tokens, token, skipped, + stringLength = string.length, + totalParsedInputLength = 0; - this.options = {}; - this.defaultOptions = { - arrows: { - to: { enabled: false, scaleFactor: 1 }, // boolean / {arrowScaleFactor:1} / {enabled: false, arrowScaleFactor:1} - middle: { enabled: false, scaleFactor: 1 }, - from: { enabled: false, scaleFactor: 1 } - }, - color: { - color: '#848484', - highlight: '#848484', - hover: '#848484', - inherit: 'from', - opacity: 1 - }, - dashes: false, - font: { - color: '#343434', - size: 14, // px - face: 'arial', - background: 'none', - strokeWidth: 2, // px - strokeColor: '#ffffff', - align: 'horizontal' - }, - hidden: false, - hoverWidth: 1.5, - label: undefined, - length: undefined, - physics: true, - scaling: { - min: 1, - max: 15, - label: { - enabled: true, - min: 14, - max: 30, - maxVisible: 30, - drawThreshold: 3 - }, - customScalingFunction: function customScalingFunction(min, max, total, value) { - if (max === min) { - return 0.5; - } else { - var scale = 1 / (max - min); - return Math.max(0, (value - min) * scale); - } - } - }, - selectionWidth: 1, - selfReferenceSize: 20, - shadow: { - enabled: false, - size: 10, - x: 5, - y: 5 - }, - smooth: { - enabled: true, - dynamic: true, - type: 'continuous', - roundness: 0.5 - }, - title: undefined, - width: 1, - value: undefined - }; - - util.extend(this.options, this.defaultOptions); - - this.bindEventListeners(); - } - - _createClass(EdgesHandler, [{ - key: 'bindEventListeners', - value: function bindEventListeners() { - var _this2 = this; - - // this allows external modules to force all dynamic curves to turn static. - this.body.emitter.on('_forceDisableDynamicCurves', function (type) { - var emitChange = false; - for (var edgeId in _this2.body.edges) { - if (_this2.body.edges.hasOwnProperty(edgeId)) { - var edge = _this2.body.edges[edgeId]; - var edgeData = _this2.body.data.edges._data[edgeId]; + tokens = expandFormat(config._f, config._locale).match(formattingTokens) || []; - // only forcilby remove the smooth curve if the data has been set of the edge has the smooth curves defined. - // this is because a change in the global would not affect these curves. - if (edgeData !== undefined) { - var edgeOptions = edgeData.smooth; - if (edgeOptions !== undefined) { - if (edgeOptions.enabled === true && edgeOptions.dynamic === true) { - if (type === undefined) { - edge.setOptions({ smooth: false }); - } else { - edge.setOptions({ smooth: { dynamic: false, type: type } }); - } - emitChange = true; + for (i = 0; i < tokens.length; i++) { + token = tokens[i]; + parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0]; + if (parsedInput) { + skipped = string.substr(0, string.indexOf(parsedInput)); + if (skipped.length > 0) { + config._pf.unusedInput.push(skipped); } - } + string = string.slice(string.indexOf(parsedInput) + parsedInput.length); + totalParsedInputLength += parsedInput.length; + } + // don't parse if it's not a known token + if (formatTokenFunctions[token]) { + if (parsedInput) { + config._pf.empty = false; + } + else { + config._pf.unusedTokens.push(token); + } + addTimeToArrayFromToken(token, parsedInput, config); + } + else if (config._strict && !parsedInput) { + config._pf.unusedTokens.push(token); } - } } - if (emitChange === true) { - _this2.body.emitter.emit('_dataChanged'); + + // add remaining unparsed input length to the string + config._pf.charsLeftOver = stringLength - totalParsedInputLength; + if (string.length > 0) { + config._pf.unusedInput.push(string); } - }); - // this is called when options of EXISTING nodes or edges have changed. - this.body.emitter.on('_dataUpdated', function () { - _this2.reconnectEdges(); - _this2.markAllEdgesAsDirty(); - }); + // clear _12h flag if hour is <= 12 + if (config._pf.bigHour === true && config._a[HOUR] <= 12) { + config._pf.bigHour = undefined; + } + // handle meridiem + config._a[HOUR] = meridiemFixWrap(config._locale, config._a[HOUR], config._meridiem); - // refresh the edges. Used when reverting from hierarchical layout - this.body.emitter.on('refreshEdges', this.refresh.bind(this)); - this.body.emitter.on('refresh', this.refresh.bind(this)); - this.body.emitter.on('destroy', function () { - delete _this2.body.functions.createEdge; - delete _this2.edgesListeners.add; - delete _this2.edgesListeners.update; - delete _this2.edgesListeners.remove; - delete _this2.edgesListeners; - }); + configFromArray(config); + checkOverflow(config); } - }, { - key: 'setOptions', - value: function setOptions(options) { - if (options !== undefined) { - // use the parser from the Edge class to fill in all shorthand notations - _componentsEdge2['default'].parseOptions(this.options, options); - // hanlde multiple input cases for color - if (options.color !== undefined) { - this.markAllEdgesAsDirty(); - } - // update smooth settings in all edges - var dataChanged = false; - if (options.smooth !== undefined) { - for (var edgeId in this.body.edges) { - if (this.body.edges.hasOwnProperty(edgeId)) { - dataChanged = this.body.edges[edgeId].updateEdgeType() || dataChanged; - } - } - } + function meridiemFixWrap (locale, hour, meridiem) { + var isPm; - // update fonts in all edges - if (options.font !== undefined) { - // use the parser from the Label class to fill in all shorthand notations - _componentsSharedLabel2['default'].parseOptions(this.options, options); - for (var edgeId in this.body.edges) { - if (this.body.edges.hasOwnProperty(edgeId)) { - this.body.edges[edgeId].updateLabelModule(); + if (meridiem == null) { + // nothing to do + return hour; + } + if (locale.meridiemHour != null) { + return locale.meridiemHour(hour, meridiem); + } else if (locale.isPM != null) { + // Fallback + isPm = locale.isPM(meridiem); + if (isPm && hour < 12) { + hour += 12; } - } + if (!isPm && hour === 12) { + hour = 0; + } + return hour; + } else { + // this is not supposed to happen + return hour; } + } - // update the state of the variables if needed - if (options.hidden !== undefined || options.physics !== undefined || dataChanged === true) { - this.body.emitter.emit('_dataChanged'); + function configFromStringAndArray(config) { + var tempConfig, + bestMoment, + + scoreToBeat, + i, + currentScore; + + if (config._f.length === 0) { + config._pf.invalidFormat = true; + config._d = new Date(NaN); + return; } - } - } - }, { - key: 'setData', - /** - * Load edges by reading the data table - * @param {Array | DataSet | DataView} edges The data containing the edges. - * @private - * @private - */ - value: function setData(edges) { - var _this3 = this; + for (i = 0; i < config._f.length; i++) { + currentScore = 0; + tempConfig = copyConfig({}, config); + if (config._useUTC != null) { + tempConfig._useUTC = config._useUTC; + } + tempConfig._pf = defaultParsingFlags(); + tempConfig._f = config._f[i]; + configFromStringAndFormat(tempConfig); - var doNotEmit = arguments[1] === undefined ? false : arguments[1]; + if (!valid__isValid(tempConfig)) { + continue; + } - var oldEdgesData = this.body.data.edges; + // if there is any input that was not parsed add a penalty for that format + currentScore += tempConfig._pf.charsLeftOver; - if (edges instanceof DataSet || edges instanceof DataView) { - this.body.data.edges = edges; - } else if (Array.isArray(edges)) { - this.body.data.edges = new DataSet(); - this.body.data.edges.add(edges); - } else if (!edges) { - this.body.data.edges = new DataSet(); - } else { - throw new TypeError('Array or DataSet expected'); - } + //or tokens + currentScore += tempConfig._pf.unusedTokens.length * 10; - // TODO: is this null or undefined or false? - if (oldEdgesData) { - // unsubscribe from old dataset - util.forEach(this.edgesListeners, function (callback, event) { - oldEdgesData.off(event, callback); - }); - } + tempConfig._pf.score = currentScore; - // remove drawn edges - this.body.edges = {}; + if (scoreToBeat == null || currentScore < scoreToBeat) { + scoreToBeat = currentScore; + bestMoment = tempConfig; + } + } - // TODO: is this null or undefined or false? - if (this.body.data.edges) { - // subscribe to new dataset - util.forEach(this.edgesListeners, function (callback, event) { - _this3.body.data.edges.on(event, callback); - }); + extend(config, bestMoment || tempConfig); + } - // draw all new nodes - var ids = this.body.data.edges.getIds(); - this.add(ids, true); - } + function configFromObject(config) { + if (config._d) { + return; + } - if (doNotEmit === false) { - this.body.emitter.emit('_dataChanged'); - } + var i = normalizeObjectUnits(config._i); + config._a = [i.year, i.month, i.day || i.date, i.hour, i.minute, i.second, i.millisecond]; + + configFromArray(config); } - }, { - key: 'add', - /** - * Add edges - * @param {Number[] | String[]} ids - * @private - */ - value: function add(ids) { - var doNotEmit = arguments[1] === undefined ? false : arguments[1]; + function createFromConfig (config) { + var input = config._i, + format = config._f, + res; - var edges = this.body.edges; - var edgesData = this.body.data.edges; + config._locale = config._locale || locale_locales__getLocale(config._l); - for (var i = 0; i < ids.length; i++) { - var id = ids[i]; + if (input === null || (format === undefined && input === '')) { + return valid__createInvalid({nullInput: true}); + } - var oldEdge = edges[id]; - if (oldEdge) { - oldEdge.disconnect(); + if (typeof input === 'string') { + config._i = input = config._locale.preparse(input); } - var data = edgesData.get(id, { 'showInternalIds': true }); - edges[id] = this.create(data); - } + if (isMoment(input)) { + return new Moment(checkOverflow(input)); + } else if (isArray(format)) { + configFromStringAndArray(config); + } else if (format) { + configFromStringAndFormat(config); + } else { + configFromInput(config); + } - if (doNotEmit === false) { - this.body.emitter.emit('_dataChanged'); - } + res = new Moment(checkOverflow(config)); + if (res._nextDay) { + // Adding is smart enough around DST + res.add(1, 'd'); + res._nextDay = undefined; + } + + return res; } - }, { - key: 'update', - /** - * Update existing edges, or create them when not yet existing - * @param {Number[] | String[]} ids - * @private - */ - value: function update(ids) { - var edges = this.body.edges; - var edgesData = this.body.data.edges; - var dataChanged = false; - for (var i = 0; i < ids.length; i++) { - var id = ids[i]; - var data = edgesData.get(id); - var edge = edges[id]; - if (edge === null) { - // update edge - edge.disconnect(); - dataChanged = edge.setOptions(data) || dataChanged; // if a support node is added, data can be changed. - edge.connect(); + function configFromInput(config) { + var input = config._i; + if (input === undefined) { + config._d = new Date(); + } else if (isDate(input)) { + config._d = new Date(+input); + } else if (typeof input === 'string') { + configFromString(config); + } else if (isArray(input)) { + config._a = map(input.slice(0), function (obj) { + return parseInt(obj, 10); + }); + configFromArray(config); + } else if (typeof(input) === 'object') { + configFromObject(config); + } else if (typeof(input) === 'number') { + // from milliseconds + config._d = new Date(input); } else { - // create edge - this.body.edges[id] = this.create(data); - dataChanged = true; + utils_hooks__hooks.createFromInputFallback(config); } - } - - if (dataChanged === true) { - this.body.emitter.emit('_dataChanged'); - } else { - this.body.emitter.emit('_dataUpdated'); - } } - }, { - key: 'remove', - /** - * Remove existing edges. Non existing ids will be ignored - * @param {Number[] | String[]} ids - * @private - */ - value: function remove(ids) { - var edges = this.body.edges; - for (var i = 0; i < ids.length; i++) { - var id = ids[i]; - var edge = edges[id]; - if (edge !== undefined) { - if (edge.via != null) { - delete this.body.supportNodes[edge.via.id]; - } - edge.disconnect(); - delete edges[id]; - } - } + function createLocalOrUTC (input, format, locale, strict, isUTC) { + var c = {}; - this.body.emitter.emit('_dataChanged'); - } - }, { - key: 'refresh', - value: function refresh() { - var edges = this.body.edges; - for (var edgeId in edges) { - var edge = undefined; - if (edges.hasOwnProperty(edgeId)) { - edge = edges[edgeId]; - } - var data = this.body.data.edges._data[edgeId]; - if (edge !== undefined && data !== undefined) { - edge.setOptions(data); + if (typeof(locale) === 'boolean') { + strict = locale; + locale = undefined; } - } - } - }, { - key: 'create', - value: function create(properties) { - return new _componentsEdge2['default'](properties, this.body, this.options); + // object construction must be done this way. + // https://github.com/moment/moment/issues/1423 + c._isAMomentObject = true; + c._useUTC = c._isUTC = isUTC; + c._l = locale; + c._i = input; + c._f = format; + c._strict = strict; + c._pf = defaultParsingFlags(); + + return createFromConfig(c); } - }, { - key: 'markAllEdgesAsDirty', - value: function markAllEdgesAsDirty() { - for (var edgeId in this.body.edges) { - this.body.edges[edgeId].edgeType.colorDirty = true; - } + + function local__createLocal (input, format, locale, strict) { + return createLocalOrUTC(input, format, locale, strict, false); } - }, { - key: 'reconnectEdges', - /** - * Reconnect all edges - * @private - */ - value: function reconnectEdges() { - var id; - var nodes = this.body.nodes; - var edges = this.body.edges; + var prototypeMin = deprecate( + 'moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548', + function () { + var other = local__createLocal.apply(null, arguments); + return other < this ? this : other; + } + ); - for (id in nodes) { - if (nodes.hasOwnProperty(id)) { - nodes[id].edges = []; + var prototypeMax = deprecate( + 'moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548', + function () { + var other = local__createLocal.apply(null, arguments); + return other > this ? this : other; } - } + ); - for (id in edges) { - if (edges.hasOwnProperty(id)) { - var edge = edges[id]; - edge.from = null; - edge.to = null; - edge.connect(); + // Pick a moment m from moments so that m[fn](other) is true for all + // other. This relies on the function fn to be transitive. + // + // moments should either be an array of moment objects or an array, whose + // first element is an array of moment objects. + function pickBy(fn, moments) { + var res, i; + if (moments.length === 1 && isArray(moments[0])) { + moments = moments[0]; } - } + if (!moments.length) { + return local__createLocal(); + } + res = moments[0]; + for (i = 1; i < moments.length; ++i) { + if (moments[i][fn](res)) { + res = moments[i]; + } + } + return res; } - }]); - return EdgesHandler; - })(); + // TODO: Use [].sort instead? + function min () { + var args = [].slice.call(arguments, 0); - exports['default'] = EdgesHandler; - module.exports = exports['default']; + return pickBy('isBefore', args); + } -/***/ }, -/* 66 */ -/***/ function(module, exports, __webpack_require__) { + function max () { + var args = [].slice.call(arguments, 0); - 'use strict'; + return pickBy('isAfter', args); + } - 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'); } } + function Duration (duration) { + var normalizedInput = normalizeObjectUnits(duration), + years = normalizedInput.year || 0, + quarters = normalizedInput.quarter || 0, + months = normalizedInput.month || 0, + weeks = normalizedInput.week || 0, + days = normalizedInput.day || 0, + hours = normalizedInput.hour || 0, + minutes = normalizedInput.minute || 0, + seconds = normalizedInput.second || 0, + milliseconds = normalizedInput.millisecond || 0; - var _componentsPhysicsBarnesHutSolver = __webpack_require__(81); + // representation for dateAddRemove + this._milliseconds = +milliseconds + + seconds * 1e3 + // 1000 + minutes * 6e4 + // 1000 * 60 + hours * 36e5; // 1000 * 60 * 60 + // Because of dateAddRemove treats 24 hours as different from a + // day when working around DST, we need to store them separately + this._days = +days + + weeks * 7; + // It is impossible translate months into days without knowing + // which months you are are talking about, so we have to store + // it separately. + this._months = +months + + quarters * 3 + + years * 12; - var _componentsPhysicsBarnesHutSolver2 = _interopRequireDefault(_componentsPhysicsBarnesHutSolver); + this._data = {}; - var _componentsPhysicsRepulsionSolver = __webpack_require__(82); + this._locale = locale_locales__getLocale(); - var _componentsPhysicsRepulsionSolver2 = _interopRequireDefault(_componentsPhysicsRepulsionSolver); + this._bubble(); + } - var _componentsPhysicsHierarchicalRepulsionSolver = __webpack_require__(83); + function isDuration (obj) { + return obj instanceof Duration; + } - var _componentsPhysicsHierarchicalRepulsionSolver2 = _interopRequireDefault(_componentsPhysicsHierarchicalRepulsionSolver); + function offset (token, separator) { + addFormatToken(token, 0, 0, function () { + var offset = this.utcOffset(); + var sign = '+'; + if (offset < 0) { + offset = -offset; + sign = '-'; + } + return sign + zeroFill(~~(offset / 60), 2) + separator + zeroFill(~~(offset) % 60, 2); + }); + } - var _componentsPhysicsSpringSolver = __webpack_require__(84); + offset('Z', ':'); + offset('ZZ', ''); - var _componentsPhysicsSpringSolver2 = _interopRequireDefault(_componentsPhysicsSpringSolver); + // PARSING - var _componentsPhysicsHierarchicalSpringSolver = __webpack_require__(85); + addRegexToken('Z', matchOffset); + addRegexToken('ZZ', matchOffset); + addParseToken(['Z', 'ZZ'], function (input, array, config) { + config._useUTC = true; + config._tzm = offsetFromString(input); + }); - var _componentsPhysicsHierarchicalSpringSolver2 = _interopRequireDefault(_componentsPhysicsHierarchicalSpringSolver); + // HELPERS - var _componentsPhysicsCentralGravitySolver = __webpack_require__(86); + // timezone chunker + // '+10:00' > ['10', '00'] + // '-1530' > ['-15', '30'] + var chunkOffset = /([\+\-]|\d\d)/gi; - var _componentsPhysicsCentralGravitySolver2 = _interopRequireDefault(_componentsPhysicsCentralGravitySolver); + function offsetFromString(string) { + var matches = ((string || '').match(matchOffset) || []); + var chunk = matches[matches.length - 1] || []; + var parts = (chunk + '').match(chunkOffset) || ['-', 0, 0]; + var minutes = +(parts[1] * 60) + toInt(parts[2]); - var _componentsPhysicsFA2BasedRepulsionSolver = __webpack_require__(87); + return parts[0] === '+' ? minutes : -minutes; + } - var _componentsPhysicsFA2BasedRepulsionSolver2 = _interopRequireDefault(_componentsPhysicsFA2BasedRepulsionSolver); + // Return a moment from input, that is local/utc/zone equivalent to model. + function cloneWithOffset(input, model) { + var res, diff; + if (model._isUTC) { + res = model.clone(); + diff = (isMoment(input) || isDate(input) ? +input : +local__createLocal(input)) - (+res); + // Use low-level api, because this fn is low-level api. + res._d.setTime(+res._d + diff); + utils_hooks__hooks.updateOffset(res, false); + return res; + } else { + return local__createLocal(input).local(); + } + return model._isUTC ? local__createLocal(input).zone(model._offset || 0) : local__createLocal(input).local(); + } - var _componentsPhysicsFA2BasedCentralGravitySolver = __webpack_require__(88); + function getDateOffset (m) { + // On Firefox.24 Date#getTimezoneOffset returns a floating point. + // https://github.com/moment/moment/pull/1871 + return -Math.round(m._d.getTimezoneOffset() / 15) * 15; + } - var _componentsPhysicsFA2BasedCentralGravitySolver2 = _interopRequireDefault(_componentsPhysicsFA2BasedCentralGravitySolver); + // HOOKS - var util = __webpack_require__(1); + // This function will be called whenever a moment is mutated. + // It is intended to keep the offset in sync with the timezone. + utils_hooks__hooks.updateOffset = function () {}; - var PhysicsEngine = (function () { - function PhysicsEngine(body) { - _classCallCheck(this, PhysicsEngine); + // MOMENTS - this.body = body; - this.physicsBody = { physicsNodeIndices: [], physicsEdgeIndices: [], forces: {}, velocities: {} }; + // keepLocalTime = true means only change the timezone, without + // affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]--> + // 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset + // +0200, so we adjust the time as needed, to be valid. + // + // Keeping the time actually adds/subtracts (one hour) + // from the actual represented time. That is why we call updateOffset + // a second time. In case it wants us to change the offset again + // _changeInProgress == true case, then we have to adjust, because + // there is no such time in the given timezone. + function getSetOffset (input, keepLocalTime) { + var offset = this._offset || 0, + localAdjust; + if (input != null) { + if (typeof input === 'string') { + input = offsetFromString(input); + } + if (Math.abs(input) < 16) { + input = input * 60; + } + if (!this._isUTC && keepLocalTime) { + localAdjust = getDateOffset(this); + } + this._offset = input; + this._isUTC = true; + if (localAdjust != null) { + this.add(localAdjust, 'm'); + } + if (offset !== input) { + if (!keepLocalTime || this._changeInProgress) { + add_subtract__addSubtract(this, create__createDuration(input - offset, 'm'), 1, false); + } else if (!this._changeInProgress) { + this._changeInProgress = true; + utils_hooks__hooks.updateOffset(this, true); + this._changeInProgress = null; + } + } + return this; + } else { + return this._isUTC ? offset : getDateOffset(this); + } + } - this.physicsEnabled = true; - this.simulationInterval = 1000 / 60; - this.requiresTimeout = true; - this.previousStates = {}; - this.freezeCache = {}; - this.renderTimer = undefined; + function getSetZone (input, keepLocalTime) { + if (input != null) { + if (typeof input !== 'string') { + input = -input; + } - this.stabilized = false; - this.stabilizationIterations = 0; - this.ready = false; // will be set to true if the stabilize + this.utcOffset(input, keepLocalTime); - // default options - this.options = {}; - this.defaultOptions = { - barnesHut: { - theta: 0.5, - gravitationalConstant: -2000, - centralGravity: 0.3, - springLength: 95, - springConstant: 0.04, - damping: 0.09 - }, - forceAtlas2Based: { - theta: 0.5, - gravitationalConstant: -50, - centralGravity: 0.01, - springConstant: 0.08, - springLength: 100, - damping: 0.4 - }, - repulsion: { - centralGravity: 0.2, - springLength: 200, - springConstant: 0.05, - nodeDistance: 100, - damping: 0.09 - }, - 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: 100, - onlyDynamicEdges: false, - fit: true - }, - timestep: 0.5 - }; - util.extend(this.options, this.defaultOptions); + return this; + } else { + return -this.utcOffset(); + } + } - this.bindEventListeners(); - } + function setOffsetToUTC (keepLocalTime) { + return this.utcOffset(0, keepLocalTime); + } - _createClass(PhysicsEngine, [{ - key: 'bindEventListeners', - value: function bindEventListeners() { - var _this = this; + function setOffsetToLocal (keepLocalTime) { + if (this._isUTC) { + this.utcOffset(0, keepLocalTime); + this._isUTC = false; - 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(); + if (keepLocalTime) { + this.subtract(getDateOffset(this), 'm'); + } } - }); - this.body.emitter.on('startSimulation', function () { - if (_this.ready === true) { - _this.startSimulation(); + return this; + } + + function setOffsetToParsedOffset () { + if (this._tzm) { + this.utcOffset(this._tzm); + } else if (typeof this._i === 'string') { + this.utcOffset(offsetFromString(this._i)); } - }); - this.body.emitter.on('stopSimulation', function () { - _this.stopSimulation(); - }); - this.body.emitter.on('destroy', function () { - _this.stopSimulation(false); - _this.body.emitter.off(); - }); + return this; } - }, { - 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'); + + function hasAlignedHourOffset (input) { + if (!input) { + input = 0; } - } - this.init(); + else { + input = local__createLocal(input).utcOffset(); + } + + return (this.utcOffset() - input) % 60 === 0; } - }, { - 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; + function isDaylightSavingTime () { + return ( + this.utcOffset() > this.clone().month(0).utcOffset() || + this.utcOffset() > this.clone().month(5).utcOffset() + ); } - }, { - 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(); + + function isDaylightSavingTimeShifted () { + if (this._a) { + var other = this._isUTC ? create_utc__createUTC(this._a) : local__createLocal(this._a); + return this.isValid() && compareArrays(this._a, other.toArray()) > 0; } - } else { - this.ready = true; - this.body.emitter.emit('fit'); - } + + return false; } - }, { - key: 'startSimulation', - /** - * Start the simulation - */ - value: function startSimulation() { - if (this.physicsEnabled === true) { - this.stabilized = false; - 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'); - } + function isLocal () { + return !this._isUTC; } - }, { - key: 'stopSimulation', - /** - * Stop the simulation, force stabilization. - */ - value: function stopSimulation() { - var emit = arguments[0] === undefined ? true : arguments[0]; + function isUtcOffset () { + return this._isUTC; + } - 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'); - } - } + function isUtc () { + return this._isUTC && this._offset === 0; } - }, { - 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; + var aspNetRegex = /(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/; - // run double speed if it is a little graph - if ((physicsTime < 0.4 * this.simulationInterval || this.runDoubleSpeed === true) && this.stabilized === false) { - this.physicsTick(); + // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html + // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere + var create__isoRegex = /^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/; - // this makes sure there is no jitter. The decision is taken once to run it at double speed. - this.runDoubleSpeed = true; - } + function create__createDuration (input, key) { + var duration = input, + // matching against regexp is expensive, do it on demand + match = null, + sign, + ret, + diffRes; - 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.stabilizationIterations = 0; - this.startedStabilization = false; - this._emitStabilized(); - } else { - this.stabilizationIterations = 0; - } - this.stopSimulation(); - } - } - }, { - key: '_emitStabilized', - value: function _emitStabilized() { - var _this2 = this; + if (isDuration(input)) { + duration = { + ms : input._milliseconds, + d : input._days, + M : input._months + }; + } else if (typeof input === 'number') { + duration = {}; + if (key) { + duration[key] = input; + } else { + duration.milliseconds = input; + } + } else if (!!(match = aspNetRegex.exec(input))) { + sign = (match[1] === '-') ? -1 : 1; + duration = { + y : 0, + d : toInt(match[DATE]) * sign, + h : toInt(match[HOUR]) * sign, + m : toInt(match[MINUTE]) * sign, + s : toInt(match[SECOND]) * sign, + ms : toInt(match[MILLISECOND]) * sign + }; + } else if (!!(match = create__isoRegex.exec(input))) { + sign = (match[1] === '-') ? -1 : 1; + duration = { + y : parseIso(match[2], sign), + M : parseIso(match[3], sign), + d : parseIso(match[4], sign), + h : parseIso(match[5], sign), + m : parseIso(match[6], sign), + s : parseIso(match[7], sign), + w : parseIso(match[8], sign) + }; + } else if (duration == null) {// checks for null or undefined + duration = {}; + } else if (typeof duration === 'object' && ('from' in duration || 'to' in duration)) { + diffRes = momentsDifference(local__createLocal(duration.from), local__createLocal(duration.to)); - if (this.stabilizationIterations > 1) { - setTimeout(function () { - _this2.body.emitter.emit('stabilized', { iterations: _this2.stabilizationIterations }); - }, 0); - } - } - }, { - key: 'physicsTick', + duration = {}; + duration.ms = diffRes.milliseconds; + duration.M = diffRes.months; + } - /** - * A single simulation step (or 'tick') in the physics simulation - * - * @private - */ - value: function physicsTick() { - if (this.stabilized === false) { - this.calculateForces(); - this.stabilized = this.moveNodes(); + ret = new Duration(duration); - // 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; - } + if (isDuration(input) && hasOwnProp(input, '_locale')) { + ret._locale = input._locale; } - this.stabilizationIterations++; - } + return ret; } - }, { - key: 'updatePhysicsIndices', - /** - * 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 updatePhysicsIndices() { - 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); - } - } - } + create__createDuration.fn = Duration.prototype; - // 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); - } - } - } + function parseIso (inp, sign) { + // We'd normally use ~~inp for this, but unfortunately it also + // converts floats to ints. + // inp may be undefined, so careful calling replace on it. + var res = inp && parseFloat(inp.replace(',', '.')); + // apply sign while we're at it + return (isNaN(res) ? 0 : res) * sign; + } - // 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 }; + function positiveMomentsDifference(base, other) { + var res = {milliseconds: 0, months: 0}; - // 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 }; + res.months = other.month() - base.month() + + (other.year() - base.year()) * 12; + if (base.clone().add(res.months, 'M').isAfter(other)) { + --res.months; } - } - // 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', + res.milliseconds = +other - +(base.clone().add(res.months, 'M')); - /** - * 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; + return res; + } - 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; - } + function momentsDifference(base, other) { + var res; + other = cloneWithOffset(other, base); + if (base.isBefore(other)) { + res = positiveMomentsDifference(base, other); } else { - delete this.previousStates[nodeId]; + res = positiveMomentsDifference(other, base); + res.milliseconds = -res.milliseconds; + res.months = -res.months; } - } + + return res; } - }, { - key: 'moveNodes', - /** - * 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 createAdder(direction, name) { + return function (val, period) { + var dur, tmp; + //invert the arguments, but complain about it + if (period !== null && !isNaN(+period)) { + deprecateSimple(name, 'moment().' + name + '(period, number) is deprecated. Please use moment().' + name + '(number, period).'); + tmp = val; val = period; period = tmp; + } - 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; - } + val = typeof val === 'string' ? +val : val; + dur = create__createDuration(val, period); + add_subtract__addSubtract(this, dur, direction); + return this; + }; + } - if (nodesPresent === true) { - if (vminCorrected > 0.5 * this.options.maxVelocity) { - return false; - } else { - return stabilized; + function add_subtract__addSubtract (mom, duration, isAdding, updateOffset) { + var milliseconds = duration._milliseconds, + days = duration._days, + months = duration._months; + updateOffset = updateOffset == null ? true : updateOffset; + + if (milliseconds) { + mom._d.setTime(+mom._d + milliseconds * isAdding); + } + if (days) { + get_set__set(mom, 'Date', get_set__get(mom, 'Date') + days * isAdding); + } + if (months) { + setMonth(mom, get_set__get(mom, 'Month') + months * isAdding); + } + if (updateOffset) { + utils_hooks__hooks.updateOffset(mom, days || months); } - } - return true; } - }, { - key: '_performStep', - /** - * 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 add_subtract__add = createAdder(1, 'add'); + var add_subtract__subtract = createAdder(-1, 'subtract'); - // store the state so we can revert - this.previousStates[nodeId] = { x: node.x, y: node.y, vx: velocities[nodeId].x, vy: velocities[nodeId].y }; + function moment_calendar__calendar (time) { + // We want to compare the start of today, vs this. + // Getting start-of-today depends on whether we're local/utc/offset or not. + var now = time || local__createLocal(), + sod = cloneWithOffset(now, this).startOf('day'), + diff = this.diff(sod, 'days', true), + format = diff < -6 ? 'sameElse' : + diff < -1 ? 'lastWeek' : + diff < 0 ? 'lastDay' : + diff < 1 ? 'sameDay' : + diff < 2 ? 'nextDay' : + diff < 7 ? 'nextWeek' : 'sameElse'; + return this.format(this.localeData().calendar(format, this, local__createLocal(now))); + } - 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; - } + function clone () { + return new Moment(this); + } - 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; - } + function isAfter (input, units) { + var inputMs; + units = normalizeUnits(typeof units !== 'undefined' ? units : 'millisecond'); + if (units === 'millisecond') { + input = isMoment(input) ? input : local__createLocal(input); + return +this > +input; + } else { + inputMs = isMoment(input) ? +input : +local__createLocal(input); + return inputMs < +this.clone().startOf(units); + } + } - var totalVelocity = Math.sqrt(Math.pow(velocities[nodeId].x, 2) + Math.pow(velocities[nodeId].y, 2)); - return totalVelocity; + function isBefore (input, units) { + var inputMs; + units = normalizeUnits(typeof units !== 'undefined' ? units : 'millisecond'); + if (units === 'millisecond') { + input = isMoment(input) ? input : local__createLocal(input); + return +this < +input; + } else { + inputMs = isMoment(input) ? +input : +local__createLocal(input); + return +this.clone().endOf(units) < inputMs; + } } - }, { - key: 'calculateForces', - /** - * calculate the forces for one physics iteration. - */ - value: function calculateForces() { - this.gravitySolver.solve(); - this.nodesSolver.solve(); - this.edgesSolver.solve(); + function isBetween (from, to, units) { + return this.isAfter(from, units) && this.isBefore(to, units); } - }, { - key: '_freezeNodes', - /** - * 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; - } + function isSame (input, units) { + var inputMs; + units = normalizeUnits(units || 'millisecond'); + if (units === 'millisecond') { + input = isMoment(input) ? input : local__createLocal(input); + return +this === +input; + } else { + inputMs = +local__createLocal(input); + return +(this.clone().startOf(units)) <= inputMs && inputMs <= +(this.clone().endOf(units)); } - } } - }, { - key: '_restoreFrozenNodes', - /** - * Unfreezes the nodes that have been frozen by _freezeDefinedNodes. - * - * @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; - } + function absFloor (number) { + if (number < 0) { + return Math.ceil(number); + } else { + return Math.floor(number); } - } - this.freezeCache = {}; } - }, { - key: 'stabilize', - /** - * Find a stable position for all nodes - * @private - */ - value: function stabilize() { - var iterations = arguments[0] === undefined ? this.options.stabilization.iterations : arguments[0]; + function diff (input, units, asFloat) { + var that = cloneWithOffset(input, this), + zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4, + delta, output; - 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; - } + units = normalizeUnits(units); - // stop the render loop - this.stopSimulation(); + if (units === 'year' || units === 'month' || units === 'quarter') { + output = monthDiff(this, that); + if (units === 'quarter') { + output = output / 3; + } else if (units === 'year') { + output = output / 12; + } + } else { + delta = this - that; + output = units === 'second' ? delta / 1e3 : // 1000 + units === 'minute' ? delta / 6e4 : // 1000 * 60 + units === 'hour' ? delta / 36e5 : // 1000 * 60 * 60 + units === 'day' ? (delta - zoneDelta) / 864e5 : // 1000 * 60 * 60 * 24, negate dst + units === 'week' ? (delta - zoneDelta) / 6048e5 : // 1000 * 60 * 60 * 24 * 7, negate dst + delta; + } + return asFloat ? output : absFloor(output); + } - // set stabilze to false - this.stabilized = false; + function monthDiff (a, b) { + // difference in months + var wholeMonthDiff = ((b.year() - a.year()) * 12) + (b.month() - a.month()), + // b is in (anchor - 1 month, anchor + 1 month) + anchor = a.clone().add(wholeMonthDiff, 'months'), + anchor2, adjust; - // block redraw requests - this.body.emitter.emit('_blockRedrawRequests'); - this.body.emitter.emit('startStabilizing'); - this.startedStabilization = true; - this.targetIterations = iterations; + if (b - anchor < 0) { + anchor2 = a.clone().add(wholeMonthDiff - 1, 'months'); + // linear across the month + adjust = (b - anchor) / (anchor - anchor2); + } else { + anchor2 = a.clone().add(wholeMonthDiff + 1, 'months'); + // linear across the month + adjust = (b - anchor) / (anchor2 - anchor); + } - // start the stabilization - if (this.options.stabilization.onlyDynamicEdges === true) { - this._freezeNodes(); - } - this.stabilizationIterations = 0; + return -(wholeMonthDiff + adjust); + } - setTimeout(this._stabilizationBatch.bind(this), 0); + utils_hooks__hooks.defaultFormat = 'YYYY-MM-DDTHH:mm:ssZ'; + + function toString () { + return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ'); } - }, { - 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.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(); - } + function moment_format__toISOString () { + var m = this.clone().utc(); + if (0 < m.year() && m.year() <= 9999) { + if ('function' === typeof Date.prototype.toISOString) { + // native implementation is ~50x faster, use it when we can + return this.toDate().toISOString(); + } else { + return formatMoment(m, 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); + } + } else { + return formatMoment(m, 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); + } } - }, { - 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(); - } + function format (inputString) { + var output = formatMoment(this, inputString || utils_hooks__hooks.defaultFormat); + return this.localeData().postformat(output); + } - this.body.emitter.emit('stabilizationIterationsDone'); - this.body.emitter.emit('_requestRedraw'); + function from (time, withoutSuffix) { + return create__createDuration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix); + } - this.ready = true; + function fromNow (withoutSuffix) { + return this.from(local__createLocal(), withoutSuffix); } - }]); - return PhysicsEngine; - })(); + function locale (key) { + var newLocaleData; - exports['default'] = PhysicsEngine; - module.exports = exports['default']; + if (key === undefined) { + return this._locale._abbr; + } else { + newLocaleData = locale_locales__getLocale(key); + if (newLocaleData != null) { + this._locale = newLocaleData; + } + return this; + } + } -/***/ }, -/* 67 */ -/***/ function(module, exports, __webpack_require__) { + var lang = deprecate( + 'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.', + function (key) { + if (key === undefined) { + return this.localeData(); + } else { + return this.locale(key); + } + } + ); - "use strict"; + function localeData () { + return this._locale; + } - Object.defineProperty(exports, "__esModule", { - value: true - }); + function startOf (units) { + units = normalizeUnits(units); + // the following switch intentionally omits break keywords + // to utilize falling through the cases. + switch (units) { + case 'year': + this.month(0); + /* falls through */ + case 'quarter': + case 'month': + this.date(1); + /* falls through */ + case 'week': + case 'isoWeek': + case 'day': + this.hours(0); + /* falls through */ + case 'hour': + this.minutes(0); + /* falls through */ + case 'minute': + this.seconds(0); + /* falls through */ + case 'second': + this.milliseconds(0); + } - 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; }; })(); + // weeks are a special case + if (units === 'week') { + this.weekday(0); + } + if (units === 'isoWeek') { + this.isoWeekday(1); + } - function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + // quarters are also special + if (units === 'quarter') { + this.month(Math.floor(this.month() / 3) * 3); + } - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + return this; + } - var _componentsNodesCluster = __webpack_require__(89); + function endOf (units) { + units = normalizeUnits(units); + if (units === undefined || units === 'millisecond') { + return this; + } + return this.startOf(units).add(1, (units === 'isoWeek' ? 'week' : units)).subtract(1, 'ms'); + } - var _componentsNodesCluster2 = _interopRequireDefault(_componentsNodesCluster); + function to_type__valueOf () { + return +this._d - ((this._offset || 0) * 60000); + } - var util = __webpack_require__(1); + function unix () { + return Math.floor(+this / 1000); + } - var ClusterEngine = (function () { - function ClusterEngine(body) { - _classCallCheck(this, ClusterEngine); + function toDate () { + return this._offset ? new Date(+this) : this._d; + } - this.body = body; - this.clusteredNodes = {}; + function toArray () { + var m = this; + return [m.year(), m.month(), m.date(), m.hour(), m.minute(), m.second(), m.millisecond()]; + } - this.options = {}; - this.defaultOptions = {}; - util.extend(this.options, this.defaultOptions); - } + function moment_valid__isValid () { + return valid__isValid(this); + } - _createClass(ClusterEngine, [{ - key: "setOptions", - value: function setOptions(options) { - if (options !== undefined) {} + function parsingFlags () { + return extend({}, this._pf); } - }, { - key: "clusterByHubsize", - /** - * - * @param hubsize - * @param options - */ - value: function clusterByHubsize(hubsize, options) { - if (hubsize === undefined) { - hubsize = this._getHubSize(); - } else if (tyepof(hubsize) === "object") { - options = this._checkOptions(hubsize); - hubsize = this._getHubSize(); - } + function invalidAt () { + return this._pf.overflow; + } - var nodesToCluster = []; - for (var i = 0; i < this.body.nodeIndices.length; i++) { - var node = this.body.nodes[this.body.nodeIndices[i]]; - if (node.edges.length >= hubsize) { - nodesToCluster.push(node.id); - } - } + addFormatToken(0, ['gg', 2], 0, function () { + return this.weekYear() % 100; + }); - for (var i = 0; i < nodesToCluster.length; i++) { - var node = this.body.nodes[nodesToCluster[i]]; - this.clusterByConnection(node, options, false); - } - this.body.emitter.emit("_dataChanged"); + addFormatToken(0, ['GG', 2], 0, function () { + return this.isoWeekYear() % 100; + }); + + function addWeekYearFormatToken (token, getter) { + addFormatToken(0, [token, token.length], 0, getter); } - }, { - key: "cluster", - /** - * loop over all nodes, check if they adhere to the condition and cluster if needed. - * @param options - * @param refreshData - */ - value: function cluster() { - var options = arguments[0] === undefined ? {} : arguments[0]; - var refreshData = arguments[1] === undefined ? true : arguments[1]; + addWeekYearFormatToken('gggg', 'weekYear'); + addWeekYearFormatToken('ggggg', 'weekYear'); + addWeekYearFormatToken('GGGG', 'isoWeekYear'); + addWeekYearFormatToken('GGGGG', 'isoWeekYear'); - if (options.joinCondition === undefined) { - throw new Error("Cannot call clusterByNodeData without a joinCondition function in the options."); - } + // ALIASES - // check if the options object is fine, append if needed - options = this._checkOptions(options); + addUnitAlias('weekYear', 'gg'); + addUnitAlias('isoWeekYear', 'GG'); - var childNodesObj = {}; - var childEdgesObj = {}; + // PARSING - // collect the nodes that will be in the cluster - for (var i = 0; i < this.body.nodeIndices.length; i++) { - var nodeId = this.body.nodeIndices[i]; - var clonedOptions = this._cloneOptions(nodeId); - if (options.joinCondition(clonedOptions) === true) { - childNodesObj[nodeId] = this.body.nodes[nodeId]; - } - } + addRegexToken('G', matchSigned); + addRegexToken('g', matchSigned); + addRegexToken('GG', match1to2, match2); + addRegexToken('gg', match1to2, match2); + addRegexToken('GGGG', match1to4, match4); + addRegexToken('gggg', match1to4, match4); + addRegexToken('GGGGG', match1to6, match6); + addRegexToken('ggggg', match1to6, match6); - this._cluster(childNodesObj, childEdgesObj, options, refreshData); - } - }, { - key: "clusterOutliers", + addWeekParseToken(['gggg', 'ggggg', 'GGGG', 'GGGGG'], function (input, week, config, token) { + week[token.substr(0, 2)] = toInt(input); + }); - /** - * Cluster all nodes in the network that have only 1 edge - * @param options - * @param refreshData - */ - value: function clusterOutliers(options) { - var refreshData = arguments[1] === undefined ? true : arguments[1]; + addWeekParseToken(['gg', 'GG'], function (input, week, config, token) { + week[token] = utils_hooks__hooks.parseTwoDigitYear(input); + }); - options = this._checkOptions(options); - var clusters = []; + // HELPERS - // collect the nodes that will be in the cluster - for (var i = 0; i < this.body.nodeIndices.length; i++) { - var childNodesObj = {}; - var childEdgesObj = {}; - var nodeId = this.body.nodeIndices[i]; - if (this.body.nodes[nodeId].edges.length === 1) { - var edge = this.body.nodes[nodeId].edges[0]; - var childNodeId = this._getConnectedId(edge, nodeId); - if (childNodeId != nodeId) { - if (options.joinCondition === undefined) { - childNodesObj[nodeId] = this.body.nodes[nodeId]; - childNodesObj[childNodeId] = this.body.nodes[childNodeId]; - } else { - var clonedOptions = this._cloneOptions(nodeId); - if (options.joinCondition(clonedOptions) === true) { - childNodesObj[nodeId] = this.body.nodes[nodeId]; - } - clonedOptions = this._cloneOptions(childNodeId); - if (options.joinCondition(clonedOptions) === true) { - childNodesObj[childNodeId] = this.body.nodes[childNodeId]; - } - } - clusters.push({ nodes: childNodesObj, edges: childEdgesObj }); - } - } - } + function weeksInYear(year, dow, doy) { + return weekOfYear(local__createLocal([year, 11, 31 + dow - doy]), dow, doy).week; + } - for (var i = 0; i < clusters.length; i++) { - this._cluster(clusters[i].nodes, clusters[i].edges, options, false); - } + // MOMENTS - if (refreshData === true) { - this.body.emitter.emit("_dataChanged"); - } + function getSetWeekYear (input) { + var year = weekOfYear(this, this.localeData()._week.dow, this.localeData()._week.doy).year; + return input == null ? year : this.add((input - year), 'y'); } - }, { - key: "clusterByConnection", - /** - * suck all connected nodes of a node into the node. - * @param nodeId - * @param options - * @param refreshData - */ - value: function clusterByConnection(nodeId, options) { - var refreshData = arguments[2] === undefined ? true : arguments[2]; + function getSetISOWeekYear (input) { + var year = weekOfYear(this, 1, 4).year; + return input == null ? year : this.add((input - year), 'y'); + } - // kill conditions - if (nodeId === undefined) { - throw new Error("No nodeId supplied to clusterByConnection!"); - } - if (this.body.nodes[nodeId] === undefined) { - throw new Error("The nodeId given to clusterByConnection does not exist!"); - } + function getISOWeeksInYear () { + return weeksInYear(this.year(), 1, 4); + } - var node = this.body.nodes[nodeId]; - options = this._checkOptions(options, node); - if (options.clusterNodeProperties.x === undefined) { - options.clusterNodeProperties.x = node.x; - } - if (options.clusterNodeProperties.y === undefined) { - options.clusterNodeProperties.y = node.y; - } - if (options.clusterNodeProperties.fixed === undefined) { - options.clusterNodeProperties.fixed = {}; - options.clusterNodeProperties.fixed.x = node.options.fixed.x; - options.clusterNodeProperties.fixed.y = node.options.fixed.y; - } + function getWeeksInYear () { + var weekInfo = this.localeData()._week; + return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy); + } - var childNodesObj = {}; - var childEdgesObj = {}; - var parentNodeId = node.id; - var parentClonedOptions = this._cloneOptions(parentNodeId); - childNodesObj[parentNodeId] = node; + addFormatToken('Q', 0, 0, 'quarter'); - // collect the nodes that will be in the cluster - for (var i = 0; i < node.edges.length; i++) { - var edge = node.edges[i]; - var childNodeId = this._getConnectedId(edge, parentNodeId); + // ALIASES - if (childNodeId !== parentNodeId) { - if (options.joinCondition === undefined) { - childEdgesObj[edge.id] = edge; - childNodesObj[childNodeId] = this.body.nodes[childNodeId]; - } else { - // clone the options and insert some additional parameters that could be interesting. - var childClonedOptions = this._cloneOptions(childNodeId); - if (options.joinCondition(parentClonedOptions, childClonedOptions) === true) { - childEdgesObj[edge.id] = edge; - childNodesObj[childNodeId] = this.body.nodes[childNodeId]; - } - } - } else { - childEdgesObj[edge.id] = edge; - } - } + addUnitAlias('quarter', 'Q'); - this._cluster(childNodesObj, childEdgesObj, options, refreshData); - } - }, { - key: "_cloneOptions", + // PARSING - /** - * This returns a clone of the options or options of the edge or node to be used for construction of new edges or check functions for new nodes. - * @param objId - * @param type - * @returns {{}} - * @private - */ - value: function _cloneOptions(objId, type) { - var clonedOptions = {}; - if (type === undefined || type === "node") { - util.deepExtend(clonedOptions, this.body.nodes[objId].options, true); - clonedOptions.x = this.body.nodes[objId].x; - clonedOptions.y = this.body.nodes[objId].y; - clonedOptions.amountOfConnections = this.body.nodes[objId].edges.length; - } else { - util.deepExtend(clonedOptions, this.body.edges[objId].options, true); - } - return clonedOptions; - } - }, { - key: "_createClusterEdges", + addRegexToken('Q', match1); + addParseToken('Q', function (input, array) { + array[MONTH] = (toInt(input) - 1) * 3; + }); - /** - * This function creates the edges that will be attached to the cluster. - * - * @param childNodesObj - * @param childEdgesObj - * @param newEdges - * @param options - * @private - */ - value: function _createClusterEdges(childNodesObj, childEdgesObj, newEdges, options) { - var edge = undefined, - childNodeId = undefined, - childNode = undefined; + // MOMENTS - var childKeys = Object.keys(childNodesObj); - for (var i = 0; i < childKeys.length; i++) { - childNodeId = childKeys[i]; - childNode = childNodesObj[childNodeId]; + function getSetQuarter (input) { + return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3); + } - // mark all edges for removal from global and construct new edges from the cluster to others - for (var j = 0; j < childNode.edges.length; j++) { - edge = childNode.edges[j]; - childEdgesObj[edge.id] = edge; + addFormatToken('D', ['DD', 2], 'Do', 'date'); - var otherNodeId = edge.toId; - var otherOnTo = true; - if (edge.toId != childNodeId) { - otherNodeId = edge.toId; - otherOnTo = true; - } else if (edge.fromId != childNodeId) { - otherNodeId = edge.fromId; - otherOnTo = false; - } + // ALIASES - if (childNodesObj[otherNodeId] === undefined) { - var clonedOptions = this._cloneOptions(edge.id, "edge"); - util.deepExtend(clonedOptions, options.clusterEdgeProperties); - if (otherOnTo === true) { - clonedOptions.from = options.clusterNodeProperties.id; - clonedOptions.to = otherNodeId; - } else { - clonedOptions.from = otherNodeId; - clonedOptions.to = options.clusterNodeProperties.id; - } - clonedOptions.id = "clusterEdge:" + util.randomUUID(); - newEdges.push(this.body.functions.createEdge(clonedOptions)); - } - } - } - } - }, { - key: "_checkOptions", + addUnitAlias('date', 'D'); - /** - * This function checks the options that can be supplied to the different cluster functions - * for certain fields and inserts defaults if needed - * @param options - * @returns {*} - * @private - */ - value: function _checkOptions() { - var options = arguments[0] === undefined ? {} : arguments[0]; + // PARSING - if (options.clusterEdgeProperties === undefined) { - options.clusterEdgeProperties = {}; - } - if (options.clusterNodeProperties === undefined) { - options.clusterNodeProperties = {}; - } + addRegexToken('D', match1to2); + addRegexToken('DD', match1to2, match2); + addRegexToken('Do', function (isStrict, locale) { + return isStrict ? locale._ordinalParse : locale._ordinalParseLenient; + }); - return options; - } - }, { - key: "_cluster", + addParseToken(['D', 'DD'], DATE); + addParseToken('Do', function (input, array) { + array[DATE] = toInt(input.match(match1to2)[0], 10); + }); - /** - * - * @param {Object} childNodesObj | object with node objects, id as keys, same as childNodes except it also contains a source node - * @param {Object} childEdgesObj | object with edge objects, id as keys - * @param {Array} options | object with {clusterNodeProperties, clusterEdgeProperties, processProperties} - * @param {Boolean} refreshData | when true, do not wrap up - * @private - */ - value: function _cluster(childNodesObj, childEdgesObj, options) { - var refreshData = arguments[3] === undefined ? true : arguments[3]; + // MOMENTS - // kill condition: no children so cant cluster - if (Object.keys(childNodesObj).length === 0) { - return; - } + var getSetDayOfMonth = makeGetSet('Date', true); - // check if we have an unique id; - if (options.clusterNodeProperties.id === undefined) { - options.clusterNodeProperties.id = "cluster:" + util.randomUUID(); - } - var clusterId = options.clusterNodeProperties.id; + addFormatToken('d', 0, 'do', 'day'); - // construct the clusterNodeProperties - var clusterNodeProperties = options.clusterNodeProperties; - if (options.processProperties !== undefined) { - // get the childNode options - var childNodesOptions = []; - for (var nodeId in childNodesObj) { - var clonedOptions = this._cloneOptions(nodeId); - childNodesOptions.push(clonedOptions); - } + addFormatToken('dd', 0, 0, function (format) { + return this.localeData().weekdaysMin(this, format); + }); - // get clusterproperties based on childNodes - var childEdgesOptions = []; - for (var edgeId in childEdgesObj) { - var clonedOptions = this._cloneOptions(edgeId, "edge"); - childEdgesOptions.push(clonedOptions); - } + addFormatToken('ddd', 0, 0, function (format) { + return this.localeData().weekdaysShort(this, format); + }); - clusterNodeProperties = options.processProperties(clusterNodeProperties, childNodesOptions, childEdgesOptions); - if (!clusterNodeProperties) { - throw new Error("The processClusterProperties function does not return properties!"); - } - } - if (clusterNodeProperties.label === undefined) { - clusterNodeProperties.label = "cluster"; - } + addFormatToken('dddd', 0, 0, function (format) { + return this.localeData().weekdays(this, format); + }); - // give the clusterNode a postion if it does not have one. - var pos = undefined; - if (clusterNodeProperties.x === undefined) { - pos = this._getClusterPosition(childNodesObj); - clusterNodeProperties.x = pos.x; - } - if (clusterNodeProperties.y === undefined) { - if (pos === undefined) { - pos = this._getClusterPosition(childNodesObj); - } - clusterNodeProperties.y = pos.y; - } + addFormatToken('e', 0, 0, 'weekday'); + addFormatToken('E', 0, 0, 'isoWeekday'); - // force the ID to remain the same - clusterNodeProperties.id = clusterId; + // ALIASES - // create the clusterNode - var clusterNode = this.body.functions.createNode(clusterNodeProperties, _componentsNodesCluster2["default"]); - clusterNode.isCluster = true; - clusterNode.containedNodes = childNodesObj; - clusterNode.containedEdges = childEdgesObj; + addUnitAlias('day', 'd'); + addUnitAlias('weekday', 'e'); + addUnitAlias('isoWeekday', 'E'); - // finally put the cluster node into global - this.body.nodes[clusterNodeProperties.id] = clusterNode; + // PARSING - // create the new edges that will connect to the cluster - var newEdges = []; - this._createClusterEdges(childNodesObj, childEdgesObj, newEdges, options); + addRegexToken('d', match1to2); + addRegexToken('e', match1to2); + addRegexToken('E', match1to2); + addRegexToken('dd', matchWord); + addRegexToken('ddd', matchWord); + addRegexToken('dddd', matchWord); - // disable the childEdges - for (var edgeId in childEdgesObj) { - if (childEdgesObj.hasOwnProperty(edgeId)) { - if (this.body.edges[edgeId] !== undefined) { - var edge = this.body.edges[edgeId]; - edge.togglePhysics(false); - edge.options.hidden = true; - } + addWeekParseToken(['dd', 'ddd', 'dddd'], function (input, week, config) { + var weekday = config._locale.weekdaysParse(input); + // if we didn't get a weekday name, mark the date as invalid + if (weekday != null) { + week.d = weekday; + } else { + config._pf.invalidWeekday = input; } - } + }); - // disable the childNodes - for (var nodeId in childNodesObj) { - if (childNodesObj.hasOwnProperty(nodeId)) { - this.clusteredNodes[nodeId] = { clusterId: clusterNodeProperties.id, node: this.body.nodes[nodeId] }; - this.body.nodes[nodeId].togglePhysics(false); - this.body.nodes[nodeId].options.hidden = true; + addWeekParseToken(['d', 'e', 'E'], function (input, week, config, token) { + week[token] = toInt(input); + }); + + // HELPERS + + function parseWeekday(input, locale) { + if (typeof input === 'string') { + if (!isNaN(input)) { + input = parseInt(input, 10); + } + else { + input = locale.weekdaysParse(input); + if (typeof input !== 'number') { + return null; + } + } } - } + return input; + } - // push new edges to global - for (var i = 0; i < newEdges.length; i++) { - this.body.edges[newEdges[i].id] = newEdges[i]; - this.body.edges[newEdges[i].id].connect(); - } + // LOCALES - // set ID to undefined so no duplicates arise - clusterNodeProperties.id = undefined; + var defaultLocaleWeekdays = 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'); + function localeWeekdays (m) { + return this._weekdays[m.day()]; + } - // wrap up - if (refreshData === true) { - this.body.emitter.emit("_dataChanged"); - } + var defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'); + function localeWeekdaysShort (m) { + return this._weekdaysShort[m.day()]; } - }, { - key: "isCluster", - /** - * Check if a node is a cluster. - * @param nodeId - * @returns {*} - */ - value: function isCluster(nodeId) { - if (this.body.nodes[nodeId] !== undefined) { - return this.body.nodes[nodeId].isCluster === true; - } else { - console.log("Node does not exist."); - return false; - } + var defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'); + function localeWeekdaysMin (m) { + return this._weekdaysMin[m.day()]; } - }, { - key: "_getClusterPosition", - /** - * get the position of the cluster node based on what's inside - * @param {object} childNodesObj | object with node objects, id as keys - * @returns {{x: number, y: number}} - * @private - */ - value: function _getClusterPosition(childNodesObj) { - var childKeys = Object.keys(childNodesObj); - var minX = childNodesObj[childKeys[0]].x; - var maxX = childNodesObj[childKeys[0]].x; - var minY = childNodesObj[childKeys[0]].y; - var maxY = childNodesObj[childKeys[0]].y; - var node = undefined; - for (var i = 0; i < childKeys.lenght; i++) { - node = childNodesObj[childKeys[0]]; - minX = node.x < minX ? node.x : minX; - maxX = node.x > maxX ? node.x : maxX; - minY = node.y < minY ? node.y : minY; - maxY = node.y > maxY ? node.y : maxY; - } - return { x: 0.5 * (minX + maxX), y: 0.5 * (minY + maxY) }; - } - }, { - key: "openCluster", - - /** - * Open a cluster by calling this function. - * @param {String} clusterNodeId | the ID of the cluster node - * @param {Boolean} refreshData | wrap up afterwards if not true - */ - value: function openCluster(clusterNodeId) { - var refreshData = arguments[1] === undefined ? true : arguments[1]; - - // kill conditions - if (clusterNodeId === undefined) { - throw new Error("No clusterNodeId supplied to openCluster."); - } - if (this.body.nodes[clusterNodeId] === undefined) { - throw new Error("The clusterNodeId supplied to openCluster does not exist."); - } - if (this.body.nodes[clusterNodeId].containedNodes === undefined) { - console.log("The node:" + clusterNodeId + " is not a cluster."); - return; - } - var clusterNode = this.body.nodes[clusterNodeId]; - var containedNodes = clusterNode.containedNodes; - var containedEdges = clusterNode.containedEdges; - - // release nodes - for (var nodeId in containedNodes) { - if (containedNodes.hasOwnProperty(nodeId)) { - var containedNode = this.body.nodes[nodeId]; - containedNode = containedNodes[nodeId]; - // inherit position - containedNode.x = clusterNode.x; - containedNode.y = clusterNode.y; - - // inherit speed - containedNode.vx = clusterNode.vx; - containedNode.vy = clusterNode.vy; - - containedNode.options.hidden = false; - containedNode.togglePhysics(true); + function localeWeekdaysParse (weekdayName) { + var i, mom, regex; - delete this.clusteredNodes[nodeId]; + if (!this._weekdaysParse) { + this._weekdaysParse = []; } - } - // release edges - for (var edgeId in containedEdges) { - if (containedEdges.hasOwnProperty(edgeId)) { - var edge = this.body.edges[edgeId]; - edge.options.hidden = false; - edge.togglePhysics(true); + for (i = 0; i < 7; i++) { + // make the regex if we don't have it already + if (!this._weekdaysParse[i]) { + mom = local__createLocal([2000, 1]).day(i); + regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, ''); + this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i'); + } + // test the regex + if (this._weekdaysParse[i].test(weekdayName)) { + return i; + } } - } - - // 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]; + // MOMENTS - if (refreshData === true) { - this.body.emitter.emit("_dataChanged"); - } + function getSetDayOfWeek (input) { + var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay(); + if (input != null) { + input = parseWeekday(input, this.localeData()); + return this.add(input - day, 'd'); + } else { + return day; + } } - }, { - key: "_connectEdge", - /** - * Connect an edge that was previously contained from cluster A to cluster B if the node that it was originally connected to - * is currently residing in cluster B - * @param edge - * @param nodeId - * @param from - * @private - */ - value: function _connectEdge(edge, nodeId, from) { - var clusterStack = this.findNode(nodeId); - if (from === true) { - edge.from = clusterStack[clusterStack.length - 1]; - edge.fromId = clusterStack[clusterStack.length - 1].id; - clusterStack.pop(); - edge.fromArray = clusterStack; - } else { - edge.to = clusterStack[clusterStack.length - 1]; - edge.toId = clusterStack[clusterStack.length - 1].id; - clusterStack.pop(); - edge.toArray = clusterStack; - } - edge.connect(); + function getSetLocaleDayOfWeek (input) { + var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7; + return input == null ? weekday : this.add(input - weekday, 'd'); } - }, { - 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; + function getSetISODayOfWeek (input) { + // behaves the same as moment#day except + // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6) + // as a setter, sunday should belong to the previous week. + return input == null ? this.day() || 7 : this.day(this.day() % 7 ? input : input - 7); } - }, { - 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", + addFormatToken('H', ['HH', 2], 0, 'hour'); + addFormatToken('h', ['hh', 2], 0, function () { + return this.hours() % 12 || 12; + }); - /** - * 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; + function meridiem (token, lowercase) { + addFormatToken(token, 0, 0, function () { + return this.localeData().meridiem(this.hours(), this.minutes(), lowercase); + }); + } - 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; + meridiem('a', true); + meridiem('A', false); - var letiance = averageSquared - Math.pow(average, 2); - var standardDeviation = Math.sqrt(letiance); + // ALIASES - var hubThreshold = Math.floor(average + 2 * standardDeviation); + addUnitAlias('hour', 'h'); - // always have at least one to cluster - if (hubThreshold > largestHub) { - hubThreshold = largestHub; - } + // PARSING - return hubThreshold; + function matchMeridiem (isStrict, locale) { + return locale._meridiemParse; } - }]); - return ClusterEngine; - })(); + addRegexToken('a', matchMeridiem); + addRegexToken('A', matchMeridiem); + addRegexToken('H', match1to2); + addRegexToken('h', match1to2); + addRegexToken('HH', match1to2, match2); + addRegexToken('hh', match1to2, match2); - exports["default"] = ClusterEngine; - module.exports = exports["default"]; + addParseToken(['H', 'HH'], HOUR); + addParseToken(['a', 'A'], function (input, array, config) { + config._isPm = config._locale.isPM(input); + config._meridiem = input; + }); + addParseToken(['h', 'hh'], function (input, array, config) { + array[HOUR] = toInt(input); + config._pf.bigHour = true; + }); -/***/ }, -/* 68 */ -/***/ function(module, exports, __webpack_require__) { + // LOCALES - 'use strict'; + function localeIsPM (input) { + // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays + // Using charAt should be more compatible. + return ((input + '').toLowerCase().charAt(0) === 'p'); + } - Object.defineProperty(exports, '__esModule', { - value: true - }); + var defaultLocaleMeridiemParse = /[ap]\.?m?\.?/i; + function localeMeridiem (hours, minutes, isLower) { + if (hours > 11) { + return isLower ? 'pm' : 'PM'; + } else { + return isLower ? 'am' : 'AM'; + } + } - 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'); } } + // MOMENTS - if (typeof window !== 'undefined') { - window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame; - } + // Setting the hour should keep the time, because the user explicitly + // specified which hour he wants. So trying to maintain the same hour (in + // a new timezone) makes sense. Adding/subtracting hours does not follow + // this rule. + var getSetHour = makeGetSet('Hours', true); - var util = __webpack_require__(1); + addFormatToken('m', ['mm', 2], 0, 'minute'); - var CanvasRenderer = (function () { - function CanvasRenderer(body, canvas) { - _classCallCheck(this, CanvasRenderer); + // ALIASES - this.body = body; - this.canvas = canvas; + addUnitAlias('minute', 'm'); - this.redrawRequested = false; - this.renderTimer = undefined; - this.requiresTimeout = true; - this.renderingActive = false; - this.renderRequests = 0; - this.pixelRatio = undefined; - this.allowRedrawRequests = true; + // PARSING - this.dragging = false; - this.options = {}; - this.defaultOptions = { - hideEdgesOnDrag: false, - hideNodesOnDrag: false - }; - util.extend(this.options, this.defaultOptions); + addRegexToken('m', match1to2); + addRegexToken('mm', match1to2, match2); + addParseToken(['m', 'mm'], MINUTE); - this._determineBrowserMethod(); - this.bindEventListeners(); - } + // MOMENTS - _createClass(CanvasRenderer, [{ - key: 'bindEventListeners', - value: function bindEventListeners() { - var _this = this; + var getSetMinute = makeGetSet('Minutes', false); - this.body.emitter.on('dragStart', function () { - _this.dragging = true; - }); - this.body.emitter.on('dragEnd', function () { - return _this.dragging = false; - }); - 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 () { - _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) { - 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 - } - } - } - } - }, { - key: '_renderStep', - value: function _renderStep() { - if (this.renderingActive === true) { - // reset the renderTimer so a new scheduled animation step can be set - this.renderTimer = undefined; + addFormatToken('s', ['ss', 2], 0, 'second'); - if (this.requiresTimeout === true) { - // this schedules a new simulation step - this._startRendering(); - } + // ALIASES - this._redraw(); + addUnitAlias('second', 's'); - if (this.requiresTimeout === false) { - // this schedules a new simulation step - this._startRendering(); - } - } - } - }, { - key: 'redraw', + // PARSING - /** - * Redraw the network with the current data - * chart will be resized too. - */ - value: function redraw() { - this.body.emitter.emit('setSize'); - this._redraw(); - } - }, { - key: '_requestRedraw', + addRegexToken('s', match1to2); + addRegexToken('ss', match1to2, match2); + addParseToken(['s', 'ss'], SECOND); - /** - * 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 _requestRedraw() { - if (this.redrawRequested !== true && this.renderingActive === false && this.allowRedrawRequests === true) { - this.redrawRequested = true; - if (this.requiresTimeout === true) { - window.setTimeout(this._redraw.bind(this, false), 0); - } else { - window.requestAnimationFrame(this._redraw.bind(this, false)); - } - } - } - }, { - key: '_redraw', - value: function _redraw() { - var hidden = arguments[0] === undefined ? false : arguments[0]; + // MOMENTS - this.body.emitter.emit('initRedraw'); + var getSetSecond = makeGetSet('Seconds', false); - this.redrawRequested = false; - var ctx = this.canvas.frame.canvas.getContext('2d'); + addFormatToken('S', 0, 0, function () { + return ~~(this.millisecond() / 100); + }); - // 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(); - } + addFormatToken(0, ['SS', 2], 0, function () { + return ~~(this.millisecond() / 10); + }); - if (this.pixelRatio === undefined) { - this.pixelRatio = (window.devicePixelRatio || 1) / (ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1); - } + function millisecond__milliseconds (token) { + addFormatToken(0, [token, 3], 0, 'millisecond'); + } - ctx.setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0); + millisecond__milliseconds('SSS'); + millisecond__milliseconds('SSSS'); - // clear the canvas - var w = this.canvas.frame.canvas.clientWidth; - var h = this.canvas.frame.canvas.clientHeight; - ctx.clearRect(0, 0, w, h); + // ALIASES - this.body.emitter.emit('beforeDrawing', ctx); + addUnitAlias('millisecond', 'ms'); - // 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); + // PARSING - if (hidden === false) { - if (this.dragging === false || this.dragging === true && this.options.hideEdgesOnDrag === false) { - this._drawEdges(ctx); - } - } + addRegexToken('S', match1to3, match1); + addRegexToken('SS', match1to3, match2); + addRegexToken('SSS', match1to3, match3); + addRegexToken('SSSS', matchUnsigned); + addParseToken(['S', 'SS', 'SSS', 'SSSS'], function (input, array) { + array[MILLISECOND] = toInt(('0.' + input) * 1000); + }); - if (this.dragging === false || this.dragging === true && this.options.hideNodesOnDrag === false) { - this._drawNodes(ctx, hidden); - } + // MOMENTS - if (this.controlNodesActive === true) { - this._drawControlNodes(ctx); - } + var getSetMillisecond = makeGetSet('Milliseconds', false); - //this.physics.nodesSolver._debug(ctx,"#F00F0F"); + addFormatToken('z', 0, 0, 'zoneAbbr'); + addFormatToken('zz', 0, 0, 'zoneName'); - this.body.emitter.emit('afterDrawing', ctx); + // MOMENTS - // restore original scaling and translation - ctx.restore(); + function getZoneAbbr () { + return this._isUTC ? 'UTC' : ''; + } - if (hidden === true) { - ctx.clearRect(0, 0, w, h); - } + function getZoneName () { + return this._isUTC ? 'Coordinated Universal Time' : ''; } - }, { - key: '_drawNodes', - /** - * 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]; + var momentPrototype__proto = Moment.prototype; - 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 }; + momentPrototype__proto.add = add_subtract__add; + momentPrototype__proto.calendar = moment_calendar__calendar; + momentPrototype__proto.clone = clone; + momentPrototype__proto.diff = diff; + momentPrototype__proto.endOf = endOf; + momentPrototype__proto.format = format; + momentPrototype__proto.from = from; + momentPrototype__proto.fromNow = fromNow; + momentPrototype__proto.get = getSet; + momentPrototype__proto.invalidAt = invalidAt; + momentPrototype__proto.isAfter = isAfter; + momentPrototype__proto.isBefore = isBefore; + momentPrototype__proto.isBetween = isBetween; + momentPrototype__proto.isSame = isSame; + momentPrototype__proto.isValid = moment_valid__isValid; + momentPrototype__proto.lang = lang; + momentPrototype__proto.locale = locale; + momentPrototype__proto.localeData = localeData; + momentPrototype__proto.max = prototypeMax; + momentPrototype__proto.min = prototypeMin; + momentPrototype__proto.parsingFlags = parsingFlags; + momentPrototype__proto.set = getSet; + momentPrototype__proto.startOf = startOf; + momentPrototype__proto.subtract = add_subtract__subtract; + momentPrototype__proto.toArray = toArray; + momentPrototype__proto.toDate = toDate; + momentPrototype__proto.toISOString = moment_format__toISOString; + momentPrototype__proto.toJSON = moment_format__toISOString; + momentPrototype__proto.toString = toString; + momentPrototype__proto.unix = unix; + momentPrototype__proto.valueOf = to_type__valueOf; - // 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); - } - } - } + // Year + momentPrototype__proto.year = getSetYear; + momentPrototype__proto.isLeapYear = getIsLeapYear; - // draw the selected nodes on top - for (var i = 0; i < selected.length; i++) { - node = nodes[selected[i]]; - node.draw(ctx); - } - } - }, { - key: '_drawEdges', + // Week Year + momentPrototype__proto.weekYear = getSetWeekYear; + momentPrototype__proto.isoWeekYear = getSetISOWeekYear; - /** - * Redraw all edges - * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d'); - * @param {CanvasRenderingContext2D} ctx - * @private - */ - value: function _drawEdges(ctx) { - var edges = this.body.edges; - var edgeIndices = this.body.edgeIndices; - var edge = undefined; + // Quarter + momentPrototype__proto.quarter = momentPrototype__proto.quarters = getSetQuarter; - for (var i = 0; i < edgeIndices.length; i++) { - edge = edges[edgeIndices[i]]; - if (edge.connected === true) { - edge.draw(ctx); - } - } + // Month + momentPrototype__proto.month = getSetMonth; + momentPrototype__proto.daysInMonth = getDaysInMonth; + + // Week + momentPrototype__proto.week = momentPrototype__proto.weeks = getSetWeek; + momentPrototype__proto.isoWeek = momentPrototype__proto.isoWeeks = getSetISOWeek; + momentPrototype__proto.weeksInYear = getWeeksInYear; + momentPrototype__proto.isoWeeksInYear = getISOWeeksInYear; + + // Day + momentPrototype__proto.date = getSetDayOfMonth; + momentPrototype__proto.day = momentPrototype__proto.days = getSetDayOfWeek; + momentPrototype__proto.weekday = getSetLocaleDayOfWeek; + momentPrototype__proto.isoWeekday = getSetISODayOfWeek; + momentPrototype__proto.dayOfYear = getSetDayOfYear; + + // Hour + momentPrototype__proto.hour = momentPrototype__proto.hours = getSetHour; + + // Minute + momentPrototype__proto.minute = momentPrototype__proto.minutes = getSetMinute; + + // Second + momentPrototype__proto.second = momentPrototype__proto.seconds = getSetSecond; + + // Millisecond + momentPrototype__proto.millisecond = momentPrototype__proto.milliseconds = getSetMillisecond; + + // Offset + momentPrototype__proto.utcOffset = getSetOffset; + momentPrototype__proto.utc = setOffsetToUTC; + momentPrototype__proto.local = setOffsetToLocal; + momentPrototype__proto.parseZone = setOffsetToParsedOffset; + momentPrototype__proto.hasAlignedHourOffset = hasAlignedHourOffset; + momentPrototype__proto.isDST = isDaylightSavingTime; + momentPrototype__proto.isDSTShifted = isDaylightSavingTimeShifted; + momentPrototype__proto.isLocal = isLocal; + momentPrototype__proto.isUtcOffset = isUtcOffset; + momentPrototype__proto.isUtc = isUtc; + momentPrototype__proto.isUTC = isUtc; + + // Timezone + momentPrototype__proto.zoneAbbr = getZoneAbbr; + momentPrototype__proto.zoneName = getZoneName; + + // Deprecations + momentPrototype__proto.dates = deprecate('dates accessor is deprecated. Use date instead.', getSetDayOfMonth); + momentPrototype__proto.months = deprecate('months accessor is deprecated. Use month instead', getSetMonth); + momentPrototype__proto.years = deprecate('years accessor is deprecated. Use year instead', getSetYear); + momentPrototype__proto.zone = deprecate('moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779', getSetZone); + + var momentPrototype = momentPrototype__proto; + + function moment__createUnix (input) { + return local__createLocal(input * 1000); } - }, { - key: '_drawControlNodes', - /** - * Redraw all edges - * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d'); - * @param {CanvasRenderingContext2D} ctx - * @private - */ - value: function _drawControlNodes(ctx) { - var edges = this.body.edges; - var edgeIndices = this.body.edgeIndices; - var edge = undefined; + function moment__createInZone () { + return local__createLocal.apply(null, arguments).parseZone(); + } - for (var i = 0; i < edgeIndices.length; i++) { - edge = edges[edgeIndices[i]]; - edge._drawControlNodes(ctx); - } + var defaultCalendar = { + sameDay : '[Today at] LT', + nextDay : '[Tomorrow at] LT', + nextWeek : 'dddd [at] LT', + lastDay : '[Yesterday at] LT', + lastWeek : '[Last] dddd [at] LT', + sameElse : 'L' + }; + + function locale_calendar__calendar (key, mom, now) { + var output = this._calendar[key]; + return typeof output === 'function' ? output.call(mom, now) : output; } - }, { - key: '_determineBrowserMethod', - /** - * 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 _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; - } + var defaultLongDateFormat = { + LTS : 'h:mm:ss A', + LT : 'h:mm A', + L : 'MM/DD/YYYY', + LL : 'MMMM D, YYYY', + LLL : 'MMMM D, YYYY LT', + LLLL : 'dddd, MMMM D, YYYY LT' + }; + + function longDateFormat (key) { + var output = this._longDateFormat[key]; + if (!output && this._longDateFormat[key.toUpperCase()]) { + output = this._longDateFormat[key.toUpperCase()].replace(/MMMM|MM|DD|dddd/g, function (val) { + return val.slice(1); + }); + this._longDateFormat[key] = output; } - } else { - this.requiresTimeout = true; - } + return output; } - }]); - return CanvasRenderer; - })(); + var defaultInvalidDate = 'Invalid date'; - exports['default'] = CanvasRenderer; - module.exports = exports['default']; + function invalidDate () { + return this._invalidDate; + } -/***/ }, -/* 69 */ -/***/ function(module, exports, __webpack_require__) { + var defaultOrdinal = '%d'; + var defaultOrdinalParse = /\d{1,2}/; - 'use strict'; + function ordinal (number) { + return this._ordinal.replace('%d', number); + } - Object.defineProperty(exports, '__esModule', { - value: true - }); + function preParsePostFormat (string) { + return string; + } - 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 defaultRelativeTime = { + future : 'in %s', + past : '%s ago', + s : 'a few seconds', + m : 'a minute', + mm : '%d minutes', + h : 'an hour', + hh : '%d hours', + d : 'a day', + dd : '%d days', + M : 'a month', + MM : '%d months', + y : 'a year', + yy : '%d years' + }; - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + function relative__relativeTime (number, withoutSuffix, string, isFuture) { + var output = this._relativeTime[string]; + return (typeof output === 'function') ? + output(number, withoutSuffix, string, isFuture) : + output.replace(/%d/i, number); + } - var Hammer = __webpack_require__(41); - var hammerUtil = __webpack_require__(49); + function pastFuture (diff, output) { + var format = this._relativeTime[diff > 0 ? 'future' : 'past']; + return typeof format === 'function' ? format(output) : format.replace(/%s/i, output); + } - var util = __webpack_require__(1); + function locale_set__set (config) { + var prop, i; + for (i in config) { + prop = config[i]; + if (typeof prop === 'function') { + this[i] = prop; + } else { + this['_' + i] = prop; + } + } + // Lenient ordinal parsing accepts just a number in addition to + // number + (possibly) stuff coming from _ordinalParseLenient. + this._ordinalParseLenient = new RegExp(this._ordinalParse.source + '|' + /\d{1,2}/.source); + } - /** - * 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 prototype__proto = Locale.prototype; - var Canvas = (function () { - function Canvas(body) { - _classCallCheck(this, Canvas); + prototype__proto._calendar = defaultCalendar; + prototype__proto.calendar = locale_calendar__calendar; + prototype__proto._longDateFormat = defaultLongDateFormat; + prototype__proto.longDateFormat = longDateFormat; + prototype__proto._invalidDate = defaultInvalidDate; + prototype__proto.invalidDate = invalidDate; + prototype__proto._ordinal = defaultOrdinal; + prototype__proto.ordinal = ordinal; + prototype__proto._ordinalParse = defaultOrdinalParse; + prototype__proto.preparse = preParsePostFormat; + prototype__proto.postformat = preParsePostFormat; + prototype__proto._relativeTime = defaultRelativeTime; + prototype__proto.relativeTime = relative__relativeTime; + prototype__proto.pastFuture = pastFuture; + prototype__proto.set = locale_set__set; - this.body = body; - this.pixelRatio = 1; - this.resizeTimer = undefined; - this.resizeFunction = this._onResize.bind(this); + // Month + prototype__proto.months = localeMonths; + prototype__proto._months = defaultLocaleMonths; + prototype__proto.monthsShort = localeMonthsShort; + prototype__proto._monthsShort = defaultLocaleMonthsShort; + prototype__proto.monthsParse = localeMonthsParse; - this.options = {}; - this.defaultOptions = { - autoResize: true, - height: '100%', - width: '100%' - }; - util.extend(this.options, this.defaultOptions); + // Week + prototype__proto.week = localeWeek; + prototype__proto._week = defaultLocaleWeek; + prototype__proto.firstDayOfYear = localeFirstDayOfYear; + prototype__proto.firstDayOfWeek = localeFirstDayOfWeek; - this.bindEventListeners(); - } + // Day of Week + prototype__proto.weekdays = localeWeekdays; + prototype__proto._weekdays = defaultLocaleWeekdays; + prototype__proto.weekdaysMin = localeWeekdaysMin; + prototype__proto._weekdaysMin = defaultLocaleWeekdaysMin; + prototype__proto.weekdaysShort = localeWeekdaysShort; + prototype__proto._weekdaysShort = defaultLocaleWeekdaysShort; + prototype__proto.weekdaysParse = localeWeekdaysParse; - _createClass(Canvas, [{ - key: 'bindEventListeners', - value: function bindEventListeners() { - var _this = this; + // Hours + prototype__proto.isPM = localeIsPM; + prototype__proto._meridiemParse = defaultLocaleMeridiemParse; + prototype__proto.meridiem = localeMeridiem; - // bind the events - this.body.emitter.once('resize', function (obj) { - if (obj.width !== 0) { - _this.body.view.translation.x = obj.width * 0.5; + function lists__get (format, index, field, setter) { + var locale = locale_locales__getLocale(); + var utc = create_utc__createUTC().set(setter, index); + return locale[field](utc, format); + } + + function list (format, index, field, count, setter) { + if (typeof format === 'number') { + index = format; + format = undefined; } - if (obj.height !== 0) { - _this.body.view.translation.y = obj.height * 0.5; + + format = format || ''; + + if (index != null) { + return lists__get(format, index, field, setter); } - }); - this.body.emitter.on('setSize', this.setSize.bind(this)); - this.body.emitter.on('destroy', function () { - _this.hammerFrame.destroy(); - _this.hammer.destroy(); - _this._cleanUp(); - }); + + var i; + var out = []; + for (i = 0; i < count; i++) { + out[i] = lists__get(format, i, field, setter); + } + return out; } - }, { - key: 'setOptions', - value: function setOptions(options) { - var _this2 = this; - if (options !== undefined) { - var fields = ['width', 'height', 'autoResize']; - util.selectiveDeepExtend(fields, this.options, options); - } + function lists__listMonths (format, index) { + return list(format, index, 'months', 12, 'month'); + } - if (this.options.autoResize === true) { - // automatically adapt to a changing size of the browser. - this._cleanUp(); - this.resizeTimer = setInterval(function () { - _this2.setSize();_this2.body.emitter.emit('_requestRedraw'); - }, 1000); - this.resizeFunction = this._onResize.bind(this); - util.addEventListener(window, 'resize', this.resizeFunction); - } + function lists__listMonthsShort (format, index) { + return list(format, index, 'monthsShort', 12, 'month'); } - }, { - 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; + + function lists__listWeekdays (format, index) { + return list(format, index, 'weekdays', 7, 'day'); } - }, { - key: '_onResize', - value: function _onResize() { - this.setSize(); - this.body.emitter.emit('_redraw'); + + function lists__listWeekdaysShort (format, index) { + return list(format, index, 'weekdaysShort', 7, 'day'); } - }, { - 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); + + function lists__listWeekdaysMin (format, index) { + return list(format, index, 'weekdaysMin', 7, 'day'); } - }, { - 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); - } + locale_locales__getSetGlobalLocale('en', { + ordinalParse: /\d{1,2}(th|st|nd|rd)/, + ordinal : function (number) { + var b = number % 10, + output = (toInt(number % 100 / 10) === 1) ? 'th' : + (b === 1) ? 'st' : + (b === 2) ? 'nd' : + (b === 3) ? 'rd' : 'th'; + return number + output; + } + }); - 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 + // Side effect imports + utils_hooks__hooks.lang = deprecate('moment.lang is deprecated. Use moment.locale instead.', locale_locales__getSetGlobalLocale); + utils_hooks__hooks.langData = deprecate('moment.langData is deprecated. Use moment.localeData instead.', locale_locales__getLocale); - ////////////////////////////////////////////////////////////////// + var mathAbs = Math.abs; - this.frame.canvas = document.createElement('canvas'); - this.frame.canvas.style.position = 'relative'; - this.frame.appendChild(this.frame.canvas); + function duration_abs__abs () { + var data = this._data; - 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); + this._milliseconds = mathAbs(this._milliseconds); + this._days = mathAbs(this._days); + this._months = mathAbs(this._months); - this.frame.canvas.getContext('2d').setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0); - } + data.milliseconds = mathAbs(data.milliseconds); + data.seconds = mathAbs(data.seconds); + data.minutes = mathAbs(data.minutes); + data.hours = mathAbs(data.hours); + data.months = mathAbs(data.months); + data.years = mathAbs(data.years); - // add the frame to the container element - this.body.container.appendChild(this.frame); + return this; + } - this.body.view.scale = 1; - this.body.view.translation = { x: 0.5 * this.frame.canvas.clientWidth, y: 0.5 * this.frame.canvas.clientHeight }; + function duration_add_subtract__addSubtract (duration, input, value, direction) { + var other = create__createDuration(input, value); - this._bindHammer(); + duration._milliseconds += direction * other._milliseconds; + duration._days += direction * other._days; + duration._months += direction * other._months; + + return duration._bubble(); } - }, { - key: '_bindHammer', - /** - * This function binds hammer, it can be repeated over and over due to the uniqueness check. - * @private - */ - value: function _bindHammer() { - var _this3 = this; + // supports only 2.0-style add(1, 's') or add(duration) + function duration_add_subtract__add (input, value) { + return duration_add_subtract__addSubtract(this, input, value, 1); + } - if (this.hammer !== undefined) { - this.hammer.destroy(); - } - this.drag = {}; - this.pinch = {}; + // supports only 2.0-style subtract(1, 's') or subtract(duration) + function duration_add_subtract__subtract (input, value) { + return duration_add_subtract__addSubtract(this, input, value, -1); + } - // 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}); + function bubble () { + var milliseconds = this._milliseconds; + var days = this._days; + var months = this._months; + var data = this._data; + var seconds, minutes, hours, years = 0; - 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); - }); + // The following code bubbles up values, see the tests for + // examples of what that means. + data.milliseconds = milliseconds % 1000; - // 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); - }); + seconds = absFloor(milliseconds / 1000); + data.seconds = seconds % 60; - this.frame.canvas.addEventListener('mousemove', function (event) { - _this3.body.eventListeners.onMouseMove(event); - }); - this.frame.canvas.addEventListener('contextmenu', function (event) { - _this3.body.eventListeners.onContext(event); - }); + minutes = absFloor(seconds / 60); + data.minutes = minutes % 60; - this.hammerFrame = new Hammer(this.frame); - hammerUtil.onRelease(this.hammerFrame, function (event) { - _this3.body.eventListeners.onRelease(event); - }); - } - }, { - key: 'setSize', + hours = absFloor(minutes / 60); + data.hours = hours % 24; - /** - * 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]; + days += absFloor(hours / 24); - width = this._prepareValue(width); - height = this._prepareValue(height); + // Accurately convert days to years, assume start from year 0. + years = absFloor(daysToYears(days)); + days -= absFloor(yearsToDays(years)); - var emitEvent = false; - var oldWidth = this.frame.canvas.width; - var oldHeight = this.frame.canvas.height; + // 30 days to a month + // TODO (iskren): Use anchor date (like 1st Jan) to compute this. + months += absFloor(days / 30); + days %= 30; - 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; + // 12 months -> 1 year + years += absFloor(months / 12); + months %= 12; - this.frame.canvas.style.width = '100%'; - this.frame.canvas.style.height = '100%'; + data.days = days; + data.months = months; + data.years = years; - this.frame.canvas.width = this.frame.canvas.clientWidth * this.pixelRatio; - this.frame.canvas.height = this.frame.canvas.clientHeight * this.pixelRatio; + return this; + } - this.options.width = width; - this.options.height = height; + function daysToYears (days) { + // 400 years have 146097 days (taking into account leap year rules) + return days * 400 / 146097; + } - emitEvent = true; - } else { - // this would adapt the width of the canvas to the width from 100% if and only if - // there is a change. + function yearsToDays (years) { + // years * 365 + absFloor(years / 4) - + // absFloor(years / 100) + absFloor(years / 400); + return years * 146097 / 400; + } - if (this.frame.canvas.width != this.frame.canvas.clientWidth * this.pixelRatio) { - this.frame.canvas.width = this.frame.canvas.clientWidth * this.pixelRatio; - emitEvent = true; - } - if (this.frame.canvas.height != this.frame.canvas.clientHeight * this.pixelRatio) { - this.frame.canvas.height = this.frame.canvas.clientHeight * this.pixelRatio; - emitEvent = true; - } - } + function as (units) { + var days; + var months; + var milliseconds = this._milliseconds; - if (emitEvent === true) { - this.body.emitter.emit('resize', { width: this.frame.canvas.width / this.pixelRatio, height: this.frame.canvas.height / this.pixelRatio, oldWidth: oldWidth / this.pixelRatio, oldHeight: oldHeight / this.pixelRatio }); - } - } - }, { - key: '_XconvertDOMtoCanvas', + units = normalizeUnits(units); - /** - * 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; + if (units === 'month' || units === 'year') { + days = this._days + milliseconds / 864e5; + months = this._months + daysToYears(days) * 12; + return units === 'month' ? months : months / 12; + } else { + // handle milliseconds separately because of floating point math errors (issue #1867) + days = this._days + Math.round(yearsToDays(this._months / 12)); + switch (units) { + case 'week' : return days / 7 + milliseconds / 6048e5; + case 'day' : return days + milliseconds / 864e5; + case 'hour' : return days * 24 + milliseconds / 36e5; + case 'minute' : return days * 24 * 60 + milliseconds / 6e4; + case 'second' : return days * 24 * 60 * 60 + milliseconds / 1000; + // Math.floor prevents floating point math errors here + case 'millisecond': return Math.floor(days * 24 * 60 * 60 * 1000) + milliseconds; + default: throw new Error('Unknown unit ' + units); + } + } } - }, { - 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; + // TODO: Use this.as('ms')? + function duration_as__valueOf () { + return ( + this._milliseconds + + this._days * 864e5 + + (this._months % 12) * 2592e6 + + toInt(this._months / 12) * 31536e6 + ); } - }, { - 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; + function makeAs (alias) { + return function () { + return this.as(alias); + }; } - }, { - 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', + var asMilliseconds = makeAs('ms'); + var asSeconds = makeAs('s'); + var asMinutes = makeAs('m'); + var asHours = makeAs('h'); + var asDays = makeAs('d'); + var asWeeks = makeAs('w'); + var asMonths = makeAs('M'); + var asYears = makeAs('y'); - /** - * - * @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) }; + function duration_get__get (units) { + units = normalizeUnits(units); + return this[units + 's'](); } - }, { - 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) }; + function makeGetter(name) { + return function () { + return this._data[name]; + }; } - }]); - - return Canvas; - })(); - exports['default'] = Canvas; - module.exports = exports['default']; + var duration_get__milliseconds = makeGetter('milliseconds'); + var seconds = makeGetter('seconds'); + var minutes = makeGetter('minutes'); + var hours = makeGetter('hours'); + var days = makeGetter('days'); + var months = makeGetter('months'); + var years = makeGetter('years'); -/***/ }, -/* 70 */ -/***/ function(module, exports, __webpack_require__) { + function weeks () { + return absFloor(this.days() / 7); + } - "use strict"; + var round = Math.round; + var thresholds = { + s: 45, // seconds to minute + m: 45, // minutes to hour + h: 22, // hours to day + d: 26, // days to month + M: 11 // months to year + }; - Object.defineProperty(exports, "__esModule", { - value: true - }); + // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize + function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) { + return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture); + } - 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 duration_humanize__relativeTime (posNegDuration, withoutSuffix, locale) { + var duration = create__createDuration(posNegDuration).abs(); + var seconds = round(duration.as('s')); + var minutes = round(duration.as('m')); + var hours = round(duration.as('h')); + var days = round(duration.as('d')); + var months = round(duration.as('M')); + var years = round(duration.as('y')); - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + var a = seconds < thresholds.s && ['s', seconds] || + minutes === 1 && ['m'] || + minutes < thresholds.m && ['mm', minutes] || + hours === 1 && ['h'] || + hours < thresholds.h && ['hh', hours] || + days === 1 && ['d'] || + days < thresholds.d && ['dd', days] || + months === 1 && ['M'] || + months < thresholds.M && ['MM', months] || + years === 1 && ['y'] || ['yy', years]; - var util = __webpack_require__(1); + a[2] = withoutSuffix; + a[3] = +posNegDuration > 0; + a[4] = locale; + return substituteTimeAgo.apply(null, a); + } - var View = (function () { - function View(body, canvas) { - var _this = this; + // This function allows you to set a threshold for relative time strings + function duration_humanize__getSetRelativeTimeThreshold (threshold, limit) { + if (thresholds[threshold] === undefined) { + return false; + } + if (limit === undefined) { + return thresholds[threshold]; + } + thresholds[threshold] = limit; + return true; + } - _classCallCheck(this, View); + function humanize (withSuffix) { + var locale = this.localeData(); + var output = duration_humanize__relativeTime(this, !withSuffix, locale); - this.body = body; - this.canvas = canvas; + if (withSuffix) { + output = locale.pastFuture(+this, output); + } - 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; + return locale.postformat(output); + } - this.viewFunction = undefined; + var iso_string__abs = Math.abs; - 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)); - } + function iso_string__toISOString() { + // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js + var Y = iso_string__abs(this.years()); + var M = iso_string__abs(this.months()); + var D = iso_string__abs(this.days()); + var h = iso_string__abs(this.hours()); + var m = iso_string__abs(this.minutes()); + var s = iso_string__abs(this.seconds() + this.milliseconds() / 1000); + var total = this.asSeconds(); - _createClass(View, [{ - key: "setOptions", - value: function setOptions() { - var options = arguments[0] === undefined ? {} : arguments[0]; + if (!total) { + // this is the same as C#'s (Noda) and python (isodate)... + // but not other JS (goog.date) + return 'P0D'; + } - this.options = options; + return (total < 0 ? '-' : '') + + 'P' + + (Y ? Y + 'Y' : '') + + (M ? M + 'M' : '') + + (D ? D + 'D' : '') + + ((h || m || s) ? 'T' : '') + + (h ? h + 'H' : '') + + (m ? m + 'M' : '') + + (s ? s + 'S' : ''); } - }, { - key: "_getRange", - /** - * Find the center position of the network - * @private - */ - value: function _getRange() { - var specificNodes = arguments[0] === undefined ? [] : arguments[0]; + var duration_prototype__proto = Duration.prototype; - 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.bottom) { - minY = node.shape.boundingBox.top; - } // top is negative, bottom is positive - if (maxY < node.shape.boundingBox.top) { - 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.bottom) { - minY = node.shape.boundingBox.top; - } // top is negative, bottom is positive - if (maxY < node.shape.boundingBox.top) { - maxY = node.shape.boundingBox.bottom; - } // top is negative, bottom is positive - } - } - } + duration_prototype__proto.abs = duration_abs__abs; + duration_prototype__proto.add = duration_add_subtract__add; + duration_prototype__proto.subtract = duration_add_subtract__subtract; + duration_prototype__proto.as = as; + duration_prototype__proto.asMilliseconds = asMilliseconds; + duration_prototype__proto.asSeconds = asSeconds; + duration_prototype__proto.asMinutes = asMinutes; + duration_prototype__proto.asHours = asHours; + duration_prototype__proto.asDays = asDays; + duration_prototype__proto.asWeeks = asWeeks; + duration_prototype__proto.asMonths = asMonths; + duration_prototype__proto.asYears = asYears; + duration_prototype__proto.valueOf = duration_as__valueOf; + duration_prototype__proto._bubble = bubble; + duration_prototype__proto.get = duration_get__get; + duration_prototype__proto.milliseconds = duration_get__milliseconds; + duration_prototype__proto.seconds = seconds; + duration_prototype__proto.minutes = minutes; + duration_prototype__proto.hours = hours; + duration_prototype__proto.days = days; + duration_prototype__proto.weeks = weeks; + duration_prototype__proto.months = months; + duration_prototype__proto.years = years; + duration_prototype__proto.humanize = humanize; + duration_prototype__proto.toISOString = iso_string__toISOString; + duration_prototype__proto.toString = iso_string__toISOString; + duration_prototype__proto.toJSON = iso_string__toISOString; + duration_prototype__proto.locale = locale; + duration_prototype__proto.localeData = localeData; - 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", + // Deprecations + duration_prototype__proto.toIsoString = deprecate('toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)', iso_string__toISOString); + duration_prototype__proto.lang = lang; - /** - * @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", + // Side effect imports - /** - * 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]; + addFormatToken('X', 0, 0, 'unix'); + addFormatToken('x', 0, 0, 'valueOf'); - var range; - var zoomLevel; + // PARSING - 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; - } + addRegexToken('x', matchSigned); + addRegexToken('X', matchTimestamp); + addParseToken('X', function (input, array, config) { + config._d = new Date(parseFloat(input, 10) * 1000); + }); + addParseToken('x', function (input, array, config) { + config._d = new Date(toInt(input)); + }); - range = this._getRange(options.nodes); + // Side effect imports - 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("_redraw", true); - 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; + utils_hooks__hooks.version = '2.10.2'; - var xZoomLevel = this.canvas.frame.canvas.clientWidth / xDistance; - var yZoomLevel = this.canvas.frame.canvas.clientHeight / yDistance; + setHookCallback(local__createLocal); - zoomLevel = xZoomLevel <= yZoomLevel ? xZoomLevel : yZoomLevel; - } + utils_hooks__hooks.fn = momentPrototype; + utils_hooks__hooks.min = min; + utils_hooks__hooks.max = max; + utils_hooks__hooks.utc = create_utc__createUTC; + utils_hooks__hooks.unix = moment__createUnix; + utils_hooks__hooks.months = lists__listMonths; + utils_hooks__hooks.isDate = isDate; + utils_hooks__hooks.locale = locale_locales__getSetGlobalLocale; + utils_hooks__hooks.invalid = valid__createInvalid; + utils_hooks__hooks.duration = create__createDuration; + utils_hooks__hooks.isMoment = isMoment; + utils_hooks__hooks.weekdays = lists__listWeekdays; + utils_hooks__hooks.parseZone = moment__createInZone; + utils_hooks__hooks.localeData = locale_locales__getLocale; + utils_hooks__hooks.isDuration = isDuration; + utils_hooks__hooks.monthsShort = lists__listMonthsShort; + utils_hooks__hooks.weekdaysMin = lists__listWeekdaysMin; + utils_hooks__hooks.defineLocale = defineLocale; + utils_hooks__hooks.weekdaysShort = lists__listWeekdaysShort; + utils_hooks__hooks.normalizeUnits = normalizeUnits; + utils_hooks__hooks.relativeTimeThreshold = duration_humanize__getSetRelativeTimeThreshold; - if (zoomLevel > 1) { - zoomLevel = 1; - } else if (zoomLevel === 0) { - zoomLevel = 1; - } + var _moment = utils_hooks__hooks; - var center = this._findCenter(range); - var animationOptions = { position: center, scale: zoomLevel, animation: options.animation }; - this.moveTo(animationOptions); - } - }, { - key: "focus", + return _moment; - // animation + })); + /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(90)(module))) - /** - * Center a node in view. - * - * @param {Number} nodeId - * @param {Number} [options] - */ - value: function focus(nodeId) { - var options = arguments[1] === undefined ? {} : arguments[1]; +/***/ }, +/* 71 */ +/***/ function(module, exports, __webpack_require__) { - 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; + var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;'use strict'; - this.moveTo(options); - } else { - console.log("Node: " + nodeId + " cannot be found."); - } + (function (factory) { + if (true) { + // AMD. Register as an anonymous module. + !(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); + } else if (typeof exports === 'object') { + // Node. Does not work with strict CommonJS, but + // only CommonJS-like environments that support module.exports, + // like Node. + module.exports = factory(); + } else { + // Browser globals (root is window) + window.propagating = factory(); + } + }(function () { + var _firstTarget = null; // singleton, will contain the target element where the touch event started + var _processing = false; // singleton, true when a touch event is being handled + + /** + * Extend an Hammer.js instance with event propagation. + * + * Features: + * - Events emitted by hammer will propagate in order from child to parent + * elements. + * - Events are extended with a function `event.stopPropagation()` to stop + * propagation to parent elements. + * - An option `preventDefault` to stop all default browser behavior. + * + * Usage: + * var hammer = propagatingHammer(new Hammer(element)); + * var hammer = propagatingHammer(new Hammer(element), {preventDefault: true}); + * + * @param {Hammer.Manager} hammer An hammer instance. + * @param {Object} [options] Available options: + * - `preventDefault: true | 'mouse' | 'touch' | 'pen'`. + * Enforce preventing the default browser behavior. + * Cannot be set to `false`. + * @return {Hammer.Manager} Returns the same hammer instance with extended + * functionality + */ + return function propagating(hammer, options) { + if (options && options.preventDefault === false) { + throw new Error('Only supports preventDefault == true'); } - }, { - key: "moveTo", + var _options = options || { + preventDefault: false + }; - /** - * - * @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.body.view.translation; - } - if (options.animation === undefined) { - options.animation = { duration: 0 }; + if (hammer.Manager) { + // This looks like the Hammer constructor. + // Overload the constructors with our own. + var Hammer = hammer; + + var PropagatingHammer = function(element, options) { + return propagating(new Hammer(element, options), _options); + }; + Hammer.extend(PropagatingHammer, Hammer); + PropagatingHammer.Manager = function (element, options) { + return propagating(new Hammer.Manager(element, options), _options); + }; + + return PropagatingHammer; + } + + // attach to DOM element + var element = hammer.element; + element.hammer = hammer; + + // move the original functions that we will wrap + hammer._on = hammer.on; + hammer._off = hammer.off; + hammer._emit = hammer.emit; + hammer._destroy = hammer.destroy; + + /** @type {Object.>} */ + hammer._handlers = {}; + + // register an event to catch the start of a gesture and store the + // target in a singleton + hammer._on('hammer.input', function (event) { + if (_options.preventDefault === true || (_options.preventDefault === event.pointerType)) { + event.preventDefault(); } - if (options.animation === false) { - options.animation = { duration: 0 }; + if (event.isFirst) { + _firstTarget = event.target; + _processing = true; } - if (options.animation === true) { - options.animation = {}; + if (event.isFinal) { + _processing = false; } - 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 + * Register a handler for one or multiple events + * @param {String} events A space separated string with events + * @param {function} handler A callback function, called as handler(event) + * @returns {Hammer.Manager} Returns the hammer instance */ - 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; - } + hammer.on = function (events, handler) { + // register the handler + split(events).forEach(function (event) { + var _handlers = hammer._handlers[event]; + if (!_handlers) { + hammer._handlers[event] = _handlers = []; - // 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. - } + // register the static, propagated handler + hammer._on(event, propagatedHandler); + } + _handlers.push(handler); + }); - this.sourceScale = this.body.view.scale; - this.sourceTranslation = this.body.view.translation; - this.targetScale = options.scale; + return hammer; + }; - // 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 - }; + /** + * Unregister a handler for one or multiple events + * @param {String} events A space separated string with events + * @param {function} [handler] Optional. The registered handler. If not + * provided, all handlers for given events + * are removed. + * @returns {Hammer.Manager} Returns the hammer instance + */ + hammer.off = function (events, handler) { + // unregister the handler + split(events).forEach(function (event) { + var _handlers = hammer._handlers[event]; + if (_handlers) { + _handlers = handler ? _handlers.filter(function (h) { + return h !== handler; + }) : []; - // 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"); + if (_handlers.length > 0) { + hammer._handlers[event] = _handlers; + } + else { + // remove static, propagated handler + hammer._off(event, propagatedHandler); + delete hammer._handlers[event]; + } } - } 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"); - } - } - }, { - key: "_lockedRedraw", + return hammer; + }; /** - * used to animate smoothly by hijacking the redraw function. - * @private + * Emit to the event listeners + * @param {string} eventType + * @param {Event} event */ - 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: "releaseNode", - value: function releaseNode() { - if (this.lockedOnNodeId !== undefined && this.viewFunction !== undefined) { - this.body.emitter.off("initRedraw", this.viewFunction); - this.lockedOnNodeId = undefined; - this.lockedOnNodeOffset = undefined; + hammer.emit = function(eventType, event) { + if (!_processing) { + _firstTarget = event.target; } + hammer._emit(eventType, event); + }; + + hammer.destroy = function () { + // Detach from DOM element + var element = hammer.element; + delete element.hammer; + + // clear all handlers + hammer._handlers = {}; + + // call original hammer destroy + hammer._destroy(); + }; + + // split a string with space separated words + function split(events) { + return events.match(/[^ ]+/g); } - }, { - key: "_transitionRedraw", /** - * - * @param easingTime - * @private + * A static event handler, applying event propagation. + * @param {Object} event */ - value: function _transitionRedraw() { - var finished = arguments[0] === undefined ? false : arguments[0]; - - this.easingTime += this.animationSpeed; - this.easingTime = finished === true ? 1 : this.easingTime; + function propagatedHandler(event) { + // let only a single hammer instance handle this event + if (event.type !== 'hammer.input') { + // it is possible that the same srcEvent is used with multiple hammer events, + // we keep track on which events are handled in an object _handled + if (!event.srcEvent._handled) { + event.srcEvent._handled = {}; + } - var progress = util.easingFunctions[this.animationEasingFunction](this.easingTime); + if (event.srcEvent._handled[event.type]) { + return; + } + else { + event.srcEvent._handled[event.type] = true; + } + } - 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 + // attach a stopPropagation function to the event + var stopped = false; + event.stopPropagation = function () { + stopped = true; }; - // 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); + // attach firstTarget property to the event + event.firstTarget = _firstTarget; + + // propagate over all elements (until stopped) + var elem = _firstTarget; + while (elem && !stopped) { + var _handlers = elem.hammer && elem.hammer._handlers[event.type]; + if (_handlers) { + for (var i = 0; i < _handlers.length && !stopped; i++) { + _handlers[i](event); + } } - this.body.emitter.emit("animationFinished"); + + elem = elem.parentNode; } } - }, { - key: "getScale", - value: function getScale() { - return this.body.view.scale; - } - }, { - key: "getPosition", - value: function getPosition() { - return { x: this.body.view.translation.x, y: this.body.view.translation.y }; - } - }]); - return View; - })(); + return hammer; + }; + })); - exports["default"] = View; - module.exports = exports["default"]; /***/ }, -/* 71 */ +/* 72 */ /***/ 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 __WEBPACK_AMD_DEFINE_RESULT__;/*! Hammer.JS - v2.0.4 - 2014-09-28 + * http://hammerjs.github.io/ + * + * Copyright (c) 2014 Jorik Tangelder; + * Licensed under the MIT license */ + (function(window, document, exportName, undefined) { + 'use strict'; - function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + var VENDOR_PREFIXES = ['', 'webkit', 'moz', 'MS', 'ms', 'o']; + var TEST_ELEMENT = document.createElement('div'); - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + var TYPE_FUNCTION = 'function'; - var _componentsNavigationHandler = __webpack_require__(90); + var round = Math.round; + var abs = Math.abs; + var now = Date.now; - var _componentsNavigationHandler2 = _interopRequireDefault(_componentsNavigationHandler); + /** + * set a timeout with a given scope + * @param {Function} fn + * @param {Number} timeout + * @param {Object} context + * @returns {number} + */ + function setTimeoutContext(fn, timeout, context) { + return setTimeout(bindFn(fn, context), timeout); + } - var _componentsPopup = __webpack_require__(91); + /** + * if the argument is an array, we want to execute the fn on each entry + * if it aint an array we don't want to do a thing. + * this is used by all the methods that accept a single and array argument. + * @param {*|Array} arg + * @param {String} fn + * @param {Object} [context] + * @returns {Boolean} + */ + function invokeArrayArg(arg, fn, context) { + if (Array.isArray(arg)) { + each(arg, context[fn], context); + return true; + } + return false; + } - var _componentsPopup2 = _interopRequireDefault(_componentsPopup); + /** + * walk objects and arrays + * @param {Object} obj + * @param {Function} iterator + * @param {Object} context + */ + function each(obj, iterator, context) { + var i; - var util = __webpack_require__(1); + if (!obj) { + return; + } - 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); + if (obj.forEach) { + obj.forEach(iterator, context); + } else if (obj.length !== undefined) { + i = 0; + while (i < obj.length) { + iterator.call(context, obj[i], i, obj); + i++; + } + } else { + for (i in obj) { + obj.hasOwnProperty(i) && iterator.call(context, obj[i], i, obj); + } + } + } - // 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); + /** + * extend object. + * means that properties in dest will be overwritten by the ones in src. + * @param {Object} dest + * @param {Object} src + * @param {Boolean} [merge] + * @returns {Object} dest + */ + function extend(dest, src, merge) { + var keys = Object.keys(src); + var i = 0; + while (i < keys.length) { + if (!merge || (merge && dest[keys[i]] === undefined)) { + dest[keys[i]] = src[keys[i]]; + } + i++; + } + return dest; + } - this.touchTime = 0; - this.drag = {}; - this.pinch = {}; - this.hoverObj = { nodes: {}, edges: {} }; - this.popup = undefined; - this.popupObj = undefined; - this.popupTimer = undefined; + /** + * merge the values from src in the dest. + * means that properties that exist in dest will not be overwritten by src + * @param {Object} dest + * @param {Object} src + * @returns {Object} dest + */ + function merge(dest, src) { + return extend(dest, src, true); + } - this.body.functions.getPointer = this.getPointer.bind(this); + /** + * simple class inheritance + * @param {Function} child + * @param {Function} base + * @param {Object} [properties] + */ + function inherit(child, base, properties) { + var baseP = base.prototype, + childP; - this.options = {}; - this.defaultOptions = { - dragNodes: true, - dragView: true, - hoverEnabled: 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); + childP = child.prototype = Object.create(baseP); + childP.constructor = child; + childP._super = baseP; - this.bindEventListeners(); - } + if (properties) { + extend(childP, properties); + } + } - _createClass(InteractionHandler, [{ - key: 'bindEventListeners', - value: function bindEventListeners() { - var _this = this; + /** + * simple function bind + * @param {Function} fn + * @param {Object} context + * @returns {Function} + */ + function bindFn(fn, context) { + return function boundFn() { + return fn.apply(context, arguments); + }; + } - this.body.emitter.on('destroy', function () { - clearTimeout(_this.popupTimer); - delete _this.body.functions.getPointer; - }); + /** + * let a boolean value also be a function that must return a boolean + * this first item in args will be used as the context + * @param {Boolean|Function} val + * @param {Array} [args] + * @returns {Boolean} + */ + function boolOrFn(val, args) { + if (typeof val == TYPE_FUNCTION) { + return val.apply(args ? args[0] || undefined : undefined, args); } - }, { - 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'); + return val; + } - if (options.tooltip) { - util.extend(this.options.tooltip, options.tooltip); - if (options.tooltip.color) { - this.options.tooltip.color = util.parseColor(options.tooltip.color); - } - } - } + /** + * use the val2 when val1 is undefined + * @param {*} val1 + * @param {*} val2 + * @returns {*} + */ + function ifUndefined(val1, val2) { + return (val1 === undefined) ? val2 : val1; + } - this.navigationHandler.setOptions(this.options); - } - }, { - key: 'getPointer', + /** + * addEventListener with multiple events at once + * @param {EventTarget} target + * @param {String} types + * @param {Function} handler + */ + function addEventListeners(target, types, handler) { + each(splitStr(types), function(type) { + target.addEventListener(type, handler, false); + }); + } - /** - * 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', + /** + * removeEventListener with multiple events at once + * @param {EventTarget} target + * @param {String} types + * @param {Function} handler + */ + function removeEventListeners(target, types, handler) { + each(splitStr(types), function(type) { + target.removeEventListener(type, handler, false); + }); + } - /** - * 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(); - } + /** + * find if a node is in the given parent + * @method hasParent + * @param {HTMLElement} node + * @param {HTMLElement} parent + * @return {Boolean} found + */ + function hasParent(node, parent) { + while (node) { + if (node == parent) { + return true; + } + node = node.parentNode; } - }, { - key: 'onTap', + return false; + } - /** - * 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; + /** + * small indexOf wrapper + * @param {String} str + * @param {String} find + * @returns {Boolean} found + */ + function inStr(str, find) { + return str.indexOf(find) > -1; + } - this.checkSelectionChanges(pointer, event, multiselect); - this.selectionHandler._generateClickEvent('click', event, pointer); - } - }, { - key: 'onDoubleTap', + /** + * split string on whitespace + * @param {String} str + * @returns {Array} words + */ + function splitStr(str) { + return str.trim().split(/\s+/g); + } - /** - * handle doubletap event - * @private - */ - value: function onDoubleTap(event) { - var pointer = this.getPointer(event.center); - this.selectionHandler._generateClickEvent('doubleClick', event, pointer); + /** + * find if a array contains the object using indexOf or a simple polyFill + * @param {Array} src + * @param {String} find + * @param {String} [findByKey] + * @return {Boolean|Number} false when not found, or the index + */ + function inArray(src, find, findByKey) { + if (src.indexOf && !findByKey) { + return src.indexOf(find); + } else { + var i = 0; + while (i < src.length) { + if ((findByKey && src[i][findByKey] == find) || (!findByKey && src[i] === find)) { + return i; + } + i++; + } + return -1; } - }, { - 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; + /** + * convert array-like objects to real arrays + * @param {Object} obj + * @returns {Array} + */ + function toArray(obj) { + return Array.prototype.slice.call(obj, 0); + } - this.checkSelectionChanges(pointer, event, multiselect); + /** + * unique array with objects based on a key (like 'id') or just by the array's value + * @param {Array} src [{id:1},{id:2},{id:1}] + * @param {String} [key] + * @param {Boolean} [sort=False] + * @returns {Array} [{id:1},{id:2}] + */ + function uniqueArray(src, key, sort) { + var results = []; + var values = []; + var i = 0; - this.selectionHandler._generateClickEvent('click', event, pointer); - this.selectionHandler._generateClickEvent('hold', event, pointer); + while (i < src.length) { + var val = key ? src[i][key] : src[i]; + if (inArray(values, val) < 0) { + results.push(src[i]); + } + values[i] = val; + i++; } - }, { - 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(); - } - } - }, { - key: 'onContext', - value: function onContext(event) { - var pointer = this.getPointer({ x: event.pageX, y: event.pageY }); - this.selectionHandler._generateClickEvent('oncontext', event, pointer); + if (sort) { + if (!key) { + results = results.sort(); + } else { + results = results.sort(function sortUniqueArray(a, b) { + return a[key] > b[key]; + }); + } } - }, { - 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 selectedEdges = this.selectionHandler._getSelectedEdgeCount(); - var selectedNodes = this.selectionHandler._getSelectedNodeCount(); + return results; + } - if (selectedNodes - previouslySelectedNodeCount > 0) { - // node was selected - this.selectionHandler._generateClickEvent('selectNode', event, pointer); - selected = true; - } else if (selectedNodes - previouslySelectedNodeCount < 0) { - // node was deselected - this.selectionHandler._generateClickEvent('deselectNode', event, pointer, previousSelection); - selected = true; - } + /** + * get the prefixed property + * @param {Object} obj + * @param {String} property + * @returns {String|Undefined} prefixed + */ + function prefixed(obj, property) { + var prefix, prop; + var camelProp = property[0].toUpperCase() + property.slice(1); - if (selectedEdges - previouslySelectedEdgeCount > 0) { - // node was selected - this.selectionHandler._generateClickEvent('selectEdge', event, pointer); - selected = true; - } else if (selectedEdges - previouslySelectedEdgeCount < 0) { - // node was deselected - this.selectionHandler._generateClickEvent('deselectEdge', event, pointer, previousSelection); - selected = true; - } + var i = 0; + while (i < VENDOR_PREFIXES.length) { + prefix = VENDOR_PREFIXES[i]; + prop = (prefix) ? prefix + camelProp : property; - if (selected === true) { - // select or unselect - this.selectionHandler._generateClickEvent('select', event, pointer); - } + if (prop in obj) { + return prop; + } + i++; } - }, { - key: 'onDragStart', + return undefined; + } - /** - * This function is called by onDragStart. - * It is separated out because we can then overload it for the datamanipulation system. - * - * @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); - } + /** + * get a unique id + * @returns {number} uniqueId + */ + var _uniqueId = 1; + function uniqueId() { + return _uniqueId++; + } - // note: drag.pointer is set in onTouch to get the initial touch location - var node = this.selectionHandler.getNodeAt(this.drag.pointer); + /** + * get the window object of an element + * @param {HTMLElement} element + * @returns {DocumentView|Window} + */ + function getWindowForElement(element) { + var doc = element.ownerDocument; + return (doc.defaultView || doc.parentWindow); + } - this.drag.dragging = true; - this.drag.selection = []; - this.drag.translation = util.extend({}, this.body.view.translation); // copy the object - this.drag.nodeId = undefined; + var MOBILE_REGEX = /mobile|tablet|ip(ad|hone|od)|android/i; - this.selectionHandler._generateClickEvent('dragStart', event, this.drag.pointer); + var SUPPORT_TOUCH = ('ontouchstart' in window); + var SUPPORT_POINTER_EVENTS = prefixed(window, 'PointerEvent') !== undefined; + var SUPPORT_ONLY_TOUCH = SUPPORT_TOUCH && MOBILE_REGEX.test(navigator.userAgent); - 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); - } + var INPUT_TYPE_TOUCH = 'touch'; + var INPUT_TYPE_PEN = 'pen'; + var INPUT_TYPE_MOUSE = 'mouse'; + var INPUT_TYPE_KINECT = 'kinect'; - 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, + var COMPUTE_INTERVAL = 25; - // 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 - }; + var INPUT_START = 1; + var INPUT_MOVE = 2; + var INPUT_END = 4; + var INPUT_CANCEL = 8; - object.options.fixed.x = true; - object.options.fixed.y = true; + var DIRECTION_NONE = 1; + var DIRECTION_LEFT = 2; + var DIRECTION_RIGHT = 4; + var DIRECTION_UP = 8; + var DIRECTION_DOWN = 16; - this.drag.selection.push(s); - } - } - } - } - }, { - key: 'onDrag', + var DIRECTION_HORIZONTAL = DIRECTION_LEFT | DIRECTION_RIGHT; + var DIRECTION_VERTICAL = DIRECTION_UP | DIRECTION_DOWN; + var DIRECTION_ALL = DIRECTION_HORIZONTAL | DIRECTION_VERTICAL; - /** - * handle drag event - * @private - */ - value: function onDrag(event) { - var _this2 = this; + var PROPS_XY = ['x', 'y']; + var PROPS_CLIENT_XY = ['clientX', 'clientY']; - 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; + /** + * create new input type manager + * @param {Manager} manager + * @param {Function} callback + * @returns {Input} + * @constructor + */ + function Input(manager, callback) { + var self = this; + this.manager = manager; + this.callback = callback; + this.element = manager.element; + this.target = manager.options.inputTarget; - // 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); - } - }); + // smaller wrapper around the handler, for the scope and the enabled state of the manager, + // so when disabled the input events are completely bypassed. + this.domHandler = function(ev) { + if (boolOrFn(manager.options.enable, [manager])) { + self.handler(ev); + } + }; - // 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.init(); - this.body.view.translation = { x: this.drag.translation.x + diffX, y: this.drag.translation.y + diffY }; - this.body.emitter.emit('_redraw'); - } - } - } - }, { - key: 'onDragEnd', + } + Input.prototype = { /** - * handle drag start event - * @private + * should handle the inputEvent data and trigger the callback + * @virtual */ - 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'); - } - this.selectionHandler._generateClickEvent('dragEnd', event, this.getPointer(event.center)); - } - }, { - key: 'onPinch', + handler: function() { }, /** - * Handle pinch event - * @param event - * @private + * bind the events */ - value: function onPinch(event) { - var pointer = this.getPointer(event.center); - - this.drag.pinched = true; - if (this.pinch['scale'] === undefined) { - this.pinch.scale = 1; - } - - // TODO: enabled moving while pinching? - var scale = this.pinch.scale * event.scale; - this.zoom(scale, pointer); - } - }, { - key: 'zoom', + init: function() { + this.evEl && addEventListeners(this.element, this.evEl, this.domHandler); + this.evTarget && addEventListeners(this.target, this.evTarget, this.domHandler); + this.evWin && addEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler); + }, /** - * 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 + * unbind the events */ - 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; + destroy: function() { + this.evEl && removeEventListeners(this.element, this.evEl, this.domHandler); + this.evTarget && removeEventListeners(this.target, this.evTarget, this.domHandler); + this.evWin && removeEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler); + } + }; - var scaleFrac = scale / scaleOld; - var tx = (1 - scaleFrac) * pointer.x + translation.x * scaleFrac; - var ty = (1 - scaleFrac) * pointer.y + translation.y * scaleFrac; + /** + * create new input type manager + * called by the Manager constructor + * @param {Hammer} manager + * @returns {Input} + */ + function createInputInstance(manager) { + var Type; + var inputClass = manager.options.inputClass; - this.body.view.scale = scale; - this.body.view.translation = { x: tx, y: ty }; + if (inputClass) { + Type = inputClass; + } else if (SUPPORT_POINTER_EVENTS) { + Type = PointerEventInput; + } else if (SUPPORT_ONLY_TOUCH) { + Type = TouchInput; + } else if (!SUPPORT_TOUCH) { + Type = MouseInput; + } else { + Type = TouchMouseInput; + } + return new (Type)(manager, inputHandler); + } - if (preScaleDragPointer != undefined) { - var postScaleDragPointer = this.canvas.canvasToDOM(preScaleDragPointer); - this.drag.pointer.x = postScaleDragPointer.x; - this.drag.pointer.y = postScaleDragPointer.y; - } + /** + * handle input events + * @param {Manager} manager + * @param {String} eventType + * @param {Object} input + */ + function inputHandler(manager, eventType, input) { + var pointersLen = input.pointers.length; + var changedPointersLen = input.changedPointers.length; + var isFirst = (eventType & INPUT_START && (pointersLen - changedPointersLen === 0)); + var isFinal = (eventType & (INPUT_END | INPUT_CANCEL) && (pointersLen - changedPointersLen === 0)); - this.body.emitter.emit('_requestRedraw'); + input.isFirst = !!isFirst; + input.isFinal = !!isFinal; - if (scaleOld < scale) { - this.body.emitter.emit('zoom', { direction: '+' }); - } else { - this.body.emitter.emit('zoom', { direction: '-' }); - } - } + if (isFirst) { + manager.session = {}; } - }, { - key: 'onMouseWheel', - /** - * 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 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; - } + // source event is the normalized value of the domEvents + // like 'touchstart, mouseup, pointerdown' + input.eventType = eventType; - // 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) { + // compute scale, rotation etc + computeInputData(manager, input); - // calculate the new scale - var scale = this.body.view.scale; - var zoom = delta / 10; - if (delta < 0) { - zoom = zoom / (1 - zoom); - } - scale *= 1 + zoom; + // emit secret event + manager.emit('hammer.input', input); - // calculate the pointer location - var pointer = this.getPointer({ x: event.pageX, y: event.pageY }); + manager.recognize(input); + manager.session.prevInput = input; + } - // apply the new scale - this.zoom(scale, pointer); - } + /** + * extend the data with some usable properties like scale, rotate, velocity etc + * @param {Object} manager + * @param {Object} input + */ + function computeInputData(manager, input) { + var session = manager.session; + var pointers = input.pointers; + var pointersLength = pointers.length; - // Prevent default actions caused by mouse wheel. - event.preventDefault(); + // store the first input to calculate the distance and direction + if (!session.firstInput) { + session.firstInput = simpleCloneInputData(input); } - }, { - key: 'onMouseMove', - - /** - * Mouse move handler for checking whether the title moves over a node with a title. - * @param {Event} event - * @private - */ - value: function onMouseMove(event) { - var _this3 = this; - var pointer = this.getPointer({ x: event.pageX, y: event.pageY }); - var popupVisible = false; + // to compute scale and rotation we need to store the multiple touches + if (pointersLength > 1 && !session.firstMultiple) { + session.firstMultiple = simpleCloneInputData(input); + } else if (pointersLength === 1) { + session.firstMultiple = false; + } - // check if the previously selected node is still selected - if (this.popup !== undefined) { - if (this.popup.hidden === false) { - this._checkHidePopup(pointer); - } + var firstInput = session.firstInput; + var firstMultiple = session.firstMultiple; + var offsetCenter = firstMultiple ? firstMultiple.center : firstInput.center; - // 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(); - } - } + var center = input.center = getCenter(pointers); + input.timeStamp = now(); + input.deltaTime = input.timeStamp - firstInput.timeStamp; - // 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(); - } + input.angle = getAngle(offsetCenter, center); + input.distance = getDistance(offsetCenter, center); - // 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); - } - } + computeDeltaXY(session, input); + input.offsetDirection = getDirection(input.deltaX, input.deltaY); - /** - * Adding hover highlights - */ - if (this.options.hoverEnabled === true) { - // removing all 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]; - } - } + input.scale = firstMultiple ? getScale(firstMultiple.pointers, pointers) : 1; + input.rotation = firstMultiple ? getRotation(firstMultiple.pointers, pointers) : 0; - // adding hover highlights - var obj = this.selectionHandler.getNodeAt(pointer); - if (obj === undefined) { - obj = this.selectionHandler.getEdgeAt(pointer); - } - if (obj != undefined) { - this.selectionHandler.hoverObject(obj); - } + computeIntervalInputData(session, input); - // removing all node hover highlights except for the selected one. - for (var nodeId in this.hoverObj.nodes) { - if (this.hoverObj.nodes.hasOwnProperty(nodeId)) { - if (obj instanceof Node && obj.id != nodeId || obj instanceof Edge || obj === undefined) { - this.selectionHandler.blurObject(this.hoverObj.nodes[nodeId]); - delete this.hoverObj.nodes[nodeId]; - } - } - } - this.body.emitter.emit('_requestRedraw'); - } + // find the correct target + var target = manager.element; + if (hasParent(input.srcEvent.target, target)) { + target = input.srcEvent.target; } - }, { - key: '_checkShowPopup', - - /** - * 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 - */ - 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 - }; + input.target = target; + } - var previousPopupObjId = this.popupObj === undefined ? undefined : this.popupObj.id; - var nodeUnderCursor = false; - var popupType = 'node'; + function computeDeltaXY(session, input) { + var center = input.center; + var offset = session.offsetDelta || {}; + var prevDelta = session.prevDelta || {}; + var prevInput = session.prevInput || {}; - // 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 (input.eventType === INPUT_START || prevInput.eventType === INPUT_END) { + prevDelta = session.prevDelta = { + x: prevInput.deltaX || 0, + y: prevInput.deltaY || 0 + }; - 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; - } - } + offset = session.offsetDelta = { + x: center.x, + y: center.y + }; + } - 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]); - } - } - } + input.deltaX = prevDelta.x + (center.x - offset.x); + input.deltaY = prevDelta.y + (center.y - offset.y); + } - if (overlappingEdges.length > 0) { - this.popupObj = edges[overlappingEdges[overlappingEdges.length - 1]]; - popupType = 'edge'; - } - } + /** + * velocity is calculated every x ms + * @param {Object} session + * @param {Object} input + */ + function computeIntervalInputData(session, input) { + var last = session.lastInterval || input, + deltaTime = input.timeStamp - last.timeStamp, + velocity, velocityX, velocityY, direction; - 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); - } + if (input.eventType != INPUT_CANCEL && (deltaTime > COMPUTE_INTERVAL || last.velocity === undefined)) { + var deltaX = last.deltaX - input.deltaX; + var deltaY = last.deltaY - input.deltaY; - this.popup.popupTargetType = popupType; - this.popup.popupTargetId = this.popupObj.id; + var v = getVelocity(deltaTime, deltaX, deltaY); + velocityX = v.x; + velocityY = v.y; + velocity = (abs(v.x) > abs(v.y)) ? v.x : v.y; + direction = getDirection(deltaX, deltaY); - // 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'); - } - } + session.lastInterval = input; + } else { + // use latest velocity info if it doesn't overtake a minimum period + velocity = last.velocity; + velocityX = last.velocityX; + velocityY = last.velocityY; + direction = last.direction; } - }, { - key: '_checkHidePopup', - /** - * 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 _checkHidePopup(pointer) { - var pointerObj = this.selectionHandler._pointerToPositionObject(pointer); + input.velocity = velocity; + input.velocityX = velocityX; + input.velocityY = velocityY; + input.direction = direction; + } - 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); + /** + * create a simple clone from the input used for storage of firstInput and firstMultiple + * @param {Object} input + * @returns {Object} clonedInputData + */ + function simpleCloneInputData(input) { + // make a simple copy of the pointers because we will get a reference if we don't + // we only need clientXY for the calculations + var pointers = []; + var i = 0; + while (i < input.pointers.length) { + pointers[i] = { + clientX: round(input.pointers[i].clientX), + clientY: round(input.pointers[i].clientY) + }; + i++; + } - // 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); - } - } - } + return { + timeStamp: now(), + pointers: pointers, + center: getCenter(pointers), + deltaX: input.deltaX, + deltaY: input.deltaY + }; + } - if (stillOnObj === false) { - this.popupObj = undefined; - this.popup.hide(); - this.body.emitter.emit('hidePopup'); - } + /** + * get the center of all the pointers + * @param {Array} pointers + * @return {Object} center contains `x` and `y` properties + */ + function getCenter(pointers) { + var pointersLength = pointers.length; + + // no need to loop when only one touch + if (pointersLength === 1) { + return { + x: round(pointers[0].clientX), + y: round(pointers[0].clientY) + }; } - }]); - return InteractionHandler; - })(); + var x = 0, y = 0, i = 0; + while (i < pointersLength) { + x += pointers[i].clientX; + y += pointers[i].clientY; + i++; + } - exports['default'] = InteractionHandler; - module.exports = exports['default']; + return { + x: round(x / pointersLength), + y: round(y / pointersLength) + }; + } -/***/ }, -/* 72 */ -/***/ function(module, exports, __webpack_require__) { + /** + * calculate the velocity between two points. unit is in px per ms. + * @param {Number} deltaTime + * @param {Number} x + * @param {Number} y + * @return {Object} velocity `x` and `y` + */ + function getVelocity(deltaTime, x, y) { + return { + x: x / deltaTime || 0, + y: y / deltaTime || 0 + }; + } - "use strict"; + /** + * get the direction between two points + * @param {Number} x + * @param {Number} y + * @return {Number} direction + */ + function getDirection(x, y) { + if (x === y) { + return DIRECTION_NONE; + } - Object.defineProperty(exports, "__esModule", { - value: true - }); + if (abs(x) >= abs(y)) { + return x > 0 ? DIRECTION_LEFT : DIRECTION_RIGHT; + } + return y > 0 ? DIRECTION_UP : DIRECTION_DOWN; + } - 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; }; })(); + /** + * calculate the absolute distance between two points + * @param {Object} p1 {x, y} + * @param {Object} p2 {x, y} + * @param {Array} [props] containing x and y keys + * @return {Number} distance + */ + function getDistance(p1, p2, props) { + if (!props) { + props = PROPS_XY; + } + var x = p2[props[0]] - p1[props[0]], + y = p2[props[1]] - p1[props[1]]; - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + return Math.sqrt((x * x) + (y * y)); + } - var Node = __webpack_require__(78); - var util = __webpack_require__(1); + /** + * calculate the angle between two coordinates + * @param {Object} p1 + * @param {Object} p2 + * @param {Array} [props] containing x and y keys + * @return {Number} angle + */ + function getAngle(p1, p2, props) { + if (!props) { + props = PROPS_XY; + } + var x = p2[props[0]] - p1[props[0]], + y = p2[props[1]] - p1[props[1]]; + return Math.atan2(y, x) * 180 / Math.PI; + } - var SelectionHandler = (function () { - function SelectionHandler(body, canvas) { - var _this = this; + /** + * calculate the rotation degrees between two pointersets + * @param {Array} start array of pointers + * @param {Array} end array of pointers + * @return {Number} rotation + */ + function getRotation(start, end) { + return getAngle(end[1], end[0], PROPS_CLIENT_XY) - getAngle(start[1], start[0], PROPS_CLIENT_XY); + } - _classCallCheck(this, SelectionHandler); + /** + * calculate the scale factor between two pointersets + * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out + * @param {Array} start array of pointers + * @param {Array} end array of pointers + * @return {Number} scale + */ + function getScale(start, end) { + return getDistance(end[0], end[1], PROPS_CLIENT_XY) / getDistance(start[0], start[1], PROPS_CLIENT_XY); + } - this.body = body; - this.canvas = canvas; - this.selectionObj = { nodes: [], edges: [] }; + var MOUSE_INPUT_MAP = { + mousedown: INPUT_START, + mousemove: INPUT_MOVE, + mouseup: INPUT_END + }; - this.options = {}; - this.defaultOptions = { - multiselect: false, - selectable: true, - selectConnectedEdges: true - }; - util.extend(this.options, this.defaultOptions); + var MOUSE_ELEMENT_EVENTS = 'mousedown'; + var MOUSE_WINDOW_EVENTS = 'mousemove mouseup'; - this.body.emitter.on("_dataChanged", function () { - _this.updateSelection(); - }); - } + /** + * Mouse events input + * @constructor + * @extends Input + */ + function MouseInput() { + this.evEl = MOUSE_ELEMENT_EVENTS; + this.evWin = MOUSE_WINDOW_EVENTS; - _createClass(SelectionHandler, [{ - key: "setOptions", - value: function setOptions(options) { - if (options !== undefined) { - var fields = ["multiselect", "selectable", "selectConnectedEdges"]; - util.selectiveDeepExtend(fields, this.options, options); - } - } - }, { - key: "selectOnPoint", + this.allow = true; // used by Input.TouchMouse to disable mouse events + this.pressed = false; // mousedown state + + Input.apply(this, arguments); + } + inherit(MouseInput, Input, { /** - * handles the selection part of the tap; - * - * @param {Object} pointer - * @private + * handle mouse events + * @param {Object} ev */ - value: function selectOnPoint(pointer) { - var selected = false; - if (this.options.selectable === true) { - this.unselectAll(); - var obj = this.getNodeAt(pointer) || this.getEdgeAt(pointer); - 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); - } + handler: function MEhandler(ev) { + var eventType = MOUSE_INPUT_MAP[ev.type]; - this.body.emitter.emit("_requestRedraw"); + // on start we want to have the left mouse button down + if (eventType & INPUT_START && ev.button === 0) { + this.pressed = true; } - } - return selectionChanged; - } - }, { - 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; - 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]; + if (eventType & INPUT_MOVE && ev.which !== 1) { + eventType = INPUT_END; + } - if (obj !== undefined) { - if (obj instanceof Node) { - if (highlightEdges === true) { - this._selectConnectedEdges(obj); - } + // mouse must be down, and mouse events are allowed (see the TouchMouse input) + if (!this.pressed || !this.allow) { + return; } - 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); + if (eventType & INPUT_END) { + this.pressed = false; } - } - return overlappingNodes; - } - }, { - key: "_pointerToPositionObject", - /** - * Return a position object in canvasspace from a single point in screenspace - * - * @param pointer - * @returns {{left: number, top: number, right: number, bottom: number}} - * @private - */ - 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 - }; + this.callback(this.manager, eventType, { + pointers: [ev], + changedPointers: [ev], + pointerType: INPUT_TYPE_MOUSE, + srcEvent: ev + }); } - }, { - key: "getNodeAt", + }); - /** - * Get the top node at the a specific point (like a click) - * - * @param {{x: Number, y: Number}} pointer - * @return {Node | undefined} node - * @private - */ - value: function getNodeAt(pointer) { - var returnNode = arguments[1] === undefined ? true : arguments[1]; + var POINTER_INPUT_MAP = { + pointerdown: INPUT_START, + pointermove: INPUT_MOVE, + pointerup: INPUT_END, + pointercancel: INPUT_CANCEL, + pointerout: INPUT_CANCEL + }; - // 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; - } - } - }, { - key: "_getEdgesOverlappingWith", + // in IE10 the pointer types is defined as an enum + var IE10_POINTER_TYPE_ENUM = { + 2: INPUT_TYPE_TOUCH, + 3: INPUT_TYPE_PEN, + 4: INPUT_TYPE_MOUSE, + 5: INPUT_TYPE_KINECT // see https://twitter.com/jacobrossi/status/480596438489890816 + }; - /** - * 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 _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: "_getAllEdgesOverlappingWith", + var POINTER_ELEMENT_EVENTS = 'pointerdown'; + var POINTER_WINDOW_EVENTS = 'pointermove pointerup pointercancel'; - /** - * 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 _getAllEdgesOverlappingWith(object) { - var overlappingEdges = []; - this._getEdgesOverlappingWith(object, overlappingEdges); - return overlappingEdges; - } - }, { - key: "getEdgeAt", + // IE10 has prefixed support, and case-sensitive + if (window.MSPointerEvent) { + POINTER_ELEMENT_EVENTS = 'MSPointerDown'; + POINTER_WINDOW_EVENTS = 'MSPointerMove MSPointerUp MSPointerCancel'; + } - /** - * 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} - * @private - */ - value: function getEdgeAt(pointer) { - var returnEdge = arguments[1] === undefined ? true : arguments[1]; + /** + * Pointer events input + * @constructor + * @extends Input + */ + function PointerEventInput() { + this.evEl = POINTER_ELEMENT_EVENTS; + this.evWin = POINTER_WINDOW_EVENTS; - var positionObject = this._pointerToPositionObject(pointer); - var overlappingEdges = this._getAllEdgesOverlappingWith(positionObject); + Input.apply(this, arguments); - if (overlappingEdges.length > 0) { - if (returnEdge === true) { - return this.body.edges[overlappingEdges[overlappingEdges.length - 1]]; - } else { - return overlappingEdges[overlappingEdges.length - 1]; - } - } else { - return undefined; - } - } - }, { - key: "_addToSelection", + this.store = (this.manager.session.pointerEvents = []); + } + inherit(PointerEventInput, Input, { /** - * Add object to the selection array. - * - * @param obj - * @private + * handle mouse events + * @param {Object} ev */ - value: function _addToSelection(obj) { - if (obj instanceof Node) { - this.selectionObj.nodes[obj.id] = obj; - } else { - this.selectionObj.edges[obj.id] = obj; - } - } - }, { - key: "_addToHover", + handler: function PEhandler(ev) { + var store = this.store; + var removePointer = false; - /** - * Add object to the selection array. - * - * @param obj - * @private - */ - value: function _addToHover(obj) { - if (obj instanceof Node) { - this.hoverObj.nodes[obj.id] = obj; - } else { - this.hoverObj.edges[obj.id] = obj; - } - } - }, { - key: "_removeFromSelection", + var eventTypeNormalized = ev.type.toLowerCase().replace('ms', ''); + var eventType = POINTER_INPUT_MAP[eventTypeNormalized]; + var pointerType = IE10_POINTER_TYPE_ENUM[ev.pointerType] || ev.pointerType; - /** - * Remove a single option from selection. - * - * @param {Object} obj - * @private - */ - value: function _removeFromSelection(obj) { - if (obj instanceof Node) { - delete this.selectionObj.nodes[obj.id]; - } else { - delete this.selectionObj.edges[obj.id]; - } - } - }, { - key: "unselectAll", + var isTouch = (pointerType == INPUT_TYPE_TOUCH); - /** - * 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(); + // get index of the event in the store + var storeIndex = inArray(store, ev.pointerId, 'pointerId'); + + // start and mouse must be down + if (eventType & INPUT_START && (ev.button === 0 || isTouch)) { + if (storeIndex < 0) { + store.push(ev); + storeIndex = store.length - 1; + } + } else if (eventType & (INPUT_END | INPUT_CANCEL)) { + removePointer = true; } - } - for (var edgeId in this.selectionObj.edges) { - if (this.selectionObj.edges.hasOwnProperty(edgeId)) { - this.selectionObj.edges[edgeId].unselect(); + + // it not found, so the pointer hasn't been down (so it's probably a hover) + if (storeIndex < 0) { + return; } - } - this.selectionObj = { nodes: {}, edges: {} }; - } - }, { - key: "_getSelectedNodeCount", - - /** - * return the number of selected nodes - * - * @returns {number} - * @private - */ - value: function _getSelectedNodeCount() { - var count = 0; - for (var nodeId in this.selectionObj.nodes) { - if (this.selectionObj.nodes.hasOwnProperty(nodeId)) { - count += 1; - } - } - return count; - } - }, { - key: "_getSelectedNode", + // update the event in the store + store[storeIndex] = ev; - /** - * 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: "_getSelectedEdge", + this.callback(this.manager, eventType, { + pointers: store, + changedPointers: [ev], + pointerType: pointerType, + srcEvent: ev + }); - /** - * 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]; + if (removePointer) { + // remove from the store + store.splice(storeIndex, 1); } - } - return undefined; } - }, { - key: "_getSelectedEdgeCount", + }); - /** - * return the number of selected edges - * - * @returns {number} - * @private - */ - 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", + var SINGLE_TOUCH_INPUT_MAP = { + touchstart: INPUT_START, + touchmove: INPUT_MOVE, + touchend: INPUT_END, + touchcancel: INPUT_CANCEL + }; - /** - * 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; - } - } - for (var edgeId in this.selectionObj.edges) { - if (this.selectionObj.edges.hasOwnProperty(edgeId)) { - count += 1; - } - } - return count; - } - }, { - key: "_selectionIsEmpty", + var SINGLE_TOUCH_TARGET_EVENTS = 'touchstart'; + var SINGLE_TOUCH_WINDOW_EVENTS = 'touchstart touchmove touchend touchcancel'; - /** - * Check if anything is selected - * - * @returns {boolean} - * @private - */ - value: function _selectionIsEmpty() { - for (var nodeId in this.selectionObj.nodes) { - if (this.selectionObj.nodes.hasOwnProperty(nodeId)) { - return false; - } - } - for (var edgeId in this.selectionObj.edges) { - if (this.selectionObj.edges.hasOwnProperty(edgeId)) { - return false; - } - } - return true; - } - }, { - key: "_clusterInSelection", + /** + * Touch events input + * @constructor + * @extends Input + */ + function SingleTouchInput() { + this.evTarget = SINGLE_TOUCH_TARGET_EVENTS; + this.evWin = SINGLE_TOUCH_WINDOW_EVENTS; + this.started = false; - /** - * check if one of the selected nodes is a cluster. - * - * @returns {boolean} - * @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; - } - } - } - return false; - } - }, { - key: "_selectConnectedEdges", + Input.apply(this, arguments); + } - /** - * select the edges connected to the node that is being selected - * - * @param {Node} node - * @private - */ - value: function _selectConnectedEdges(node) { - for (var i = 0; i < node.edges.length; i++) { - var edge = node.edges[i]; - edge.select(); - this._addToSelection(edge); - } - } - }, { - key: "_hoverConnectedEdges", + inherit(SingleTouchInput, Input, { + handler: function TEhandler(ev) { + var type = SINGLE_TOUCH_INPUT_MAP[ev.type]; - /** - * 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); - } - } - }, { - key: "_unselectConnectedEdges", + // should we handle the touch events? + if (type === INPUT_START) { + this.started = true; + } - /** - * 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", + if (!this.started) { + return; + } - /** - * 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: "hoverObject", + var touches = normalizeSingleTouches.call(this, ev, type); - /** - * 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 hoverObject(object) { - if (object.hover === false) { - object.hover = true; - this._addToHover(object); - if (object instanceof Node) { - this.body.emitter.emit("hoverNode", { node: object.id }); + // when done, reset the started state + if (type & (INPUT_END | INPUT_CANCEL) && touches[0].length - touches[1].length === 0) { + this.started = false; } - } - if (object instanceof Node) { - this._hoverConnectedEdges(object); - } - } - }, { - 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 }; + this.callback(this.manager, type, { + pointers: touches[0], + changedPointers: touches[1], + pointerType: INPUT_TYPE_TOUCH, + srcEvent: ev + }); } - }, { - key: "getSelectedNodes", + }); - /** - * - * retrieve the currently selected nodes - * @return {String[]} selection An array with the ids of the - * selected nodes. - */ - 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); - } - } - } - return idArray; - } - }, { - key: "getSelectedEdges", + /** + * @this {TouchInput} + * @param {Object} ev + * @param {Number} type flag + * @returns {undefined|Array} [all, changed] + */ + function normalizeSingleTouches(ev, type) { + var all = toArray(ev.touches); + var changed = toArray(ev.changedTouches); - /** - * - * retrieve the currently selected edges - * @return {Array} selection An array with the ids of the - * selected nodes. - */ - 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; + if (type & (INPUT_END | INPUT_CANCEL)) { + all = uniqueArray(all.concat(changed), 'identifier', true); } - }, { - 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]; + return [all, changed]; + } - var i = undefined, - id = undefined; + var TOUCH_INPUT_MAP = { + touchstart: INPUT_START, + touchmove: INPUT_MOVE, + touchend: INPUT_END, + touchcancel: INPUT_CANCEL + }; - if (!selection || selection.length === undefined) throw "Selection must be an array with ids"; + var TOUCH_TARGET_EVENTS = 'touchstart touchmove touchend touchcancel'; - // first unselect any selected node - this.unselectAll(); + /** + * Multi-user touch events input + * @constructor + * @extends Input + */ + function TouchInput() { + this.evTarget = TOUCH_TARGET_EVENTS; + this.targetIds = {}; - for (i = 0; i < selection.length; i++) { - id = selection[i]; + Input.apply(this, arguments); + } - var node = this.body.nodes[id]; - if (!node) { - throw new RangeError("Node with id \"" + id + "\" not found"); + inherit(TouchInput, Input, { + handler: function MTEhandler(ev) { + var type = TOUCH_INPUT_MAP[ev.type]; + var touches = getTouches.call(this, ev, type); + if (!touches) { + return; } - this.selectObject(node, highlightEdges); - } - this.body.emitter.emit("_requestRedraw"); + + this.callback(this.manager, type, { + pointers: touches[0], + changedPointers: touches[1], + pointerType: INPUT_TYPE_TOUCH, + srcEvent: ev + }); } - }, { - key: "selectEdges", + }); - /** - * select zero or more edges - * @param {Number[] | String[]} selection An array with the ids of the - * selected nodes. - */ - value: function selectEdges(selection) { - var i = undefined, - id = undefined; + /** + * @this {TouchInput} + * @param {Object} ev + * @param {Number} type flag + * @returns {undefined|Array} [all, changed] + */ + function getTouches(ev, type) { + var allTouches = toArray(ev.touches); + var targetIds = this.targetIds; - if (!selection || selection.length === undefined) throw "Selection must be an array with ids"; + // when there is only one touch, the process can be simplified + if (type & (INPUT_START | INPUT_MOVE) && allTouches.length === 1) { + targetIds[allTouches[0].identifier] = true; + return [allTouches, allTouches]; + } - // first unselect any selected objects - this.unselectAll(); + var i, + targetTouches, + changedTouches = toArray(ev.changedTouches), + changedTargetTouches = [], + target = this.target; - for (i = 0; i < selection.length; i++) { - id = selection[i]; + // get target touches from touches + targetTouches = allTouches.filter(function(touch) { + return hasParent(touch.target, target); + }); - var edge = this.body.edges[id]; - if (!edge) { - throw new RangeError("Edge with id \"" + id + "\" not found"); + // collect touches + if (type === INPUT_START) { + i = 0; + while (i < targetTouches.length) { + targetIds[targetTouches[i].identifier] = true; + i++; } - this.selectObject(edge); - } - this.body.emitter.emit("_requestRedraw"); } - }, { - key: "updateSelection", - /** - * 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]; - } + // filter changed touches to only contain touches that exist in the collected target ids + i = 0; + while (i < changedTouches.length) { + if (targetIds[changedTouches[i].identifier]) { + changedTargetTouches.push(changedTouches[i]); } - } - for (var edgeId in this.selectionObj.edges) { - if (this.selectionObj.edges.hasOwnProperty(edgeId)) { - if (!this.body.edges.hasOwnProperty(edgeId)) { - delete this.selectionObj.edges[edgeId]; - } + + // cleanup removed touches + if (type & (INPUT_END | INPUT_CANCEL)) { + delete targetIds[changedTouches[i].identifier]; } - } + i++; } - }]); - - return SelectionHandler; - })(); - exports["default"] = SelectionHandler; - module.exports = exports["default"]; + if (!changedTargetTouches.length) { + return; + } -/***/ }, -/* 73 */ -/***/ function(module, exports, __webpack_require__) { + return [ + // merge targetTouches with changedTargetTouches so it contains ALL touches, including 'end' and 'cancel' + uniqueArray(targetTouches.concat(changedTargetTouches), 'identifier', true), + changedTargetTouches + ]; + } - 'use strict'; + /** + * Combined touch and mouse input + * + * Touch has a higher priority then mouse, and while touching no mouse events are allowed. + * This because touch devices also emit mouse events while doing a touch. + * + * @constructor + * @extends Input + */ + function TouchMouseInput() { + Input.apply(this, arguments); - Object.defineProperty(exports, '__esModule', { - value: true - }); + var handler = bindFn(this.handler, this); + this.touch = new TouchInput(this.manager, handler); + this.mouse = new MouseInput(this.manager, handler); + } - 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; }; })(); + inherit(TouchMouseInput, Input, { + /** + * handle mouse and touch events + * @param {Hammer} manager + * @param {String} inputEvent + * @param {Object} inputData + */ + handler: function TMEhandler(manager, inputEvent, inputData) { + var isTouch = (inputData.pointerType == INPUT_TYPE_TOUCH), + isMouse = (inputData.pointerType == INPUT_TYPE_MOUSE); - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + // when we're in a touch event, so block all upcoming mouse events + // most mobile browser also emit mouseevents, right after touchstart + if (isTouch) { + this.mouse.allow = false; + } else if (isMouse && !this.mouse.allow) { + return; + } - var util = __webpack_require__(1); + // reset the allowMouse when we're done + if (inputEvent & (INPUT_END | INPUT_CANCEL)) { + this.mouse.allow = true; + } - var LayoutEngine = (function () { - function LayoutEngine(body) { - _classCallCheck(this, LayoutEngine); + this.callback(manager, inputEvent, inputData); + }, - this.body = body; - - 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); + /** + * remove the event listeners + */ + destroy: function destroy() { + this.touch.destroy(); + this.mouse.destroy(); + } + }); - this.hierarchicalLevels = {}; + var PREFIXED_TOUCH_ACTION = prefixed(TEST_ELEMENT.style, 'touchAction'); + var NATIVE_TOUCH_ACTION = PREFIXED_TOUCH_ACTION !== undefined; - this.bindEventListeners(); - } + // magical touchAction value + var TOUCH_ACTION_COMPUTE = 'compute'; + var TOUCH_ACTION_AUTO = 'auto'; + var TOUCH_ACTION_MANIPULATION = 'manipulation'; // not implemented + var TOUCH_ACTION_NONE = 'none'; + var TOUCH_ACTION_PAN_X = 'pan-x'; + var TOUCH_ACTION_PAN_Y = 'pan-y'; - _createClass(LayoutEngine, [{ - key: 'bindEventListeners', - value: function bindEventListeners() { - var _this = this; + /** + * Touch Action + * sets the touchAction property or uses the js alternative + * @param {Manager} manager + * @param {String} value + * @constructor + */ + function TouchAction(manager, value) { + this.manager = manager; + this.set(value); + } - this.body.emitter.on('_dataChanged', function () { - _this.setupHierarchicalLayout(); - }); - this.body.emitter.on('_resetHierarchicalLayout', function () { - _this.setupHierarchicalLayout(); - }); - } - }, { - key: 'setOptions', - value: function setOptions(options, allOptions) { - if (options !== undefined) { - var prevHierarchicalState = this.options.hierarchical.enabled; + TouchAction.prototype = { + /** + * set the touchAction value on the element or enable the polyfill + * @param {String} value + */ + set: function(value) { + // find out the touch-action by the event handlers + if (value == TOUCH_ACTION_COMPUTE) { + value = this.compute(); + } - util.mergeOptions(this.options, options, 'hierarchical'); - if (options.randomSeed !== undefined) { - this.randomSeed = options.randomSeed; + if (NATIVE_TOUCH_ACTION) { + this.manager.element.style[PREFIXED_TOUCH_ACTION] = value; } + this.actions = value.toLowerCase().trim(); + }, - 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; + /** + * just re-set the touchAction value + */ + update: function() { + this.set(this.manager.options.touchAction); + }, + + /** + * compute the value for the touchAction property based on the recognizer's settings + * @returns {String} value + */ + compute: function() { + var actions = []; + each(this.manager.recognizers, function(recognizer) { + if (boolOrFn(recognizer.options.enable, [recognizer])) { + actions = actions.concat(recognizer.getTouchAction()); } - } + }); + return cleanTouchActions(actions.join(' ')); + }, - 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: '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 options.physics === 'object') { - this.optionsBackup.physics = { solver: 'barnesHut' }; - if (options.physics.solver !== undefined) { - this.optionsBackup.physics = { solver: options.physics.solver }; - } - allOptions.physics['solver'] = 'hierarchicalRepulsion'; - } else if (options.physics !== false) { - this.optionsBackup.physics = { solver: 'barnesHut' }; - allOptions.physics['solver'] = 'hierarchicalRepulsion'; + /** + * this method is called on each input cycle and provides the preventing of the browser behavior + * @param {Object} input + */ + preventDefaults: function(input) { + // not needed with native support for the touchAction property + if (NATIVE_TOUCH_ACTION) { + return; } - // 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'; - } + var srcEvent = input.srcEvent; + var direction = input.offsetDirection; - // 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: true, dynamic: true }; - allOptions.edges = { smooth: false }; - } else if (allOptions.edges.smooth === undefined) { - this.optionsBackup.edges = { smooth: true, dynamic: true }; - allOptions.edges.smooth = false; - } else { - if (typeof allOptions.edges.smooth === 'boolean') { - this.optionsBackup.edges = { smooth: allOptions.edges.smooth, dynamic: true }; - allOptions.edges.smooth = { enabled: allOptions.edges.smooth, dynamic: false, type: type }; - } else { - this.optionsBackup.edges = { smooth: allOptions.edges.smooth.enabled === undefined ? true : allOptions.edges.smooth.enabled, dynamic: true }; - allOptions.edges.smooth = { enabled: allOptions.edges.smooth.enabled === undefined ? true : allOptions.edges.smooth.enabled, dynamic: false, type: type }; - } + // if the touch action did prevented once this session + if (this.manager.session.prevented) { + srcEvent.preventDefault(); + return; } - // 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) { - 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(); + var actions = this.actions; + var hasNone = inStr(actions, TOUCH_ACTION_NONE); + var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y); + var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X); - if (node.options.fixed.x === false) { - node.x = radius * Math.cos(angle); - } - if (node.options.fixed.x === false) { - node.y = radius * Math.sin(angle); - } - } + if (hasNone || + (hasPanY && direction & DIRECTION_HORIZONTAL) || + (hasPanX && direction & DIRECTION_VERTICAL)) { + return this.preventSrc(srcEvent); } - } - } - }, { - key: 'getSeed', - value: function getSeed() { - return this.initialRandomSeed; - } - }, { - key: 'setupHierarchicalLayout', + }, /** - * This is the main function to layout the nodes in a hierarchical way. - * It checks if the node details are supplied correctly - * - * @private + * call preventDefault to prevent the browser's default behavior (scrolling in most cases) + * @param {Object} srcEvent */ - 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; + preventSrc: function(srcEvent) { + this.manager.session.prevented = true; + srcEvent.preventDefault(); + } + }; - 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; - } - } - } + /** + * when the touchActions are collected they are not a valid value, so we need to clean things up. * + * @param {String} actions + * @returns {*} + */ + function cleanTouchActions(actions) { + // none + if (inStr(actions, TOUCH_ACTION_NONE)) { + return TOUCH_ACTION_NONE; + } - // 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(); + var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X); + var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y); - // 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(); - } - } + // pan-x and pan-y can be combined + if (hasPanX && hasPanY) { + return TOUCH_ACTION_PAN_X + ' ' + TOUCH_ACTION_PAN_Y; + } - // check the distribution of the nodes per level. - var distribution = this._getDistribution(); + // pan-x OR pan-y + if (hasPanX || hasPanY) { + return hasPanX ? TOUCH_ACTION_PAN_X : TOUCH_ACTION_PAN_Y; + } - // place the nodes on the canvas. - this._placeNodesByHierarchy(distribution); - } - } + // manipulation + if (inStr(actions, TOUCH_ACTION_MANIPULATION)) { + return TOUCH_ACTION_MANIPULATION; } - }, { - key: '_placeNodesByHierarchy', - /** - * 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 _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)) { + return TOUCH_ACTION_AUTO; + } - node = distribution[level].nodes[nodeId]; + /** + * Recognizer flow explained; * + * All recognizers have the initial state of POSSIBLE when a input session starts. + * The definition of a input session is from the first input until the last input, with all it's movement in it. * + * Example session for mouse-input: mousedown -> mousemove -> mouseup + * + * On each recognizing cycle (see Manager.recognize) the .recognize() method is executed + * which determines with state it should be. + * + * If the recognizer has the state FAILED, CANCELLED or RECOGNIZED (equals ENDED), it is reset to + * POSSIBLE to give it another change on the next cycle. + * + * Possible + * | + * +-----+---------------+ + * | | + * +-----+-----+ | + * | | | + * Failed Cancelled | + * +-------+------+ + * | | + * Recognized Began + * | + * Changed + * | + * Ended/Recognized + */ + var STATE_POSSIBLE = 1; + var STATE_BEGAN = 2; + var STATE_CHANGED = 4; + var STATE_ENDED = 8; + var STATE_RECOGNIZED = STATE_ENDED; + var STATE_CANCELLED = 16; + var STATE_FAILED = 32; - 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; - } + /** + * Recognizer + * Every recognizer needs to extend from this class. + * @constructor + * @param {Object} options + */ + function Recognizer(options) { + this.id = uniqueId(); - this.positionedNodes[nodeId] = true; - this._placeBranchNodes(node.edges, node.id, distribution, level); - } - } - } - } - } - }, { - key: '_getDistribution', + this.manager = null; + this.options = merge(options || {}, this.defaults); - /** - * This function get the distribution of levels based on hubsize - * - * @returns {Object} - * @private - */ - value: function _getDistribution() { - var distribution = {}; - var nodeId = undefined, - node = undefined; + // default is enable true + this.options.enable = ifUndefined(this.options.enable, true); - // 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; - } - } - return distribution; - } - }, { - key: '_getHubSize', + this.state = STATE_POSSIBLE; + + this.simultaneous = {}; + this.requireFail = []; + } + Recognizer.prototype = { /** - * Get the hubsize from all remaining unlevelled nodes. - * - * @returns {number} - * @private + * @virtual + * @type {Object} */ - 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; - } - } - } - return hubSize; - } - }, { - key: '_determineLevelsByHubsize', + defaults: {}, /** - * this function allocates nodes in levels based on the recursive branching from the largest hubs. - * - * @param hubsize - * @private + * set options + * @param {Object} options + * @return {Recognizer} */ - value: function _determineLevelsByHubsize() { - var nodeId = undefined, - node = undefined; - var hubSize = 1; - - while (hubSize > 0) { - // determine hubs - hubSize = this._getHubSize(); - if (hubSize === 0) break; + set: function(options) { + extend(this.options, options); - for (nodeId in this.body.nodes) { - if (this.body.nodes.hasOwnProperty(nodeId)) { - node = this.body.nodes[nodeId]; - if (node.edges.length === hubSize) { - this._setLevel(0, node); - } - } - } - } - } - }, { - key: '_setLevel', + // also update the touchAction, in case something changed about the directions/enabled state + this.manager && this.manager.touchAction.update(); + return this; + }, /** - * 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 + * recognize simultaneous with an other recognizer. + * @param {Recognizer} otherRecognizer + * @returns {Recognizer} this */ - value: function _setLevel(level, node) { - if (this.hierarchicalLevels[node.id] !== undefined) return; + recognizeWith: function(otherRecognizer) { + if (invokeArrayArg(otherRecognizer, 'recognizeWith', this)) { + return this; + } - 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 { - childNode = node.edges[i].to; + var simultaneous = this.simultaneous; + otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this); + if (!simultaneous[otherRecognizer.id]) { + simultaneous[otherRecognizer.id] = otherRecognizer; + otherRecognizer.recognizeWith(this); } - this._setLevel(level + 1, childNode); - } - } - }, { - key: '_determineLevelsDirected', + return this; + }, /** - * this function allocates nodes in levels based on the direction of the edges - * - * @param hubsize - * @private + * drop the simultaneous link. it doesnt remove the link on the other recognizer. + * @param {Recognizer} otherRecognizer + * @returns {Recognizer} this */ - 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; + dropRecognizeWith: function(otherRecognizer) { + if (invokeArrayArg(otherRecognizer, 'dropRecognizeWith', this)) { + return this; } - } - // 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; - } - } - } - }, { - key: '_setLevelDirected', + otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this); + delete this.simultaneous[otherRecognizer.id]; + return this; + }, /** - * 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 + * recognizer can only run when an other is failing + * @param {Recognizer} otherRecognizer + * @returns {Recognizer} this */ - value: function _setLevelDirected(level, node) { - if (this.hierarchicalLevels[node.id] !== undefined) return; - - var childNode = undefined; - this.hierarchicalLevels[node.id] = level; + requireFailure: function(otherRecognizer) { + if (invokeArrayArg(otherRecognizer, 'requireFailure', this)) { + return this; + } - 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); + var requireFail = this.requireFail; + otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this); + if (inArray(requireFail, otherRecognizer) === -1) { + requireFail.push(otherRecognizer); + otherRecognizer.requireFailure(this); } - } - } - }, { - key: '_placeBranchNodes', + return this; + }, /** - * 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 + * drop the requireFailure link. it does not remove the link on the other recognizer. + * @param {Recognizer} otherRecognizer + * @returns {Recognizer} this */ - 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; + dropRequireFailure: function(otherRecognizer) { + if (invokeArrayArg(otherRecognizer, 'dropRequireFailure', this)) { + return this; } - 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 { - 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; - if (childNode.edges.length > 1) { - this._placeBranchNodes(childNode.edges, childNode.id, distribution, childNodeLevel); - } - } + otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this); + var index = inArray(this.requireFail, otherRecognizer); + if (index > -1) { + this.requireFail.splice(index, 1); } - } - } - }]); - - return LayoutEngine; - })(); - - exports['default'] = LayoutEngine; - module.exports = exports['default']; - -/***/ }, -/* 74 */ -/***/ function(module, exports, __webpack_require__) { - - 'use strict'; + return this; + }, - Object.defineProperty(exports, '__esModule', { - value: true - }); + /** + * has require failures boolean + * @returns {boolean} + */ + hasRequireFailures: function() { + return this.requireFail.length > 0; + }, - var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + /** + * if the recognizer can recognize simultaneous with an other recognizer + * @param {Recognizer} otherRecognizer + * @returns {Boolean} + */ + canRecognizeWith: function(otherRecognizer) { + return !!this.simultaneous[otherRecognizer.id]; + }, - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + /** + * You should use `tryEmit` instead of `emit` directly to check + * that all the needed recognizers has failed before emitting. + * @param {Object} input + */ + emit: function(input) { + var self = this; + var state = this.state; - var util = __webpack_require__(1); - var Hammer = __webpack_require__(41); - var hammerUtil = __webpack_require__(49); + function emit(withState) { + self.manager.emit(self.options.event + (withState ? stateStr(state) : ''), input); + } - /** - * clears the toolbar div element of children - * - * @private - */ + // 'panstart' and 'panmove' + if (state < STATE_ENDED) { + emit(true); + } - var ManipulationSystem = (function () { - function ManipulationSystem(body, canvas, selectionHandler) { - var _this = this; + emit(); // simple 'eventName' events - _classCallCheck(this, ManipulationSystem); + // panend and pancancel + if (state >= STATE_ENDED) { + emit(true); + } + }, - this.body = body; - this.canvas = canvas; - this.selectionHandler = selectionHandler; + /** + * Check that all the require failure recognizers has failed, + * if true, it emits a gesture event, + * otherwise, setup the state to FAILED. + * @param {Object} input + */ + tryEmit: function(input) { + if (this.canEmit()) { + return this.emit(input); + } + // it's failing anyway + this.state = STATE_FAILED; + }, - this.editMode = false; - this.manipulationDiv = undefined; - this.editModeDiv = undefined; - this.closeDiv = undefined; + /** + * can we emit? + * @returns {boolean} + */ + canEmit: function() { + var i = 0; + while (i < this.requireFail.length) { + if (!(this.requireFail[i].state & (STATE_FAILED | STATE_POSSIBLE))) { + return false; + } + i++; + } + return true; + }, - this.manipulationHammers = []; - this.temporaryUIFunctions = {}; - this.temporaryEventFunctions = []; + /** + * update the recognizer + * @param {Object} inputData + */ + recognize: function(inputData) { + // make a new copy of the inputData + // so we can change the inputData without messing up the other recognizers + var inputDataClone = extend({}, inputData); - this.touchTime = 0; - this.temporaryIds = { nodes: [], edges: [] }; - this.guiEnabled = false; - this.inMode = false; - this.selectedControlNode = undefined; + // is is enabled and allow recognizing? + if (!boolOrFn(this.options.enable, [this, inputDataClone])) { + this.reset(); + this.state = STATE_FAILED; + return; + } - 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); + // reset when we've reached the end + if (this.state & (STATE_RECOGNIZED | STATE_CANCELLED | STATE_FAILED)) { + this.state = STATE_POSSIBLE; + } - 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)); - } + this.state = this.process(inputDataClone); - _createClass(ManipulationSystem, [{ - key: '_restore', + // the recognizer has recognized a gesture + // so trigger an event + if (this.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED | STATE_CANCELLED)) { + this.tryEmit(inputDataClone); + } + }, /** - * If something changes in the data during editing, switch back to the initial datamanipulation state and close all edit modes. - * @private + * return the state of the recognizer + * the actual recognizing happens in this method + * @virtual + * @param {Object} inputData + * @returns {Const} STATE */ - value: function _restore() { - if (this.inMode !== false) { - if (this.options.initiallyActive === true) { - this.enableEditMode(); - } else { - this.disableEditMode(); - } - } - } - }, { - key: 'setOptions', + process: function(inputData) { }, // jshint ignore:line /** - * Set the Options - * @param options + * return the preferred touch-action + * @virtual + * @returns {Array} */ - value: function setOptions(options, allOptions) { - if (allOptions !== undefined) { - if (allOptions.locale !== undefined) { - this.options.locale = allOptions.locale; - }; - if (allOptions.locales !== undefined) { - this.options.locales = allOptions.locales; - }; - } - - 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(); - } - } - }, { - key: 'toggleEditMode', + getTouchAction: function() { }, /** - * Enable or disable edit-mode. Draws the DOM required and cleans up after itself. - * - * @private + * called when the gesture isn't allowed to recognize + * like when another is being recognized or it is disabled + * @virtual */ - value: function toggleEditMode() { - if (this.editMode === true) { - this.disableEditMode(); - } else { - this.enableEditMode(); - } + reset: function() { } + }; + + /** + * get a usable string, used as event postfix + * @param {Const} state + * @returns {String} state + */ + function stateStr(state) { + if (state & STATE_CANCELLED) { + return 'cancel'; + } else if (state & STATE_ENDED) { + return 'end'; + } else if (state & STATE_CHANGED) { + return 'move'; + } else if (state & STATE_BEGAN) { + return 'start'; } - }, { - key: 'enableEditMode', - value: function enableEditMode() { - this.editMode = true; + return ''; + } - this._clean(); - if (this.guiEnabled === true) { - this.manipulationDiv.style.display = 'block'; - this.closeDiv.style.display = 'block'; - this.editModeDiv.style.display = 'none'; - this.showManipulatorToolbar(); - } + /** + * direction cons to string + * @param {Const} direction + * @returns {String} + */ + function directionStr(direction) { + if (direction == DIRECTION_DOWN) { + return 'down'; + } else if (direction == DIRECTION_UP) { + return 'up'; + } else if (direction == DIRECTION_LEFT) { + return 'left'; + } else if (direction == DIRECTION_RIGHT) { + return 'right'; } - }, { - key: 'disableEditMode', - value: function disableEditMode() { - this.editMode = false; + return ''; + } - this._clean(); - if (this.guiEnabled === true) { - this.manipulationDiv.style.display = 'none'; - this.closeDiv.style.display = 'none'; - this.editModeDiv.style.display = 'block'; - this._createEditButton(); - } + /** + * get a recognizer by name if it is bound to a manager + * @param {Recognizer|String} otherRecognizer + * @param {Recognizer} recognizer + * @returns {Recognizer} + */ + function getRecognizerByNameIfManager(otherRecognizer, recognizer) { + var manager = recognizer.manager; + if (manager) { + return manager.get(otherRecognizer); } - }, { - key: 'showManipulatorToolbar', + return otherRecognizer; + } + + /** + * This recognizer is just used as a base for the simple attribute recognizers. + * @constructor + * @extends Recognizer + */ + function AttrRecognizer() { + Recognizer.apply(this, arguments); + } + inherit(AttrRecognizer, Recognizer, { /** - * Creates the main toolbar. Removes functions bound to the select event. Binds all the buttons of the toolbar. - * - * @private + * @namespace + * @memberof AttrRecognizer */ - value: function showManipulatorToolbar() { - // restore the state of any bound functions or events, remove control nodes, restore physics - this._clean(); + defaults: { + /** + * @type {Number} + * @default 1 + */ + pointers: 1 + }, - // reset global letiables - this.manipulationDOM = {}; + /** + * Used to check if it the recognizer receives valid input, like input.distance > 10. + * @memberof AttrRecognizer + * @param {Object} input + * @returns {Boolean} recognized + */ + attrTest: function(input) { + var optionPointers = this.options.pointers; + return optionPointers === 0 || input.pointers.length === optionPointers; + }, - // 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'; + /** + * Process the input and return the state for the recognizer + * @memberof AttrRecognizer + * @param {Object} input + * @returns {*} State + */ + process: function(input) { + var state = this.state; + var eventType = input.eventType; - 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; + var isRecognized = state & (STATE_BEGAN | STATE_CHANGED); + var isValid = this.attrTest(input); - 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); + // on cancel input and we've recognized before, return STATE_CANCELLED + if (isRecognized && (eventType & INPUT_CANCEL || !isValid)) { + return state | STATE_CANCELLED; + } else if (isRecognized || isValid) { + if (eventType & INPUT_END) { + return state | STATE_ENDED; + } else if (!(state & STATE_BEGAN)) { + return STATE_BEGAN; + } + return state | STATE_CHANGED; } + return STATE_FAILED; + } + }); - 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); + /** + * Pan + * Recognized when the pointer is down and moved in the allowed direction. + * @constructor + * @extends AttrRecognizer + */ + function PanRecognizer() { + AttrRecognizer.apply(this, arguments); + + this.pX = null; + this.pY = null; + } + + inherit(PanRecognizer, AttrRecognizer, { + /** + * @namespace + * @memberof PanRecognizer + */ + defaults: { + event: 'pan', + threshold: 10, + pointers: 1, + direction: DIRECTION_ALL + }, + + getTouchAction: function() { + var direction = this.options.direction; + var actions = []; + if (direction & DIRECTION_HORIZONTAL) { + actions.push(TOUCH_ACTION_PAN_Y); } + if (direction & DIRECTION_VERTICAL) { + actions.push(TOUCH_ACTION_PAN_X); + } + return actions; + }, - // 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); + directionTest: function(input) { + var options = this.options; + var hasMoved = true; + var distance = input.distance; + var direction = input.direction; + var x = input.deltaX; + var y = input.deltaY; + + // lock to axis? + if (!(direction & options.direction)) { + if (options.direction & DIRECTION_HORIZONTAL) { + direction = (x === 0) ? DIRECTION_NONE : (x < 0) ? DIRECTION_LEFT : DIRECTION_RIGHT; + hasMoved = x != this.pX; + distance = Math.abs(input.deltaX); + } else { + direction = (y === 0) ? DIRECTION_NONE : (y < 0) ? DIRECTION_UP : DIRECTION_DOWN; + hasMoved = y != this.pY; + distance = Math.abs(input.deltaY); } - this._createDeleteButton(locale); - } } + input.direction = direction; + return hasMoved && distance > options.threshold && direction & options.direction; + }, - // bind the close button - this._bindHammerToDiv(this.closeDiv, this.toggleEditMode.bind(this)); + attrTest: function(input) { + return AttrRecognizer.prototype.attrTest.call(this, input) && + (this.state & STATE_BEGAN || (!(this.state & STATE_BEGAN) && this.directionTest(input))); + }, - // refresh this bar based on what has been selected - this._temporaryBindEvent('select', this.showManipulatorToolbar.bind(this)); - } + emit: function(input) { + this.pX = input.deltaX; + this.pY = input.deltaY; - // redraw to show any possible changes - this.body.emitter.emit('_redraw'); + var direction = directionStr(input.direction); + if (direction) { + this.manager.emit(this.options.event + direction, input); + } + + this._super.emit.call(this, input); } - }, { - key: 'addNodeMode', + }); + + /** + * Pinch + * Recognized when two or more pointers are moving toward (zoom-in) or away from each other (zoom-out). + * @constructor + * @extends AttrRecognizer + */ + function PinchRecognizer() { + AttrRecognizer.apply(this, arguments); + } + inherit(PinchRecognizer, AttrRecognizer, { /** - * Create the toolbar for adding Nodes - * - * @private + * @namespace + * @memberof PinchRecognizer */ - value: function addNodeMode() { - // 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(); + defaults: { + event: 'pinch', + threshold: 0, + pointers: 2 + }, - 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']); + getTouchAction: function() { + return [TOUCH_ACTION_NONE]; + }, - // bind the close button - this._bindHammerToDiv(this.closeDiv, this.toggleEditMode.bind(this)); - } + attrTest: function(input) { + return this._super.attrTest.call(this, input) && + (Math.abs(input.scale - 1) > this.options.threshold || this.state & STATE_BEGAN); + }, - this._temporaryBindEvent('click', this._performAddNode.bind(this)); + emit: function(input) { + this._super.emit.call(this, input); + if (input.scale !== 1) { + var inOut = input.scale < 1 ? 'in' : 'out'; + this.manager.emit(this.options.event + inOut, input); + } } - }, { - key: 'editNodeMode', + }); + + /** + * Press + * Recognized when the pointer is down for x ms without any movement. + * @constructor + * @extends Recognizer + */ + function PressRecognizer() { + Recognizer.apply(this, arguments); + + this._timer = null; + this._input = null; + } + inherit(PressRecognizer, Recognizer, { /** - * call the bound function to handle the editing of the node. The node has to be selected. - * - * @private + * @namespace + * @memberof PressRecognizer */ - value: function editNodeMode() { - var _this2 = this; + defaults: { + event: 'press', + pointers: 1, + time: 500, // minimal time of the pointer to be pressed + threshold: 5 // a minimal movement is ok, but keep it low + }, - // when using the gui, enable edit mode if it wasnt already. - if (this.editMode !== true) { - this.enableEditMode(); - } + getTouchAction: function() { + return [TOUCH_ACTION_AUTO]; + }, - // restore the state of any bound functions or events, remove control nodes, restore physics - this._clean(); + process: function(input) { + var options = this.options; + var validPointers = input.pointers.length === options.pointers; + var validMovement = input.distance < options.threshold; + var validTime = input.deltaTime > options.time; - this.inMode = 'editNode'; - if (typeof this.options.editNode === 'function') { - var node = this.selectionHandler._getSelectedNode(); - if (node.isCluster !== true) { - var data = util.deepExtend({}, node.options, true); - data.x = node.x; - data.y = node.y; + this._input = input; - if (this.options.editNode.length === 2) { - this.options.editNode(data, function (finalizedData) { - if (finalizedData !== null && finalizedData !== undefined && _this2.inMode === 'delete') { - // if for whatever reason the mode has changes (due to dataset change) disregard the callback) { - _this2.body.data.nodes.update(finalizedData); - _this2.showManipulatorToolbar(); - } - }); - } else { - throw new Error('The function for edit does not support two arguments (data, callback)'); - } + // we only allow little movement + // and we've reached an end event, so a tap is possible + if (!validMovement || !validPointers || (input.eventType & (INPUT_END | INPUT_CANCEL) && !validTime)) { + this.reset(); + } else if (input.eventType & INPUT_START) { + this.reset(); + this._timer = setTimeoutContext(function() { + this.state = STATE_RECOGNIZED; + this.tryEmit(); + }, options.time, this); + } else if (input.eventType & INPUT_END) { + return STATE_RECOGNIZED; + } + return STATE_FAILED; + }, + + reset: function() { + clearTimeout(this._timer); + }, + + emit: function(input) { + if (this.state !== STATE_RECOGNIZED) { + return; + } + + if (input && (input.eventType & INPUT_END)) { + this.manager.emit(this.options.event + 'up', input); } else { - alert(this.options.locales[this.options.locale]['editClusterError'] || this.options.locales['en']['editClusterError']); + this._input.timeStamp = now(); + this.manager.emit(this.options.event, this._input); } - } else { - throw new Error('No function has been configured to handle the editing of nodes.'); - } } - }, { - key: 'addEdgeMode', + }); + + /** + * Rotate + * Recognized when two or more pointer are moving in a circular motion. + * @constructor + * @extends AttrRecognizer + */ + function RotateRecognizer() { + AttrRecognizer.apply(this, arguments); + } + inherit(RotateRecognizer, AttrRecognizer, { /** - * create the toolbar to connect nodes - * - * @private + * @namespace + * @memberof RotateRecognizer */ - 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)); - } + defaults: { + event: 'rotate', + threshold: 0, + pointers: 2 + }, - // 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)); + getTouchAction: function() { + return [TOUCH_ACTION_NONE]; + }, - this._temporaryBindUI('onDragStart', function () {}); - this._temporaryBindUI('onHold', function () {}); + attrTest: function(input) { + return this._super.attrTest.call(this, input) && + (Math.abs(input.rotation) > this.options.threshold || this.state & STATE_BEGAN); } - }, { - key: 'editEdgeMode', + }); + + /** + * Swipe + * Recognized when the pointer is moving fast (velocity), with enough distance in the allowed direction. + * @constructor + * @extends AttrRecognizer + */ + function SwipeRecognizer() { + AttrRecognizer.apply(this, arguments); + } + inherit(SwipeRecognizer, AttrRecognizer, { /** - * create the toolbar to edit edges - * - * @private + * @namespace + * @memberof SwipeRecognizer */ - value: function editEdgeMode() { - // 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(); + defaults: { + event: 'swipe', + threshold: 10, + velocity: 0.65, + direction: DIRECTION_HORIZONTAL | DIRECTION_VERTICAL, + pointers: 1 + }, - 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']); + getTouchAction: function() { + return PanRecognizer.prototype.getTouchAction.call(this); + }, - // bind the close button - this._bindHammerToDiv(this.closeDiv, this.toggleEditMode.bind(this)); - } + attrTest: function(input) { + var direction = this.options.direction; + var velocity; - this.edgeBeingEditedId = this.selectionHandler.getSelectedEdges()[0]; - var edge = this.body.edges[this.edgeBeingEditedId]; + if (direction & (DIRECTION_HORIZONTAL | DIRECTION_VERTICAL)) { + velocity = input.velocity; + } else if (direction & DIRECTION_HORIZONTAL) { + velocity = input.velocityX; + } else if (direction & DIRECTION_VERTICAL) { + velocity = input.velocityY; + } - // create control nodes - var controlNodeFrom = this._getNewTargetNode(edge.from.x, edge.from.y); - var controlNodeTo = this._getNewTargetNode(edge.to.x, edge.to.y); + return this._super.attrTest.call(this, input) && + direction & input.direction && + input.distance > this.options.threshold && + abs(velocity) > this.options.velocity && input.eventType & INPUT_END; + }, - this.temporaryIds.nodes.push(controlNodeFrom.id); - this.temporaryIds.nodes.push(controlNodeTo.id); + emit: function(input) { + var direction = directionStr(input.direction); + if (direction) { + this.manager.emit(this.options.event + direction, input); + } - this.body.nodes[controlNodeFrom.id] = controlNodeFrom; - this.body.nodeIndices.push(controlNodeFrom.id); - this.body.nodes[controlNodeTo.id] = controlNodeTo; - this.body.nodeIndices.push(controlNodeTo.id); + this.manager.emit(this.options.event, input); + } + }); - // temporarily overload UI functions, cleaned up automatically because of _temporaryBindUI - this._temporaryBindUI('onTouch', this._controlNodeTouch.bind(this)); // used to get the position - this._temporaryBindUI('onTap', function () {}); // disabled - this._temporaryBindUI('onHold', function () {}); // disabled - this._temporaryBindUI('onDragStart', this._controlNodeDragStart.bind(this)); // used to select control node - this._temporaryBindUI('onDrag', this._controlNodeDrag.bind(this)); // used to drag control node - this._temporaryBindUI('onDragEnd', this._controlNodeDragEnd.bind(this)); // used to connect or revert control nodes - this._temporaryBindUI('onMouseMove', function () {}); // disabled + /** + * A tap is ecognized when the pointer is doing a small tap/click. Multiple taps are recognized if they occur + * between the given interval and position. The delay option can be used to recognize multi-taps without firing + * a single tap. + * + * The eventData from the emitted event contains the property `tapCount`, which contains the amount of + * multi-taps being recognized. + * @constructor + * @extends Recognizer + */ + function TapRecognizer() { + Recognizer.apply(this, arguments); - // create function to position control nodes correctly on movement - // automatically cleaned up because we use the temporary bind - this._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; - } - }); + // previous time and center, + // used for tap counting + this.pTime = false; + this.pCenter = false; - this.body.emitter.emit('_redraw'); - } - }, { - key: 'deleteSelected', + this._timer = null; + this._input = null; + this.count = 0; + } + inherit(TapRecognizer, Recognizer, { /** - * delete everything in the selection - * - * @private + * @namespace + * @memberof PinchRecognizer */ - value: function deleteSelected() { - var _this3 = this; + defaults: { + event: 'tap', + pointers: 1, + taps: 1, + interval: 300, // max time between the multi-tap taps + time: 250, // max time of the pointer to be down (like finger on the screen) + threshold: 2, // a minimal movement is ok, but keep it low + posThreshold: 10 // a multi-tap can be a bit off the initial position + }, - // when using the gui, enable edit mode if it wasnt already. - if (this.editMode !== true) { - this.enableEditMode(); - } + getTouchAction: function() { + return [TOUCH_ACTION_MANIPULATION]; + }, - // restore the state of any bound functions or events, remove control nodes, restore physics - this._clean(); + process: function(input) { + var options = this.options; - 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; - } - } + var validPointers = input.pointers.length === options.pointers; + var validMovement = input.distance < options.threshold; + var validTouchTime = input.deltaTime < options.time; - 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; + this.reset(); + + if ((input.eventType & INPUT_START) && (this.count === 0)) { + return this.failTimeout(); } - } - if (typeof deleteFunction === 'function') { - var data = { nodes: selectedNodes, edges: selectedEdges }; - if (deleteFunction.length === 2) { - deleteFunction(data, function (finalizedData) { - if (finalizedData !== null && finalizedData !== undefined && _this3.inMode === 'delete') { - // if for whatever reason the mode has changes (due to dataset change) disregard the callback) { - _this3.body.data.edges.remove(finalizedData.edges); - _this3.body.data.nodes.remove(finalizedData.nodes); - _this3.body.emitter.emit('startSimulation'); + // we only allow little movement + // and we've reached an end event, so a tap is possible + if (validMovement && validTouchTime && validPointers) { + if (input.eventType != INPUT_END) { + return this.failTimeout(); } - }); - } else { - throw new Error('The function for delete does not support two arguments (data, callback)'); - } - } else { - this.body.data.edges.remove(selectedEdges); - this.body.data.nodes.remove(selectedNodes); - this.body.emitter.emit('startSimulation'); - } - } - }, { - key: '_setup', - //********************************************** PRIVATE ***************************************// + var validInterval = this.pTime ? (input.timeStamp - this.pTime < options.interval) : true; + var validMultiTap = !this.pCenter || getDistance(this.pCenter, input.center) < options.posThreshold; - /** - * draw or remove the DOM - * @private - */ - value: function _setup() { - if (this.options.enabled === true) { - // Enable the GUI - this.guiEnabled = true; + this.pTime = input.timeStamp; + this.pCenter = input.center; - this._createWrappers(); - if (this.editMode === false) { - this._createEditButton(); - } else { - this.showManipulatorToolbar(); + if (!validMultiTap || !validInterval) { + this.count = 1; + } else { + this.count += 1; + } + + this._input = input; + + // if tap count matches we have recognized it, + // else it has began recognizing... + var tapCount = this.count % options.taps; + if (tapCount === 0) { + // no failing requirements, immediately trigger the tap event + // or wait as long as the multitap interval to trigger + if (!this.hasRequireFailures()) { + return STATE_RECOGNIZED; + } else { + this._timer = setTimeoutContext(function() { + this.state = STATE_RECOGNIZED; + this.tryEmit(); + }, options.interval, this); + return STATE_BEGAN; + } + } } - } else { - this._removeManipulationDOM(); + return STATE_FAILED; + }, - // disable the gui - this.guiEnabled = false; - } + failTimeout: function() { + this._timer = setTimeoutContext(function() { + this.state = STATE_FAILED; + }, this.options.interval, this); + return STATE_FAILED; + }, + + reset: function() { + clearTimeout(this._timer); + }, + + emit: function() { + if (this.state == STATE_RECOGNIZED ) { + this._input.tapCount = this.count; + this.manager.emit(this.options.event, this._input); + } } - }, { - key: '_createWrappers', + }); + /** + * Simple way to create an manager with a default set of recognizers. + * @param {HTMLElement} element + * @param {Object} [options] + * @constructor + */ + function Hammer(element, options) { + options = options || {}; + options.recognizers = ifUndefined(options.recognizers, Hammer.defaults.preset); + return new Manager(element, options); + } + + /** + * @const {string} + */ + Hammer.VERSION = '2.0.4'; + + /** + * default settings + * @namespace + */ + Hammer.defaults = { /** - * create the div overlays that contain the DOM - * @private + * set if DOM events are being triggered. + * But this is slower and unused by simple implementations, so disabled by default. + * @type {Boolean} + * @default false */ - 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); - } + domEvents: false, - // 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); - } + /** + * The value for the touchAction property/fallback. + * When set to `compute` it will magically set the correct value based on the added recognizers. + * @type {String} + * @default compute + */ + touchAction: TOUCH_ACTION_COMPUTE, - // 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', + /** + * @type {Boolean} + * @default true + */ + enable: true, /** - * generate a new target node. Used for creating new edges and editing edges - * @param x - * @param y - * @returns {*} - * @private + * EXPERIMENTAL FEATURE -- can be removed/changed + * Change the parent input target element. + * If Null, then it is being set the to main element. + * @type {Null|EventTarget} + * @default null */ - value: function _getNewTargetNode(x, y) { - var controlNodeStyle = util.deepExtend({}, this.options.controlNodeStyle); + inputTarget: null, - controlNodeStyle.id = 'targetNode' + util.randomUUID(); - controlNodeStyle.hidden = false; - controlNodeStyle.physics = false; - controlNodeStyle.x = x; - controlNodeStyle.y = y; + /** + * force an input class + * @type {Null|Function} + * @default null + */ + inputClass: null, - return this.body.functions.createNode(controlNodeStyle); - } - }, { - key: '_createEditButton', + /** + * Default recognizer setup when calling `Hammer()` + * When creating a new Manager these will be skipped. + * @type {Array} + */ + preset: [ + // RecognizerClass, options, [recognizeWith, ...], [requireFailure, ...] + [RotateRecognizer, { enable: false }], + [PinchRecognizer, { enable: false }, ['rotate']], + [SwipeRecognizer,{ direction: DIRECTION_HORIZONTAL }], + [PanRecognizer, { direction: DIRECTION_HORIZONTAL }, ['swipe']], + [TapRecognizer], + [TapRecognizer, { event: 'doubletap', taps: 2 }, ['tap']], + [PressRecognizer] + ], /** - * Create the edit button + * Some CSS properties can be used to improve the working of Hammer. + * Add them to this method and they will be set when creating a new Manager. + * @namespace */ - value: function _createEditButton() { - // restore everything to it's original state (if applicable) - this._clean(); + cssProps: { + /** + * Disables text selection to improve the dragging gesture. Mainly for desktop browsers. + * @type {String} + * @default 'none' + */ + userSelect: 'none', - // reset the manipulationDOM - this.manipulationDOM = {}; + /** + * Disable the Windows Phone grippers when pressing an element. + * @type {String} + * @default 'none' + */ + touchSelect: 'none', - // empty the editModeDiv - util.recursiveDOMDelete(this.editModeDiv); + /** + * Disables the default callout shown when you touch and hold a touch target. + * On iOS, when you touch and hold a touch target such as a link, Safari displays + * a callout containing information about the link. This property allows you to disable that callout. + * @type {String} + * @default 'none' + */ + touchCallout: 'none', - // 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); + /** + * Specifies whether zooming is enabled. Used by IE10> + * @type {String} + * @default 'none' + */ + contentZooming: 'none', - // bind a hammer listener to the button, calling the function toggleEditMode. - this._bindHammerToDiv(button, this.toggleEditMode.bind(this)); + /** + * Specifies that an entire element should be draggable instead of its contents. Mainly for desktop browsers. + * @type {String} + * @default 'none' + */ + userDrag: 'none', + + /** + * Overrides the highlight color shown when the user taps a link or a JavaScript + * clickable element in iOS. This property obeys the alpha value, if specified. + * @type {String} + * @default 'rgba(0,0,0,0)' + */ + tapHighlightColor: 'rgba(0,0,0,0)' } - }, { - key: '_clean', + }; - /** - * 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; + var STOP = 1; + var FORCED_STOP = 2; - // _clean the divs - if (this.guiEnabled === true) { - util.recursiveDOMDelete(this.editModeDiv); - util.recursiveDOMDelete(this.manipulationDiv); + /** + * Manager + * @param {HTMLElement} element + * @param {Object} [options] + * @constructor + */ + function Manager(element, options) { + options = options || {}; - // removes all the bindings and overloads - this._cleanManipulatorHammers(); - } + this.options = merge(options, Hammer.defaults); + this.options.inputTarget = this.options.inputTarget || element; - // remove temporary nodes and edges - this._cleanupTemporaryNodesAndEdges(); + this.handlers = {}; + this.session = {}; + this.recognizers = []; - // restore overloaded UI functions - this._unbindTemporaryUIs(); + this.element = element; + this.input = createInputInstance(this); + this.touchAction = new TouchAction(this, this.options.touchAction); - // remove the temporaryEventFunctions - this._unbindTemporaryEvents(); + toggleCssProps(this, true); - // restore the physics if required - this.body.emitter.emit('restorePhysics'); - } - }, { - key: '_cleanManipulatorHammers', + each(options.recognizers, function(item) { + var recognizer = this.add(new (item[0])(item[1])); + item[2] && recognizer.recognizeWith(item[2]); + item[3] && recognizer.requireFailure(item[3]); + }, this); + } + Manager.prototype = { /** - * Each dom element has it's own hammer. They are stored in this.manipulationHammers. This cleans them up. - * @private + * set options + * @param {Object} options + * @returns {Manager} */ - value: function _cleanManipulatorHammers() { - // _clean hammer bindings - if (this.manipulationHammers.length != 0) { - for (var i = 0; i < this.manipulationHammers.length; i++) { - this.manipulationHammers[i].destroy(); + set: function(options) { + extend(this.options, options); + + // Options that need a little more setup + if (options.touchAction) { + this.touchAction.update(); } - this.manipulationHammers = []; - } - } - }, { - key: '_removeManipulationDOM', + if (options.inputTarget) { + // Clean up existing event listeners and reinitialize + this.input.destroy(); + this.input.target = options.inputTarget; + this.input.init(); + } + return this; + }, /** - * Remove all DOM elements created by this module. - * @private + * stop recognizing for this session. + * This session will be discarded, when a new [input]start event is fired. + * When forced, the recognizer cycle is stopped immediately. + * @param {Boolean} [force] */ - value: function _removeManipulationDOM() { - // removes all the bindings and overloads - this._clean(); - - // empty the manipulation divs - util.recursiveDOMDelete(this.manipulationDiv); - util.recursiveDOMDelete(this.editModeDiv); - util.recursiveDOMDelete(this.closeDiv); - - // remove the manipulation divs - this.canvas.frame.removeChild(this.manipulationDiv); - this.canvas.frame.removeChild(this.editModeDiv); - this.canvas.frame.removeChild(this.closeDiv); - - // set the references to undefined - this.manipulationDiv = undefined; - this.editModeDiv = undefined; - this.closeDiv = undefined; - } - }, { - key: '_createSeperator', + stop: function(force) { + this.session.stopped = force ? FORCED_STOP : STOP; + }, /** - * create a seperator line. the index is to differentiate in the manipulation dom - * @param index - * @private + * run the recognizers! + * called by the inputHandler function on every movement of the pointers (touches) + * it walks through all the recognizers and tries to detect the gesture that is being made + * @param {Object} inputData */ - value: function _createSeperator() { - var index = arguments[0] === undefined ? 1 : arguments[0]; + recognize: function(inputData) { + var session = this.session; + if (session.stopped) { + return; + } - this.manipulationDOM['seperatorLineDiv' + index] = document.createElement('div'); - this.manipulationDOM['seperatorLineDiv' + index].className = 'vis-separator-line'; - this.manipulationDiv.appendChild(this.manipulationDOM['seperatorLineDiv' + index]); - } - }, { - key: '_createAddNodeButton', + // run the touch-action polyfill + this.touchAction.preventDefaults(inputData); - // ---------------------- DOM functions for buttons --------------------------// + var recognizer; + var recognizers = this.recognizers; - 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('editNodeMode', 'vis-button vis-edit', locale['editNode'] || this.options.locales['en']['editNode']); - this.manipulationDiv.appendChild(button); - this._bindHammerToDiv(button, this.editNodeMode.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]; + // this holds the recognizer that is being recognized. + // so the recognizer's state needs to be BEGAN, CHANGED, ENDED or RECOGNIZED + // if no recognizer is detecting a thing, it is set to `null` + var curRecognizer = session.curRecognizer; - 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', + // reset when the last recognizer is recognized + // or when we're in a new session + if (!curRecognizer || (curRecognizer && curRecognizer.state & STATE_RECOGNIZED)) { + curRecognizer = session.curRecognizer = null; + } - // -------------------------- End of DOM functions for buttons ------------------------------// + var i = 0; + while (i < recognizers.length) { + recognizer = recognizers[i]; - /** - * 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', + // find out if we are allowed try to recognize the input for this one. + // 1. allow if the session is NOT forced stopped (see the .stop() method) + // 2. allow if we still haven't recognized a gesture in this session, or the this recognizer is the one + // that is being recognized. + // 3. allow if the recognizer is allowed to run simultaneous with the current recognized recognizer. + // this can be setup with the `recognizeWith()` method on the recognizer. + if (session.stopped !== FORCED_STOP && ( // 1 + !curRecognizer || recognizer == curRecognizer || // 2 + recognizer.canRecognizeWith(curRecognizer))) { // 3 + recognizer.recognize(inputData); + } else { + recognizer.reset(); + } - /** - * 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', + // if the recognizer has been recognizing the input as a valid gesture, we want to store this one as the + // current active recognizer. but only if we don't already have an active recognizer + if (!curRecognizer && recognizer.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED)) { + curRecognizer = session.curRecognizer = recognizer; + } + i++; + } + }, /** - * Restore the overridden UI functions to their original state. - * - * @private + * get a recognizer by its event name. + * @param {Recognizer|String} recognizer + * @returns {Recognizer|Null} */ - 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: function(recognizer) { + if (recognizer instanceof Recognizer) { + return recognizer; } - } - this.temporaryUIFunctions = {}; + + var recognizers = this.recognizers; + for (var i = 0; i < recognizers.length; i++) { + if (recognizers[i].options.event == recognizer) { + return recognizers[i]; + } + } + return null; + }, + + /** + * add a recognizer to the manager + * existing recognizers with the same event name will be removed + * @param {Recognizer} recognizer + * @returns {Recognizer|Manager} + */ + add: function(recognizer) { + if (invokeArrayArg(recognizer, 'add', this)) { + return this; + } + + // remove existing + var existing = this.get(recognizer.options.event); + if (existing) { + this.remove(existing); + } + + this.recognizers.push(recognizer); + recognizer.manager = this; + + this.touchAction.update(); + return recognizer; + }, + + /** + * remove a recognizer by name or instance + * @param {Recognizer|String} recognizer + * @returns {Manager} + */ + remove: function(recognizer) { + if (invokeArrayArg(recognizer, 'remove', this)) { + return this; + } + + var recognizers = this.recognizers; + recognizer = this.get(recognizer); + recognizers.splice(inArray(recognizers, recognizer), 1); + + this.touchAction.update(); + return this; + }, + + /** + * bind event + * @param {String} events + * @param {Function} handler + * @returns {EventEmitter} this + */ + on: function(events, handler) { + var handlers = this.handlers; + each(splitStr(events), function(event) { + handlers[event] = handlers[event] || []; + handlers[event].push(handler); + }); + return this; + }, + + /** + * unbind event, leave emit blank to remove all handlers + * @param {String} events + * @param {Function} [handler] + * @returns {EventEmitter} this + */ + off: function(events, handler) { + var handlers = this.handlers; + each(splitStr(events), function(event) { + if (!handler) { + delete handlers[event]; + } else { + handlers[event].splice(inArray(handlers[event], handler), 1); + } + }); + return this; + }, + + /** + * emit event to the listeners + * @param {String} event + * @param {Object} data + */ + emit: function(event, data) { + // we also want to trigger dom events + if (this.options.domEvents) { + triggerDomEvent(event, data); + } + + // no handlers, so skip it all + var handlers = this.handlers[event] && this.handlers[event].slice(); + if (!handlers || !handlers.length) { + return; + } + + data.type = event; + data.preventDefault = function() { + data.srcEvent.preventDefault(); + }; + + var i = 0; + while (i < handlers.length) { + handlers[i](data); + i++; + } + }, + + /** + * destroy the manager and unbinds all events + * it doesn't unbind dom events, that is the user own responsibility + */ + destroy: function() { + this.element && toggleCssProps(this, false); + + this.handlers = {}; + this.session = {}; + this.input.destroy(); + this.element = null; } - }, { - key: '_unbindTemporaryEvents', + }; + + /** + * add/remove the css properties as defined in manager.options.cssProps + * @param {Manager} manager + * @param {Boolean} add + */ + function toggleCssProps(manager, add) { + var element = manager.element; + each(manager.options.cssProps, function(value, name) { + element.style[prefixed(element.style, name)] = add ? value : ''; + }); + } + + /** + * trigger dom event + * @param {String} event + * @param {Object} data + */ + function triggerDomEvent(event, data) { + var gestureEvent = document.createEvent('Event'); + gestureEvent.initEvent(event, true, true); + gestureEvent.gesture = data; + data.target.dispatchEvent(gestureEvent); + } + + extend(Hammer, { + INPUT_START: INPUT_START, + INPUT_MOVE: INPUT_MOVE, + INPUT_END: INPUT_END, + INPUT_CANCEL: INPUT_CANCEL, + + STATE_POSSIBLE: STATE_POSSIBLE, + STATE_BEGAN: STATE_BEGAN, + STATE_CHANGED: STATE_CHANGED, + STATE_ENDED: STATE_ENDED, + STATE_RECOGNIZED: STATE_RECOGNIZED, + STATE_CANCELLED: STATE_CANCELLED, + STATE_FAILED: STATE_FAILED, + + DIRECTION_NONE: DIRECTION_NONE, + DIRECTION_LEFT: DIRECTION_LEFT, + DIRECTION_RIGHT: DIRECTION_RIGHT, + DIRECTION_UP: DIRECTION_UP, + DIRECTION_DOWN: DIRECTION_DOWN, + DIRECTION_HORIZONTAL: DIRECTION_HORIZONTAL, + DIRECTION_VERTICAL: DIRECTION_VERTICAL, + DIRECTION_ALL: DIRECTION_ALL, + + Manager: Manager, + Input: Input, + TouchAction: TouchAction, + + TouchInput: TouchInput, + MouseInput: MouseInput, + PointerEventInput: PointerEventInput, + TouchMouseInput: TouchMouseInput, + SingleTouchInput: SingleTouchInput, + + Recognizer: Recognizer, + AttrRecognizer: AttrRecognizer, + Tap: TapRecognizer, + Pan: PanRecognizer, + Swipe: SwipeRecognizer, + Pinch: PinchRecognizer, + Rotate: RotateRecognizer, + Press: PressRecognizer, + + on: addEventListeners, + off: removeEventListeners, + each: each, + merge: merge, + extend: extend, + inherit: inherit, + bindFn: bindFn, + prefixed: prefixed + }); + + if ("function" == TYPE_FUNCTION && __webpack_require__(91)) { + !(__WEBPACK_AMD_DEFINE_RESULT__ = function() { + return Hammer; + }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); + } else if (typeof module != 'undefined' && module.exports) { + module.exports = Hammer; + } else { + window[exportName] = Hammer; + } + + })(window, document, 'Hammer'); + + +/***/ }, +/* 73 */ +/***/ 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__(41); + var hammerUtil = __webpack_require__(49); + var util = __webpack_require__(1); + + var ColorPicker = (function () { + function ColorPicker() { + var pixelRatio = arguments[0] === undefined ? 1 : arguments[0]; + + _classCallCheck(this, ColorPicker); + + this.pixelRatio = pixelRatio; + this.generated = false; + this.centerCoordinates = { x: 289 / 2, y: 289 / 2 }; + this.r = 289 * 0.49; + this.color = { r: 255, g: 255, b: 255, a: 1 }; + this.hueCircle = undefined; + this.initialColor = { r: 255, g: 255, b: 255, a: 1 }; + this.previousColor = undefined; + this.applied = false; + + // bound by + this.updateCallback = function () {}; + + // create all DOM elements + this._create(); + } + + _createClass(ColorPicker, [{ + key: 'insertTo', /** - * Unbind the events created by _temporaryBindEvent - * @private + * this inserts the colorPicker into a div from the DOM + * @param container */ - 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); + value: function insertTo(container) { + if (this.hammer !== undefined) { + this.hammer.destroy(); + this.hammer = undefined; } - this.temporaryEventFunctions = []; + this.container = container; + this.container.appendChild(this.frame); + this._bindHammer(); + + this._setSize(); } }, { - key: '_bindHammerToDiv', + key: 'setCallback', /** - * Bind an hammer instance to a DOM element. - * @param domElement - * @param funct + * the callback is executed on apply and save. Bind it to the application + * @param callback */ - value: function _bindHammerToDiv(domElement, boundFunction) { - var hammer = new Hammer(domElement, {}); - hammerUtil.onTouch(hammer, boundFunction); - this.manipulationHammers.push(hammer); + value: function setCallback(callback) { + if (typeof callback === 'function') { + this.updateCallback = callback; + } else { + throw new Error('Function attempted to set as colorPicker callback is not a function.'); + } } }, { - key: '_cleanupTemporaryNodesAndEdges', + key: '_isColorString', + value: function _isColorString(color) { + var htmlColors = { black: '#000000', navy: '#000080', darkblue: '#00008B', mediumblue: '#0000CD', blue: '#0000FF', darkgreen: '#006400', green: '#008000', teal: '#008080', darkcyan: '#008B8B', deepskyblue: '#00BFFF', darkturquoise: '#00CED1', mediumspringgreen: '#00FA9A', lime: '#00FF00', springgreen: '#00FF7F', aqua: '#00FFFF', cyan: '#00FFFF', midnightblue: '#191970', dodgerblue: '#1E90FF', lightseagreen: '#20B2AA', forestgreen: '#228B22', seagreen: '#2E8B57', darkslategray: '#2F4F4F', limegreen: '#32CD32', mediumseagreen: '#3CB371', turquoise: '#40E0D0', royalblue: '#4169E1', steelblue: '#4682B4', darkslateblue: '#483D8B', mediumturquoise: '#48D1CC', indigo: '#4B0082', darkolivegreen: '#556B2F', cadetblue: '#5F9EA0', cornflowerblue: '#6495ED', mediumaquamarine: '#66CDAA', dimgray: '#696969', slateblue: '#6A5ACD', olivedrab: '#6B8E23', slategray: '#708090', lightslategray: '#778899', mediumslateblue: '#7B68EE', lawngreen: '#7CFC00', chartreuse: '#7FFF00', aquamarine: '#7FFFD4', maroon: '#800000', purple: '#800080', olive: '#808000', gray: '#808080', skyblue: '#87CEEB', lightskyblue: '#87CEFA', blueviolet: '#8A2BE2', darkred: '#8B0000', darkmagenta: '#8B008B', saddlebrown: '#8B4513', darkseagreen: '#8FBC8F', lightgreen: '#90EE90', mediumpurple: '#9370D8', darkviolet: '#9400D3', palegreen: '#98FB98', darkorchid: '#9932CC', yellowgreen: '#9ACD32', sienna: '#A0522D', brown: '#A52A2A', darkgray: '#A9A9A9', lightblue: '#ADD8E6', greenyellow: '#ADFF2F', paleturquoise: '#AFEEEE', lightsteelblue: '#B0C4DE', powderblue: '#B0E0E6', firebrick: '#B22222', darkgoldenrod: '#B8860B', mediumorchid: '#BA55D3', rosybrown: '#BC8F8F', darkkhaki: '#BDB76B', silver: '#C0C0C0', mediumvioletred: '#C71585', indianred: '#CD5C5C', peru: '#CD853F', chocolate: '#D2691E', tan: '#D2B48C', lightgrey: '#D3D3D3', palevioletred: '#D87093', thistle: '#D8BFD8', orchid: '#DA70D6', goldenrod: '#DAA520', crimson: '#DC143C', gainsboro: '#DCDCDC', plum: '#DDA0DD', burlywood: '#DEB887', lightcyan: '#E0FFFF', lavender: '#E6E6FA', darksalmon: '#E9967A', violet: '#EE82EE', palegoldenrod: '#EEE8AA', lightcoral: '#F08080', khaki: '#F0E68C', aliceblue: '#F0F8FF', honeydew: '#F0FFF0', azure: '#F0FFFF', sandybrown: '#F4A460', wheat: '#F5DEB3', beige: '#F5F5DC', whitesmoke: '#F5F5F5', mintcream: '#F5FFFA', ghostwhite: '#F8F8FF', salmon: '#FA8072', antiquewhite: '#FAEBD7', linen: '#FAF0E6', lightgoldenrodyellow: '#FAFAD2', oldlace: '#FDF5E6', red: '#FF0000', fuchsia: '#FF00FF', magenta: '#FF00FF', deeppink: '#FF1493', orangered: '#FF4500', tomato: '#FF6347', hotpink: '#FF69B4', coral: '#FF7F50', darkorange: '#FF8C00', lightsalmon: '#FFA07A', orange: '#FFA500', lightpink: '#FFB6C1', pink: '#FFC0CB', gold: '#FFD700', peachpuff: '#FFDAB9', navajowhite: '#FFDEAD', moccasin: '#FFE4B5', bisque: '#FFE4C4', mistyrose: '#FFE4E1', blanchedalmond: '#FFEBCD', papayawhip: '#FFEFD5', lavenderblush: '#FFF0F5', seashell: '#FFF5EE', cornsilk: '#FFF8DC', lemonchiffon: '#FFFACD', floralwhite: '#FFFAF0', snow: '#FFFAFA', yellow: '#FFFF00', lightyellow: '#FFFFE0', ivory: '#FFFFF0', white: '#FFFFFF' }; + if (typeof color === 'string') { + return htmlColors[color]; + } + } + }, { + key: 'setColor', /** - * Neatly clean up temporary edges and nodes - * @private + * Set the color of the colorPicker + * Supported formats: + * 'red' --> HTML color string + * '#ffffff' --> hex string + * 'rbg(255,255,255)' --> rgb string + * 'rgba(255,255,255,1.0)' --> rgba string + * {r:255,g:255,b:255} --> rgb object + * {r:255,g:255,b:255,a:1.0} --> rgba object + * @param color + * @param setInitial */ - 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 setColor(color) { + var setInitial = arguments[1] === undefined ? true : arguments[1]; + + if (color === 'none') { + return; } - // _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); + var rgba = undefined; + + // if a html color shorthand is used, convert to hex + var htmlColor = this._isColorString(color); + if (htmlColor !== undefined) { + color = htmlColor; + } + + // check format + if (util.isString(color) === true) { + if (util.isValidRGB(color) === true) { + var rgbaArray = color.substr(4).substr(0, color.length - 5).split(','); + rgba = { r: rgbaArray[0], g: rgbaArray[1], b: rgbaArray[2], a: 1 }; + } else if (util.isValidRGBA(color) === true) { + var rgbaArray = color.substr(5).substr(0, color.length - 6).split(','); + rgba = { r: rgbaArray[0], g: rgbaArray[1], b: rgbaArray[2], a: rgbaArray[3] }; + } else if (util.isValidHex(color) === true) { + var rgbObj = util.hexToRGB(color); + rgba = { r: rgbObj.r, g: rgbObj.g, b: rgbObj.b, a: 1 }; + } + } else { + if (color instanceof Object) { + if (color.r !== undefined && color.g !== undefined && color.b !== undefined) { + var alpha = color.a !== undefined ? color.a : '1.0'; + rgba = { r: color.r, g: color.g, b: color.b, a: alpha }; + } } } - this.temporaryIds = { nodes: [], edges: [] }; + // set color + if (rgba === undefined) { + throw new Error('Unknown color passed to the colorPicker. Supported are strings: rgb, hex, rgba. Object: rgb ({r:r,g:g,b:b,[a:a]}). Supplied: ' + JSON.stringify(color)); + } else { + this._setColor(rgba, setInitial); + } } }, { - key: '_controlNodeTouch', - - // ------------------------------------------ EDIT EDGE FUNCTIONS -----------------------------------------// + key: 'show', /** - * the touch is used to get the position of the initial click - * @param event - * @private + * this shows the color picker at a location. The hue circle is constructed once and stored. + * @param x + * @param y */ - 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 + value: function show(x, y) { + this.applied = false; + this.frame.style.display = 'block'; + this.frame.style.top = y + 'px'; + this.frame.style.left = x + 'px'; + this._generateHueCircle(); } }, { - key: '_controlNodeDragStart', + key: '_hide', + + // ------------------------------------------ PRIVATE ----------------------------- // /** - * the drag start is used to mark one of the control nodes as selected. - * @param event + * Hide the picker. Is called by the cancel button. + * Optional boolean to store the previous color for easy access later on. + * @param storePrevious * @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 _hide() { + var storePrevious = arguments[0] === undefined ? true : arguments[0]; - var fromSelect = from.isOverlappingWith(pointerObj); - var toSelect = to.isOverlappingWith(pointerObj); + // store the previous color for next time; + if (storePrevious === true) { + this.previousColor = util.extend({}, this.color); + } - if (fromSelect === true) { - this.selectedControlNode = from; - edge.edgeType.from = from; - } else if (toSelect === true) { - this.selectedControlNode = to; - edge.edgeType.to = to; + if (this.applied === true) { + this.updateCallback(this.initialColor); } - this.body.emitter.emit('_redraw'); + this.frame.style.display = 'none'; } }, { - key: '_controlNodeDrag', + key: '_save', /** - * dragging the control nodes or the canvas - * @param event + * bound to the save button. Saves and hides. * @private */ - value: function _controlNodeDrag(event) { - this.body.emitter.emit('disablePhysics'); - var pointer = this.body.functions.getPointer(event.center); - var pos = this.canvas.DOMtoCanvas(pointer); - - 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'); + value: function _save() { + this.updateCallback(this.color); + this.applied = false; + this._hide(); } }, { - key: '_controlNodeDragEnd', + key: '_apply', /** - * connecting or restoring the control nodes. - * @param event + * Bound to apply button. Saves but does not close. Is undone by the cancel button. * @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; - } - } + value: function _apply() { + this.applied = true; + this.updateCallback(this.color); + this._updatePicker(this.color); + } + }, { + key: '_loadLast', - // 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); - } - } + /** + * load the color from the previous session. + * @private + */ + value: function _loadLast() { + if (this.previousColor !== undefined) { + this.setColor(this.previousColor, false); } else { - edge.updateEdgeType(); - this.body.emitter.emit('restorePhysics'); + alert('There is no last color to load...'); } - this.body.emitter.emit('_redraw'); } }, { - key: '_handleConnect', - - // ------------------------------------ END OF EDIT EDGE FUNCTIONS -----------------------------------------// + key: '_setColor', - // ------------------------------------------- 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. - * + * set the color, place the picker + * @param rgba + * @param setInitial * @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, - dynamic: false, - type: 'continuous', - roundness: 0.5 - } - }); - this.body.edges[connectionEdge.id] = connectionEdge; - this.body.edgeIndices.push(connectionEdge.id); + value: function _setColor(rgba) { + var setInitial = arguments[1] === undefined ? true : arguments[1]; - 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 }; + // store the initial color + if (setInitial === true) { + this.initialColor = util.extend({}, rgba); } - } - }, { - key: '_finishConnect', - - /** - * 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); - // remember the edge id - var connectFromId = undefined; - if (this.temporaryIds.edges[0] !== undefined) { - connectFromId = this.body.edges[this.temporaryIds.edges[0]].fromId; - } + this.color = rgba; + var hsv = util.RGBToHSV(rgba.r, rgba.g, rgba.b); - // 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 angleConvert = 2 * Math.PI; + var radius = this.r * hsv.s; + var x = this.centerCoordinates.x + radius * Math.sin(angleConvert * hsv.h); + var y = this.centerCoordinates.y + radius * Math.cos(angleConvert * hsv.h); - // clean temporary nodes and edges. - this._cleanupTemporaryNodesAndEdges(); + this.colorPickerSelector.style.left = x - 0.5 * this.colorPickerSelector.clientWidth + 'px'; + this.colorPickerSelector.style.top = y - 0.5 * this.colorPickerSelector.clientHeight + 'px'; - // 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'); + this._updatePicker(rgba); } }, { - key: '_performAddNode', - - // --------------------------------------- END OF ADD EDGE FUNCTIONS -------------------------------------// - - // ------------------------------ Performing all the actual data manipulation ------------------------// + key: '_setOpacity', /** - * Adds a node on the specified location + * bound to opacity control + * @param value + * @private */ - value: function _performAddNode(clickData) { - var _this4 = this; - - var defaultData = { - id: util.randomUUID(), - x: clickData.pointer.canvas.x, - y: clickData.pointer.canvas.y, - label: 'new' - }; - - if (typeof this.options.addNode === 'function') { - if (this.options.addNode.length === 2) { - this.options.addNode(defaultData, function (finalizedData) { - if (finalizedData !== null && finalizedData !== undefined && _this4.inMode === 'addNode') { - // if for whatever reason the mode has changes (due to dataset change) disregard the callback - _this4.body.data.nodes.add(finalizedData); - _this4.showManipulatorToolbar(); - } - }); - } else { - throw new Error('The function for add does not support two arguments (data,callback)'); - this.showManipulatorToolbar(); - } - } else { - this.body.data.nodes.add(defaultData); - this.showManipulatorToolbar(); - } + value: function _setOpacity(value) { + this.color.a = value / 100; + this._updatePicker(this.color); } }, { - key: '_performAddEdge', + key: '_setBrightness', /** - * connect two nodes with a new edge. - * + * bound to brightness control + * @param value * @private */ - value: function _performAddEdge(sourceNodeId, targetNodeId) { - var _this5 = this; - - 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 && _this5.inMode === 'addEdge') { - // if for whatever reason the mode has changes (due to dataset change) disregard the callback - _this5.body.data.edges.add(finalizedData); - _this5.selectionHandler.unselectAll(); - _this5.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(); - } + value: function _setBrightness(value) { + var hsv = util.RGBToHSV(this.color.r, this.color.g, this.color.b); + hsv.v = value / 100; + var rgba = util.HSVToRGB(hsv.h, hsv.s, hsv.v); + rgba['a'] = this.color.a; + this.color = rgba; + this._updatePicker(); } }, { - key: '_performEditEdge', + key: '_updatePicker', /** - * connect two nodes with a new edge. - * + * update the colorpicker. A black circle overlays the hue circle to mimic the brightness decreasing. + * @param rgba * @private */ - value: function _performEditEdge(sourceNodeId, targetNodeId) { - var _this6 = 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 || _this6.inMode !== 'editEdge') { - // if for whatever reason the mode has changes (due to dataset change) disregard the callback) { - _this6.body.edges[defaultData.id].updateEdgeType(); - _this6.body.emitter.emit('_redraw'); - } else { - _this6.body.data.edges.update(finalizedData); - _this6.selectionHandler.unselectAll(); - _this6.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']; - -/***/ }, -/* 75 */ -/***/ function(module, exports, __webpack_require__) { - - 'use strict'; - - Object.defineProperty(exports, '__esModule', { - value: true - }); - /** - * 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 - */ - 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 fn = 'function'; - var undef = 'undefined'; - var any = 'any'; - - var allOptions = { - configure: { - enabled: { boolean: boolean }, - filter: { boolean: boolean, string: string, array: array, fn: fn }, - container: { dom: dom }, - __type__: { object: object, boolean: boolean, string: string, array: array, fn: fn } - }, - 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: { fn: fn, number: number }, - label: { string: string, undef: undef }, - length: { number: number, undef: undef }, - 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: { fn: fn }, - __type__: { object: object } - }, - selectionWidth: { fn: fn, 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 }, - dynamic: { boolean: boolean }, - type: { string: string }, - roundness: { number: number }, - __type__: { object: object, boolean: boolean } - }, - title: { string: string, undef: undef }, - width: { number: number }, - value: { number: number, undef: undef }, - __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 }, - hoverEnabled: { 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 }, - tooltipDelay: { number: number }, - zoomView: { boolean: boolean }, - __type__: { object: object } - }, - layout: { - randomSeed: { undef: undef, 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, fn: fn }, - addEdge: { boolean: boolean, fn: fn }, - editNode: { fn: fn }, - editEdge: { boolean: boolean, fn: fn }, - deleteNode: { boolean: boolean, fn: fn }, - deleteEdge: { boolean: boolean, fn: fn }, - controlNodeStyle: 'get from nodes, will be overwritten below', - __type__: { object: object, boolean: boolean } - }, - nodes: { - borderWidth: { number: number }, - borderWidthSelected: { number: number, undef: undef }, - brokenImage: { string: string, undef: undef }, - 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 }, - stroke: { number: number }, // px - strokeColor: { string: string }, - __type__: { object: object, string: string } - }, - group: { string: string, number: number, undef: undef }, - 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, undef: undef }, // --> URL - label: { string: string, undef: undef }, - level: { number: number, undef: undef }, - 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: { fn: fn }, - __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, undef: undef }, - value: { number: number, undef: undef }, - 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 }, - __type__: { object: object } - }, - forceAtlas2Based: { - gravitationalConstant: { number: number }, - centralGravity: { number: number }, - springLength: { number: number }, - springConstant: { number: number }, - damping: { 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; + value: function _updatePicker() { + var rgba = arguments[0] === undefined ? this.color : arguments[0]; - 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: true, - min: [14, 0, 200, 1], - max: [30, 0, 200, 1], - maxVisible: [30, 0, 200, 1], - drawThreshold: [3, 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: [1, 0, 50, 1], // px - strokeColor: ['color', '#ffffff'], - align: ['horizontal', 'top', 'middle', 'bottom'] - }, - hidden: false, - hoverWidth: [2, 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: [3, 0, 20, 1] + var hsv = util.RGBToHSV(rgba.r, rgba.g, rgba.b); + var ctx = this.colorPickerCanvas.getContext('2d'); + if (this.pixelRation === undefined) { + this.pixelRatio = (window.devicePixelRatio || 1) / (ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 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, - dynamic: true, - type: ['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, - hoverEnabled: 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, - 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, 5, 0.005], - damping: [0.09, 0, 1, 0.01] - }, - forceAtlas2Based: { - //theta: [0.5, 0.1, 1, 0.05], - gravitationalConstant: [-800, -3000, 0, 1], - centralGravity: [0.01, 0, 1, 0.005], - springLength: [95, 0, 500, 5], - springConstant: [0.08, 0, 5, 0.005], - damping: [0.4, 0, 1, 0.01] - }, - repulsion: { - centralGravity: [0.2, 0, 10, 0.05], - springLength: [200, 0, 500, 5], - springConstant: [0.05, 0, 5, 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, 5, 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, 1, 0.05] - }, - global: { - locale: ['en', 'nl'] - } - }; - - exports.allOptions = allOptions; - exports.configureOptions = configureOptions; + ctx.setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0); -/***/ }, -/* 76 */ -/***/ function(module, exports, __webpack_require__) { + // clear the canvas + var w = this.colorPickerCanvas.clientWidth; + var h = this.colorPickerCanvas.clientHeight; + ctx.clearRect(0, 0, w, h); - /** - * Canvas shapes used by Network - */ - 'use strict'; + ctx.putImageData(this.hueCircle, 0, 0); + ctx.fillStyle = 'rgba(0,0,0,' + (1 - hsv.v) + ')'; + ctx.circle(this.centerCoordinates.x, this.centerCoordinates.y, this.r); + ctx.fill(); - if (typeof CanvasRenderingContext2D !== 'undefined') { + this.brightnessRange.value = 100 * hsv.v; + this.opacityRange.value = 100 * rgba.a; - /** - * Draw a circle shape - */ - CanvasRenderingContext2D.prototype.circle = function (x, y, r) { - this.beginPath(); - this.arc(x, y, r, 0, 2 * Math.PI, false); - }; + this.initialColorDiv.style.backgroundColor = 'rgba(' + this.initialColor.r + ',' + this.initialColor.g + ',' + this.initialColor.b + ',' + this.initialColor.a + ')'; + this.newColorDiv.style.backgroundColor = 'rgba(' + this.color.r + ',' + this.color.g + ',' + this.color.b + ',' + this.color.a + ')'; + } + }, { + key: '_setSize', - /** - * Draw a square shape - * @param {Number} x horizontal center - * @param {Number} y vertical center - * @param {Number} r size, width and height of the square - */ - CanvasRenderingContext2D.prototype.square = function (x, y, r) { - this.beginPath(); - this.rect(x - r, y - r, r * 2, r * 2); - }; + /** + * used by create to set the size of the canvas. + * @private + */ + value: function _setSize() { + this.colorPickerCanvas.style.width = '100%'; + this.colorPickerCanvas.style.height = '100%'; - /** - * Draw a triangle shape - * @param {Number} x horizontal center - * @param {Number} y vertical center - * @param {Number} r radius, half the length of the sides of the triangle - */ - CanvasRenderingContext2D.prototype.triangle = function (x, y, r) { - // http://en.wikipedia.org/wiki/Equilateral_triangle - this.beginPath(); + this.colorPickerCanvas.width = 289 * this.pixelRatio; + this.colorPickerCanvas.height = 289 * this.pixelRatio; + } + }, { + key: '_create', - // the change in radius and the offset is here to center the shape - r *= 1.15; - y += 0.275 * r; + /** + * create all dom elements + * TODO: cleanup, lots of similar dom elements + * @private + */ + value: function _create() { + this.frame = document.createElement('div'); + this.frame.className = 'vis-color-picker'; - var s = r * 2; - var s2 = s / 2; - var ir = Math.sqrt(3) / 6 * s; // radius of inner circle - var h = Math.sqrt(s * s - s2 * s2); // height + this.colorPickerDiv = document.createElement('div'); + this.colorPickerSelector = document.createElement('div'); + this.colorPickerSelector.className = 'vis-selector'; + this.colorPickerDiv.appendChild(this.colorPickerSelector); - this.moveTo(x, y - (h - ir)); - this.lineTo(x + s2, y + ir); - this.lineTo(x - s2, y + ir); - this.lineTo(x, y - (h - ir)); - this.closePath(); - }; + this.colorPickerCanvas = document.createElement('canvas'); + this.colorPickerDiv.appendChild(this.colorPickerCanvas); - /** - * Draw a triangle shape in downward orientation - * @param {Number} x horizontal center - * @param {Number} y vertical center - * @param {Number} r radius - */ - CanvasRenderingContext2D.prototype.triangleDown = function (x, y, r) { - // http://en.wikipedia.org/wiki/Equilateral_triangle - this.beginPath(); + if (!this.colorPickerCanvas.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.colorPickerCanvas.appendChild(noCanvas); + } else { + var ctx = this.colorPickerCanvas.getContext('2d'); + this.pixelRatio = (window.devicePixelRatio || 1) / (ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1); - // the change in radius and the offset is here to center the shape - r *= 1.15; - y -= 0.275 * r; + this.colorPickerCanvas.getContext('2d').setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0); + } - var s = r * 2; - var s2 = s / 2; - var ir = Math.sqrt(3) / 6 * s; // radius of inner circle - var h = Math.sqrt(s * s - s2 * s2); // height + this.colorPickerDiv.className = 'vis-color'; - this.moveTo(x, y + (h - ir)); - this.lineTo(x + s2, y - ir); - this.lineTo(x - s2, y - ir); - this.lineTo(x, y + (h - ir)); - this.closePath(); - }; + this.opacityDiv = document.createElement('div'); + this.opacityDiv.className = 'vis-opacity'; - /** - * Draw a star shape, a star with 5 points - * @param {Number} x horizontal center - * @param {Number} y vertical center - * @param {Number} r radius, half the length of the sides of the triangle - */ - CanvasRenderingContext2D.prototype.star = function (x, y, r) { - // http://www.html5canvastutorials.com/labs/html5-canvas-star-spinner/ - this.beginPath(); + this.brightnessDiv = document.createElement('div'); + this.brightnessDiv.className = 'vis-brightness'; - // the change in radius and the offset is here to center the shape - r *= 0.82; - y += 0.1 * r; + this.arrowDiv = document.createElement('div'); + this.arrowDiv.className = 'vis-arrow'; - for (var n = 0; n < 10; n++) { - var radius = n % 2 === 0 ? r * 1.3 : r * 0.5; - this.lineTo(x + radius * Math.sin(n * 2 * Math.PI / 10), y - radius * Math.cos(n * 2 * Math.PI / 10)); - } + this.opacityRange = document.createElement('input'); + this.opacityRange.type = 'range'; + this.opacityRange.min = '0'; + this.opacityRange.max = '100'; + this.opacityRange.value = '100'; + this.opacityRange.className = 'vis-range'; - this.closePath(); - }; + this.brightnessRange = document.createElement('input'); + this.brightnessRange.type = 'range'; + this.brightnessRange.min = '0'; + this.brightnessRange.max = '100'; + this.brightnessRange.value = '100'; + this.brightnessRange.className = 'vis-range'; - /** - * Draw a Diamond shape - * @param {Number} x horizontal center - * @param {Number} y vertical center - * @param {Number} r radius, half the length of the sides of the triangle - */ - CanvasRenderingContext2D.prototype.diamond = function (x, y, r) { - // http://www.html5canvastutorials.com/labs/html5-canvas-star-spinner/ - this.beginPath(); + this.opacityDiv.appendChild(this.opacityRange); + this.brightnessDiv.appendChild(this.brightnessRange); - this.lineTo(x, y + r); - this.lineTo(x + r, y); - this.lineTo(x, y - r); - this.lineTo(x - r, y); + var me = this; + this.opacityRange.onchange = function () { + me._setOpacity(this.value); + }; + this.opacityRange.oninput = function () { + me._setOpacity(this.value); + }; + this.brightnessRange.onchange = function () { + me._setBrightness(this.value); + }; + this.brightnessRange.oninput = function () { + me._setBrightness(this.value); + }; - this.closePath(); - }; + this.brightnessLabel = document.createElement('div'); + this.brightnessLabel.className = 'vis-label vis-brightness'; + this.brightnessLabel.innerHTML = 'brightness:'; - /** - * http://stackoverflow.com/questions/1255512/how-to-draw-a-rounded-rectangle-on-html-canvas - */ - CanvasRenderingContext2D.prototype.roundRect = function (x, y, w, h, r) { - var r2d = Math.PI / 180; - if (w - 2 * r < 0) { - r = w / 2; - } //ensure that the radius isn't too large for x - if (h - 2 * r < 0) { - r = h / 2; - } //ensure that the radius isn't too large for y - this.beginPath(); - this.moveTo(x + r, y); - this.lineTo(x + w - r, y); - this.arc(x + w - r, y + r, r, r2d * 270, r2d * 360, false); - this.lineTo(x + w, y + h - r); - this.arc(x + w - r, y + h - r, r, 0, r2d * 90, false); - this.lineTo(x + r, y + h); - this.arc(x + r, y + h - r, r, r2d * 90, r2d * 180, false); - this.lineTo(x, y + r); - this.arc(x + r, y + r, r, r2d * 180, r2d * 270, false); - }; + this.opacityLabel = document.createElement('div'); + this.opacityLabel.className = 'vis-label vis-opacity'; + this.opacityLabel.innerHTML = 'opacity:'; - /** - * http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - */ - CanvasRenderingContext2D.prototype.ellipse = function (x, y, w, h) { - var kappa = 0.5522848, - ox = w / 2 * kappa, - // control point offset horizontal - oy = h / 2 * kappa, - // control point offset vertical - xe = x + w, - // x-end - ye = y + h, - // y-end - xm = x + w / 2, - // x-middle - ym = y + h / 2; // y-middle + this.newColorDiv = document.createElement('div'); + this.newColorDiv.className = 'vis-new-color'; + this.newColorDiv.innerHTML = 'new'; - this.beginPath(); - this.moveTo(x, ym); - this.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - this.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - this.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - this.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - }; + this.initialColorDiv = document.createElement('div'); + this.initialColorDiv.className = 'vis-initial-color'; + this.initialColorDiv.innerHTML = 'initial'; - /** - * http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - */ - CanvasRenderingContext2D.prototype.database = function (x, y, w, h) { - var f = 1 / 3; - var wEllipse = w; - var hEllipse = h * f; + this.cancelButton = document.createElement('div'); + this.cancelButton.className = 'vis-button vis-cancel'; + this.cancelButton.innerHTML = 'cancel'; + this.cancelButton.onclick = this._hide.bind(this, false); - var kappa = 0.5522848, - ox = wEllipse / 2 * kappa, - // control point offset horizontal - oy = hEllipse / 2 * kappa, - // control point offset vertical - xe = x + wEllipse, - // x-end - ye = y + hEllipse, - // y-end - xm = x + wEllipse / 2, - // x-middle - ym = y + hEllipse / 2, - // y-middle - ymb = y + (h - hEllipse / 2), - // y-midlle, bottom ellipse - yeb = y + h; // y-end, bottom ellipse + this.applyButton = document.createElement('div'); + this.applyButton.className = 'vis-button vis-apply'; + this.applyButton.innerHTML = 'apply'; + this.applyButton.onclick = this._apply.bind(this); - this.beginPath(); - this.moveTo(xe, ym); + this.saveButton = document.createElement('div'); + this.saveButton.className = 'vis-button vis-save'; + this.saveButton.innerHTML = 'save'; + this.saveButton.onclick = this._save.bind(this); - this.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - this.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + this.loadButton = document.createElement('div'); + this.loadButton.className = 'vis-button vis-load'; + this.loadButton.innerHTML = 'load last'; + this.loadButton.onclick = this._loadLast.bind(this); - this.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - this.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + this.frame.appendChild(this.colorPickerDiv); + this.frame.appendChild(this.arrowDiv); + this.frame.appendChild(this.brightnessLabel); + this.frame.appendChild(this.brightnessDiv); + this.frame.appendChild(this.opacityLabel); + this.frame.appendChild(this.opacityDiv); + this.frame.appendChild(this.newColorDiv); + this.frame.appendChild(this.initialColorDiv); - this.lineTo(xe, ymb); + this.frame.appendChild(this.cancelButton); + this.frame.appendChild(this.applyButton); + this.frame.appendChild(this.saveButton); + this.frame.appendChild(this.loadButton); + } + }, { + key: '_bindHammer', - this.bezierCurveTo(xe, ymb + oy, xm + ox, yeb, xm, yeb); - this.bezierCurveTo(xm - ox, yeb, x, ymb + oy, x, ymb); + /** + * bind hammer to the color picker + * @private + */ + value: function _bindHammer() { + var _this = this; - this.lineTo(x, ym); - }; + this.drag = {}; + this.pinch = {}; + this.hammer = new Hammer(this.colorPickerCanvas); + this.hammer.get('pinch').set({ enable: true }); - /** - * Draw an arrow point (no line) - */ - CanvasRenderingContext2D.prototype.arrow = function (x, y, angle, length) { - // tail - var xt = x - length * Math.cos(angle); - var yt = y - length * Math.sin(angle); + hammerUtil.onTouch(this.hammer, function (event) { + _this._moveSelector(event); + }); + this.hammer.on('tap', function (event) { + _this._moveSelector(event); + }); + this.hammer.on('panstart', function (event) { + _this._moveSelector(event); + }); + this.hammer.on('panmove', function (event) { + _this._moveSelector(event); + }); + this.hammer.on('panend', function (event) { + _this._moveSelector(event); + }); + } + }, { + key: '_generateHueCircle', - // inner tail - // TODO: allow to customize different shapes - var xi = x - length * 0.9 * Math.cos(angle); - var yi = y - length * 0.9 * Math.sin(angle); + /** + * generate the hue circle. This is relatively heavy (200ms) and is done only once on the first time it is shown. + * @private + */ + value: function _generateHueCircle() { + if (this.generated === false) { + var ctx = this.colorPickerCanvas.getContext('2d'); + if (this.pixelRation === 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); - // left - var xl = xt + length / 3 * Math.cos(angle + 0.5 * Math.PI); - var yl = yt + length / 3 * Math.sin(angle + 0.5 * Math.PI); + // clear the canvas + var w = this.colorPickerCanvas.clientWidth; + var h = this.colorPickerCanvas.clientHeight; + ctx.clearRect(0, 0, w, h); - // right - var xr = xt + length / 3 * Math.cos(angle - 0.5 * Math.PI); - var yr = yt + length / 3 * Math.sin(angle - 0.5 * Math.PI); + // draw hue circle + var x = undefined, + y = undefined, + hue = undefined, + sat = undefined; + this.centerCoordinates = { x: w * 0.5, y: h * 0.5 }; + this.r = 0.49 * w; + var angleConvert = 2 * Math.PI / 360; + var hfac = 1 / 360; + var sfac = 1 / this.r; + var rgb = undefined; + for (hue = 0; hue < 360; hue++) { + for (sat = 0; sat < this.r; sat++) { + x = this.centerCoordinates.x + sat * Math.sin(angleConvert * hue); + y = this.centerCoordinates.y + sat * Math.cos(angleConvert * hue); + rgb = util.HSVToRGB(hue * hfac, sat * sfac, 1); + ctx.fillStyle = 'rgb(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ')'; + ctx.fillRect(x - 0.5, y - 0.5, 2, 2); + } + } + ctx.strokeStyle = 'rgba(0,0,0,1)'; + ctx.circle(this.centerCoordinates.x, this.centerCoordinates.y, this.r); + ctx.stroke(); - this.beginPath(); - this.moveTo(x, y); - this.lineTo(xl, yl); - this.lineTo(xi, yi); - this.lineTo(xr, yr); - this.closePath(); - }; + this.hueCircle = ctx.getImageData(0, 0, w, h); + } + this.generated = true; + } + }, { + key: '_moveSelector', - /** - * Sets up the dashedLine functionality for drawing - * Original code came from http://stackoverflow.com/questions/4576724/dotted-stroke-in-canvas - * @author David Jordan - * @date 2012-08-08 - */ - CanvasRenderingContext2D.prototype.dashedLine = function (x, y, x2, y2, pattern) { - this.beginPath(); - this.moveTo(x, y); + /** + * move the selector. This is called by hammer functions. + * + * @param event + * @private + */ + value: function _moveSelector(event) { + var rect = this.colorPickerDiv.getBoundingClientRect(); + var left = event.center.x - rect.left; + var top = event.center.y - rect.top; - var patternLength = pattern.length; - var dx = x2 - x; - var dy = y2 - y; - var slope = dy / dx; - var distRemaining = Math.sqrt(dx * dx + dy * dy); - var patternIndex = 0; - var draw = true; - var xStep = 0; - var dashLength = pattern[0]; + var centerY = 0.5 * this.colorPickerDiv.clientHeight; + var centerX = 0.5 * this.colorPickerDiv.clientWidth; - while (distRemaining >= 0.1) { - dashLength = pattern[patternIndex++ % patternLength]; - if (dashLength > distRemaining) { - dashLength = distRemaining; - } + var x = left - centerX; + var y = top - centerY; - xStep = Math.sqrt(dashLength * dashLength / (1 + slope * slope)); - xStep = dx < 0 ? -xStep : xStep; - x += xStep; - y += slope * xStep; + var angle = Math.atan2(x, y); + var radius = 0.98 * Math.min(Math.sqrt(x * x + y * y), centerX); - if (draw === true) { - this.lineTo(x, y); - } else { - this.moveTo(x, y); - } + var newTop = Math.cos(angle) * radius + centerY; + var newLeft = Math.sin(angle) * radius + centerX; - distRemaining -= dashLength; - draw = !draw; - } - }; - } + this.colorPickerSelector.style.top = newTop - 0.5 * this.colorPickerSelector.clientHeight + 'px'; + this.colorPickerSelector.style.left = newLeft - 0.5 * this.colorPickerSelector.clientWidth + 'px'; -/***/ }, -/* 77 */ -/***/ function(module, exports, __webpack_require__) { + // set color + var h = angle / (2 * Math.PI); + h = h < 0 ? h + 1 : h; + var s = radius / this.r; + var hsv = util.RGBToHSV(this.color.r, this.color.g, this.color.b); + hsv.h = h; + hsv.s = s; + var rgba = util.HSVToRGB(hsv.h, hsv.s, hsv.v); + rgba['a'] = this.color.a; + this.color = rgba; - // English - 'use strict'; + // update previews + this.initialColorDiv.style.backgroundColor = 'rgba(' + this.initialColor.r + ',' + this.initialColor.g + ',' + this.initialColor.b + ',' + this.initialColor.a + ')'; + this.newColorDiv.style.backgroundColor = 'rgba(' + this.color.r + ',' + this.color.g + ',' + this.color.b + ',' + this.color.a + ')'; + } + }]); - exports['en'] = { - edit: 'Edit', - del: 'Delete selected', - back: 'Back', - addNode: 'Add Node', - addEdge: 'Add Edge', - editNode: 'Edit Node', - editEdge: 'Edit Edge', - addDescription: 'Click in an empty space to place a new node.', - edgeDescription: 'Click on a node and drag the edge to another node to connect them.', - editEdgeDescription: 'Click on the control points and drag them to a node to connect to it.', - createEdgeError: 'Cannot link edges to a cluster.', - deleteClusterError: 'Clusters cannot be deleted.', - editClusterError: 'Clusters cannot be edited.' - }; - exports['en_EN'] = exports['en']; - exports['en_US'] = exports['en']; + return ColorPicker; + })(); - // Dutch - exports['nl'] = { - edit: 'Wijzigen', - del: 'Selectie verwijderen', - back: 'Terug', - addNode: 'Node toevoegen', - addEdge: 'Link toevoegen', - editNode: 'Node wijzigen', - editEdge: 'Link wijzigen', - addDescription: 'Klik op een leeg gebied om een nieuwe node te maken.', - edgeDescription: 'Klik op een node en sleep de link naar een andere node om ze te verbinden.', - editEdgeDescription: 'Klik op de verbindingspunten en sleep ze naar een node om daarmee te verbinden.', - createEdgeError: 'Kan geen link maken naar een cluster.', - deleteClusterError: 'Clusters kunnen niet worden verwijderd.', - editClusterError: 'Clusters kunnen niet worden aangepast.' - }; - exports['nl_NL'] = exports['nl']; - exports['nl_BE'] = exports['nl']; + exports['default'] = ColorPicker; + module.exports = exports['default']; /***/ }, -/* 78 */ +/* 74 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -34739,7 +34517,7 @@ 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__(79); + var _sharedLabel = __webpack_require__(75); var _sharedLabel2 = _interopRequireDefault(_sharedLabel); @@ -35228,7 +35006,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 79 */ +/* 75 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -35543,7 +35321,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 80 */ +/* 76 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -35558,7 +35336,7 @@ 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__(79); + var _sharedLabel = __webpack_require__(75); var _sharedLabel2 = _interopRequireDefault(_sharedLabel); @@ -36093,7 +35871,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 81 */ +/* 77 */ /***/ function(module, exports, __webpack_require__) { "use strict"; @@ -36579,7 +36357,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports["default"]; /***/ }, -/* 82 */ +/* 78 */ /***/ function(module, exports, __webpack_require__) { "use strict"; @@ -36674,7 +36452,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports["default"]; /***/ }, -/* 83 */ +/* 79 */ /***/ function(module, exports, __webpack_require__) { "use strict"; @@ -36765,7 +36543,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports["default"]; /***/ }, -/* 84 */ +/* 80 */ /***/ function(module, exports, __webpack_require__) { "use strict"; @@ -36876,7 +36654,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports["default"]; /***/ }, -/* 85 */ +/* 81 */ /***/ function(module, exports, __webpack_require__) { "use strict"; @@ -36997,7 +36775,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports["default"]; /***/ }, -/* 86 */ +/* 82 */ /***/ function(module, exports, __webpack_require__) { "use strict"; @@ -37066,7 +36844,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports["default"]; /***/ }, -/* 87 */ +/* 83 */ /***/ function(module, exports, __webpack_require__) { "use strict"; @@ -37087,7 +36865,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 _BarnesHutSolver2 = __webpack_require__(81); + var _BarnesHutSolver2 = __webpack_require__(77); var _BarnesHutSolver3 = _interopRequireDefault(_BarnesHutSolver2); @@ -37137,7 +36915,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports["default"]; /***/ }, -/* 88 */ +/* 84 */ /***/ function(module, exports, __webpack_require__) { "use strict"; @@ -37158,7 +36936,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 _CentralGravitySolver2 = __webpack_require__(86); + var _CentralGravitySolver2 = __webpack_require__(82); var _CentralGravitySolver3 = _interopRequireDefault(_CentralGravitySolver2); @@ -37195,7 +36973,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports["default"]; /***/ }, -/* 89 */ +/* 85 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -37214,7 +36992,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 _Node2 = __webpack_require__(78); + var _Node2 = __webpack_require__(74); var _Node3 = _interopRequireDefault(_Node2); @@ -37242,7 +37020,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 90 */ +/* 86 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -37258,7 +37036,7 @@ return /******/ (function(modules) { // webpackBootstrap var util = __webpack_require__(1); var Hammer = __webpack_require__(41); var hammerUtil = __webpack_require__(49); - var keycharm = __webpack_require__(62); + var keycharm = __webpack_require__(88); var NavigationHandler = (function () { function NavigationHandler(body, canvas) { @@ -37512,7 +37290,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = exports['default']; /***/ }, -/* 91 */ +/* 87 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -37638,6 +37416,241 @@ return /******/ (function(modules) { // webpackBootstrap exports['default'] = Popup; module.exports = exports['default']; +/***/ }, +/* 88 */ +/***/ function(module, exports, __webpack_require__) { + + var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;"use strict"; + /** + * Created by Alex on 11/6/2014. + */ + + // https://github.com/umdjs/umd/blob/master/returnExports.js#L40-L60 + // if the module has no dependencies, the above pattern can be simplified to + (function (root, factory) { + if (true) { + // AMD. Register as an anonymous module. + !(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); + } else if (typeof exports === 'object') { + // Node. Does not work with strict CommonJS, but + // only CommonJS-like environments that support module.exports, + // like Node. + module.exports = factory(); + } else { + // Browser globals (root is window) + root.keycharm = factory(); + } + }(this, function () { + + function keycharm(options) { + var preventDefault = options && options.preventDefault || false; + + var container = options && options.container || window; + var _exportFunctions = {}; + var _bound = {keydown:{}, keyup:{}}; + var _keys = {}; + var i; + + // a - z + for (i = 97; i <= 122; i++) {_keys[String.fromCharCode(i)] = {code:65 + (i - 97), shift: false};} + // A - Z + for (i = 65; i <= 90; i++) {_keys[String.fromCharCode(i)] = {code:i, shift: true};} + // 0 - 9 + for (i = 0; i <= 9; i++) {_keys['' + i] = {code:48 + i, shift: false};} + // F1 - F12 + for (i = 1; i <= 12; i++) {_keys['F' + i] = {code:111 + i, shift: false};} + // num0 - num9 + for (i = 0; i <= 9; i++) {_keys['num' + i] = {code:96 + i, shift: false};} + + // numpad misc + _keys['num*'] = {code:106, shift: false}; + _keys['num+'] = {code:107, shift: false}; + _keys['num-'] = {code:109, shift: false}; + _keys['num/'] = {code:111, shift: false}; + _keys['num.'] = {code:110, shift: false}; + // arrows + _keys['left'] = {code:37, shift: false}; + _keys['up'] = {code:38, shift: false}; + _keys['right'] = {code:39, shift: false}; + _keys['down'] = {code:40, shift: false}; + // extra keys + _keys['space'] = {code:32, shift: false}; + _keys['enter'] = {code:13, shift: false}; + _keys['shift'] = {code:16, shift: undefined}; + _keys['esc'] = {code:27, shift: false}; + _keys['backspace'] = {code:8, shift: false}; + _keys['tab'] = {code:9, shift: false}; + _keys['ctrl'] = {code:17, shift: false}; + _keys['alt'] = {code:18, shift: false}; + _keys['delete'] = {code:46, shift: false}; + _keys['pageup'] = {code:33, shift: false}; + _keys['pagedown'] = {code:34, shift: false}; + // symbols + _keys['='] = {code:187, shift: false}; + _keys['-'] = {code:189, shift: false}; + _keys[']'] = {code:221, shift: false}; + _keys['['] = {code:219, shift: false}; + + + + var down = function(event) {handleEvent(event,'keydown');}; + var up = function(event) {handleEvent(event,'keyup');}; + + // handle the actualy bound key with the event + var handleEvent = function(event,type) { + if (_bound[type][event.keyCode] !== undefined) { + var bound = _bound[type][event.keyCode]; + for (var i = 0; i < bound.length; i++) { + if (bound[i].shift === undefined) { + bound[i].fn(event); + } + else if (bound[i].shift == true && event.shiftKey == true) { + bound[i].fn(event); + } + else if (bound[i].shift == false && event.shiftKey == false) { + bound[i].fn(event); + } + } + + if (preventDefault == true) { + event.preventDefault(); + } + } + }; + + // bind a key to a callback + _exportFunctions.bind = function(key, callback, type) { + if (type === undefined) { + type = 'keydown'; + } + if (_keys[key] === undefined) { + throw new Error("unsupported key: " + key); + } + if (_bound[type][_keys[key].code] === undefined) { + _bound[type][_keys[key].code] = []; + } + _bound[type][_keys[key].code].push({fn:callback, shift:_keys[key].shift}); + }; + + + // bind all keys to a call back (demo purposes) + _exportFunctions.bindAll = function(callback, type) { + if (type === undefined) { + type = 'keydown'; + } + for (var key in _keys) { + if (_keys.hasOwnProperty(key)) { + _exportFunctions.bind(key,callback,type); + } + } + }; + + // get the key label from an event + _exportFunctions.getKey = function(event) { + for (var key in _keys) { + if (_keys.hasOwnProperty(key)) { + if (event.shiftKey == true && _keys[key].shift == true && event.keyCode == _keys[key].code) { + return key; + } + else if (event.shiftKey == false && _keys[key].shift == false && event.keyCode == _keys[key].code) { + return key; + } + else if (event.keyCode == _keys[key].code && key == 'shift') { + return key; + } + } + } + return "unknown key, currently not supported"; + }; + + // unbind either a specific callback from a key or all of them (by leaving callback undefined) + _exportFunctions.unbind = function(key, callback, type) { + if (type === undefined) { + type = 'keydown'; + } + if (_keys[key] === undefined) { + throw new Error("unsupported key: " + key); + } + if (callback !== undefined) { + var newBindings = []; + var bound = _bound[type][_keys[key].code]; + if (bound !== undefined) { + for (var i = 0; i < bound.length; i++) { + if (!(bound[i].fn == callback && bound[i].shift == _keys[key].shift)) { + newBindings.push(_bound[type][_keys[key].code][i]); + } + } + } + _bound[type][_keys[key].code] = newBindings; + } + else { + _bound[type][_keys[key].code] = []; + } + }; + + // reset all bound variables. + _exportFunctions.reset = function() { + _bound = {keydown:{}, keyup:{}}; + }; + + // unbind all listeners and reset all variables. + _exportFunctions.destroy = function() { + _bound = {keydown:{}, keyup:{}}; + container.removeEventListener('keydown', down, true); + container.removeEventListener('keyup', up, true); + }; + + // create listeners. + container.addEventListener('keydown',down,true); + container.addEventListener('keyup',up,true); + + // return the public functions. + return _exportFunctions; + } + + return keycharm; + })); + + + + +/***/ }, +/* 89 */ +/***/ function(module, exports, __webpack_require__) { + + function webpackContext(req) { + throw new Error("Cannot find module '" + req + "'."); + } + webpackContext.keys = function() { return []; }; + webpackContext.resolve = webpackContext; + module.exports = webpackContext; + webpackContext.id = 89; + + +/***/ }, +/* 90 */ +/***/ function(module, exports, __webpack_require__) { + + module.exports = function(module) { + if(!module.webpackPolyfill) { + module.deprecate = function() {}; + module.paths = []; + // module.parent = undefined by default + module.children = []; + module.webpackPolyfill = 1; + } + return module; + } + + +/***/ }, +/* 91 */ +/***/ function(module, exports, __webpack_require__) { + + /* WEBPACK VAR INJECTION */(function(__webpack_amd_options__) {module.exports = __webpack_amd_options__; + + /* WEBPACK VAR INJECTION */}.call(exports, {})) + /***/ }, /* 92 */ /***/ function(module, exports, __webpack_require__) { diff --git a/docs/network/interaction.html b/docs/network/interaction.html index 2f834dcc..6b08d21b 100644 --- a/docs/network/interaction.html +++ b/docs/network/interaction.html @@ -88,7 +88,7 @@ var options = { dragView: true, hideEdgesOnDrag: false, hideNodesOnDrag: false, - hoverEnabled: false, + hover: false, keyboard: { enabled: false, speed: {x: 10, y: 10, zoom: 0.02}, @@ -123,7 +123,7 @@ network.setOptions(options); dragView Boolean true When true, the view can be dragged around by the user. hideEdgesOnDrag Boolean false When true, the edges are not drawn when dragging the view. This can greatly speed up responsiveness on dragging, improving user experience. hideNodesOnDrag Boolean false When true, the nodes are not drawn when dragging the view. This can greatly speed up responsiveness on dragging, improving user experience. - hoverEnabled Boolean false When true, the nodes use their hover colors when the mouse moves over them. + hover Boolean false When true, the nodes use their hover colors when the mouse moves over them. keyboard Object or Boolean Object When true, the keyboard shortcuts are enabled with the default settings. For further customization, you can supply an object. keyboard.enabled Boolean false Toggle the usage of the keyboard shortcuts. If this option is not defined, it is set to true if any of the properties in this object are defined. keyboard.speed.x Number 1 The speed at which the view moves in the x direction on pressing a key or pressing a navigation button. diff --git a/examples/network/04_shapes.html b/examples/network/04_shapes.html deleted file mode 100644 index 22e5b107..00000000 --- a/examples/network/04_shapes.html +++ /dev/null @@ -1,76 +0,0 @@ - - - - Network | Shapes - - - - - - - - - - - -

- Nodes can have all sorts of shapes, colors and sizes. Edges can be drawn solid or dashed, and can have one or multiple arrows. -

-
- -
- - diff --git a/examples/network/06_groups.html b/examples/network/06_groups.html deleted file mode 100644 index 88179661..00000000 --- a/examples/network/06_groups.html +++ /dev/null @@ -1,171 +0,0 @@ - - - - Network | Groups - - - - - - - - - - - -

- Apply styling to groups of nodes. -

-
- Number of groups: - Number of nodes per group: - -
-
-
- - - diff --git a/examples/network/01_basic_usage.html b/examples/network/categories/01_basic_usage.html similarity index 100% rename from examples/network/01_basic_usage.html rename to examples/network/categories/01_basic_usage.html diff --git a/examples/network/02_random_nodes.html b/examples/network/categories/02_random_nodes.html similarity index 100% rename from examples/network/02_random_nodes.html rename to examples/network/categories/02_random_nodes.html diff --git a/examples/network/05_social_network.html b/examples/network/categories/05_social_network.html similarity index 100% rename from examples/network/05_social_network.html rename to examples/network/categories/05_social_network.html diff --git a/examples/network/07_selections.html b/examples/network/categories/07_selections.html similarity index 100% rename from examples/network/07_selections.html rename to examples/network/categories/07_selections.html diff --git a/examples/network/09_scaling.html b/examples/network/categories/09_scaling.html similarity index 100% rename from examples/network/09_scaling.html rename to examples/network/categories/09_scaling.html diff --git a/examples/network/10_multiline_text.html b/examples/network/categories/10_multiline_text.html similarity index 100% rename from examples/network/10_multiline_text.html rename to examples/network/categories/10_multiline_text.html diff --git a/examples/network/11_styling.html b/examples/network/categories/11_styling.html similarity index 100% rename from examples/network/11_styling.html rename to examples/network/categories/11_styling.html diff --git a/examples/network/12_arrows.html b/examples/network/categories/12_arrows.html similarity index 100% rename from examples/network/12_arrows.html rename to examples/network/categories/12_arrows.html diff --git a/examples/network/13_dashed_lines.html b/examples/network/categories/13_dashed_lines.html similarity index 100% rename from examples/network/13_dashed_lines.html rename to examples/network/categories/13_dashed_lines.html diff --git a/examples/network/14_dot_language.html b/examples/network/categories/14_dot_language.html similarity index 100% rename from examples/network/14_dot_language.html rename to examples/network/categories/14_dot_language.html diff --git a/examples/network/15_dot_language_playground.html b/examples/network/categories/15_dot_language_playground.html similarity index 100% rename from examples/network/15_dot_language_playground.html rename to examples/network/categories/15_dot_language_playground.html diff --git a/examples/network/16_dynamic_data.html b/examples/network/categories/16_dynamic_data.html similarity index 100% rename from examples/network/16_dynamic_data.html rename to examples/network/categories/16_dynamic_data.html diff --git a/examples/network/17_network_info.html b/examples/network/categories/17_network_info.html similarity index 100% rename from examples/network/17_network_info.html rename to examples/network/categories/17_network_info.html diff --git a/examples/network/18_fully_random_nodes_clustering.html b/examples/network/categories/18_fully_random_nodes_clustering.html similarity index 100% rename from examples/network/18_fully_random_nodes_clustering.html rename to examples/network/categories/18_fully_random_nodes_clustering.html diff --git a/examples/network/19_scale_free_graph_clustering.html b/examples/network/categories/19_scale_free_graph_clustering.html similarity index 100% rename from examples/network/19_scale_free_graph_clustering.html rename to examples/network/categories/19_scale_free_graph_clustering.html diff --git a/examples/network/20_navigation.html b/examples/network/categories/20_navigation.html similarity index 100% rename from examples/network/20_navigation.html rename to examples/network/categories/20_navigation.html diff --git a/examples/network/22_les_miserables.html b/examples/network/categories/22_les_miserables.html similarity index 100% rename from examples/network/22_les_miserables.html rename to examples/network/categories/22_les_miserables.html diff --git a/examples/network/23_hierarchical_layout.html b/examples/network/categories/23_hierarchical_layout.html similarity index 100% rename from examples/network/23_hierarchical_layout.html rename to examples/network/categories/23_hierarchical_layout.html diff --git a/examples/network/24_hierarchical_layout_userdefined.html b/examples/network/categories/24_hierarchical_layout_userdefined.html similarity index 100% rename from examples/network/24_hierarchical_layout_userdefined.html rename to examples/network/categories/24_hierarchical_layout_userdefined.html diff --git a/examples/network/25_physics_configuration.html b/examples/network/categories/25_physics_configuration.html similarity index 100% rename from examples/network/25_physics_configuration.html rename to examples/network/categories/25_physics_configuration.html diff --git a/examples/network/26_staticSmoothCurves.html b/examples/network/categories/26_staticSmoothCurves.html similarity index 100% rename from examples/network/26_staticSmoothCurves.html rename to examples/network/categories/26_staticSmoothCurves.html diff --git a/examples/network/27_world_cup_network.html b/examples/network/categories/27_world_cup_network.html similarity index 100% rename from examples/network/27_world_cup_network.html rename to examples/network/categories/27_world_cup_network.html diff --git a/examples/network/28_world_cup_network_performance.html b/examples/network/categories/28_world_cup_network_performance.html similarity index 100% rename from examples/network/28_world_cup_network_performance.html rename to examples/network/categories/28_world_cup_network_performance.html diff --git a/examples/network/29_neighbourhood_highlight.html b/examples/network/categories/29_neighbourhood_highlight.html similarity index 100% rename from examples/network/29_neighbourhood_highlight.html rename to examples/network/categories/29_neighbourhood_highlight.html diff --git a/examples/network/30_importing_from_gephi.html b/examples/network/categories/30_importing_from_gephi.html similarity index 100% rename from examples/network/30_importing_from_gephi.html rename to examples/network/categories/30_importing_from_gephi.html diff --git a/examples/network/31_manipulation_and_localization.html b/examples/network/categories/31_manipulation_and_localization.html similarity index 100% rename from examples/network/31_manipulation_and_localization.html rename to examples/network/categories/31_manipulation_and_localization.html diff --git a/examples/network/32_hierarchicaLayoutMethods.html b/examples/network/categories/32_hierarchicaLayoutMethods.html similarity index 100% rename from examples/network/32_hierarchicaLayoutMethods.html rename to examples/network/categories/32_hierarchicaLayoutMethods.html diff --git a/examples/network/33_animation.html b/examples/network/categories/33_animation.html similarity index 100% rename from examples/network/33_animation.html rename to examples/network/categories/33_animation.html diff --git a/examples/network/35_label_stroke.html b/examples/network/categories/35_label_stroke.html similarity index 100% rename from examples/network/35_label_stroke.html rename to examples/network/categories/35_label_stroke.html diff --git a/examples/network/37_label_alignment.html b/examples/network/categories/37_label_alignment.html similarity index 100% rename from examples/network/37_label_alignment.html rename to examples/network/categories/37_label_alignment.html diff --git a/examples/network/39_newClustering.html b/examples/network/categories/39_newClustering.html similarity index 100% rename from examples/network/39_newClustering.html rename to examples/network/categories/39_newClustering.html diff --git a/examples/network/categories/edgeStyles/smooth.html b/examples/network/categories/edgeStyles/smooth.html new file mode 100644 index 00000000..9854a61d --- /dev/null +++ b/examples/network/categories/edgeStyles/smooth.html @@ -0,0 +1,115 @@ + + + + Network | Static smooth curves + + + + + + + + + + +

Static smooth curves

+
+ All the smooth curves in the examples so far have been using dynamic smooth curves. This means that each curve has a + support node which takes part in the physics simulation. For large networks or dense clusters, this may not be the ideal + solution. To solve this, static smooth curves have been added. The static smooth curves are based only on the positions of the connected + nodes. There are multiple ways to determine the way this curve is drawn. This example shows the effect of the different + types.

+ Drag the nodes around each other to see how the smooth curves are drawn for each setting. For animated system, we + recommend only the continuous mode. In the next example you can see the effect of these methods on a large network. Keep in mind + that the direction (the from and to) of the curve matters. +

+
+ +

+ Smooth curve type: + +

+

+ Roundness (0..1): (0.5 is max roundness for continuous, 1.0 for the others) +

+ +
+ + + + + diff --git a/examples/network/categories/edgeStyles/styles.html b/examples/network/categories/edgeStyles/styles.html new file mode 100644 index 00000000..278b938e --- /dev/null +++ b/examples/network/categories/edgeStyles/styles.html @@ -0,0 +1,55 @@ + + + + Network | Basic usage + + + + + + + + +

+ Create a simple network with some nodes and edges. +

+ +
+ + + + + + diff --git a/examples/network/index.html b/examples/network/categories/index.html similarity index 100% rename from examples/network/index.html rename to examples/network/categories/index.html diff --git a/examples/network/36_HTML_in_Nodes.html b/examples/network/categories/nodeStyles/HTML_in_Nodes.html similarity index 88% rename from examples/network/36_HTML_in_Nodes.html rename to examples/network/categories/nodeStyles/HTML_in_Nodes.html index 9ab39801..c3a8713e 100644 --- a/examples/network/36_HTML_in_Nodes.html +++ b/examples/network/categories/nodeStyles/HTML_in_Nodes.html @@ -16,8 +16,8 @@ - - + + - + diff --git a/examples/network/34_circular_images.html b/examples/network/categories/nodeStyles/circular_images.html similarity index 91% rename from examples/network/34_circular_images.html rename to examples/network/categories/nodeStyles/circular_images.html index c4a48f29..a4399f9b 100644 --- a/examples/network/34_circular_images.html +++ b/examples/network/categories/nodeStyles/circular_images.html @@ -15,8 +15,8 @@ } - - + + - + +
diff --git a/examples/network/categories/nodeStyles/colors.html b/examples/network/categories/nodeStyles/colors.html new file mode 100644 index 00000000..33adbde5 --- /dev/null +++ b/examples/network/categories/nodeStyles/colors.html @@ -0,0 +1,57 @@ + + + + Network | Basic usage + + + + + + + + +

+ Create a simple network with some nodes and edges. +

+ +
+ + + + + + diff --git a/examples/network/categories/nodeStyles/customGroups.html b/examples/network/categories/nodeStyles/customGroups.html new file mode 100644 index 00000000..681ac270 --- /dev/null +++ b/examples/network/categories/nodeStyles/customGroups.html @@ -0,0 +1,134 @@ + + + + Network | Custom Groups + + + + + + + + + + + + +
+ + + diff --git a/examples/network/categories/nodeStyles/groups.html b/examples/network/categories/nodeStyles/groups.html new file mode 100644 index 00000000..9ab6507e --- /dev/null +++ b/examples/network/categories/nodeStyles/groups.html @@ -0,0 +1,119 @@ + + + + Network | Groups + + + + + + + + + + + +
+ + + diff --git a/examples/network/38_node_as_icon.html b/examples/network/categories/nodeStyles/icons.html similarity index 94% rename from examples/network/38_node_as_icon.html rename to examples/network/categories/nodeStyles/icons.html index 6877c9ee..e9d1aca7 100644 --- a/examples/network/38_node_as_icon.html +++ b/examples/network/categories/nodeStyles/icons.html @@ -5,8 +5,8 @@ Network | node as icon - - + + @@ -168,7 +168,7 @@ var networkIO = new vis.Network(containerIO, dataIO, optionsIO); } - + diff --git a/examples/network/03_images.html b/examples/network/categories/nodeStyles/images.html similarity index 70% rename from examples/network/03_images.html rename to examples/network/categories/nodeStyles/images.html index b9428d75..79ca6598 100644 --- a/examples/network/03_images.html +++ b/examples/network/categories/nodeStyles/images.html @@ -11,17 +11,17 @@ } - - + + - + diff --git a/examples/network/categories/nodeStyles/shapes.html b/examples/network/categories/nodeStyles/shapes.html new file mode 100644 index 00000000..761fcbfb --- /dev/null +++ b/examples/network/categories/nodeStyles/shapes.html @@ -0,0 +1,72 @@ + + + + Network | Shapes + + + + + + + + + + + +

+ Nodes can have all sorts of shapes. Note the exception where the nodes with text inside and the text type's size are determined by the font size, not the node size. +

+
+ +
+ + diff --git a/lib/network/modules/InteractionHandler.js b/lib/network/modules/InteractionHandler.js index 90bc6354..bf610b14 100644 --- a/lib/network/modules/InteractionHandler.js +++ b/lib/network/modules/InteractionHandler.js @@ -27,7 +27,6 @@ class InteractionHandler { this.touchTime = 0; this.drag = {}; this.pinch = {}; - this.hoverObj = {nodes:{},edges:{}}; this.popup = undefined; this.popupObj = undefined; this.popupTimer = undefined; @@ -38,7 +37,7 @@ class InteractionHandler { this.defaultOptions = { dragNodes:true, dragView: true, - hoverEnabled: false, + hover: false, keyboard: { enabled: false, speed: {x: 10, y: 10, zoom: 0.02}, @@ -502,34 +501,13 @@ class InteractionHandler { /** * Adding hover highlights */ - if (this.options.hoverEnabled === true) { - // removing all hover highlights - for (let edgeId in this.hoverObj.edges) { - if (this.hoverObj.edges.hasOwnProperty(edgeId)) { - this.hoverObj.edges[edgeId].hover = false; - delete this.hoverObj.edges[edgeId]; - } - } - + if (this.options.hover === true) { // adding hover highlights let obj = this.selectionHandler.getNodeAt(pointer); if (obj === undefined) { obj = this.selectionHandler.getEdgeAt(pointer); } - if (obj != undefined) { - this.selectionHandler.hoverObject(obj); - } - - // removing all node hover highlights except for the selected one. - for (let nodeId in this.hoverObj.nodes) { - if (this.hoverObj.nodes.hasOwnProperty(nodeId)) { - if (obj instanceof Node && obj.id != nodeId || obj instanceof Edge || obj === undefined) { - this.selectionHandler.blurObject(this.hoverObj.nodes[nodeId]); - delete this.hoverObj.nodes[nodeId]; - } - } - } - this.body.emitter.emit('_requestRedraw'); + this.selectionHandler.hoverObject(obj); } } diff --git a/lib/network/modules/SelectionHandler.js b/lib/network/modules/SelectionHandler.js index df256b39..673d9130 100644 --- a/lib/network/modules/SelectionHandler.js +++ b/lib/network/modules/SelectionHandler.js @@ -1,4 +1,5 @@ let Node = require("./components/Node"); +let Edge = require("./components/Edge"); let util = require('../../util'); class SelectionHandler { @@ -6,6 +7,7 @@ class SelectionHandler { this.body = body; this.canvas = canvas; this.selectionObj = {nodes: [], edges: []}; + this.hoverObj = {nodes:{},edges:{}}; this.options = {}; this.defaultOptions = { @@ -485,15 +487,46 @@ class SelectionHandler { * @private */ hoverObject(object) { - if (object.hover === false) { - object.hover = true; - this._addToHover(object); - if (object instanceof Node) { - this.body.emitter.emit("hoverNode",{node:object.id}); + let hoverChanged = false; + // remove all node hover highlights + for (let 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]; + } + } + } + + //// removing all edge hover highlights + for (let edgeId in this.hoverObj.edges) { + if (this.hoverObj.edges.hasOwnProperty(edgeId)) { + this.hoverObj.edges[edgeId].hover = false; + delete this.hoverObj.edges[edgeId]; } } - if (object instanceof Node) { - this._hoverConnectedEdges(object); + + 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.selectConnectedEdges === true) { + this._hoverConnectedEdges(object); + } + } + + if (hoverChanged === true) { + this.body.emitter.emit('_requestRedraw'); } } diff --git a/lib/network/modules/View.js b/lib/network/modules/View.js index d3e3242a..70c426a4 100644 --- a/lib/network/modules/View.js +++ b/lib/network/modules/View.js @@ -44,10 +44,10 @@ class View { if (maxX < (node.shape.boundingBox.right)) { maxX = node.shape.boundingBox.right; } - if (minY > (node.shape.boundingBox.bottom)) { + if (minY > (node.shape.boundingBox.top)) { minY = node.shape.boundingBox.top; } // top is negative, bottom is positive - if (maxY < (node.shape.boundingBox.top)) { + if (maxY < (node.shape.boundingBox.bottom)) { maxY = node.shape.boundingBox.bottom; } // top is negative, bottom is positive } @@ -62,10 +62,10 @@ class View { if (maxX < (node.shape.boundingBox.right)) { maxX = node.shape.boundingBox.right; } - if (minY > (node.shape.boundingBox.bottom)) { + if (minY > (node.shape.boundingBox.top)) { minY = node.shape.boundingBox.top; } // top is negative, bottom is positive - if (maxY < (node.shape.boundingBox.top)) { + if (maxY < (node.shape.boundingBox.bottom)) { maxY = node.shape.boundingBox.bottom; } // top is negative, bottom is positive } diff --git a/lib/network/options.js b/lib/network/options.js index 297aba6f..a05e72a6 100644 --- a/lib/network/options.js +++ b/lib/network/options.js @@ -99,7 +99,7 @@ let allOptions = { dragView: {boolean}, hideEdgesOnDrag: {boolean}, hideNodesOnDrag: {boolean}, - hoverEnabled: {boolean}, + hover: {boolean}, keyboard: { enabled: {boolean}, speed: {x: {number}, y: {number}, zoom: {number}, __type__: {object}}, @@ -404,7 +404,7 @@ let configureOptions = { dragView: true, hideEdgesOnDrag: false, hideNodesOnDrag: false, - hoverEnabled: 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]}, diff --git a/lib/timeline/component/GraphGroup.js b/lib/timeline/component/GraphGroup.js index 3cc78c4e..5add18ec 100644 --- a/lib/timeline/component/GraphGroup.js +++ b/lib/timeline/component/GraphGroup.js @@ -185,19 +185,19 @@ GraphGroup.prototype.getLegend = function(iconWidth, iconHeight) { var svg = document.createElementNS('http://www.w3.org/2000/svg',"svg"); this.drawIcon(0,0.5*iconHeight,[],svg,iconWidth,iconHeight); return {icon: svg, label: this.content, orientation:this.options.yAxisOrientation}; -} +}; GraphGroup.prototype.getYRange = function(groupData) { return this.type.getYRange(groupData); -} +}; GraphGroup.prototype.getData = function(groupData) { return this.type.getData(groupData); -} +}; GraphGroup.prototype.draw = function(dataset, group, framework) { this.type.draw(dataset, group, framework); -} +}; module.exports = GraphGroup; diff --git a/lib/util.js b/lib/util.js index 0c655a1c..d6f74e8a 100644 --- a/lib/util.js +++ b/lib/util.js @@ -934,8 +934,9 @@ exports.RGBToHex = function(red,green,blue) { exports.parseColor = function(color) { var c; if (exports.isString(color) === true) { + console.log(color, exports.isValidRGB(color)) if (exports.isValidRGB(color) === true) { - var rgb = color.substr(4).substr(0,color.length-5).split(','); + 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]); } if (exports.isValidHex(color) === true) {