diff --git a/dist/vis.js b/dist/vis.js index f4f77e44..1d82bc58 100644 --- a/dist/vis.js +++ b/dist/vis.js @@ -139,8 +139,7 @@ return /******/ (function(modules) { // webpackBootstrap // Network exports.Network = __webpack_require__(53); exports.network = { - Groups: __webpack_require__(57), - Images: __webpack_require__(58), + Images: __webpack_require__(57), dotparser: __webpack_require__(55), gephiParser: __webpack_require__(56) }; @@ -23303,32 +23302,33 @@ return /******/ (function(modules) { // webpackBootstrap var DataView = __webpack_require__(9); var dotparser = __webpack_require__(55); var gephiParser = __webpack_require__(56); - var Groups = __webpack_require__(57); - var Images = __webpack_require__(58); + var Images = __webpack_require__(57); var Activator = __webpack_require__(38); - var locales = __webpack_require__(59); + var locales = __webpack_require__(58); + var Groups = _interopRequire(__webpack_require__(59)); + var NodesHandler = _interopRequire(__webpack_require__(60)); var EdgesHandler = _interopRequire(__webpack_require__(80)); - var PhysicsEngine = _interopRequire(__webpack_require__(87)); + var PhysicsEngine = _interopRequire(__webpack_require__(82)); - var ClusterEngine = _interopRequire(__webpack_require__(94)); + var ClusterEngine = _interopRequire(__webpack_require__(89)); - var CanvasRenderer = _interopRequire(__webpack_require__(96)); + var CanvasRenderer = _interopRequire(__webpack_require__(91)); - var Canvas = _interopRequire(__webpack_require__(97)); + var Canvas = _interopRequire(__webpack_require__(92)); - var View = _interopRequire(__webpack_require__(98)); + var View = _interopRequire(__webpack_require__(93)); - var InteractionHandler = _interopRequire(__webpack_require__(99)); + var InteractionHandler = _interopRequire(__webpack_require__(94)); - var SelectionHandler = _interopRequire(__webpack_require__(100)); + var SelectionHandler = _interopRequire(__webpack_require__(97)); - var LayoutEngine = _interopRequire(__webpack_require__(101)); + var LayoutEngine = _interopRequire(__webpack_require__(98)); /** * @constructor Network @@ -23354,9 +23354,7 @@ return /******/ (function(modules) { // webpackBootstrap initiallyVisible: false }, locale: "en", - locales: locales, - useDefaultGroups: true - }; + locales: locales }; // containers for nodes and edges this.body = { @@ -23402,11 +23400,11 @@ return /******/ (function(modules) { // webpackBootstrap this.bindEventListeners(); // setting up all modules - var groups = new Groups(); // object with groups var images = new Images(function () { return _this.body.emitter.emit("_requestRedraw"); }); // object with images + this.groups = new Groups(); // object with groups this.canvas = new Canvas(this.body); // DOM handler this.selectionHandler = new SelectionHandler(this.body, this.canvas); // Selection handler this.interactionHandler = new InteractionHandler(this.body, this.canvas, this.selectionHandler); // Interaction handler handles all the hammer bindings (that are bound by canvas), key @@ -23416,8 +23414,8 @@ return /******/ (function(modules) { // webpackBootstrap this.layoutEngine = new LayoutEngine(this.body); this.clustering = new ClusterEngine(this.body); // clustering api - this.nodesHandler = new NodesHandler(this.body, images, groups, this.layoutEngine); // Handle adding, deleting and updating of nodes as well as global options - this.edgesHandler = new EdgesHandler(this.body, images, groups); // Handle adding, deleting and updating of edges as well as global options + this.nodesHandler = new NodesHandler(this.body, images, this.groups, this.layoutEngine); // Handle adding, deleting and updating of nodes as well as global options + this.edgesHandler = new EdgesHandler(this.body, images, this.groups); // Handle adding, deleting and updating of edges as well as global options // create the DOM elements this.canvas.create(); @@ -23472,8 +23470,6 @@ return /******/ (function(modules) { // webpackBootstrap // call the dataUpdated event because the only difference between the two is the updating of the indices _this.body.emitter.emit("_dataUpdated"); - // start simulation (can be called safely, even if already running) - _this.body.emitter.emit("startSimulation"); console.log("_dataChanged took:", new Date().valueOf() - t0); }); @@ -23483,11 +23479,9 @@ return /******/ (function(modules) { // webpackBootstrap // update values _this._updateValueRange(_this.body.nodes); _this._updateValueRange(_this.body.edges); - // update edges - _this._reconnectEdges(); - _this._markAllEdgesAsDirty(); // start simulation (can be called safely, even if already running) _this.body.emitter.emit("startSimulation"); + console.log("_dataUpdated took:", new Date().valueOf() - t0); }); }; @@ -23550,14 +23544,6 @@ return /******/ (function(modules) { // webpackBootstrap */ Network.prototype.setOptions = function (options) { if (options !== undefined) { - //var fields = ['nodes','edges','smoothCurves','hierarchicalLayout','navigation', - // 'keyboard','dataManipulation','onAdd','onEdit','onEditEdge','onConnect','onDelete','clickToUse' - //]; - // extend all but the values in fields - //util.selectiveNotDeepExtend(fields,this.constants, options); - //util.selectiveNotDeepExtend(['color'],this.constants.nodes, options.nodes); - //util.selectiveNotDeepExtend(['color','length'],this.constants.edges, options.edges); - //this.groups.useDefaultGroups = this.constants.useDefaultGroups; // the hierarchical system can adapt the edges and the physics to it's own options because not all combinations work with the hierarichical system. @@ -23573,32 +23559,8 @@ return /******/ (function(modules) { // webpackBootstrap this.selectionHandler.setOptions(options.selection); this.clustering.setOptions(options.clustering); - //util.mergeOptions(this.constants, options,'smoothCurves'); - //util.mergeOptions(this.constants, options,'hierarchicalLayout'); - //util.mergeOptions(this.constants, options,'clustering'); - //util.mergeOptions(this.constants, options,'navigation'); - //util.mergeOptions(this.constants, options,'keyboard'); - //util.mergeOptions(this.constants, options,'dataManipulation'); - - - //if (options.dataManipulation) { - // this.editMode = this.constants.dataManipulation.initiallyVisible; - //} - - - //// TODO: work out these options and document them // // - // - //if (options.groups) { - // for (var groupname in options.groups) { - // if (options.groups.hasOwnProperty(groupname)) { - // var group = options.groups[groupname]; - // this.groups.add(groupname, group); - // } - // } - //} - // //if (options.tooltip) { // for (prop in options.tooltip) { // if (options.tooltip.hasOwnProperty(prop)) { @@ -25024,117 +24986,6 @@ return /******/ (function(modules) { // webpackBootstrap "use strict"; - var util = __webpack_require__(1); - - /** - * @class Groups - * This class can store groups and options specific for groups. - */ - function Groups() { - this.clear(); - this.defaultIndex = 0; - this.groupsArray = []; - this.groupIndex = 0; - this.useDefaultGroups = true; - } - - - /** - * default constants for group colors - */ - Groups.DEFAULT = [{ 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 - - { border: "#990000", background: "#EE0000", highlight: { border: "#BB0000", background: "#FF3333" }, hover: { border: "#BB0000", background: "#FF3333" } }, // 10:bright red - - { 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 - - { border: "#EE0000", background: "#990000", highlight: { border: "#FF3333", background: "#BB0000" }, hover: { border: "#FF3333", background: "#BB0000" } }]; - - - /** - * Clear all groups - */ - Groups.prototype.clear = function () { - this.groups = {}; - this.groups.length = function () { - var i = 0; - for (var p in this) { - if (this.hasOwnProperty(p)) { - i++; - } - } - return i; - }; - }; - - - /** - * 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 - */ - Groups.prototype.get = function (groupname) { - var group = this.groups[groupname]; - if (group == undefined) { - if (this.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 % Groups.DEFAULT.length; - this.defaultIndex++; - group = {}; - group.color = Groups.DEFAULT[index]; - this.groups[groupname] = group; - } - } - - return group; - }; - - /** - * Add a custom group style - * @param {String} groupName - * @param {Object} style An object containing borderColor, - * backgroundColor, etc. - * @return {Object} group The created group object - */ - Groups.prototype.add = function (groupName, style) { - this.groups[groupName] = style; - this.groupsArray.push(groupName); - return style; - }; - - module.exports = Groups; - // 20:bright red - -/***/ }, -/* 58 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - /** * @class Images * This class loads images and keeps them stored. @@ -25203,7 +25054,7 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = Images; /***/ }, -/* 59 */ +/* 58 */ /***/ function(module, exports, __webpack_require__) { "use strict"; @@ -25244,6 +25095,152 @@ return /******/ (function(modules) { // webpackBootstrap exports.nl_NL = exports.nl; exports.nl_BE = exports.nl; +/***/ }, +/* 59 */ +/***/ function(module, exports, __webpack_require__) { + + "use strict"; + + var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; + + var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; + + var util = __webpack_require__(1); + + /** + * @class Groups + * This class can store groups and options specific for groups. + */ + var Groups = (function () { + function Groups() { + _classCallCheck(this, Groups); + + this.clear(); + this.defaultIndex = 0; + this.groupsArray = []; + this.groupIndex = 0; + + 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 + + { border: "#990000", background: "#EE0000", highlight: { border: "#BB0000", background: "#FF3333" }, hover: { border: "#BB0000", background: "#FF3333" } }, // 10:bright red + + { 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 + + { border: "#EE0000", background: "#990000", highlight: { border: "#FF3333", background: "#BB0000" }, hover: { border: "#FF3333", background: "#BB0000" } }]; + + this.options = {}; + this.defaultOptions = { + useDefaultGroups: true + }; + util.extend(this.options, this.defaultOptions); + } + + _prototypeProperties(Groups, null, { + setOptions: { + value: function setOptions(options) { + var optionFields = ["useDefaultGroups"]; + + 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); + } + } + } + } + }, + writable: true, + configurable: true + }, + clear: { + + + /** + * Clear all groups + */ + value: function clear() { + this.groups = {}; + this.groupsArray = []; + }, + writable: true, + configurable: true + }, + 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; + } + } + + return group; + }, + writable: true, + configurable: true + }, + 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; + }, + writable: true, + configurable: true + } + }); + + return Groups; + })(); + + module.exports = Groups; + // 20:bright red + /***/ }, /* 60 */ /***/ function(module, exports, __webpack_require__) { @@ -27759,12 +27756,8 @@ return /******/ (function(modules) { // webpackBootstrap // this is called when options of EXISTING nodes or edges have changed. this.body.emitter.on("_dataUpdated", function () { - var t0 = new Date().valueOf(); - // update values _this.reconnectEdges(); _this.markAllEdgesAsDirty(); - // start simulation (can be called safely, even if already running) - console.log("_dataUpdated took:", new Date().valueOf() - t0); }); } @@ -28041,11 +28034,11 @@ return /******/ (function(modules) { // webpackBootstrap var Label = _interopRequire(__webpack_require__(62)); - var BezierEdgeDynamic = _interopRequire(__webpack_require__(82)); + var BezierEdgeDynamic = _interopRequire(__webpack_require__(99)); - var BezierEdgeStatic = _interopRequire(__webpack_require__(85)); + var BezierEdgeStatic = _interopRequire(__webpack_require__(102)); - var StraightEdge = _interopRequire(__webpack_require__(86)); + var StraightEdge = _interopRequire(__webpack_require__(103)); /** * @class Edge @@ -28375,13 +28368,13 @@ return /******/ (function(modules) { // webpackBootstrap drawArrows: { value: function drawArrows(ctx, viaNode) { if (this.options.arrows.from.enabled === true) { - this.edgeType.drawArrowHead(ctx, "from", viaNode); + this.edgeType.drawArrowHead(ctx, "from", viaNode, this.selected, this.hover); } if (this.options.arrows.middle.enabled === true) { - this.edgeType.drawArrowHead(ctx, "middle", viaNode); + this.edgeType.drawArrowHead(ctx, "middle", viaNode, this.selected, this.hover); } if (this.options.arrows.to.enabled === true) { - this.edgeType.drawArrowHead(ctx, "to", viaNode); + this.edgeType.drawArrowHead(ctx, "to", viaNode, this.selected, this.hover); } }, writable: true, @@ -28789,306 +28782,525 @@ return /******/ (function(modules) { // webpackBootstrap var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; - var _get = function get(object, property, receiver) { var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc && desc.writable) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; - - var _inherits = function (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 _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; /** - * Created by Alex on 3/20/2015. + * Created by Alex on 2/23/2015. */ - var BezierEdgeBase = _interopRequire(__webpack_require__(83)); + var BarnesHutSolver = _interopRequire(__webpack_require__(83)); - var BezierEdgeDynamic = (function (BezierEdgeBase) { - function BezierEdgeDynamic(options, body, labelModule) { - _classCallCheck(this, BezierEdgeDynamic); + var Repulsion = _interopRequire(__webpack_require__(84)); - this.via = undefined; - _get(Object.getPrototypeOf(BezierEdgeDynamic.prototype), "constructor", this).call(this, options, body, labelModule); // --> this calls the setOptions below - } + var HierarchicalRepulsion = _interopRequire(__webpack_require__(85)); - _inherits(BezierEdgeDynamic, BezierEdgeBase); + var SpringSolver = _interopRequire(__webpack_require__(86)); - _prototypeProperties(BezierEdgeDynamic, null, { + var HierarchicalSpringSolver = _interopRequire(__webpack_require__(87)); + + var CentralGravitySolver = _interopRequire(__webpack_require__(88)); + + var util = __webpack_require__(1); + + + var PhysicsEngine = (function () { + function PhysicsEngine(body) { + var _this = this; + _classCallCheck(this, PhysicsEngine); + + this.body = body; + this.physicsBody = { physicsNodeIndices: [], physicsEdgeIndices: [], forces: {}, velocities: {} }; + + this.physicsEnabled = true; + this.simulationInterval = 1000 / 60; + this.requiresTimeout = true; + this.previousStates = {}; + this.freezeCache = {}; + this.renderTimer == undefined; + + this.stabilized = false; + this.stabilizationIterations = 0; + this.ready = false; // will be set to true if the stabilize + + // default options + this.options = {}; + this.defaultOptions = { + barnesHut: { + thetaInverted: 1 / 0.5, // inverted to save time during calculation + gravitationalConstant: -2000, + centralGravity: 0.3, + springLength: 95, + springConstant: 0.04, + damping: 0.09 + }, + repulsion: { + centralGravity: 0, + springLength: 200, + springConstant: 0.05, + nodeDistance: 100, + damping: 0.09 + }, + hierarchicalRepulsion: { + centralGravity: 0, + springLength: 100, + springConstant: 0.01, + nodeDistance: 120, + damping: 0.09 + }, + solver: "BarnesHut", + timestep: 0.5, + maxVelocity: 50, + minVelocity: 0.1, // px/s + stabilization: { + enabled: true, + iterations: 1000, // maximum number of iteration to stabilize + updateInterval: 100, + onlyDynamicEdges: false, + zoomExtent: true + } + }; + util.extend(this.options, this.defaultOptions); + + this.body.emitter.on("initPhysics", function () { + _this.initPhysics(); + }); + this.body.emitter.on("resetPhysics", function () { + _this.stopSimulation();_this.ready = false; + }); + this.body.emitter.on("startSimulation", function () { + if (_this.ready === true) { + _this.stabilized = false; + _this.runSimulation(); + } + }); + this.body.emitter.on("stopSimulation", function () { + console.log(4);_this.stopSimulation(); + }); + } + + _prototypeProperties(PhysicsEngine, null, { setOptions: { value: function setOptions(options) { - this.options = options; - this.from = this.body.nodes[this.options.from]; - this.to = this.body.nodes[this.options.to]; - this.id = this.options.id; - this.setupSupportNode(); + if (options === false) { + this.physicsEnabled = false; + this.stopSimulation(); + } else { + if (options !== undefined) { + util.selectiveNotDeepExtend(["stabilization"], this.options, options); + util.mergeOptions(this.options, options, "stabilization"); + } + this.init(); + } }, writable: true, configurable: true }, - cleanup: { - value: function cleanup() { - if (this.via !== undefined) { - delete this.body.nodes[this.via.id]; - this.via = undefined; - return true; + init: { + value: function init() { + var options; + if (this.options.solver == "repulsion") { + options = this.options.repulsion; + this.nodesSolver = new Repulsion(this.body, this.physicsBody, options); + this.edgesSolver = new SpringSolver(this.body, this.physicsBody, options); + } else if (this.options.solver == "hierarchicalRepulsion") { + options = this.options.hierarchicalRepulsion; + this.nodesSolver = new HierarchicalRepulsion(this.body, this.physicsBody, options); + this.edgesSolver = new HierarchicalSpringSolver(this.body, this.physicsBody, options); + } else { + // barnesHut + options = this.options.barnesHut; + this.nodesSolver = new BarnesHutSolver(this.body, this.physicsBody, options); + this.edgesSolver = new SpringSolver(this.body, this.physicsBody, options); } - return false; + + this.gravitySolver = new CentralGravitySolver(this.body, this.physicsBody, options); + this.modelOptions = options; }, writable: true, configurable: true }, - setupSupportNode: { - - /** - * Bezier curves require an anchor point to calculate the smooth flow. These points are nodes. These nodes are invisible but - * are used for the force calculation. - * - * The changed data is not called, if needed, it is returned by the main edge constructor. - * @private - */ - value: function setupSupportNode() { - if (this.via === undefined) { - var nodeId = "edgeId:" + this.id; - var node = this.body.functions.createNode({ - id: nodeId, - mass: 1, - shape: "circle", - image: "", - physics: true, - hidden: true - }); - this.body.nodes[nodeId] = node; - this.via = node; - this.via.parentEdgeId = this.id; - this.positionBezierNode(); + initPhysics: { + value: function initPhysics() { + if (this.physicsEnabled === true) { + this.stabilized = false; + if (this.options.stabilization.enabled === true) { + this.stabilize(); + } else { + this.ready = true; + this.body.emitter.emit("zoomExtent", { duration: 0 }, true); + this.runSimulation(); + } + } else { + this.ready = true; + this.body.emitter.emit("_redraw"); } }, writable: true, configurable: true }, - positionBezierNode: { - value: function positionBezierNode() { - if (this.via !== undefined && this.from !== undefined && this.to !== undefined) { - this.via.x = 0.5 * (this.from.x + this.to.x); - this.via.y = 0.5 * (this.from.y + this.to.y); - } else if (this.via !== undefined) { - this.via.x = 0; - this.via.y = 0; + stopSimulation: { + value: function stopSimulation() { + this.stabilized = true; + if (this.viewFunction !== undefined) { + this.body.emitter.off("initRedraw", this.viewFunction); + this.viewFunction = undefined; + this.body.emitter.emit("_stopRendering"); } }, writable: true, configurable: true }, - _line: { - - /** - * Draw a line between two nodes - * @param {CanvasRenderingContext2D} ctx - * @private - */ - value: function _line(ctx) { - // draw a straight line - ctx.beginPath(); - ctx.moveTo(this.from.x, this.from.y); - ctx.quadraticCurveTo(this.via.x, this.via.y, this.to.x, this.to.y); - ctx.stroke(); - return this.via; + runSimulation: { + value: function runSimulation() { + if (this.physicsEnabled === true) { + 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"); + } }, writable: true, configurable: true }, - getPoint: { + simulationStep: { + value: function simulationStep() { + // check if the physics have settled + var startTime = Date.now(); + this.physicsTick(); + var physicsTime = Date.now() - startTime; + // run double speed if it is a little graph + if ((physicsTime < 0.4 * this.simulationInterval || this.runDoubleSpeed == true) && this.stabilized === false) { + this.physicsTick(); - /** - * Combined function of pointOnLine and pointOnBezier. This gives the coordinates of a point on the line at a certain percentage of the way - * @param percentage - * @param via - * @returns {{x: number, y: number}} - * @private - */ - value: function getPoint(percentage) { - var t = percentage; - var x = Math.pow(1 - t, 2) * this.from.x + 2 * t * (1 - t) * this.via.x + Math.pow(t, 2) * this.to.x; - var y = Math.pow(1 - t, 2) * this.from.y + 2 * t * (1 - t) * this.via.y + Math.pow(t, 2) * this.to.y; + // this makes sure there is no jitter. The decision is taken once to run it at double speed. + this.runDoubleSpeed = true; + } - return { x: x, y: y }; + 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 + var me = this; + var params = { + iterations: this.stabilizationIterations + }; + this.stabilizationIterations = 0; + this.startedStabilization = false; + setTimeout(function () { + me.body.emitter.emit("stabilized", params); + }, 0); + } else { + this.stabilizationIterations = 0; + } + this.stopSimulation(); + } }, writable: true, configurable: true }, - _findBorderPosition: { - value: function _findBorderPosition(nearNode, ctx) { - return this._findBorderPositionBezier(nearNode, ctx, this.via); + physicsTick: { + + /** + * A single simulation step (or "tick") in the physics simulation + * + * @private + */ + value: function physicsTick() { + if (this.stabilized === false) { + this.calculateForces(); + this.stabilized = this.moveNodes(); + + // 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; + } + } + + this.stabilizationIterations++; + } }, writable: true, configurable: true }, - _getDistanceToEdge: { - value: function _getDistanceToEdge(x1, y1, x2, y2, x3, y3) { - // x3,y3 is the point - return this._getDistanceToBezierEdge(x1, y1, x2, y2, x3, y3, this.via); - }, - writable: true, - configurable: true - } - }); - - return BezierEdgeDynamic; - })(BezierEdgeBase); - - module.exports = BezierEdgeDynamic; - -/***/ }, -/* 83 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - - var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; + updatePhysicsIndices: { - var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; + /** + * Smooth curves are created by adding invisible nodes in the center of the edges. These nodes are also + * handled in the calculateForces function. We then use a quadratic curve with the center node as control. + * This function joins the datanodes and invisible (called support) nodes into one object. + * We do this so we do not contaminate this.body.nodes with the support nodes. + * + * @private + */ + value: function updatePhysicsIndices() { + this.physicsBody.forces = {}; + this.physicsBody.physicsNodeIndices = []; + this.physicsBody.physicsEdgeIndices = []; + var nodes = this.body.nodes; + var edges = this.body.edges; - var _get = function get(object, property, receiver) { var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc && desc.writable) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + // 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); + } + } + } - var _inherits = function (subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; }; + // get edge indices for physics + for (var edgeId in edges) { + if (edges.hasOwnProperty(edgeId)) { + if (edges[edgeId].options.physics === true) { + this.physicsBody.physicsEdgeIndices.push(edgeId); + } + } + } - var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; + // 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 }; - /** - * Created by Alex on 3/20/2015. - */ + // 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 }; + } + } - var EdgeBase = _interopRequire(__webpack_require__(84)); + // clean deleted nodes from the velocity vector + for (var nodeId in this.physicsBody.velocities) { + if (nodes[nodeId] === undefined) { + delete this.physicsBody.velocities[nodeId]; + } + } + }, + writable: true, + configurable: true + }, + revert: { + value: function revert() { + var nodeIds = Object.keys(this.previousStates); + var nodes = this.body.nodes; + var velocities = this.physicsBody.velocities; - var BezierEdgeBase = (function (EdgeBase) { - function BezierEdgeBase(options, body, labelModule) { - _classCallCheck(this, BezierEdgeBase); - - _get(Object.getPrototypeOf(BezierEdgeBase.prototype), "constructor", this).call(this, options, body, labelModule); - } - - _inherits(BezierEdgeBase, EdgeBase); - - _prototypeProperties(BezierEdgeBase, null, { - _findBorderPositionBezier: { + for (var i = 0; i < nodeIds.length; i++) { + var nodeId = nodeIds[i]; + if (nodes[nodeId] !== undefined) { + 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]; + } + } + }, + writable: true, + configurable: true + }, + moveNodes: { + value: function moveNodes() { + var nodesPresent = false; + var nodeIndices = this.physicsBody.physicsNodeIndices; + var maxVelocity = this.options.maxVelocity === 0 ? 1000000000 : this.options.maxVelocity; + var stabilized = true; + var vminCorrected = this.options.minVelocity / Math.max(this.body.view.scale, 0.05); - /** - * This function uses binary search to look for the point where the bezier curve crosses the border of the node. - * - * @param nearNode - * @param ctx - * @param viaNode - * @param nearNode - * @param ctx - * @param viaNode - * @param nearNode - * @param ctx - * @param viaNode - */ - value: function _findBorderPositionBezier(nearNode, ctx) { - var viaNode = arguments[2] === undefined ? this._getViaCoordinates() : arguments[2]; - var maxIterations = 10; - var iteration = 0; - var low = 0; - var high = 1; - var pos, angle, distanceToBorder, distanceToPoint, difference; - var threshold = 0.2; - var node = this.to; - var from = false; - if (nearNode.id === this.from.id) { - node = this.from; - from = true; + 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; } - while (low <= high && iteration < maxIterations) { - var middle = (low + high) * 0.5; - pos = this.getPoint(middle, viaNode); - angle = Math.atan2(node.y - pos.y, node.x - pos.x); - distanceToBorder = node.distanceToBorder(ctx, angle); - distanceToPoint = Math.sqrt(Math.pow(pos.x - node.x, 2) + Math.pow(pos.y - node.y, 2)); - difference = distanceToBorder - distanceToPoint; - if (Math.abs(difference) < threshold) { - break; // found - } else if (difference < 0) { - // distance to nodes is larger than distance to border --> t needs to be bigger if we're looking at the to node. - if (from == false) { - low = middle; - } else { - high = middle; - } + if (nodesPresent == true) { + if (vminCorrected > 0.5 * this.options.maxVelocity) { + return false; } else { - if (from == false) { - high = middle; - } else { - low = middle; - } + return stabilized; } + } + return true; + }, + writable: true, + configurable: true + }, + _performStep: { + 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; - iteration++; + // store the state so we can revert + this.previousStates[nodeId] = { x: node.x, y: node.y, vx: velocities[nodeId].x, vy: velocities[nodeId].y }; + + if (node.options.fixed.x === false) { + var dx = this.modelOptions.damping * velocities[nodeId].x; // damping force + var ax = (forces[nodeId].x - dx) / node.options.mass; // acceleration + velocities[nodeId].x += ax * timestep; // velocity + velocities[nodeId].x = Math.abs(velocities[nodeId].x) > maxVelocity ? velocities[nodeId].x > 0 ? maxVelocity : -maxVelocity : velocities[nodeId].x; + node.x += velocities[nodeId].x * timestep; // position + } else { + forces[nodeId].x = 0; + velocities[nodeId].x = 0; } - pos.t = middle; - return pos; + if (node.options.fixed.y === false) { + var dy = this.modelOptions.damping * velocities[nodeId].y; // damping force + var ay = (forces[nodeId].y - dy) / node.options.mass; // acceleration + velocities[nodeId].y += ay * timestep; // velocity + velocities[nodeId].y = Math.abs(velocities[nodeId].y) > maxVelocity ? velocities[nodeId].y > 0 ? maxVelocity : -maxVelocity : velocities[nodeId].y; + node.y += velocities[nodeId].y * timestep; // position + } else { + forces[nodeId].y = 0; + velocities[nodeId].y = 0; + } + + var totalVelocity = Math.sqrt(Math.pow(velocities[nodeId].x, 2) + Math.pow(velocities[nodeId].y, 2)); + return totalVelocity; }, writable: true, configurable: true }, - _getDistanceToBezierEdge: { + calculateForces: { + value: function calculateForces() { + this.gravitySolver.solve(); + this.nodesSolver.solve(); + this.edgesSolver.solve(); + }, + writable: true, + configurable: true + }, + _freezeNodes: { + + + + + + /** - * Calculate the distance between a point (x3,y3) and a line segment from - * (x1,y1) to (x2,y2). - * http://stackoverflow.com/questions/849211/shortest-distancae-between-a-point-and-a-line-segment - * @param {number} x1 - * @param {number} y1 - * @param {number} x2 - * @param {number} y2 - * @param {number} x3 - * @param {number} y3 + * 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 _getDistanceToBezierEdge(x1, y1, x2, y2, x3, y3, via) { - // x3,y3 is the point - var xVia = undefined, - yVia = undefined; - xVia = via.x; - yVia = via.y; - var minDistance = 1000000000; - var distance = undefined; - var i = undefined, - t = undefined, - x = undefined, - y = undefined; - var lastX = x1; - var lastY = y1; - for (i = 1; i < 10; i++) { - t = 0.1 * i; - x = Math.pow(1 - t, 2) * x1 + 2 * t * (1 - t) * xVia + Math.pow(t, 2) * x2; - y = Math.pow(1 - t, 2) * y1 + 2 * t * (1 - t) * yVia + Math.pow(t, 2) * y2; - if (i > 0) { - distance = this._getDistanceToLine(lastX, lastY, x, y, x3, y3); - minDistance = distance < minDistance ? distance : minDistance; + 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; + } + } + } + }, + writable: true, + configurable: true + }, + _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; + } } - lastX = x; - lastY = y; } + this.freezeCache = {}; + }, + writable: true, + configurable: true + }, + stabilize: { - return minDistance; + /** + * Find a stable position for all nodes + * @private + */ + value: function stabilize() { + if (this.options.stabilization.onlyDynamicEdges == true) { + this._freezeNodes(); + } + this.stabilizationSteps = 0; + + setTimeout(this._stabilizationBatch.bind(this), 0); + }, + writable: true, + configurable: true + }, + _stabilizationBatch: { + value: function _stabilizationBatch() { + var count = 0; + while (this.stabilized == false && count < this.options.stabilization.updateInterval && this.stabilizationSteps < this.options.stabilization.iterations) { + this.physicsTick(); + this.stabilizationSteps++; + count++; + } + + if (this.stabilized == false && this.stabilizationSteps < this.options.stabilization.iterations) { + this.body.emitter.emit("stabilizationProgress", { steps: this.stabilizationSteps, total: this.options.stabilization.iterations }); + setTimeout(this._stabilizationBatch.bind(this), 0); + } else { + this._finalizeStabilization(); + } + }, + writable: true, + configurable: true + }, + _finalizeStabilization: { + value: function _finalizeStabilization() { + if (this.options.stabilization.zoomExtent == true) { + this.body.emitter.emit("zoomExtent", { duration: 0 }); + } + + if (this.options.stabilization.onlyDynamicEdges == true) { + this._restoreFrozenNodes(); + } + + this.body.emitter.emit("stabilizationIterationsDone"); + this.body.emitter.emit("_requestRedraw"); + this.ready = true; }, writable: true, configurable: true } }); - return BezierEdgeBase; - })(EdgeBase); + return PhysicsEngine; + })(); - module.exports = BezierEdgeBase; + module.exports = PhysicsEngine; /***/ }, -/* 84 */ +/* 83 */ /***/ function(module, exports, __webpack_require__) { "use strict"; @@ -29098,744 +29310,696 @@ return /******/ (function(modules) { // webpackBootstrap var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; /** - * Created by Alex on 3/20/2015. + * Created by Alex on 2/23/2015. */ - var util = __webpack_require__(1); - var EdgeBase = (function () { - function EdgeBase(options, body, labelModule) { - _classCallCheck(this, EdgeBase); + var BarnesHutSolver = (function () { + function BarnesHutSolver(body, physicsBody, options) { + _classCallCheck(this, BarnesHutSolver); this.body = body; - this.labelModule = labelModule; + this.physicsBody = physicsBody; + this.barnesHutTree; this.setOptions(options); - this.colorDirty = true; } - _prototypeProperties(EdgeBase, null, { + _prototypeProperties(BarnesHutSolver, null, { setOptions: { value: function setOptions(options) { this.options = options; - this.from = this.body.nodes[this.options.from]; - this.to = this.body.nodes[this.options.to]; - this.id = this.options.id; }, writable: true, configurable: true }, - drawLine: { + solve: { + /** - * Redraw a edge as a line - * Draw this edge in the given canvas - * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d"); - * @param {CanvasRenderingContext2D} ctx + * This function calculates the forces the nodes apply on eachother based on a gravitational model. + * The Barnes Hut method is used to speed up this N-body simulation. + * * @private */ - value: function drawLine(ctx, selected, hover) { - // set style - ctx.strokeStyle = this.getColor(ctx); - ctx.lineWidth = this.getLineWidth(); - var via = undefined; - if (this.from != this.to) { - // draw line - if (this.options.dashes.enabled == true) { - via = this._drawDashedLine(ctx); - } else { - via = this._line(ctx); - } - } else { - var x = undefined, - y = undefined; - var radius = this.options.selfReferenceSize; - var node = this.from; - node.resize(ctx); - if (node.shape.width > node.shape.height) { - x = node.x + node.shape.width * 0.5; - y = node.y - radius; - } else { - x = node.x + radius; - y = node.y - node.shape.height * 0.5; - } - this._circle(ctx, x, y, radius); - } - - return via; - }, - writable: true, - configurable: true - }, - _drawDashedLine: { - value: function _drawDashedLine(ctx) { - var via = undefined; - // only firefox and chrome support this method, else we use the legacy one. - if (ctx.setLineDash !== undefined) { - ctx.save(); - // configure the dash pattern - var pattern = [0]; - if (this.options.dashes.length !== undefined && this.options.dashes.gap !== undefined) { - pattern = [this.options.dashes.length, this.options.dashes.gap]; - } else { - pattern = [5, 5]; - } + value: function solve() { + if (this.options.gravitationalConstant != 0) { + var node; + var nodes = this.body.nodes; + var nodeIndices = this.physicsBody.physicsNodeIndices; + var nodeCount = nodeIndices.length; - // set dash settings for chrome or firefox - ctx.setLineDash(pattern); - ctx.lineDashOffset = 0; + // create the tree + var barnesHutTree = this._formBarnesHutTree(nodes, nodeIndices); - // draw the line - via = this._line(ctx); + // for debugging + this.barnesHutTree = barnesHutTree; - // restore the dash settings. - ctx.setLineDash([0]); - ctx.lineDashOffset = 0; - ctx.restore(); - } else { - // unsupporting smooth lines - // draw dashes line - ctx.beginPath(); - ctx.lineCap = "round"; - if (this.options.dashes.altLength !== undefined) //If an alt dash value has been set add to the array this value - { - ctx.dashesLine(this.from.x, this.from.y, this.to.x, this.to.y, [this.options.dashes.length, this.options.dashes.gap, this.options.dashes.altLength, this.options.dashes.gap]); - } else if (this.options.dashes.length !== undefined && this.options.dashes.gap !== undefined) //If a dash and gap value has been set add to the array this value - { - ctx.dashesLine(this.from.x, this.from.y, this.to.x, this.to.y, [this.options.dashes.length, this.options.dashes.gap]); - } else //If all else fails draw a line - { - ctx.moveTo(this.from.x, this.from.y); - ctx.lineTo(this.to.x, this.to.y); + // place the nodes one by one recursively + for (var i = 0; i < nodeCount; i++) { + node = nodes[nodeIndices[i]]; + if (node.options.mass > 0) { + // starting with root is irrelevant, it never passes the BarnesHutSolver condition + this._getForceContribution(barnesHutTree.root.children.NW, node); + this._getForceContribution(barnesHutTree.root.children.NE, node); + this._getForceContribution(barnesHutTree.root.children.SW, node); + this._getForceContribution(barnesHutTree.root.children.SE, node); } - ctx.stroke(); - } - return via; - }, - writable: true, - configurable: true - }, - findBorderPosition: { - value: function findBorderPosition(nearNode, ctx, options) { - if (this.from != this.to) { - return this._findBorderPosition(nearNode, ctx, options); - } else { - return this._findBorderPositionCircle(nearNode, ctx, options); + } } }, writable: true, configurable: true }, - _findBorderPositionCircle: { - - + _getForceContribution: { /** - * This function uses binary search to look for the point where the circle crosses the border of the node. - * @param x - * @param y - * @param radius + * This function traverses the barnesHutTree. It checks when it can approximate distant nodes with their center of mass. + * If a region contains a single node, we check if it is not itself, then we apply the force. + * + * @param parentBranch * @param node - * @param low - * @param high - * @param direction - * @param ctx - * @returns {*} * @private */ - value: function _findBorderPositionCircle(node, ctx, options) { - var x = options.x; - var y = options.y; - var low = options.low; - var high = options.high; - var direction = options.direction; - - var maxIterations = 10; - var iteration = 0; - var radius = this.options.selfReferenceSize; - var pos = undefined, - angle = undefined, - distanceToBorder = undefined, - distanceToPoint = undefined, - difference = undefined; - var threshold = 0.05; + value: function _getForceContribution(parentBranch, node) { + // we get no force contribution from an empty region + if (parentBranch.childrenCount > 0) { + var dx, dy, distance; - while (low <= high && iteration < maxIterations) { - var _middle = (low + high) * 0.5; + // get the distance from the center of mass to the node. + dx = parentBranch.centerOfMass.x - node.x; + dy = parentBranch.centerOfMass.y - node.y; + distance = Math.sqrt(dx * dx + dy * dy); - pos = this._pointOnCircle(x, y, radius, _middle); - angle = Math.atan2(node.y - pos.y, node.x - pos.x); - distanceToBorder = node.distanceToBorder(ctx, angle); - distanceToPoint = Math.sqrt(Math.pow(pos.x - node.x, 2) + Math.pow(pos.y - node.y, 2)); - difference = distanceToBorder - distanceToPoint; - if (Math.abs(difference) < threshold) { - break; // found - } else if (difference > 0) { - // distance to nodes is larger than distance to border --> t needs to be bigger if we're looking at the to node. - if (direction > 0) { - low = _middle; - } else { - high = _middle; + // BarnesHutSolver condition + // original condition : s/d < thetaInverted = passed === d/s > 1/theta = passed + // calcSize = 1/s --> d * 1/s > 1/theta = passed + if (distance * parentBranch.calcSize > this.options.thetaInverted) { + // duplicate code to reduce function calls to speed up program + if (distance == 0) { + distance = 0.1 * Math.random(); + dx = distance; } + var gravityForce = this.options.gravitationalConstant * parentBranch.mass * node.options.mass / (distance * distance * distance); + var fx = dx * gravityForce; + var fy = dy * gravityForce; + + this.physicsBody.forces[node.id].x += fx; + this.physicsBody.forces[node.id].y += fy; } else { - if (direction > 0) { - high = _middle; + // Did not pass the condition, go into children if available + if (parentBranch.childrenCount == 4) { + this._getForceContribution(parentBranch.children.NW, node); + this._getForceContribution(parentBranch.children.NE, node); + this._getForceContribution(parentBranch.children.SW, node); + this._getForceContribution(parentBranch.children.SE, node); } else { - low = _middle; + // parentBranch must have only one node, if it was empty we wouldnt be here + if (parentBranch.children.data.id != node.id) { + // if it is not self + // duplicate code to reduce function calls to speed up program + if (distance == 0) { + distance = 0.5 * Math.random(); + dx = distance; + } + var gravityForce = this.options.gravitationalConstant * parentBranch.mass * node.options.mass / (distance * distance * distance); + var fx = dx * gravityForce; + var fy = dy * gravityForce; + + this.physicsBody.forces[node.id].x += fx; + this.physicsBody.forces[node.id].y += fy; + } } } - iteration++; } - pos.t = middle; - - return pos; }, writable: true, configurable: true }, - getLineWidth: { + _formBarnesHutTree: { + /** - * Get the line width of the edge. Depends on width and whether one of the - * connected nodes is selected. - * @return {Number} width + * This function constructs the barnesHut tree recursively. It creates the root, splits it and starts placing the nodes. + * + * @param nodes + * @param nodeIndices * @private */ - value: function getLineWidth(selected, hover) { - if (selected == true) { - return Math.max(Math.min(this.options.widthSelectionMultiplier * this.options.width, this.options.scaling.max), 0.3 / this.body.view.scale); - } else { - if (hover == true) { - return Math.max(Math.min(this.options.hoverWidth, this.options.scaling.max), 0.3 / this.body.view.scale); - } else { - return Math.max(this.options.width, 0.3 / this.body.view.scale); + value: function _formBarnesHutTree(nodes, nodeIndices) { + var node; +