ngraph.forcelayout.js 53 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825
  1. (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.ngraphCreateLayout = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&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?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
  2. module.exports = createLayout;
  3. module.exports.simulator = require('./lib/createPhysicsSimulator');
  4. var eventify = require('ngraph.events');
  5. /**
  6. * Creates force based layout for a given graph.
  7. *
  8. * @param {ngraph.graph} graph which needs to be laid out
  9. * @param {object} physicsSettings if you need custom settings
  10. * for physics simulator you can pass your own settings here. If it's not passed
  11. * a default one will be created.
  12. */
  13. function createLayout(graph, physicsSettings) {
  14. if (!graph) {
  15. throw new Error('Graph structure cannot be undefined');
  16. }
  17. var createSimulator = (physicsSettings && physicsSettings.createSimulator) || require('./lib/createPhysicsSimulator');
  18. var physicsSimulator = createSimulator(physicsSettings);
  19. if (Array.isArray(physicsSettings)) throw new Error('Physics settings is expected to be an object');
  20. var nodeMass = defaultNodeMass;
  21. if (physicsSettings && typeof physicsSettings.nodeMass === 'function') {
  22. nodeMass = physicsSettings.nodeMass;
  23. }
  24. var nodeBodies = new Map();
  25. var springs = {};
  26. var bodiesCount = 0;
  27. var springTransform = physicsSimulator.settings.springTransform || noop;
  28. // Initialize physics with what we have in the graph:
  29. initPhysics();
  30. listenToEvents();
  31. var wasStable = false;
  32. var api = {
  33. /**
  34. * Performs one step of iterative layout algorithm
  35. *
  36. * @returns {boolean} true if the system should be considered stable; False otherwise.
  37. * The system is stable if no further call to `step()` can improve the layout.
  38. */
  39. step: function() {
  40. if (bodiesCount === 0) {
  41. updateStableStatus(true);
  42. return true;
  43. }
  44. var lastMove = physicsSimulator.step();
  45. // Save the movement in case if someone wants to query it in the step
  46. // callback.
  47. api.lastMove = lastMove;
  48. // Allow listeners to perform low-level actions after nodes are updated.
  49. api.fire('step');
  50. var ratio = lastMove/bodiesCount;
  51. var isStableNow = ratio <= 0.01; // TODO: The number is somewhat arbitrary...
  52. updateStableStatus(isStableNow);
  53. return isStableNow;
  54. },
  55. /**
  56. * For a given `nodeId` returns position
  57. */
  58. getNodePosition: function (nodeId) {
  59. return getInitializedBody(nodeId).pos;
  60. },
  61. /**
  62. * Sets position of a node to a given coordinates
  63. * @param {string} nodeId node identifier
  64. * @param {number} x position of a node
  65. * @param {number} y position of a node
  66. * @param {number=} z position of node (only if applicable to body)
  67. */
  68. setNodePosition: function (nodeId) {
  69. var body = getInitializedBody(nodeId);
  70. body.setPosition.apply(body, Array.prototype.slice.call(arguments, 1));
  71. },
  72. /**
  73. * @returns {Object} Link position by link id
  74. * @returns {Object.from} {x, y} coordinates of link start
  75. * @returns {Object.to} {x, y} coordinates of link end
  76. */
  77. getLinkPosition: function (linkId) {
  78. var spring = springs[linkId];
  79. if (spring) {
  80. return {
  81. from: spring.from.pos,
  82. to: spring.to.pos
  83. };
  84. }
  85. },
  86. /**
  87. * @returns {Object} area required to fit in the graph. Object contains
  88. * `x1`, `y1` - top left coordinates
  89. * `x2`, `y2` - bottom right coordinates
  90. */
  91. getGraphRect: function () {
  92. return physicsSimulator.getBBox();
  93. },
  94. /**
  95. * Iterates over each body in the layout simulator and performs a callback(body, nodeId)
  96. */
  97. forEachBody: forEachBody,
  98. /*
  99. * Requests layout algorithm to pin/unpin node to its current position
  100. * Pinned nodes should not be affected by layout algorithm and always
  101. * remain at their position
  102. */
  103. pinNode: function (node, isPinned) {
  104. var body = getInitializedBody(node.id);
  105. body.isPinned = !!isPinned;
  106. },
  107. /**
  108. * Checks whether given graph's node is currently pinned
  109. */
  110. isNodePinned: function (node) {
  111. return getInitializedBody(node.id).isPinned;
  112. },
  113. /**
  114. * Request to release all resources
  115. */
  116. dispose: function() {
  117. graph.off('changed', onGraphChanged);
  118. api.fire('disposed');
  119. },
  120. /**
  121. * Gets physical body for a given node id. If node is not found undefined
  122. * value is returned.
  123. */
  124. getBody: getBody,
  125. /**
  126. * Gets spring for a given edge.
  127. *
  128. * @param {string} linkId link identifer. If two arguments are passed then
  129. * this argument is treated as formNodeId
  130. * @param {string=} toId when defined this parameter denotes head of the link
  131. * and first argument is treated as tail of the link (fromId)
  132. */
  133. getSpring: getSpring,
  134. /**
  135. * Returns length of cumulative force vector. The closer this to zero - the more stable the system is
  136. */
  137. getForceVectorLength: getForceVectorLength,
  138. /**
  139. * [Read only] Gets current physics simulator
  140. */
  141. simulator: physicsSimulator,
  142. /**
  143. * Gets the graph that was used for layout
  144. */
  145. graph: graph,
  146. /**
  147. * Gets amount of movement performed during last step operation
  148. */
  149. lastMove: 0
  150. };
  151. eventify(api);
  152. return api;
  153. function updateStableStatus(isStableNow) {
  154. if (wasStable !== isStableNow) {
  155. wasStable = isStableNow;
  156. onStableChanged(isStableNow);
  157. }
  158. }
  159. function forEachBody(cb) {
  160. nodeBodies.forEach(cb);
  161. }
  162. function getForceVectorLength() {
  163. var fx = 0, fy = 0;
  164. forEachBody(function(body) {
  165. fx += Math.abs(body.force.x);
  166. fy += Math.abs(body.force.y);
  167. });
  168. return Math.sqrt(fx * fx + fy * fy);
  169. }
  170. function getSpring(fromId, toId) {
  171. var linkId;
  172. if (toId === undefined) {
  173. if (typeof fromId !== 'object') {
  174. // assume fromId as a linkId:
  175. linkId = fromId;
  176. } else {
  177. // assume fromId to be a link object:
  178. linkId = fromId.id;
  179. }
  180. } else {
  181. // toId is defined, should grab link:
  182. var link = graph.hasLink(fromId, toId);
  183. if (!link) return;
  184. linkId = link.id;
  185. }
  186. return springs[linkId];
  187. }
  188. function getBody(nodeId) {
  189. return nodeBodies.get(nodeId);
  190. }
  191. function listenToEvents() {
  192. graph.on('changed', onGraphChanged);
  193. }
  194. function onStableChanged(isStable) {
  195. api.fire('stable', isStable);
  196. }
  197. function onGraphChanged(changes) {
  198. for (var i = 0; i < changes.length; ++i) {
  199. var change = changes[i];
  200. if (change.changeType === 'add') {
  201. if (change.node) {
  202. initBody(change.node.id);
  203. }
  204. if (change.link) {
  205. initLink(change.link);
  206. }
  207. } else if (change.changeType === 'remove') {
  208. if (change.node) {
  209. releaseNode(change.node);
  210. }
  211. if (change.link) {
  212. releaseLink(change.link);
  213. }
  214. }
  215. }
  216. bodiesCount = graph.getNodesCount();
  217. }
  218. function initPhysics() {
  219. bodiesCount = 0;
  220. graph.forEachNode(function (node) {
  221. initBody(node.id);
  222. bodiesCount += 1;
  223. });
  224. graph.forEachLink(initLink);
  225. }
  226. function initBody(nodeId) {
  227. var body = nodeBodies.get(nodeId);
  228. if (!body) {
  229. var node = graph.getNode(nodeId);
  230. if (!node) {
  231. throw new Error('initBody() was called with unknown node id');
  232. }
  233. var pos = node.position;
  234. if (!pos) {
  235. var neighbors = getNeighborBodies(node);
  236. pos = physicsSimulator.getBestNewBodyPosition(neighbors);
  237. }
  238. body = physicsSimulator.addBodyAt(pos);
  239. body.id = nodeId;
  240. nodeBodies.set(nodeId, body);
  241. updateBodyMass(nodeId);
  242. if (isNodeOriginallyPinned(node)) {
  243. body.isPinned = true;
  244. }
  245. }
  246. }
  247. function releaseNode(node) {
  248. var nodeId = node.id;
  249. var body = nodeBodies.get(nodeId);
  250. if (body) {
  251. nodeBodies.delete(nodeId);
  252. physicsSimulator.removeBody(body);
  253. }
  254. }
  255. function initLink(link) {
  256. updateBodyMass(link.fromId);
  257. updateBodyMass(link.toId);
  258. var fromBody = nodeBodies.get(link.fromId),
  259. toBody = nodeBodies.get(link.toId),
  260. spring = physicsSimulator.addSpring(fromBody, toBody, link.length);
  261. springTransform(link, spring);
  262. springs[link.id] = spring;
  263. }
  264. function releaseLink(link) {
  265. var spring = springs[link.id];
  266. if (spring) {
  267. var from = graph.getNode(link.fromId),
  268. to = graph.getNode(link.toId);
  269. if (from) updateBodyMass(from.id);
  270. if (to) updateBodyMass(to.id);
  271. delete springs[link.id];
  272. physicsSimulator.removeSpring(spring);
  273. }
  274. }
  275. function getNeighborBodies(node) {
  276. // TODO: Could probably be done better on memory
  277. var neighbors = [];
  278. if (!node.links) {
  279. return neighbors;
  280. }
  281. var maxNeighbors = Math.min(node.links.length, 2);
  282. for (var i = 0; i < maxNeighbors; ++i) {
  283. var link = node.links[i];
  284. var otherBody = link.fromId !== node.id ? nodeBodies.get(link.fromId) : nodeBodies.get(link.toId);
  285. if (otherBody && otherBody.pos) {
  286. neighbors.push(otherBody);
  287. }
  288. }
  289. return neighbors;
  290. }
  291. function updateBodyMass(nodeId) {
  292. var body = nodeBodies.get(nodeId);
  293. body.mass = nodeMass(nodeId);
  294. if (Number.isNaN(body.mass)) {
  295. throw new Error('Node mass should be a number');
  296. }
  297. }
  298. /**
  299. * Checks whether graph node has in its settings pinned attribute,
  300. * which means layout algorithm cannot move it. Node can be marked
  301. * as pinned, if it has "isPinned" attribute, or when node.data has it.
  302. *
  303. * @param {Object} node a graph node to check
  304. * @return {Boolean} true if node should be treated as pinned; false otherwise.
  305. */
  306. function isNodeOriginallyPinned(node) {
  307. return (node && (node.isPinned || (node.data && node.data.isPinned)));
  308. }
  309. function getInitializedBody(nodeId) {
  310. var body = nodeBodies.get(nodeId);
  311. if (!body) {
  312. initBody(nodeId);
  313. body = nodeBodies.get(nodeId);
  314. }
  315. return body;
  316. }
  317. /**
  318. * Calculates mass of a body, which corresponds to node with given id.
  319. *
  320. * @param {String|Number} nodeId identifier of a node, for which body mass needs to be calculated
  321. * @returns {Number} recommended mass of the body;
  322. */
  323. function defaultNodeMass(nodeId) {
  324. var links = graph.getLinks(nodeId);
  325. if (!links) return 1;
  326. return 1 + links.length / 3.0;
  327. }
  328. }
  329. function noop() { }
  330. },{"./lib/createPhysicsSimulator":10,"ngraph.events":12}],2:[function(require,module,exports){
  331. const getVariableName = require('./getVariableName');
  332. module.exports = function createPatternBuilder(dimension) {
  333. return pattern;
  334. function pattern(template, config) {
  335. let indent = (config && config.indent) || 0;
  336. let join = (config && config.join !== undefined) ? config.join : '\n';
  337. let indentString = Array(indent + 1).join(' ');
  338. let buffer = [];
  339. for (let i = 0; i < dimension; ++i) {
  340. let variableName = getVariableName(i);
  341. let prefix = (i === 0) ? '' : indentString;
  342. buffer.push(prefix + template.replace(/{var}/g, variableName));
  343. }
  344. return buffer.join(join);
  345. }
  346. };
  347. },{"./getVariableName":9}],3:[function(require,module,exports){
  348. module.exports = generateBoundsFunction;
  349. module.exports.generateFunctionBody = generateBoundsFunctionBody;
  350. const createPatternBuilder = require('./createPatternBuilder');
  351. function generateBoundsFunction(dimension) {
  352. let code = generateBoundsFunctionBody(dimension);
  353. return new Function('bodies', 'settings', 'random', code);
  354. }
  355. function generateBoundsFunctionBody(dimension) {
  356. let pattern = createPatternBuilder(dimension);
  357. let code = `
  358. var boundingBox = {
  359. ${pattern('min_{var}: 0, max_{var}: 0,', {indent: 4})}
  360. };
  361. return {
  362. box: boundingBox,
  363. update: updateBoundingBox,
  364. reset: resetBoundingBox,
  365. getBestNewPosition: function (neighbors) {
  366. var ${pattern('base_{var} = 0', {join: ', '})};
  367. if (neighbors.length) {
  368. for (var i = 0; i < neighbors.length; ++i) {
  369. let neighborPos = neighbors[i].pos;
  370. ${pattern('base_{var} += neighborPos.{var};', {indent: 10})}
  371. }
  372. ${pattern('base_{var} /= neighbors.length;', {indent: 8})}
  373. } else {
  374. ${pattern('base_{var} = (boundingBox.min_{var} + boundingBox.max_{var}) / 2;', {indent: 8})}
  375. }
  376. var springLength = settings.springLength;
  377. return {
  378. ${pattern('{var}: base_{var} + (random.nextDouble() - 0.5) * springLength,', {indent: 8})}
  379. };
  380. }
  381. };
  382. function updateBoundingBox() {
  383. var i = bodies.length;
  384. if (i === 0) return; // No bodies - no borders.
  385. ${pattern('var max_{var} = -Infinity;', {indent: 4})}
  386. ${pattern('var min_{var} = Infinity;', {indent: 4})}
  387. while(i--) {
  388. // this is O(n), it could be done faster with quadtree, if we check the root node bounds
  389. var bodyPos = bodies[i].pos;
  390. ${pattern('if (bodyPos.{var} < min_{var}) min_{var} = bodyPos.{var};', {indent: 6})}
  391. ${pattern('if (bodyPos.{var} > max_{var}) max_{var} = bodyPos.{var};', {indent: 6})}
  392. }
  393. ${pattern('boundingBox.min_{var} = min_{var};', {indent: 4})}
  394. ${pattern('boundingBox.max_{var} = max_{var};', {indent: 4})}
  395. }
  396. function resetBoundingBox() {
  397. ${pattern('boundingBox.min_{var} = boundingBox.max_{var} = 0;', {indent: 4})}
  398. }
  399. `;
  400. return code;
  401. }
  402. },{"./createPatternBuilder":2}],4:[function(require,module,exports){
  403. const createPatternBuilder = require('./createPatternBuilder');
  404. module.exports = generateCreateBodyFunction;
  405. module.exports.generateCreateBodyFunctionBody = generateCreateBodyFunctionBody;
  406. // InlineTransform: getVectorCode
  407. module.exports.getVectorCode = getVectorCode;
  408. // InlineTransform: getBodyCode
  409. module.exports.getBodyCode = getBodyCode;
  410. // InlineTransformExport: module.exports = function() { return Body; }
  411. function generateCreateBodyFunction(dimension, debugSetters) {
  412. let code = generateCreateBodyFunctionBody(dimension, debugSetters);
  413. let {Body} = (new Function(code))();
  414. return Body;
  415. }
  416. function generateCreateBodyFunctionBody(dimension, debugSetters) {
  417. let code = `
  418. ${getVectorCode(dimension, debugSetters)}
  419. ${getBodyCode(dimension, debugSetters)}
  420. return {Body: Body, Vector: Vector};
  421. `;
  422. return code;
  423. }
  424. function getBodyCode(dimension) {
  425. let pattern = createPatternBuilder(dimension);
  426. let variableList = pattern('{var}', {join: ', '});
  427. return `
  428. function Body(${variableList}) {
  429. this.isPinned = false;
  430. this.pos = new Vector(${variableList});
  431. this.force = new Vector();
  432. this.velocity = new Vector();
  433. this.mass = 1;
  434. this.springCount = 0;
  435. this.springLength = 0;
  436. }
  437. Body.prototype.reset = function() {
  438. this.force.reset();
  439. this.springCount = 0;
  440. this.springLength = 0;
  441. }
  442. Body.prototype.setPosition = function (${variableList}) {
  443. ${pattern('this.pos.{var} = {var} || 0;', {indent: 2})}
  444. };`;
  445. }
  446. function getVectorCode(dimension, debugSetters) {
  447. let pattern = createPatternBuilder(dimension);
  448. let setters = '';
  449. if (debugSetters) {
  450. setters = `${pattern("\n\
  451. var v{var};\n\
  452. Object.defineProperty(this, '{var}', {\n\
  453. set: function(v) { \n\
  454. if (!Number.isFinite(v)) throw new Error('Cannot set non-numbers to {var}');\n\
  455. v{var} = v; \n\
  456. },\n\
  457. get: function() { return v{var}; }\n\
  458. });")}`;
  459. }
  460. let variableList = pattern('{var}', {join: ', '});
  461. return `function Vector(${variableList}) {
  462. ${setters}
  463. if (typeof arguments[0] === 'object') {
  464. // could be another vector
  465. let v = arguments[0];
  466. ${pattern('if (!Number.isFinite(v.{var})) throw new Error("Expected value is not a finite number at Vector constructor ({var})");', {indent: 4})}
  467. ${pattern('this.{var} = v.{var};', {indent: 4})}
  468. } else {
  469. ${pattern('this.{var} = typeof {var} === "number" ? {var} : 0;', {indent: 4})}
  470. }
  471. }
  472. Vector.prototype.reset = function () {
  473. ${pattern('this.{var} = ', {join: ''})}0;
  474. };`;
  475. }
  476. },{"./createPatternBuilder":2}],5:[function(require,module,exports){
  477. const createPatternBuilder = require('./createPatternBuilder');
  478. module.exports = generateCreateDragForceFunction;
  479. module.exports.generateCreateDragForceFunctionBody = generateCreateDragForceFunctionBody;
  480. function generateCreateDragForceFunction(dimension) {
  481. let code = generateCreateDragForceFunctionBody(dimension);
  482. return new Function('options', code);
  483. }
  484. function generateCreateDragForceFunctionBody(dimension) {
  485. let pattern = createPatternBuilder(dimension);
  486. let code = `
  487. if (!Number.isFinite(options.dragCoefficient)) throw new Error('dragCoefficient is not a finite number');
  488. return {
  489. update: function(body) {
  490. ${pattern('body.force.{var} -= options.dragCoefficient * body.velocity.{var};', {indent: 6})}
  491. }
  492. };
  493. `;
  494. return code;
  495. }
  496. },{"./createPatternBuilder":2}],6:[function(require,module,exports){
  497. const createPatternBuilder = require('./createPatternBuilder');
  498. module.exports = generateCreateSpringForceFunction;
  499. module.exports.generateCreateSpringForceFunctionBody = generateCreateSpringForceFunctionBody;
  500. function generateCreateSpringForceFunction(dimension) {
  501. let code = generateCreateSpringForceFunctionBody(dimension);
  502. return new Function('options', 'random', code);
  503. }
  504. function generateCreateSpringForceFunctionBody(dimension) {
  505. let pattern = createPatternBuilder(dimension);
  506. let code = `
  507. if (!Number.isFinite(options.springCoefficient)) throw new Error('Spring coefficient is not a number');
  508. if (!Number.isFinite(options.springLength)) throw new Error('Spring length is not a number');
  509. return {
  510. /**
  511. * Updates forces acting on a spring
  512. */
  513. update: function (spring) {
  514. var body1 = spring.from;
  515. var body2 = spring.to;
  516. var length = spring.length < 0 ? options.springLength : spring.length;
  517. ${pattern('var d{var} = body2.pos.{var} - body1.pos.{var};', {indent: 6})}
  518. var r = Math.sqrt(${pattern('d{var} * d{var}', {join: ' + '})});
  519. if (r === 0) {
  520. ${pattern('d{var} = (random.nextDouble() - 0.5) / 50;', {indent: 8})}
  521. r = Math.sqrt(${pattern('d{var} * d{var}', {join: ' + '})});
  522. }
  523. var d = r - length;
  524. var coefficient = ((spring.coefficient > 0) ? spring.coefficient : options.springCoefficient) * d / r;
  525. ${pattern('body1.force.{var} += coefficient * d{var}', {indent: 6})};
  526. body1.springCount += 1;
  527. body1.springLength += r;
  528. ${pattern('body2.force.{var} -= coefficient * d{var}', {indent: 6})};
  529. body2.springCount += 1;
  530. body2.springLength += r;
  531. }
  532. };
  533. `;
  534. return code;
  535. }
  536. },{"./createPatternBuilder":2}],7:[function(require,module,exports){
  537. const createPatternBuilder = require('./createPatternBuilder');
  538. module.exports = generateIntegratorFunction;
  539. module.exports.generateIntegratorFunctionBody = generateIntegratorFunctionBody;
  540. function generateIntegratorFunction(dimension) {
  541. let code = generateIntegratorFunctionBody(dimension);
  542. return new Function('bodies', 'timeStep', 'adaptiveTimeStepWeight', code);
  543. }
  544. function generateIntegratorFunctionBody(dimension) {
  545. let pattern = createPatternBuilder(dimension);
  546. let code = `
  547. var length = bodies.length;
  548. if (length === 0) return 0;
  549. ${pattern('var d{var} = 0, t{var} = 0;', {indent: 2})}
  550. for (var i = 0; i < length; ++i) {
  551. var body = bodies[i];
  552. if (body.isPinned) continue;
  553. if (adaptiveTimeStepWeight && body.springCount) {
  554. timeStep = (adaptiveTimeStepWeight * body.springLength/body.springCount);
  555. }
  556. var coeff = timeStep / body.mass;
  557. ${pattern('body.velocity.{var} += coeff * body.force.{var};', {indent: 4})}
  558. ${pattern('var v{var} = body.velocity.{var};', {indent: 4})}
  559. var v = Math.sqrt(${pattern('v{var} * v{var}', {join: ' + '})});
  560. if (v > 1) {
  561. // We normalize it so that we move within timeStep range.
  562. // for the case when v <= 1 - we let velocity to fade out.
  563. ${pattern('body.velocity.{var} = v{var} / v;', {indent: 6})}
  564. }
  565. ${pattern('d{var} = timeStep * body.velocity.{var};', {indent: 4})}
  566. ${pattern('body.pos.{var} += d{var};', {indent: 4})}
  567. ${pattern('t{var} += Math.abs(d{var});', {indent: 4})}
  568. }
  569. return (${pattern('t{var} * t{var}', {join: ' + '})})/length;
  570. `;
  571. return code;
  572. }
  573. },{"./createPatternBuilder":2}],8:[function(require,module,exports){
  574. const createPatternBuilder = require('./createPatternBuilder');
  575. const getVariableName = require('./getVariableName');
  576. module.exports = generateQuadTreeFunction;
  577. module.exports.generateQuadTreeFunctionBody = generateQuadTreeFunctionBody;
  578. // These exports are for InlineTransform tool.
  579. // InlineTransform: getInsertStackCode
  580. module.exports.getInsertStackCode = getInsertStackCode;
  581. // InlineTransform: getQuadNodeCode
  582. module.exports.getQuadNodeCode = getQuadNodeCode;
  583. // InlineTransform: isSamePosition
  584. module.exports.isSamePosition = isSamePosition;
  585. // InlineTransform: getChildBodyCode
  586. module.exports.getChildBodyCode = getChildBodyCode;
  587. // InlineTransform: setChildBodyCode
  588. module.exports.setChildBodyCode = setChildBodyCode;
  589. function generateQuadTreeFunction(dimension) {
  590. let code = generateQuadTreeFunctionBody(dimension);
  591. return (new Function(code))();
  592. }
  593. function generateQuadTreeFunctionBody(dimension) {
  594. let pattern = createPatternBuilder(dimension);
  595. let quadCount = Math.pow(2, dimension);
  596. let code = `
  597. ${getInsertStackCode()}
  598. ${getQuadNodeCode(dimension)}
  599. ${isSamePosition(dimension)}
  600. ${getChildBodyCode(dimension)}
  601. ${setChildBodyCode(dimension)}
  602. function createQuadTree(options, random) {
  603. options = options || {};
  604. options.gravity = typeof options.gravity === 'number' ? options.gravity : -1;
  605. options.theta = typeof options.theta === 'number' ? options.theta : 0.8;
  606. var gravity = options.gravity;
  607. var updateQueue = [];
  608. var insertStack = new InsertStack();
  609. var theta = options.theta;
  610. var nodesCache = [];
  611. var currentInCache = 0;
  612. var root = newNode();
  613. return {
  614. insertBodies: insertBodies,
  615. /**
  616. * Gets root node if it is present
  617. */
  618. getRoot: function() {
  619. return root;
  620. },
  621. updateBodyForce: update,
  622. options: function(newOptions) {
  623. if (newOptions) {
  624. if (typeof newOptions.gravity === 'number') {
  625. gravity = newOptions.gravity;
  626. }
  627. if (typeof newOptions.theta === 'number') {
  628. theta = newOptions.theta;
  629. }
  630. return this;
  631. }
  632. return {
  633. gravity: gravity,
  634. theta: theta
  635. };
  636. }
  637. };
  638. function newNode() {
  639. // To avoid pressure on GC we reuse nodes.
  640. var node = nodesCache[currentInCache];
  641. if (node) {
  642. ${assignQuads(' node.')}
  643. node.body = null;
  644. node.mass = ${pattern('node.mass_{var} = ', {join: ''})}0;
  645. ${pattern('node.min_{var} = node.max_{var} = ', {join: ''})}0;
  646. } else {
  647. node = new QuadNode();
  648. nodesCache[currentInCache] = node;
  649. }
  650. ++currentInCache;
  651. return node;
  652. }
  653. function update(sourceBody) {
  654. var queue = updateQueue;
  655. var v;
  656. ${pattern('var d{var};', {indent: 4})}
  657. var r;
  658. ${pattern('var f{var} = 0;', {indent: 4})}
  659. var queueLength = 1;
  660. var shiftIdx = 0;
  661. var pushIdx = 1;
  662. queue[0] = root;
  663. while (queueLength) {
  664. var node = queue[shiftIdx];
  665. var body = node.body;
  666. queueLength -= 1;
  667. shiftIdx += 1;
  668. var differentBody = (body !== sourceBody);
  669. if (body && differentBody) {
  670. // If the current node is a leaf node (and it is not source body),
  671. // calculate the force exerted by the current node on body, and add this
  672. // amount to body's net force.
  673. ${pattern('d{var} = body.pos.{var} - sourceBody.pos.{var};', {indent: 8})}
  674. r = Math.sqrt(${pattern('d{var} * d{var}', {join: ' + '})});
  675. if (r === 0) {
  676. // Poor man's protection against zero distance.
  677. ${pattern('d{var} = (random.nextDouble() - 0.5) / 50;', {indent: 10})}
  678. r = Math.sqrt(${pattern('d{var} * d{var}', {join: ' + '})});
  679. }
  680. // This is standard gravitation force calculation but we divide
  681. // by r^3 to save two operations when normalizing force vector.
  682. v = gravity * body.mass * sourceBody.mass / (r * r * r);
  683. ${pattern('f{var} += v * d{var};', {indent: 8})}
  684. } else if (differentBody) {
  685. // Otherwise, calculate the ratio s / r, where s is the width of the region
  686. // represented by the internal node, and r is the distance between the body
  687. // and the node's center-of-mass
  688. ${pattern('d{var} = node.mass_{var} / node.mass - sourceBody.pos.{var};', {indent: 8})}
  689. r = Math.sqrt(${pattern('d{var} * d{var}', {join: ' + '})});
  690. if (r === 0) {
  691. // Sorry about code duplication. I don't want to create many functions
  692. // right away. Just want to see performance first.
  693. ${pattern('d{var} = (random.nextDouble() - 0.5) / 50;', {indent: 10})}
  694. r = Math.sqrt(${pattern('d{var} * d{var}', {join: ' + '})});
  695. }
  696. // If s / r < θ, treat this internal node as a single body, and calculate the
  697. // force it exerts on sourceBody, and add this amount to sourceBody's net force.
  698. if ((node.max_${getVariableName(0)} - node.min_${getVariableName(0)}) / r < theta) {
  699. // in the if statement above we consider node's width only
  700. // because the region was made into square during tree creation.
  701. // Thus there is no difference between using width or height.
  702. v = gravity * node.mass * sourceBody.mass / (r * r * r);
  703. ${pattern('f{var} += v * d{var};', {indent: 10})}
  704. } else {
  705. // Otherwise, run the procedure recursively on each of the current node's children.
  706. // I intentionally unfolded this loop, to save several CPU cycles.
  707. ${runRecursiveOnChildren()}
  708. }
  709. }
  710. }
  711. ${pattern('sourceBody.force.{var} += f{var};', {indent: 4})}
  712. }
  713. function insertBodies(bodies) {
  714. ${pattern('var {var}min = Number.MAX_VALUE;', {indent: 4})}
  715. ${pattern('var {var}max = Number.MIN_VALUE;', {indent: 4})}
  716. var i = bodies.length;
  717. // To reduce quad tree depth we are looking for exact bounding box of all particles.
  718. while (i--) {
  719. var pos = bodies[i].pos;
  720. ${pattern('if (pos.{var} < {var}min) {var}min = pos.{var};', {indent: 6})}
  721. ${pattern('if (pos.{var} > {var}max) {var}max = pos.{var};', {indent: 6})}
  722. }
  723. // Makes the bounds square.
  724. var maxSideLength = -Infinity;
  725. ${pattern('if ({var}max - {var}min > maxSideLength) maxSideLength = {var}max - {var}min ;', {indent: 4})}
  726. currentInCache = 0;
  727. root = newNode();
  728. ${pattern('root.min_{var} = {var}min;', {indent: 4})}
  729. ${pattern('root.max_{var} = {var}min + maxSideLength;', {indent: 4})}
  730. i = bodies.length - 1;
  731. if (i >= 0) {
  732. root.body = bodies[i];
  733. }
  734. while (i--) {
  735. insert(bodies[i], root);
  736. }
  737. }
  738. function insert(newBody) {
  739. insertStack.reset();
  740. insertStack.push(root, newBody);
  741. while (!insertStack.isEmpty()) {
  742. var stackItem = insertStack.pop();
  743. var node = stackItem.node;
  744. var body = stackItem.body;
  745. if (!node.body) {
  746. // This is internal node. Update the total mass of the node and center-of-mass.
  747. ${pattern('var {var} = body.pos.{var};', {indent: 8})}
  748. node.mass += body.mass;
  749. ${pattern('node.mass_{var} += body.mass * {var};', {indent: 8})}
  750. // Recursively insert the body in the appropriate quadrant.
  751. // But first find the appropriate quadrant.
  752. var quadIdx = 0; // Assume we are in the 0's quad.
  753. ${pattern('var min_{var} = node.min_{var};', {indent: 8})}
  754. ${pattern('var max_{var} = (min_{var} + node.max_{var}) / 2;', {indent: 8})}
  755. ${assignInsertionQuadIndex(8)}
  756. var child = getChild(node, quadIdx);
  757. if (!child) {
  758. // The node is internal but this quadrant is not taken. Add
  759. // subnode to it.
  760. child = newNode();
  761. ${pattern('child.min_{var} = min_{var};', {indent: 10})}
  762. ${pattern('child.max_{var} = max_{var};', {indent: 10})}
  763. child.body = body;
  764. setChild(node, quadIdx, child);
  765. } else {
  766. // continue searching in this quadrant.
  767. insertStack.push(child, body);
  768. }
  769. } else {
  770. // We are trying to add to the leaf node.
  771. // We have to convert current leaf into internal node
  772. // and continue adding two nodes.
  773. var oldBody = node.body;
  774. node.body = null; // internal nodes do not cary bodies
  775. if (isSamePosition(oldBody.pos, body.pos)) {
  776. // Prevent infinite subdivision by bumping one node
  777. // anywhere in this quadrant
  778. var retriesCount = 3;
  779. do {
  780. var offset = random.nextDouble();
  781. ${pattern('var d{var} = (node.max_{var} - node.min_{var}) * offset;', {indent: 12})}
  782. ${pattern('oldBody.pos.{var} = node.min_{var} + d{var};', {indent: 12})}
  783. retriesCount -= 1;
  784. // Make sure we don't bump it out of the box. If we do, next iteration should fix it
  785. } while (retriesCount > 0 && isSamePosition(oldBody.pos, body.pos));
  786. if (retriesCount === 0 && isSamePosition(oldBody.pos, body.pos)) {
  787. // This is very bad, we ran out of precision.
  788. // if we do not return from the method we'll get into
  789. // infinite loop here. So we sacrifice correctness of layout, and keep the app running
  790. // Next layout iteration should get larger bounding box in the first step and fix this
  791. return;
  792. }
  793. }
  794. // Next iteration should subdivide node further.
  795. insertStack.push(node, oldBody);
  796. insertStack.push(node, body);
  797. }
  798. }
  799. }
  800. }
  801. return createQuadTree;
  802. `;
  803. return code;
  804. function assignInsertionQuadIndex(indentCount) {
  805. let insertionCode = [];
  806. let indent = Array(indentCount + 1).join(' ');
  807. for (let i = 0; i < dimension; ++i) {
  808. insertionCode.push(indent + `if (${getVariableName(i)} > max_${getVariableName(i)}) {`);
  809. insertionCode.push(indent + ` quadIdx = quadIdx + ${Math.pow(2, i)};`);
  810. insertionCode.push(indent + ` min_${getVariableName(i)} = max_${getVariableName(i)};`);
  811. insertionCode.push(indent + ` max_${getVariableName(i)} = node.max_${getVariableName(i)};`);
  812. insertionCode.push(indent + `}`);
  813. }
  814. return insertionCode.join('\n');
  815. // if (x > max_x) { // somewhere in the eastern part.
  816. // quadIdx = quadIdx + 1;
  817. // left = right;
  818. // right = node.right;
  819. // }
  820. }
  821. function runRecursiveOnChildren() {
  822. let indent = Array(11).join(' ');
  823. let recursiveCode = [];
  824. for (let i = 0; i < quadCount; ++i) {
  825. recursiveCode.push(indent + `if (node.quad${i}) {`);
  826. recursiveCode.push(indent + ` queue[pushIdx] = node.quad${i};`);
  827. recursiveCode.push(indent + ` queueLength += 1;`);
  828. recursiveCode.push(indent + ` pushIdx += 1;`);
  829. recursiveCode.push(indent + `}`);
  830. }
  831. return recursiveCode.join('\n');
  832. // if (node.quad0) {
  833. // queue[pushIdx] = node.quad0;
  834. // queueLength += 1;
  835. // pushIdx += 1;
  836. // }
  837. }
  838. function assignQuads(indent) {
  839. // this.quad0 = null;
  840. // this.quad1 = null;
  841. // this.quad2 = null;
  842. // this.quad3 = null;
  843. let quads = [];
  844. for (let i = 0; i < quadCount; ++i) {
  845. quads.push(`${indent}quad${i} = null;`);
  846. }
  847. return quads.join('\n');
  848. }
  849. }
  850. function isSamePosition(dimension) {
  851. let pattern = createPatternBuilder(dimension);
  852. return `
  853. function isSamePosition(point1, point2) {
  854. ${pattern('var d{var} = Math.abs(point1.{var} - point2.{var});', {indent: 2})}
  855. return ${pattern('d{var} < 1e-8', {join: ' && '})};
  856. }
  857. `;
  858. }
  859. function setChildBodyCode(dimension) {
  860. var quadCount = Math.pow(2, dimension);
  861. return `
  862. function setChild(node, idx, child) {
  863. ${setChildBody()}
  864. }`;
  865. function setChildBody() {
  866. let childBody = [];
  867. for (let i = 0; i < quadCount; ++i) {
  868. let prefix = (i === 0) ? ' ' : ' else ';
  869. childBody.push(`${prefix}if (idx === ${i}) node.quad${i} = child;`);
  870. }
  871. return childBody.join('\n');
  872. // if (idx === 0) node.quad0 = child;
  873. // else if (idx === 1) node.quad1 = child;
  874. // else if (idx === 2) node.quad2 = child;
  875. // else if (idx === 3) node.quad3 = child;
  876. }
  877. }
  878. function getChildBodyCode(dimension) {
  879. return `function getChild(node, idx) {
  880. ${getChildBody()}
  881. return null;
  882. }`;
  883. function getChildBody() {
  884. let childBody = [];
  885. let quadCount = Math.pow(2, dimension);
  886. for (let i = 0; i < quadCount; ++i) {
  887. childBody.push(` if (idx === ${i}) return node.quad${i};`);
  888. }
  889. return childBody.join('\n');
  890. // if (idx === 0) return node.quad0;
  891. // if (idx === 1) return node.quad1;
  892. // if (idx === 2) return node.quad2;
  893. // if (idx === 3) return node.quad3;
  894. }
  895. }
  896. function getQuadNodeCode(dimension) {
  897. let pattern = createPatternBuilder(dimension);
  898. let quadCount = Math.pow(2, dimension);
  899. var quadNodeCode = `
  900. function QuadNode() {
  901. // body stored inside this node. In quad tree only leaf nodes (by construction)
  902. // contain bodies:
  903. this.body = null;
  904. // Child nodes are stored in quads. Each quad is presented by number:
  905. // 0 | 1
  906. // -----
  907. // 2 | 3
  908. ${assignQuads(' this.')}
  909. // Total mass of current node
  910. this.mass = 0;
  911. // Center of mass coordinates
  912. ${pattern('this.mass_{var} = 0;', {indent: 2})}
  913. // bounding box coordinates
  914. ${pattern('this.min_{var} = 0;', {indent: 2})}
  915. ${pattern('this.max_{var} = 0;', {indent: 2})}
  916. }
  917. `;
  918. return quadNodeCode;
  919. function assignQuads(indent) {
  920. // this.quad0 = null;
  921. // this.quad1 = null;
  922. // this.quad2 = null;
  923. // this.quad3 = null;
  924. let quads = [];
  925. for (let i = 0; i < quadCount; ++i) {
  926. quads.push(`${indent}quad${i} = null;`);
  927. }
  928. return quads.join('\n');
  929. }
  930. }
  931. function getInsertStackCode() {
  932. return `
  933. /**
  934. * Our implementation of QuadTree is non-recursive to avoid GC hit
  935. * This data structure represent stack of elements
  936. * which we are trying to insert into quad tree.
  937. */
  938. function InsertStack () {
  939. this.stack = [];
  940. this.popIdx = 0;
  941. }
  942. InsertStack.prototype = {
  943. isEmpty: function() {
  944. return this.popIdx === 0;
  945. },
  946. push: function (node, body) {
  947. var item = this.stack[this.popIdx];
  948. if (!item) {
  949. // we are trying to avoid memory pressure: create new element
  950. // only when absolutely necessary
  951. this.stack[this.popIdx] = new InsertStackElement(node, body);
  952. } else {
  953. item.node = node;
  954. item.body = body;
  955. }
  956. ++this.popIdx;
  957. },
  958. pop: function () {
  959. if (this.popIdx > 0) {
  960. return this.stack[--this.popIdx];
  961. }
  962. },
  963. reset: function () {
  964. this.popIdx = 0;
  965. }
  966. };
  967. function InsertStackElement(node, body) {
  968. this.node = node; // QuadTree node
  969. this.body = body; // physical body which needs to be inserted to node
  970. }
  971. `;
  972. }
  973. },{"./createPatternBuilder":2,"./getVariableName":9}],9:[function(require,module,exports){
  974. module.exports = function getVariableName(index) {
  975. if (index === 0) return 'x';
  976. if (index === 1) return 'y';
  977. if (index === 2) return 'z';
  978. return 'c' + (index + 1);
  979. };
  980. },{}],10:[function(require,module,exports){
  981. /**
  982. * Manages a simulation of physical forces acting on bodies and springs.
  983. */
  984. module.exports = createPhysicsSimulator;
  985. var generateCreateBodyFunction = require('./codeGenerators/generateCreateBody');
  986. var generateQuadTreeFunction = require('./codeGenerators/generateQuadTree');
  987. var generateBoundsFunction = require('./codeGenerators/generateBounds');
  988. var generateCreateDragForceFunction = require('./codeGenerators/generateCreateDragForce');
  989. var generateCreateSpringForceFunction = require('./codeGenerators/generateCreateSpringForce');
  990. var generateIntegratorFunction = require('./codeGenerators/generateIntegrator');
  991. var dimensionalCache = {};
  992. function createPhysicsSimulator(settings) {
  993. var Spring = require('./spring');
  994. var merge = require('ngraph.merge');
  995. var eventify = require('ngraph.events');
  996. if (settings) {
  997. // Check for names from older versions of the layout
  998. if (settings.springCoeff !== undefined) throw new Error('springCoeff was renamed to springCoefficient');
  999. if (settings.dragCoeff !== undefined) throw new Error('dragCoeff was renamed to dragCoefficient');
  1000. }
  1001. settings = merge(settings, {
  1002. /**
  1003. * Ideal length for links (springs in physical model).
  1004. */
  1005. springLength: 10,
  1006. /**
  1007. * Hook's law coefficient. 1 - solid spring.
  1008. */
  1009. springCoefficient: 0.8,
  1010. /**
  1011. * Coulomb's law coefficient. It's used to repel nodes thus should be negative
  1012. * if you make it positive nodes start attract each other :).
  1013. */
  1014. gravity: -12,
  1015. /**
  1016. * Theta coefficient from Barnes Hut simulation. Ranged between (0, 1).
  1017. * The closer it's to 1 the more nodes algorithm will have to go through.
  1018. * Setting it to one makes Barnes Hut simulation no different from
  1019. * brute-force forces calculation (each node is considered).
  1020. */
  1021. theta: 0.8,
  1022. /**
  1023. * Drag force coefficient. Used to slow down system, thus should be less than 1.
  1024. * The closer it is to 0 the less tight system will be.
  1025. */
  1026. dragCoefficient: 0.9, // TODO: Need to rename this to something better. E.g. `dragCoefficient`
  1027. /**
  1028. * Default time step (dt) for forces integration
  1029. */
  1030. timeStep : 0.5,
  1031. /**
  1032. * Adaptive time step uses average spring length to compute actual time step:
  1033. * See: https://twitter.com/anvaka/status/1293067160755957760
  1034. */
  1035. adaptiveTimeStepWeight: 0,
  1036. /**
  1037. * This parameter defines number of dimensions of the space where simulation
  1038. * is performed.
  1039. */
  1040. dimensions: 2,
  1041. /**
  1042. * In debug mode more checks are performed, this will help you catch errors
  1043. * quickly, however for production build it is recommended to turn off this flag
  1044. * to speed up computation.
  1045. */
  1046. debug: false
  1047. });
  1048. var factory = dimensionalCache[settings.dimensions];
  1049. if (!factory) {
  1050. var dimensions = settings.dimensions;
  1051. factory = {
  1052. Body: generateCreateBodyFunction(dimensions, settings.debug),
  1053. createQuadTree: generateQuadTreeFunction(dimensions),
  1054. createBounds: generateBoundsFunction(dimensions),
  1055. createDragForce: generateCreateDragForceFunction(dimensions),
  1056. createSpringForce: generateCreateSpringForceFunction(dimensions),
  1057. integrate: generateIntegratorFunction(dimensions),
  1058. };
  1059. dimensionalCache[dimensions] = factory;
  1060. }
  1061. var Body = factory.Body;
  1062. var createQuadTree = factory.createQuadTree;
  1063. var createBounds = factory.createBounds;
  1064. var createDragForce = factory.createDragForce;
  1065. var createSpringForce = factory.createSpringForce;
  1066. var integrate = factory.integrate;
  1067. var createBody = pos => new Body(pos);
  1068. var random = require('ngraph.random').random(42);
  1069. var bodies = []; // Bodies in this simulation.
  1070. var springs = []; // Springs in this simulation.
  1071. var quadTree = createQuadTree(settings, random);
  1072. var bounds = createBounds(bodies, settings, random);
  1073. var springForce = createSpringForce(settings, random);
  1074. var dragForce = createDragForce(settings);
  1075. var totalMovement = 0; // how much movement we made on last step
  1076. var forces = [];
  1077. var forceMap = new Map();
  1078. var iterationNumber = 0;
  1079. addForce('nbody', nbodyForce);
  1080. addForce('spring', updateSpringForce);
  1081. var publicApi = {
  1082. /**
  1083. * Array of bodies, registered with current simulator
  1084. *
  1085. * Note: To add new body, use addBody() method. This property is only
  1086. * exposed for testing/performance purposes.
  1087. */
  1088. bodies: bodies,
  1089. quadTree: quadTree,
  1090. /**
  1091. * Array of springs, registered with current simulator
  1092. *
  1093. * Note: To add new spring, use addSpring() method. This property is only
  1094. * exposed for testing/performance purposes.
  1095. */
  1096. springs: springs,
  1097. /**
  1098. * Returns settings with which current simulator was initialized
  1099. */
  1100. settings: settings,
  1101. /**
  1102. * Adds a new force to simulation
  1103. */
  1104. addForce: addForce,
  1105. /**
  1106. * Removes a force from the simulation.
  1107. */
  1108. removeForce: removeForce,
  1109. /**
  1110. * Returns a map of all registered forces.
  1111. */
  1112. getForces: getForces,
  1113. /**
  1114. * Performs one step of force simulation.
  1115. *
  1116. * @returns {boolean} true if system is considered stable; False otherwise.
  1117. */
  1118. step: function () {
  1119. for (var i = 0; i < forces.length; ++i) {
  1120. forces[i](iterationNumber);
  1121. }
  1122. var movement = integrate(bodies, settings.timeStep, settings.adaptiveTimeStepWeight);
  1123. iterationNumber += 1;
  1124. return movement;
  1125. },
  1126. /**
  1127. * Adds body to the system
  1128. *
  1129. * @param {ngraph.physics.primitives.Body} body physical body
  1130. *
  1131. * @returns {ngraph.physics.primitives.Body} added body
  1132. */
  1133. addBody: function (body) {
  1134. if (!body) {
  1135. throw new Error('Body is required');
  1136. }
  1137. bodies.push(body);
  1138. return body;
  1139. },
  1140. /**
  1141. * Adds body to the system at given position
  1142. *
  1143. * @param {Object} pos position of a body
  1144. *
  1145. * @returns {ngraph.physics.primitives.Body} added body
  1146. */
  1147. addBodyAt: function (pos) {
  1148. if (!pos) {
  1149. throw new Error('Body position is required');
  1150. }
  1151. var body = createBody(pos);
  1152. bodies.push(body);
  1153. return body;
  1154. },
  1155. /**
  1156. * Removes body from the system
  1157. *
  1158. * @param {ngraph.physics.primitives.Body} body to remove
  1159. *
  1160. * @returns {Boolean} true if body found and removed. falsy otherwise;
  1161. */
  1162. removeBody: function (body) {
  1163. if (!body) { return; }
  1164. var idx = bodies.indexOf(body);
  1165. if (idx < 0) { return; }
  1166. bodies.splice(idx, 1);
  1167. if (bodies.length === 0) {
  1168. bounds.reset();
  1169. }
  1170. return true;
  1171. },
  1172. /**
  1173. * Adds a spring to this simulation.
  1174. *
  1175. * @returns {Object} - a handle for a spring. If you want to later remove
  1176. * spring pass it to removeSpring() method.
  1177. */
  1178. addSpring: function (body1, body2, springLength, springCoefficient) {
  1179. if (!body1 || !body2) {
  1180. throw new Error('Cannot add null spring to force simulator');
  1181. }
  1182. if (typeof springLength !== 'number') {
  1183. springLength = -1; // assume global configuration
  1184. }
  1185. var spring = new Spring(body1, body2, springLength, springCoefficient >= 0 ? springCoefficient : -1);
  1186. springs.push(spring);
  1187. // TODO: could mark simulator as dirty.
  1188. return spring;
  1189. },
  1190. /**
  1191. * Returns amount of movement performed on last step() call
  1192. */
  1193. getTotalMovement: function () {
  1194. return totalMovement;
  1195. },
  1196. /**
  1197. * Removes spring from the system
  1198. *
  1199. * @param {Object} spring to remove. Spring is an object returned by addSpring
  1200. *
  1201. * @returns {Boolean} true if spring found and removed. falsy otherwise;
  1202. */
  1203. removeSpring: function (spring) {
  1204. if (!spring) { return; }
  1205. var idx = springs.indexOf(spring);
  1206. if (idx > -1) {
  1207. springs.splice(idx, 1);
  1208. return true;
  1209. }
  1210. },
  1211. getBestNewBodyPosition: function (neighbors) {
  1212. return bounds.getBestNewPosition(neighbors);
  1213. },
  1214. /**
  1215. * Returns bounding box which covers all bodies
  1216. */
  1217. getBBox: getBoundingBox,
  1218. getBoundingBox: getBoundingBox,
  1219. invalidateBBox: function () {
  1220. console.warn('invalidateBBox() is deprecated, bounds always recomputed on `getBBox()` call');
  1221. },
  1222. // TODO: Move the force specific stuff to force
  1223. gravity: function (value) {
  1224. if (value !== undefined) {
  1225. settings.gravity = value;
  1226. quadTree.options({gravity: value});
  1227. return this;
  1228. } else {
  1229. return settings.gravity;
  1230. }
  1231. },
  1232. theta: function (value) {
  1233. if (value !== undefined) {
  1234. settings.theta = value;
  1235. quadTree.options({theta: value});
  1236. return this;
  1237. } else {
  1238. return settings.theta;
  1239. }
  1240. },
  1241. /**
  1242. * Returns pseudo-random number generator instance.
  1243. */
  1244. random: random
  1245. };
  1246. // allow settings modification via public API:
  1247. expose(settings, publicApi);
  1248. eventify(publicApi);
  1249. return publicApi;
  1250. function getBoundingBox() {
  1251. bounds.update();
  1252. return bounds.box;
  1253. }
  1254. function addForce(forceName, forceFunction) {
  1255. if (forceMap.has(forceName)) throw new Error('Force ' + forceName + ' is already added');
  1256. forceMap.set(forceName, forceFunction);
  1257. forces.push(forceFunction);
  1258. }
  1259. function removeForce(forceName) {
  1260. var forceIndex = forces.indexOf(forceMap.get(forceName));
  1261. if (forceIndex < 0) return;
  1262. forces.splice(forceIndex, 1);
  1263. forceMap.delete(forceName);
  1264. }
  1265. function getForces() {
  1266. // TODO: Should I trust them or clone the forces?
  1267. return forceMap;
  1268. }
  1269. function nbodyForce(/* iterationUmber */) {
  1270. if (bodies.length === 0) return;
  1271. quadTree.insertBodies(bodies);
  1272. var i = bodies.length;
  1273. while (i--) {
  1274. var body = bodies[i];
  1275. if (!body.isPinned) {
  1276. body.reset();
  1277. quadTree.updateBodyForce(body);
  1278. dragForce.update(body);
  1279. }
  1280. }
  1281. }
  1282. function updateSpringForce() {
  1283. var i = springs.length;
  1284. while (i--) {
  1285. springForce.update(springs[i]);
  1286. }
  1287. }
  1288. }
  1289. function expose(settings, target) {
  1290. for (var key in settings) {
  1291. augment(settings, target, key);
  1292. }
  1293. }
  1294. function augment(source, target, key) {
  1295. if (!source.hasOwnProperty(key)) return;
  1296. if (typeof target[key] === 'function') {
  1297. // this accessor is already defined. Ignore it
  1298. return;
  1299. }
  1300. var sourceIsNumber = Number.isFinite(source[key]);
  1301. if (sourceIsNumber) {
  1302. target[key] = function (value) {
  1303. if (value !== undefined) {
  1304. if (!Number.isFinite(value)) throw new Error('Value of ' + key + ' should be a valid number.');
  1305. source[key] = value;
  1306. return target;
  1307. }
  1308. return source[key];
  1309. };
  1310. } else {
  1311. target[key] = function (value) {
  1312. if (value !== undefined) {
  1313. source[key] = value;
  1314. return target;
  1315. }
  1316. return source[key];
  1317. };
  1318. }
  1319. }
  1320. },{"./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){
  1321. module.exports = Spring;
  1322. /**
  1323. * Represents a physical spring. Spring connects two bodies, has rest length
  1324. * stiffness coefficient and optional weight
  1325. */
  1326. function Spring(fromBody, toBody, length, springCoefficient) {
  1327. this.from = fromBody;
  1328. this.to = toBody;
  1329. this.length = length;
  1330. this.coefficient = springCoefficient;
  1331. }
  1332. },{}],12:[function(require,module,exports){
  1333. module.exports = function eventify(subject) {
  1334. validateSubject(subject);
  1335. var eventsStorage = createEventsStorage(subject);
  1336. subject.on = eventsStorage.on;
  1337. subject.off = eventsStorage.off;
  1338. subject.fire = eventsStorage.fire;
  1339. return subject;
  1340. };
  1341. function createEventsStorage(subject) {
  1342. // Store all event listeners to this hash. Key is event name, value is array
  1343. // of callback records.
  1344. //
  1345. // A callback record consists of callback function and its optional context:
  1346. // { 'eventName' => [{callback: function, ctx: object}] }
  1347. var registeredEvents = Object.create(null);
  1348. return {
  1349. on: function (eventName, callback, ctx) {
  1350. if (typeof callback !== 'function') {
  1351. throw new Error('callback is expected to be a function');
  1352. }
  1353. var handlers = registeredEvents[eventName];
  1354. if (!handlers) {
  1355. handlers = registeredEvents[eventName] = [];
  1356. }
  1357. handlers.push({callback: callback, ctx: ctx});
  1358. return subject;
  1359. },
  1360. off: function (eventName, callback) {
  1361. var wantToRemoveAll = (typeof eventName === 'undefined');
  1362. if (wantToRemoveAll) {
  1363. // Killing old events storage should be enough in this case:
  1364. registeredEvents = Object.create(null);
  1365. return subject;
  1366. }
  1367. if (registeredEvents[eventName]) {
  1368. var deleteAllCallbacksForEvent = (typeof callback !== 'function');
  1369. if (deleteAllCallbacksForEvent) {
  1370. delete registeredEvents[eventName];
  1371. } else {
  1372. var callbacks = registeredEvents[eventName];
  1373. for (var i = 0; i < callbacks.length; ++i) {
  1374. if (callbacks[i].callback === callback) {
  1375. callbacks.splice(i, 1);
  1376. }
  1377. }
  1378. }
  1379. }
  1380. return subject;
  1381. },
  1382. fire: function (eventName) {
  1383. var callbacks = registeredEvents[eventName];
  1384. if (!callbacks) {
  1385. return subject;
  1386. }
  1387. var fireArguments;
  1388. if (arguments.length > 1) {
  1389. fireArguments = Array.prototype.splice.call(arguments, 1);
  1390. }
  1391. for(var i = 0; i < callbacks.length; ++i) {
  1392. var callbackInfo = callbacks[i];
  1393. callbackInfo.callback.apply(callbackInfo.ctx, fireArguments);
  1394. }
  1395. return subject;
  1396. }
  1397. };
  1398. }
  1399. function validateSubject(subject) {
  1400. if (!subject) {
  1401. throw new Error('Eventify cannot use falsy object as events subject');
  1402. }
  1403. var reservedWords = ['on', 'fire', 'off'];
  1404. for (var i = 0; i < reservedWords.length; ++i) {
  1405. if (subject.hasOwnProperty(reservedWords[i])) {
  1406. throw new Error("Subject cannot be eventified, since it already has property '" + reservedWords[i] + "'");
  1407. }
  1408. }
  1409. }
  1410. },{}],13:[function(require,module,exports){
  1411. module.exports = merge;
  1412. /**
  1413. * Augments `target` with properties in `options`. Does not override
  1414. * target's properties if they are defined and matches expected type in
  1415. * options
  1416. *
  1417. * @returns {Object} merged object
  1418. */
  1419. function merge(target, options) {
  1420. var key;
  1421. if (!target) { target = {}; }
  1422. if (options) {
  1423. for (key in options) {
  1424. if (options.hasOwnProperty(key)) {
  1425. var targetHasIt = target.hasOwnProperty(key),
  1426. optionsValueType = typeof options[key],
  1427. shouldReplace = !targetHasIt || (typeof target[key] !== optionsValueType);
  1428. if (shouldReplace) {
  1429. target[key] = options[key];
  1430. } else if (optionsValueType === 'object') {
  1431. // go deep, don't care about loops here, we are simple API!:
  1432. target[key] = merge(target[key], options[key]);
  1433. }
  1434. }
  1435. }
  1436. }
  1437. return target;
  1438. }
  1439. },{}],14:[function(require,module,exports){
  1440. module.exports = random;
  1441. // TODO: Deprecate?
  1442. module.exports.random = random,
  1443. module.exports.randomIterator = randomIterator
  1444. /**
  1445. * Creates seeded PRNG with two methods:
  1446. * next() and nextDouble()
  1447. */
  1448. function random(inputSeed) {
  1449. var seed = typeof inputSeed === 'number' ? inputSeed : (+new Date());
  1450. return new Generator(seed)
  1451. }
  1452. function Generator(seed) {
  1453. this.seed = seed;
  1454. }
  1455. /**
  1456. * Generates random integer number in the range from 0 (inclusive) to maxValue (exclusive)
  1457. *
  1458. * @param maxValue Number REQUIRED. Omitting this number will result in NaN values from PRNG.
  1459. */
  1460. Generator.prototype.next = next;
  1461. /**
  1462. * Generates random double number in the range from 0 (inclusive) to 1 (exclusive)
  1463. * This function is the same as Math.random() (except that it could be seeded)
  1464. */
  1465. Generator.prototype.nextDouble = nextDouble;
  1466. /**
  1467. * Returns a random real number uniformly in [0, 1)
  1468. */
  1469. Generator.prototype.uniform = nextDouble;
  1470. Generator.prototype.gaussian = gaussian;
  1471. function gaussian() {
  1472. // use the polar form of the Box-Muller transform
  1473. // based on https://introcs.cs.princeton.edu/java/23recursion/StdRandom.java
  1474. var r, x, y;
  1475. do {
  1476. x = this.nextDouble() * 2 - 1;
  1477. y = this.nextDouble() * 2 - 1;
  1478. r = x * x + y * y;
  1479. } while (r >= 1 || r === 0);
  1480. return x * Math.sqrt(-2 * Math.log(r)/r);
  1481. }
  1482. function nextDouble() {
  1483. var seed = this.seed;
  1484. // Robert Jenkins' 32 bit integer hash function.
  1485. seed = ((seed + 0x7ed55d16) + (seed << 12)) & 0xffffffff;
  1486. seed = ((seed ^ 0xc761c23c) ^ (seed >>> 19)) & 0xffffffff;
  1487. seed = ((seed + 0x165667b1) + (seed << 5)) & 0xffffffff;
  1488. seed = ((seed + 0xd3a2646c) ^ (seed << 9)) & 0xffffffff;
  1489. seed = ((seed + 0xfd7046c5) + (seed << 3)) & 0xffffffff;
  1490. seed = ((seed ^ 0xb55a4f09) ^ (seed >>> 16)) & 0xffffffff;
  1491. this.seed = seed;
  1492. return (seed & 0xfffffff) / 0x10000000;
  1493. }
  1494. function next(maxValue) {
  1495. return Math.floor(this.nextDouble() * maxValue);
  1496. }
  1497. /*
  1498. * Creates iterator over array, which returns items of array in random order
  1499. * Time complexity is guaranteed to be O(n);
  1500. */
  1501. function randomIterator(array, customRandom) {
  1502. var localRandom = customRandom || random();
  1503. if (typeof localRandom.next !== 'function') {
  1504. throw new Error('customRandom does not match expected API: next() function is missing');
  1505. }
  1506. return {
  1507. forEach: forEach,
  1508. /**
  1509. * Shuffles array randomly, in place.
  1510. */
  1511. shuffle: shuffle
  1512. };
  1513. function shuffle() {
  1514. var i, j, t;
  1515. for (i = array.length - 1; i > 0; --i) {
  1516. j = localRandom.next(i + 1); // i inclusive
  1517. t = array[j];
  1518. array[j] = array[i];
  1519. array[i] = t;
  1520. }
  1521. return array;
  1522. }
  1523. function forEach(callback) {
  1524. var i, j, t;
  1525. for (i = array.length - 1; i > 0; --i) {
  1526. j = localRandom.next(i + 1); // i inclusive
  1527. t = array[j];
  1528. array[j] = array[i];
  1529. array[i] = t;
  1530. callback(t);
  1531. }
  1532. if (array.length) {
  1533. callback(array[0]);
  1534. }
  1535. }
  1536. }
  1537. },{}]},{},[1])(1)
  1538. });