1 |
- !function(f){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=f();else if("function"==typeof define&&define.amd)define([],f);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).ngraphCreateLayout=f()}}((function(){return function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a="function"==typeof require&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,(function(e){var n=t[o][1][e];return s(n||e)}),l,l.exports,e,t,n,r)}return n[o].exports}for(var i="function"==typeof require&&require,o=0;o<r.length;o++)s(r[o]);return s}({1:[function(require,module,exports){module.exports=function(graph,physicsSettings){if(!graph)throw new Error("Graph structure cannot be undefined");var physicsSimulator=(physicsSettings&&physicsSettings.createSimulator||require("./lib/createPhysicsSimulator"))(physicsSettings);if(Array.isArray(physicsSettings))throw new Error("Physics settings is expected to be an object");var nodeMass=function(nodeId){var links=graph.getLinks(nodeId);return links?1+links.length/3:1};physicsSettings&&"function"==typeof physicsSettings.nodeMass&&(nodeMass=physicsSettings.nodeMass);var nodeBodies=new Map,springs={},bodiesCount=0,springTransform=physicsSimulator.settings.springTransform||noop;bodiesCount=0,graph.forEachNode((function(node){initBody(node.id),bodiesCount+=1})),graph.forEachLink(initLink),graph.on("changed",onGraphChanged);var wasStable=!1,api={step:function(){if(0===bodiesCount)return updateStableStatus(!0),!0;var lastMove=physicsSimulator.step();api.lastMove=lastMove,api.fire("step");var isStableNow=lastMove/bodiesCount<=.01;return updateStableStatus(isStableNow),isStableNow},getNodePosition:function(nodeId){return getInitializedBody(nodeId).pos},setNodePosition:function(nodeId){var body=getInitializedBody(nodeId);body.setPosition.apply(body,Array.prototype.slice.call(arguments,1))},getLinkPosition:function(linkId){var spring=springs[linkId];if(spring)return{from:spring.from.pos,to:spring.to.pos}},getGraphRect:function(){return physicsSimulator.getBBox()},forEachBody:forEachBody,pinNode:function(node,isPinned){getInitializedBody(node.id).isPinned=!!isPinned},isNodePinned:function(node){return getInitializedBody(node.id).isPinned},dispose:function(){graph.off("changed",onGraphChanged),api.fire("disposed")},getBody:function(nodeId){return nodeBodies.get(nodeId)},getSpring:function(fromId,toId){var linkId;if(void 0===toId)linkId="object"!=typeof fromId?fromId:fromId.id;else{var link=graph.hasLink(fromId,toId);if(!link)return;linkId=link.id}return springs[linkId]},getForceVectorLength:function(){var fx=0,fy=0;return forEachBody((function(body){fx+=Math.abs(body.force.x),fy+=Math.abs(body.force.y)})),Math.sqrt(fx*fx+fy*fy)},simulator:physicsSimulator,graph:graph,lastMove:0};return eventify(api),api;function updateStableStatus(isStableNow){var isStable;wasStable!==isStableNow&&(wasStable=isStableNow,isStable=isStableNow,api.fire("stable",isStable))}function forEachBody(cb){nodeBodies.forEach(cb)}function onGraphChanged(changes){for(var i=0;i<changes.length;++i){var change=changes[i];"add"===change.changeType?(change.node&&initBody(change.node.id),change.link&&initLink(change.link)):"remove"===change.changeType&&(change.node&&releaseNode(change.node),change.link&&releaseLink(change.link))}bodiesCount=graph.getNodesCount()}function initBody(nodeId){var body=nodeBodies.get(nodeId);if(!body){var node=graph.getNode(nodeId);if(!node)throw new Error("initBody() was called with unknown node id");var pos=node.position;if(!pos){var neighbors=function(node){var neighbors=[];if(!node.links)return neighbors;for(var maxNeighbors=Math.min(node.links.length,2),i=0;i<maxNeighbors;++i){var link=node.links[i],otherBody=link.fromId!==node.id?nodeBodies.get(link.fromId):nodeBodies.get(link.toId);otherBody&&otherBody.pos&&neighbors.push(otherBody)}return neighbors}(node);pos=physicsSimulator.getBestNewBodyPosition(neighbors)}(body=physicsSimulator.addBodyAt(pos)).id=nodeId,nodeBodies.set(nodeId,body),updateBodyMass(nodeId),function(node){return node&&(node.isPinned||node.data&&node.data.isPinned)}(node)&&(body.isPinned=!0)}}function releaseNode(node){var nodeId=node.id,body=nodeBodies.get(nodeId);body&&(nodeBodies.delete(nodeId),physicsSimulator.removeBody(body))}function initLink(link){updateBodyMass(link.fromId),updateBodyMass(link.toId);var fromBody=nodeBodies.get(link.fromId),toBody=nodeBodies.get(link.toId),spring=physicsSimulator.addSpring(fromBody,toBody,link.length);springTransform(link,spring),springs[link.id]=spring}function releaseLink(link){var spring=springs[link.id];if(spring){var from=graph.getNode(link.fromId),to=graph.getNode(link.toId);from&&updateBodyMass(from.id),to&&updateBodyMass(to.id),delete springs[link.id],physicsSimulator.removeSpring(spring)}}function updateBodyMass(nodeId){var body=nodeBodies.get(nodeId);if(body.mass=nodeMass(nodeId),Number.isNaN(body.mass))throw new Error("Node mass should be a number")}function getInitializedBody(nodeId){var body=nodeBodies.get(nodeId);return body||(initBody(nodeId),body=nodeBodies.get(nodeId)),body}},module.exports.simulator=require("./lib/createPhysicsSimulator");var eventify=require("ngraph.events");function noop(){}},{"./lib/createPhysicsSimulator":10,"ngraph.events":12}],2:[function(require,module,exports){const getVariableName=require("./getVariableName");module.exports=function(dimension){return function(template,config){let indent=config&&config.indent||0,join=config&&void 0!==config.join?config.join:"\n",indentString=Array(indent+1).join(" "),buffer=[];for(let i=0;i<dimension;++i){let variableName=getVariableName(i),prefix=0===i?"":indentString;buffer.push(prefix+template.replace(/{var}/g,variableName))}return buffer.join(join)}}},{"./getVariableName":9}],3:[function(require,module,exports){module.exports=function(dimension){let code=generateBoundsFunctionBody(dimension);return new Function("bodies","settings","random",code)},module.exports.generateFunctionBody=generateBoundsFunctionBody;const createPatternBuilder=require("./createPatternBuilder");function generateBoundsFunctionBody(dimension){let pattern=createPatternBuilder(dimension);return`\n var boundingBox = {\n ${pattern("min_{var}: 0, max_{var}: 0,",{indent:4})}\n };\n\n return {\n box: boundingBox,\n\n update: updateBoundingBox,\n\n reset: resetBoundingBox,\n\n getBestNewPosition: function (neighbors) {\n var ${pattern("base_{var} = 0",{join:", "})};\n\n if (neighbors.length) {\n for (var i = 0; i < neighbors.length; ++i) {\n let neighborPos = neighbors[i].pos;\n ${pattern("base_{var} += neighborPos.{var};",{indent:10})}\n }\n\n ${pattern("base_{var} /= neighbors.length;",{indent:8})}\n } else {\n ${pattern("base_{var} = (boundingBox.min_{var} + boundingBox.max_{var}) / 2;",{indent:8})}\n }\n\n var springLength = settings.springLength;\n return {\n ${pattern("{var}: base_{var} + (random.nextDouble() - 0.5) * springLength,",{indent:8})}\n };\n }\n };\n\n function updateBoundingBox() {\n var i = bodies.length;\n if (i === 0) return; // No bodies - no borders.\n\n ${pattern("var max_{var} = -Infinity;",{indent:4})}\n ${pattern("var min_{var} = Infinity;",{indent:4})}\n\n while(i--) {\n // this is O(n), it could be done faster with quadtree, if we check the root node bounds\n var bodyPos = bodies[i].pos;\n ${pattern("if (bodyPos.{var} < min_{var}) min_{var} = bodyPos.{var};",{indent:6})}\n ${pattern("if (bodyPos.{var} > max_{var}) max_{var} = bodyPos.{var};",{indent:6})}\n }\n\n ${pattern("boundingBox.min_{var} = min_{var};",{indent:4})}\n ${pattern("boundingBox.max_{var} = max_{var};",{indent:4})}\n }\n\n function resetBoundingBox() {\n ${pattern("boundingBox.min_{var} = boundingBox.max_{var} = 0;",{indent:4})}\n }\n`}},{"./createPatternBuilder":2}],4:[function(require,module,exports){const createPatternBuilder=require("./createPatternBuilder");function generateCreateBodyFunctionBody(dimension,debugSetters){return`\n${getVectorCode(dimension,debugSetters)}\n${getBodyCode(dimension)}\nreturn {Body: Body, Vector: Vector};\n`}function getBodyCode(dimension){let pattern=createPatternBuilder(dimension),variableList=pattern("{var}",{join:", "});return`\nfunction Body(${variableList}) {\n this.isPinned = false;\n this.pos = new Vector(${variableList});\n this.force = new Vector();\n this.velocity = new Vector();\n this.mass = 1;\n\n this.springCount = 0;\n this.springLength = 0;\n}\n\nBody.prototype.reset = function() {\n this.force.reset();\n this.springCount = 0;\n this.springLength = 0;\n}\n\nBody.prototype.setPosition = function (${variableList}) {\n ${pattern("this.pos.{var} = {var} || 0;",{indent:2})}\n};`}function getVectorCode(dimension,debugSetters){let pattern=createPatternBuilder(dimension),setters="";return debugSetters&&(setters=""+pattern("\n var v{var};\nObject.defineProperty(this, '{var}', {\n set: function(v) { \n if (!Number.isFinite(v)) throw new Error('Cannot set non-numbers to {var}');\n v{var} = v; \n },\n get: function() { return v{var}; }\n});")),`function Vector(${pattern("{var}",{join:", "})}) {\n ${setters}\n if (typeof arguments[0] === 'object') {\n // could be another vector\n let v = arguments[0];\n ${pattern('if (!Number.isFinite(v.{var})) throw new Error("Expected value is not a finite number at Vector constructor ({var})");',{indent:4})}\n ${pattern("this.{var} = v.{var};",{indent:4})}\n } else {\n ${pattern('this.{var} = typeof {var} === "number" ? {var} : 0;',{indent:4})}\n }\n }\n \n Vector.prototype.reset = function () {\n ${pattern("this.{var} = ",{join:""})}0;\n };`}module.exports=function(dimension,debugSetters){let code=generateCreateBodyFunctionBody(dimension,debugSetters),{Body:Body}=new Function(code)();return Body},module.exports.generateCreateBodyFunctionBody=generateCreateBodyFunctionBody,module.exports.getVectorCode=getVectorCode,module.exports.getBodyCode=getBodyCode},{"./createPatternBuilder":2}],5:[function(require,module,exports){const createPatternBuilder=require("./createPatternBuilder");function generateCreateDragForceFunctionBody(dimension){return`\n if (!Number.isFinite(options.dragCoefficient)) throw new Error('dragCoefficient is not a finite number');\n\n return {\n update: function(body) {\n ${createPatternBuilder(dimension)("body.force.{var} -= options.dragCoefficient * body.velocity.{var};",{indent:6})}\n }\n };\n`}module.exports=function(dimension){let code=generateCreateDragForceFunctionBody(dimension);return new Function("options",code)},module.exports.generateCreateDragForceFunctionBody=generateCreateDragForceFunctionBody},{"./createPatternBuilder":2}],6:[function(require,module,exports){const createPatternBuilder=require("./createPatternBuilder");function generateCreateSpringForceFunctionBody(dimension){let pattern=createPatternBuilder(dimension);return`\n if (!Number.isFinite(options.springCoefficient)) throw new Error('Spring coefficient is not a number');\n if (!Number.isFinite(options.springLength)) throw new Error('Spring length is not a number');\n\n return {\n /**\n * Updates forces acting on a spring\n */\n update: function (spring) {\n var body1 = spring.from;\n var body2 = spring.to;\n var length = spring.length < 0 ? options.springLength : spring.length;\n ${pattern("var d{var} = body2.pos.{var} - body1.pos.{var};",{indent:6})}\n var r = Math.sqrt(${pattern("d{var} * d{var}",{join:" + "})});\n\n if (r === 0) {\n ${pattern("d{var} = (random.nextDouble() - 0.5) / 50;",{indent:8})}\n r = Math.sqrt(${pattern("d{var} * d{var}",{join:" + "})});\n }\n\n var d = r - length;\n var coefficient = ((spring.coefficient > 0) ? spring.coefficient : options.springCoefficient) * d / r;\n\n ${pattern("body1.force.{var} += coefficient * d{var}",{indent:6})};\n body1.springCount += 1;\n body1.springLength += r;\n\n ${pattern("body2.force.{var} -= coefficient * d{var}",{indent:6})};\n body2.springCount += 1;\n body2.springLength += r;\n }\n };\n`}module.exports=function(dimension){let code=generateCreateSpringForceFunctionBody(dimension);return new Function("options","random",code)},module.exports.generateCreateSpringForceFunctionBody=generateCreateSpringForceFunctionBody},{"./createPatternBuilder":2}],7:[function(require,module,exports){const createPatternBuilder=require("./createPatternBuilder");function generateIntegratorFunctionBody(dimension){let pattern=createPatternBuilder(dimension);return`\n var length = bodies.length;\n if (length === 0) return 0;\n\n ${pattern("var d{var} = 0, t{var} = 0;",{indent:2})}\n\n for (var i = 0; i < length; ++i) {\n var body = bodies[i];\n if (body.isPinned) continue;\n\n if (adaptiveTimeStepWeight && body.springCount) {\n timeStep = (adaptiveTimeStepWeight * body.springLength/body.springCount);\n }\n\n var coeff = timeStep / body.mass;\n\n ${pattern("body.velocity.{var} += coeff * body.force.{var};",{indent:4})}\n ${pattern("var v{var} = body.velocity.{var};",{indent:4})}\n var v = Math.sqrt(${pattern("v{var} * v{var}",{join:" + "})});\n\n if (v > 1) {\n // We normalize it so that we move within timeStep range. \n // for the case when v <= 1 - we let velocity to fade out.\n ${pattern("body.velocity.{var} = v{var} / v;",{indent:6})}\n }\n\n ${pattern("d{var} = timeStep * body.velocity.{var};",{indent:4})}\n\n ${pattern("body.pos.{var} += d{var};",{indent:4})}\n\n ${pattern("t{var} += Math.abs(d{var});",{indent:4})}\n }\n\n return (${pattern("t{var} * t{var}",{join:" + "})})/length;\n`}module.exports=function(dimension){let code=generateIntegratorFunctionBody(dimension);return new Function("bodies","timeStep","adaptiveTimeStepWeight",code)},module.exports.generateIntegratorFunctionBody=generateIntegratorFunctionBody},{"./createPatternBuilder":2}],8:[function(require,module,exports){const createPatternBuilder=require("./createPatternBuilder"),getVariableName=require("./getVariableName");function generateQuadTreeFunctionBody(dimension){let pattern=createPatternBuilder(dimension),quadCount=Math.pow(2,dimension);return`\n${getInsertStackCode()}\n${getQuadNodeCode(dimension)}\n${isSamePosition(dimension)}\n${getChildBodyCode(dimension)}\n${setChildBodyCode(dimension)}\n\nfunction createQuadTree(options, random) {\n options = options || {};\n options.gravity = typeof options.gravity === 'number' ? options.gravity : -1;\n options.theta = typeof options.theta === 'number' ? options.theta : 0.8;\n\n var gravity = options.gravity;\n var updateQueue = [];\n var insertStack = new InsertStack();\n var theta = options.theta;\n\n var nodesCache = [];\n var currentInCache = 0;\n var root = newNode();\n\n return {\n insertBodies: insertBodies,\n\n /**\n * Gets root node if it is present\n */\n getRoot: function() {\n return root;\n },\n\n updateBodyForce: update,\n\n options: function(newOptions) {\n if (newOptions) {\n if (typeof newOptions.gravity === 'number') {\n gravity = newOptions.gravity;\n }\n if (typeof newOptions.theta === 'number') {\n theta = newOptions.theta;\n }\n\n return this;\n }\n\n return {\n gravity: gravity,\n theta: theta\n };\n }\n };\n\n function newNode() {\n // To avoid pressure on GC we reuse nodes.\n var node = nodesCache[currentInCache];\n if (node) {\n${function(indent){let quads=[];for(let i=0;i<quadCount;++i)quads.push(`${indent}quad${i} = null;`);return quads.join("\n")}(" node.")}\n node.body = null;\n node.mass = ${pattern("node.mass_{var} = ",{join:""})}0;\n ${pattern("node.min_{var} = node.max_{var} = ",{join:""})}0;\n } else {\n node = new QuadNode();\n nodesCache[currentInCache] = node;\n }\n\n ++currentInCache;\n return node;\n }\n\n function update(sourceBody) {\n var queue = updateQueue;\n var v;\n ${pattern("var d{var};",{indent:4})}\n var r; \n ${pattern("var f{var} = 0;",{indent:4})}\n var queueLength = 1;\n var shiftIdx = 0;\n var pushIdx = 1;\n\n queue[0] = root;\n\n while (queueLength) {\n var node = queue[shiftIdx];\n var body = node.body;\n\n queueLength -= 1;\n shiftIdx += 1;\n var differentBody = (body !== sourceBody);\n if (body && differentBody) {\n // If the current node is a leaf node (and it is not source body),\n // calculate the force exerted by the current node on body, and add this\n // amount to body's net force.\n ${pattern("d{var} = body.pos.{var} - sourceBody.pos.{var};",{indent:8})}\n r = Math.sqrt(${pattern("d{var} * d{var}",{join:" + "})});\n\n if (r === 0) {\n // Poor man's protection against zero distance.\n ${pattern("d{var} = (random.nextDouble() - 0.5) / 50;",{indent:10})}\n r = Math.sqrt(${pattern("d{var} * d{var}",{join:" + "})});\n }\n\n // This is standard gravitation force calculation but we divide\n // by r^3 to save two operations when normalizing force vector.\n v = gravity * body.mass * sourceBody.mass / (r * r * r);\n ${pattern("f{var} += v * d{var};",{indent:8})}\n } else if (differentBody) {\n // Otherwise, calculate the ratio s / r, where s is the width of the region\n // represented by the internal node, and r is the distance between the body\n // and the node's center-of-mass\n ${pattern("d{var} = node.mass_{var} / node.mass - sourceBody.pos.{var};",{indent:8})}\n r = Math.sqrt(${pattern("d{var} * d{var}",{join:" + "})});\n\n if (r === 0) {\n // Sorry about code duplication. I don't want to create many functions\n // right away. Just want to see performance first.\n ${pattern("d{var} = (random.nextDouble() - 0.5) / 50;",{indent:10})}\n r = Math.sqrt(${pattern("d{var} * d{var}",{join:" + "})});\n }\n // If s / r < θ, treat this internal node as a single body, and calculate the\n // force it exerts on sourceBody, and add this amount to sourceBody's net force.\n if ((node.max_${getVariableName(0)} - node.min_${getVariableName(0)}) / r < theta) {\n // in the if statement above we consider node's width only\n // because the region was made into square during tree creation.\n // Thus there is no difference between using width or height.\n v = gravity * node.mass * sourceBody.mass / (r * r * r);\n ${pattern("f{var} += v * d{var};",{indent:10})}\n } else {\n // Otherwise, run the procedure recursively on each of the current node's children.\n\n // I intentionally unfolded this loop, to save several CPU cycles.\n${function(){let indent=Array(11).join(" "),recursiveCode=[];for(let i=0;i<quadCount;++i)recursiveCode.push(indent+`if (node.quad${i}) {`),recursiveCode.push(indent+` queue[pushIdx] = node.quad${i};`),recursiveCode.push(indent+" queueLength += 1;"),recursiveCode.push(indent+" pushIdx += 1;"),recursiveCode.push(indent+"}");return recursiveCode.join("\n")}()}\n }\n }\n }\n\n ${pattern("sourceBody.force.{var} += f{var};",{indent:4})}\n }\n\n function insertBodies(bodies) {\n ${pattern("var {var}min = Number.MAX_VALUE;",{indent:4})}\n ${pattern("var {var}max = Number.MIN_VALUE;",{indent:4})}\n var i = bodies.length;\n\n // To reduce quad tree depth we are looking for exact bounding box of all particles.\n while (i--) {\n var pos = bodies[i].pos;\n ${pattern("if (pos.{var} < {var}min) {var}min = pos.{var};",{indent:6})}\n ${pattern("if (pos.{var} > {var}max) {var}max = pos.{var};",{indent:6})}\n }\n\n // Makes the bounds square.\n var maxSideLength = -Infinity;\n ${pattern("if ({var}max - {var}min > maxSideLength) maxSideLength = {var}max - {var}min ;",{indent:4})}\n\n currentInCache = 0;\n root = newNode();\n ${pattern("root.min_{var} = {var}min;",{indent:4})}\n ${pattern("root.max_{var} = {var}min + maxSideLength;",{indent:4})}\n\n i = bodies.length - 1;\n if (i >= 0) {\n root.body = bodies[i];\n }\n while (i--) {\n insert(bodies[i], root);\n }\n }\n\n function insert(newBody) {\n insertStack.reset();\n insertStack.push(root, newBody);\n\n while (!insertStack.isEmpty()) {\n var stackItem = insertStack.pop();\n var node = stackItem.node;\n var body = stackItem.body;\n\n if (!node.body) {\n // This is internal node. Update the total mass of the node and center-of-mass.\n ${pattern("var {var} = body.pos.{var};",{indent:8})}\n node.mass += body.mass;\n ${pattern("node.mass_{var} += body.mass * {var};",{indent:8})}\n\n // Recursively insert the body in the appropriate quadrant.\n // But first find the appropriate quadrant.\n var quadIdx = 0; // Assume we are in the 0's quad.\n ${pattern("var min_{var} = node.min_{var};",{indent:8})}\n ${pattern("var max_{var} = (min_{var} + node.max_{var}) / 2;",{indent:8})}\n\n${function(indentCount){let insertionCode=[],indent=Array(indentCount+1).join(" ");for(let i=0;i<dimension;++i)insertionCode.push(indent+`if (${getVariableName(i)} > max_${getVariableName(i)}) {`),insertionCode.push(indent+` quadIdx = quadIdx + ${Math.pow(2,i)};`),insertionCode.push(indent+` min_${getVariableName(i)} = max_${getVariableName(i)};`),insertionCode.push(indent+` max_${getVariableName(i)} = node.max_${getVariableName(i)};`),insertionCode.push(indent+"}");return insertionCode.join("\n")}(8)}\n\n var child = getChild(node, quadIdx);\n\n if (!child) {\n // The node is internal but this quadrant is not taken. Add\n // subnode to it.\n child = newNode();\n ${pattern("child.min_{var} = min_{var};",{indent:10})}\n ${pattern("child.max_{var} = max_{var};",{indent:10})}\n child.body = body;\n\n setChild(node, quadIdx, child);\n } else {\n // continue searching in this quadrant.\n insertStack.push(child, body);\n }\n } else {\n // We are trying to add to the leaf node.\n // We have to convert current leaf into internal node\n // and continue adding two nodes.\n var oldBody = node.body;\n node.body = null; // internal nodes do not cary bodies\n\n if (isSamePosition(oldBody.pos, body.pos)) {\n // Prevent infinite subdivision by bumping one node\n // anywhere in this quadrant\n var retriesCount = 3;\n do {\n var offset = random.nextDouble();\n ${pattern("var d{var} = (node.max_{var} - node.min_{var}) * offset;",{indent:12})}\n\n ${pattern("oldBody.pos.{var} = node.min_{var} + d{var};",{indent:12})}\n retriesCount -= 1;\n // Make sure we don't bump it out of the box. If we do, next iteration should fix it\n } while (retriesCount > 0 && isSamePosition(oldBody.pos, body.pos));\n\n if (retriesCount === 0 && isSamePosition(oldBody.pos, body.pos)) {\n // This is very bad, we ran out of precision.\n // if we do not return from the method we'll get into\n // infinite loop here. So we sacrifice correctness of layout, and keep the app running\n // Next layout iteration should get larger bounding box in the first step and fix this\n return;\n }\n }\n // Next iteration should subdivide node further.\n insertStack.push(node, oldBody);\n insertStack.push(node, body);\n }\n }\n }\n}\nreturn createQuadTree;\n\n`}function isSamePosition(dimension){let pattern=createPatternBuilder(dimension);return`\n function isSamePosition(point1, point2) {\n ${pattern("var d{var} = Math.abs(point1.{var} - point2.{var});",{indent:2})}\n \n return ${pattern("d{var} < 1e-8",{join:" && "})};\n } \n`}function setChildBodyCode(dimension){var quadCount=Math.pow(2,dimension);return`\nfunction setChild(node, idx, child) {\n ${function(){let childBody=[];for(let i=0;i<quadCount;++i){let prefix=0===i?" ":" else ";childBody.push(`${prefix}if (idx === ${i}) node.quad${i} = child;`)}return childBody.join("\n")}()}\n}`}function getChildBodyCode(dimension){return`function getChild(node, idx) {\n${function(){let childBody=[],quadCount=Math.pow(2,dimension);for(let i=0;i<quadCount;++i)childBody.push(` if (idx === ${i}) return node.quad${i};`);return childBody.join("\n")}()}\n return null;\n}`}function getQuadNodeCode(dimension){let pattern=createPatternBuilder(dimension),quadCount=Math.pow(2,dimension);return`\nfunction QuadNode() {\n // body stored inside this node. In quad tree only leaf nodes (by construction)\n // contain bodies:\n this.body = null;\n\n // Child nodes are stored in quads. Each quad is presented by number:\n // 0 | 1\n // -----\n // 2 | 3\n${function(indent){let quads=[];for(let i=0;i<quadCount;++i)quads.push(`${indent}quad${i} = null;`);return quads.join("\n")}(" this.")}\n\n // Total mass of current node\n this.mass = 0;\n\n // Center of mass coordinates\n ${pattern("this.mass_{var} = 0;",{indent:2})}\n\n // bounding box coordinates\n ${pattern("this.min_{var} = 0;",{indent:2})}\n ${pattern("this.max_{var} = 0;",{indent:2})}\n}\n`}function getInsertStackCode(){return"\n/**\n * Our implementation of QuadTree is non-recursive to avoid GC hit\n * This data structure represent stack of elements\n * which we are trying to insert into quad tree.\n */\nfunction InsertStack () {\n this.stack = [];\n this.popIdx = 0;\n}\n\nInsertStack.prototype = {\n isEmpty: function() {\n return this.popIdx === 0;\n },\n push: function (node, body) {\n var item = this.stack[this.popIdx];\n if (!item) {\n // we are trying to avoid memory pressure: create new element\n // only when absolutely necessary\n this.stack[this.popIdx] = new InsertStackElement(node, body);\n } else {\n item.node = node;\n item.body = body;\n }\n ++this.popIdx;\n },\n pop: function () {\n if (this.popIdx > 0) {\n return this.stack[--this.popIdx];\n }\n },\n reset: function () {\n this.popIdx = 0;\n }\n};\n\nfunction InsertStackElement(node, body) {\n this.node = node; // QuadTree node\n this.body = body; // physical body which needs to be inserted to node\n}\n"}module.exports=function(dimension){let code=generateQuadTreeFunctionBody(dimension);return new Function(code)()},module.exports.generateQuadTreeFunctionBody=generateQuadTreeFunctionBody,module.exports.getInsertStackCode=getInsertStackCode,module.exports.getQuadNodeCode=getQuadNodeCode,module.exports.isSamePosition=isSamePosition,module.exports.getChildBodyCode=getChildBodyCode,module.exports.setChildBodyCode=setChildBodyCode},{"./createPatternBuilder":2,"./getVariableName":9}],9:[function(require,module,exports){module.exports=function(index){return 0===index?"x":1===index?"y":2===index?"z":"c"+(index+1)}},{}],10:[function(require,module,exports){module.exports=function(settings){var Spring=require("./spring"),merge=require("ngraph.merge"),eventify=require("ngraph.events");if(settings){if(void 0!==settings.springCoeff)throw new Error("springCoeff was renamed to springCoefficient");if(void 0!==settings.dragCoeff)throw new Error("dragCoeff was renamed to dragCoefficient")}settings=merge(settings,{springLength:10,springCoefficient:.8,gravity:-12,theta:.8,dragCoefficient:.9,timeStep:.5,adaptiveTimeStepWeight:0,dimensions:2,debug:!1});var factory=dimensionalCache[settings.dimensions];if(!factory){var dimensions=settings.dimensions;factory={Body:generateCreateBodyFunction(dimensions,settings.debug),createQuadTree:generateQuadTreeFunction(dimensions),createBounds:generateBoundsFunction(dimensions),createDragForce:generateCreateDragForceFunction(dimensions),createSpringForce:generateCreateSpringForceFunction(dimensions),integrate:generateIntegratorFunction(dimensions)},dimensionalCache[dimensions]=factory}var Body=factory.Body,createQuadTree=factory.createQuadTree,createBounds=factory.createBounds,createDragForce=factory.createDragForce,createSpringForce=factory.createSpringForce,integrate=factory.integrate,random=require("ngraph.random").random(42),bodies=[],springs=[],quadTree=createQuadTree(settings,random),bounds=createBounds(bodies,settings,random),springForce=createSpringForce(settings,random),dragForce=createDragForce(settings),forces=[],forceMap=new Map,iterationNumber=0;addForce("nbody",(function(){if(0===bodies.length)return;quadTree.insertBodies(bodies);var i=bodies.length;for(;i--;){var body=bodies[i];body.isPinned||(body.reset(),quadTree.updateBodyForce(body),dragForce.update(body))}})),addForce("spring",(function(){var i=springs.length;for(;i--;)springForce.update(springs[i])}));var publicApi={bodies:bodies,quadTree:quadTree,springs:springs,settings:settings,addForce:addForce,removeForce:function(forceName){var forceIndex=forces.indexOf(forceMap.get(forceName));if(forceIndex<0)return;forces.splice(forceIndex,1),forceMap.delete(forceName)},getForces:function(){return forceMap},step:function(){for(var i=0;i<forces.length;++i)forces[i](iterationNumber);var movement=integrate(bodies,settings.timeStep,settings.adaptiveTimeStepWeight);return iterationNumber+=1,movement},addBody:function(body){if(!body)throw new Error("Body is required");return bodies.push(body),body},addBodyAt:function(pos){if(!pos)throw new Error("Body position is required");var body=(pos=>new Body(pos))(pos);return bodies.push(body),body},removeBody:function(body){if(body){var idx=bodies.indexOf(body);if(!(idx<0))return bodies.splice(idx,1),0===bodies.length&&bounds.reset(),!0}},addSpring:function(body1,body2,springLength,springCoefficient){if(!body1||!body2)throw new Error("Cannot add null spring to force simulator");"number"!=typeof springLength&&(springLength=-1);var spring=new Spring(body1,body2,springLength,springCoefficient>=0?springCoefficient:-1);return springs.push(spring),spring},getTotalMovement:function(){return 0},removeSpring:function(spring){if(spring){var idx=springs.indexOf(spring);return idx>-1?(springs.splice(idx,1),!0):void 0}},getBestNewBodyPosition:function(neighbors){return bounds.getBestNewPosition(neighbors)},getBBox:getBoundingBox,getBoundingBox:getBoundingBox,invalidateBBox:function(){console.warn("invalidateBBox() is deprecated, bounds always recomputed on `getBBox()` call")},gravity:function(value){return void 0!==value?(settings.gravity=value,quadTree.options({gravity:value}),this):settings.gravity},theta:function(value){return void 0!==value?(settings.theta=value,quadTree.options({theta:value}),this):settings.theta},random:random};return function(settings,target){for(var key in settings)augment(settings,target,key)}(settings,publicApi),eventify(publicApi),publicApi;function getBoundingBox(){return bounds.update(),bounds.box}function addForce(forceName,forceFunction){if(forceMap.has(forceName))throw new Error("Force "+forceName+" is already added");forceMap.set(forceName,forceFunction),forces.push(forceFunction)}};var generateCreateBodyFunction=require("./codeGenerators/generateCreateBody"),generateQuadTreeFunction=require("./codeGenerators/generateQuadTree"),generateBoundsFunction=require("./codeGenerators/generateBounds"),generateCreateDragForceFunction=require("./codeGenerators/generateCreateDragForce"),generateCreateSpringForceFunction=require("./codeGenerators/generateCreateSpringForce"),generateIntegratorFunction=require("./codeGenerators/generateIntegrator"),dimensionalCache={};function augment(source,target,key){if(source.hasOwnProperty(key)&&"function"!=typeof target[key]){var sourceIsNumber=Number.isFinite(source[key]);target[key]=sourceIsNumber?function(value){if(void 0!==value){if(!Number.isFinite(value))throw new Error("Value of "+key+" should be a valid number.");return source[key]=value,target}return source[key]}:function(value){return void 0!==value?(source[key]=value,target):source[key]}}}},{"./codeGenerators/generateBounds":3,"./codeGenerators/generateCreateBody":4,"./codeGenerators/generateCreateDragForce":5,"./codeGenerators/generateCreateSpringForce":6,"./codeGenerators/generateIntegrator":7,"./codeGenerators/generateQuadTree":8,"./spring":11,"ngraph.events":12,"ngraph.merge":13,"ngraph.random":14}],11:[function(require,module,exports){module.exports=function(fromBody,toBody,length,springCoefficient){this.from=fromBody,this.to=toBody,this.length=length,this.coefficient=springCoefficient}},{}],12:[function(require,module,exports){module.exports=function(subject){!function(subject){if(!subject)throw new Error("Eventify cannot use falsy object as events subject");for(var reservedWords=["on","fire","off"],i=0;i<reservedWords.length;++i)if(subject.hasOwnProperty(reservedWords[i]))throw new Error("Subject cannot be eventified, since it already has property '"+reservedWords[i]+"'")}(subject);var eventsStorage=function(subject){var registeredEvents=Object.create(null);return{on:function(eventName,callback,ctx){if("function"!=typeof callback)throw new Error("callback is expected to be a function");var handlers=registeredEvents[eventName];return handlers||(handlers=registeredEvents[eventName]=[]),handlers.push({callback:callback,ctx:ctx}),subject},off:function(eventName,callback){if(void 0===eventName)return registeredEvents=Object.create(null),subject;if(registeredEvents[eventName])if("function"!=typeof callback)delete registeredEvents[eventName];else for(var callbacks=registeredEvents[eventName],i=0;i<callbacks.length;++i)callbacks[i].callback===callback&&callbacks.splice(i,1);return subject},fire:function(eventName){var fireArguments,callbacks=registeredEvents[eventName];if(!callbacks)return subject;arguments.length>1&&(fireArguments=Array.prototype.splice.call(arguments,1));for(var i=0;i<callbacks.length;++i){var callbackInfo=callbacks[i];callbackInfo.callback.apply(callbackInfo.ctx,fireArguments)}return subject}}}(subject);return subject.on=eventsStorage.on,subject.off=eventsStorage.off,subject.fire=eventsStorage.fire,subject}},{}],13:[function(require,module,exports){module.exports=function merge(target,options){var key;target||(target={});if(options)for(key in options)if(options.hasOwnProperty(key)){var targetHasIt=target.hasOwnProperty(key),optionsValueType=typeof options[key];!targetHasIt||typeof target[key]!==optionsValueType?target[key]=options[key]:"object"===optionsValueType&&(target[key]=merge(target[key],options[key]))}return target}},{}],14:[function(require,module,exports){function random(inputSeed){return new Generator("number"==typeof inputSeed?inputSeed:+new Date)}function Generator(seed){this.seed=seed}function nextDouble(){var seed=this.seed;return seed=4294967295&(3042594569^(seed=(seed=4294967295&((seed=(seed=4294967295&(3345072700^(seed=seed+2127912214+(seed<<12)&4294967295)^seed>>>19))+374761393+(seed<<5)&4294967295)+3550635116^seed<<9))+4251993797+(seed<<3)&4294967295)^seed>>>16),this.seed=seed,(268435455&seed)/268435456}module.exports=random,module.exports.random=random,module.exports.randomIterator=function(array,customRandom){var localRandom=customRandom||random();if("function"!=typeof localRandom.next)throw new Error("customRandom does not match expected API: next() function is missing");return{forEach:function(callback){var i,j,t;for(i=array.length-1;i>0;--i)j=localRandom.next(i+1),t=array[j],array[j]=array[i],array[i]=t,callback(t);array.length&&callback(array[0])},shuffle:function(){var i,j,t;for(i=array.length-1;i>0;--i)j=localRandom.next(i+1),t=array[j],array[j]=array[i],array[i]=t;return array}}},Generator.prototype.next=function(maxValue){return Math.floor(this.nextDouble()*maxValue)},Generator.prototype.nextDouble=nextDouble,Generator.prototype.uniform=nextDouble,Generator.prototype.gaussian=function(){var r,x,y;do{x=2*this.nextDouble()-1,y=2*this.nextDouble()-1,r=x*x+y*y}while(r>=1||0===r);return x*Math.sqrt(-2*Math.log(r)/r)}},{}]},{},[1])(1)}));
|