ngraph.graph.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667
  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.createGraph = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
  2. /**
  3. * @fileOverview Contains definition of the core graph object.
  4. */
  5. // TODO: need to change storage layer:
  6. // 1. Be able to get all nodes O(1)
  7. // 2. Be able to get number of links O(1)
  8. /**
  9. * @example
  10. * var graph = require('ngraph.graph')();
  11. * graph.addNode(1); // graph has one node.
  12. * graph.addLink(2, 3); // now graph contains three nodes and one link.
  13. *
  14. */
  15. module.exports = createGraph;
  16. var eventify = require('ngraph.events');
  17. /**
  18. * Creates a new graph
  19. */
  20. function createGraph(options) {
  21. // Graph structure is maintained as dictionary of nodes
  22. // and array of links. Each node has 'links' property which
  23. // hold all links related to that node. And general links
  24. // array is used to speed up all links enumeration. This is inefficient
  25. // in terms of memory, but simplifies coding.
  26. options = options || {};
  27. if ('uniqueLinkId' in options) {
  28. console.warn(
  29. 'ngraph.graph: Starting from version 0.14 `uniqueLinkId` is deprecated.\n' +
  30. 'Use `multigraph` option instead\n',
  31. '\n',
  32. 'Note: there is also change in default behavior: From now on each graph\n'+
  33. 'is considered to be not a multigraph by default (each edge is unique).'
  34. );
  35. options.multigraph = options.uniqueLinkId;
  36. }
  37. // Dear reader, the non-multigraphs do not guarantee that there is only
  38. // one link for a given pair of node. When this option is set to false
  39. // we can save some memory and CPU (18% faster for non-multigraph);
  40. if (options.multigraph === undefined) options.multigraph = false;
  41. if (typeof Map !== 'function') {
  42. // TODO: Should we polyfill it ourselves? We don't use much operations there..
  43. throw new Error('ngraph.graph requires `Map` to be defined. Please polyfill it before using ngraph');
  44. }
  45. var nodes = new Map();
  46. var links = [],
  47. // Hash of multi-edges. Used to track ids of edges between same nodes
  48. multiEdges = {},
  49. suspendEvents = 0,
  50. createLink = options.multigraph ? createUniqueLink : createSingleLink,
  51. // Our graph API provides means to listen to graph changes. Users can subscribe
  52. // to be notified about changes in the graph by using `on` method. However
  53. // in some cases they don't use it. To avoid unnecessary memory consumption
  54. // we will not record graph changes until we have at least one subscriber.
  55. // Code below supports this optimization.
  56. //
  57. // Accumulates all changes made during graph updates.
  58. // Each change element contains:
  59. // changeType - one of the strings: 'add', 'remove' or 'update';
  60. // node - if change is related to node this property is set to changed graph's node;
  61. // link - if change is related to link this property is set to changed graph's link;
  62. changes = [],
  63. recordLinkChange = noop,
  64. recordNodeChange = noop,
  65. enterModification = noop,
  66. exitModification = noop;
  67. // this is our public API:
  68. var graphPart = {
  69. /**
  70. * Adds node to the graph. If node with given id already exists in the graph
  71. * its data is extended with whatever comes in 'data' argument.
  72. *
  73. * @param nodeId the node's identifier. A string or number is preferred.
  74. * @param [data] additional data for the node being added. If node already
  75. * exists its data object is augmented with the new one.
  76. *
  77. * @return {node} The newly added node or node with given id if it already exists.
  78. */
  79. addNode: addNode,
  80. /**
  81. * Adds a link to the graph. The function always create a new
  82. * link between two nodes. If one of the nodes does not exists
  83. * a new node is created.
  84. *
  85. * @param fromId link start node id;
  86. * @param toId link end node id;
  87. * @param [data] additional data to be set on the new link;
  88. *
  89. * @return {link} The newly created link
  90. */
  91. addLink: addLink,
  92. /**
  93. * Removes link from the graph. If link does not exist does nothing.
  94. *
  95. * @param link - object returned by addLink() or getLinks() methods.
  96. *
  97. * @returns true if link was removed; false otherwise.
  98. */
  99. removeLink: removeLink,
  100. /**
  101. * Removes node with given id from the graph. If node does not exist in the graph
  102. * does nothing.
  103. *
  104. * @param nodeId node's identifier passed to addNode() function.
  105. *
  106. * @returns true if node was removed; false otherwise.
  107. */
  108. removeNode: removeNode,
  109. /**
  110. * Gets node with given identifier. If node does not exist undefined value is returned.
  111. *
  112. * @param nodeId requested node identifier;
  113. *
  114. * @return {node} in with requested identifier or undefined if no such node exists.
  115. */
  116. getNode: getNode,
  117. /**
  118. * Gets number of nodes in this graph.
  119. *
  120. * @return number of nodes in the graph.
  121. */
  122. getNodesCount: function () {
  123. return nodes.size;
  124. },
  125. /**
  126. * Gets total number of links in the graph.
  127. */
  128. getLinksCount: function () {
  129. return links.length;
  130. },
  131. /**
  132. * Gets all links (inbound and outbound) from the node with given id.
  133. * If node with given id is not found null is returned.
  134. *
  135. * @param nodeId requested node identifier.
  136. *
  137. * @return Array of links from and to requested node if such node exists;
  138. * otherwise null is returned.
  139. */
  140. getLinks: getLinks,
  141. /**
  142. * Invokes callback on each node of the graph.
  143. *
  144. * @param {Function(node)} callback Function to be invoked. The function
  145. * is passed one argument: visited node.
  146. */
  147. forEachNode: forEachNode,
  148. /**
  149. * Invokes callback on every linked (adjacent) node to the given one.
  150. *
  151. * @param nodeId Identifier of the requested node.
  152. * @param {Function(node, link)} callback Function to be called on all linked nodes.
  153. * The function is passed two parameters: adjacent node and link object itself.
  154. * @param oriented if true graph treated as oriented.
  155. */
  156. forEachLinkedNode: forEachLinkedNode,
  157. /**
  158. * Enumerates all links in the graph
  159. *
  160. * @param {Function(link)} callback Function to be called on all links in the graph.
  161. * The function is passed one parameter: graph's link object.
  162. *
  163. * Link object contains at least the following fields:
  164. * fromId - node id where link starts;
  165. * toId - node id where link ends,
  166. * data - additional data passed to graph.addLink() method.
  167. */
  168. forEachLink: forEachLink,
  169. /**
  170. * Suspend all notifications about graph changes until
  171. * endUpdate is called.
  172. */
  173. beginUpdate: enterModification,
  174. /**
  175. * Resumes all notifications about graph changes and fires
  176. * graph 'changed' event in case there are any pending changes.
  177. */
  178. endUpdate: exitModification,
  179. /**
  180. * Removes all nodes and links from the graph.
  181. */
  182. clear: clear,
  183. /**
  184. * Detects whether there is a link between two nodes.
  185. * Operation complexity is O(n) where n - number of links of a node.
  186. * NOTE: this function is synonim for getLink()
  187. *
  188. * @returns link if there is one. null otherwise.
  189. */
  190. hasLink: getLink,
  191. /**
  192. * Detects whether there is a node with given id
  193. *
  194. * Operation complexity is O(1)
  195. * NOTE: this function is synonim for getNode()
  196. *
  197. * @returns node if there is one; Falsy value otherwise.
  198. */
  199. hasNode: getNode,
  200. /**
  201. * Gets an edge between two nodes.
  202. * Operation complexity is O(n) where n - number of links of a node.
  203. *
  204. * @param {string} fromId link start identifier
  205. * @param {string} toId link end identifier
  206. *
  207. * @returns link if there is one. null otherwise.
  208. */
  209. getLink: getLink
  210. };
  211. // this will add `on()` and `fire()` methods.
  212. eventify(graphPart);
  213. monitorSubscribers();
  214. return graphPart;
  215. function monitorSubscribers() {
  216. var realOn = graphPart.on;
  217. // replace real `on` with our temporary on, which will trigger change
  218. // modification monitoring:
  219. graphPart.on = on;
  220. function on() {
  221. // now it's time to start tracking stuff:
  222. graphPart.beginUpdate = enterModification = enterModificationReal;
  223. graphPart.endUpdate = exitModification = exitModificationReal;
  224. recordLinkChange = recordLinkChangeReal;
  225. recordNodeChange = recordNodeChangeReal;
  226. // this will replace current `on` method with real pub/sub from `eventify`.
  227. graphPart.on = realOn;
  228. // delegate to real `on` handler:
  229. return realOn.apply(graphPart, arguments);
  230. }
  231. }
  232. function recordLinkChangeReal(link, changeType) {
  233. changes.push({
  234. link: link,
  235. changeType: changeType
  236. });
  237. }
  238. function recordNodeChangeReal(node, changeType) {
  239. changes.push({
  240. node: node,
  241. changeType: changeType
  242. });
  243. }
  244. function addNode(nodeId, data) {
  245. if (nodeId === undefined) {
  246. throw new Error('Invalid node identifier');
  247. }
  248. enterModification();
  249. var node = getNode(nodeId);
  250. if (!node) {
  251. node = new Node(nodeId, data);
  252. recordNodeChange(node, 'add');
  253. } else {
  254. node.data = data;
  255. recordNodeChange(node, 'update');
  256. }
  257. nodes.set(nodeId, node);
  258. exitModification();
  259. return node;
  260. }
  261. function getNode(nodeId) {
  262. return nodes.get(nodeId);
  263. }
  264. function removeNode(nodeId) {
  265. var node = getNode(nodeId);
  266. if (!node) {
  267. return false;
  268. }
  269. enterModification();
  270. var prevLinks = node.links;
  271. if (prevLinks) {
  272. node.links = null;
  273. for(var i = 0; i < prevLinks.length; ++i) {
  274. removeLink(prevLinks[i]);
  275. }
  276. }
  277. nodes.delete(nodeId)
  278. recordNodeChange(node, 'remove');
  279. exitModification();
  280. return true;
  281. }
  282. function addLink(fromId, toId, data) {
  283. enterModification();
  284. var fromNode = getNode(fromId) || addNode(fromId);
  285. var toNode = getNode(toId) || addNode(toId);
  286. var link = createLink(fromId, toId, data);
  287. links.push(link);
  288. // TODO: this is not cool. On large graphs potentially would consume more memory.
  289. addLinkToNode(fromNode, link);
  290. if (fromId !== toId) {
  291. // make sure we are not duplicating links for self-loops
  292. addLinkToNode(toNode, link);
  293. }
  294. recordLinkChange(link, 'add');
  295. exitModification();
  296. return link;
  297. }
  298. function createSingleLink(fromId, toId, data) {
  299. var linkId = makeLinkId(fromId, toId);
  300. return new Link(fromId, toId, data, linkId);
  301. }
  302. function createUniqueLink(fromId, toId, data) {
  303. // TODO: Get rid of this method.
  304. var linkId = makeLinkId(fromId, toId);
  305. var isMultiEdge = multiEdges.hasOwnProperty(linkId);
  306. if (isMultiEdge || getLink(fromId, toId)) {
  307. if (!isMultiEdge) {
  308. multiEdges[linkId] = 0;
  309. }
  310. var suffix = '@' + (++multiEdges[linkId]);
  311. linkId = makeLinkId(fromId + suffix, toId + suffix);
  312. }
  313. return new Link(fromId, toId, data, linkId);
  314. }
  315. function getLinks(nodeId) {
  316. var node = getNode(nodeId);
  317. return node ? node.links : null;
  318. }
  319. function removeLink(link) {
  320. if (!link) {
  321. return false;
  322. }
  323. var idx = indexOfElementInArray(link, links);
  324. if (idx < 0) {
  325. return false;
  326. }
  327. enterModification();
  328. links.splice(idx, 1);
  329. var fromNode = getNode(link.fromId);
  330. var toNode = getNode(link.toId);
  331. if (fromNode) {
  332. idx = indexOfElementInArray(link, fromNode.links);
  333. if (idx >= 0) {
  334. fromNode.links.splice(idx, 1);
  335. }
  336. }
  337. if (toNode) {
  338. idx = indexOfElementInArray(link, toNode.links);
  339. if (idx >= 0) {
  340. toNode.links.splice(idx, 1);
  341. }
  342. }
  343. recordLinkChange(link, 'remove');
  344. exitModification();
  345. return true;
  346. }
  347. function getLink(fromNodeId, toNodeId) {
  348. // TODO: Use sorted links to speed this up
  349. var node = getNode(fromNodeId),
  350. i;
  351. if (!node || !node.links) {
  352. return null;
  353. }
  354. for (i = 0; i < node.links.length; ++i) {
  355. var link = node.links[i];
  356. if (link.fromId === fromNodeId && link.toId === toNodeId) {
  357. return link;
  358. }
  359. }
  360. return null; // no link.
  361. }
  362. function clear() {
  363. enterModification();
  364. forEachNode(function(node) {
  365. removeNode(node.id);
  366. });
  367. exitModification();
  368. }
  369. function forEachLink(callback) {
  370. var i, length;
  371. if (typeof callback === 'function') {
  372. for (i = 0, length = links.length; i < length; ++i) {
  373. callback(links[i]);
  374. }
  375. }
  376. }
  377. function forEachLinkedNode(nodeId, callback, oriented) {
  378. var node = getNode(nodeId);
  379. if (node && node.links && typeof callback === 'function') {
  380. if (oriented) {
  381. return forEachOrientedLink(node.links, nodeId, callback);
  382. } else {
  383. return forEachNonOrientedLink(node.links, nodeId, callback);
  384. }
  385. }
  386. }
  387. function forEachNonOrientedLink(links, nodeId, callback) {
  388. var quitFast;
  389. for (var i = 0; i < links.length; ++i) {
  390. var link = links[i];
  391. var linkedNodeId = link.fromId === nodeId ? link.toId : link.fromId;
  392. quitFast = callback(nodes.get(linkedNodeId), link);
  393. if (quitFast) {
  394. return true; // Client does not need more iterations. Break now.
  395. }
  396. }
  397. }
  398. function forEachOrientedLink(links, nodeId, callback) {
  399. var quitFast;
  400. for (var i = 0; i < links.length; ++i) {
  401. var link = links[i];
  402. if (link.fromId === nodeId) {
  403. quitFast = callback(nodes.get(link.toId), link)
  404. if (quitFast) {
  405. return true; // Client does not need more iterations. Break now.
  406. }
  407. }
  408. }
  409. }
  410. // we will not fire anything until users of this library explicitly call `on()`
  411. // method.
  412. function noop() {}
  413. // Enter, Exit modification allows bulk graph updates without firing events.
  414. function enterModificationReal() {
  415. suspendEvents += 1;
  416. }
  417. function exitModificationReal() {
  418. suspendEvents -= 1;
  419. if (suspendEvents === 0 && changes.length > 0) {
  420. graphPart.fire('changed', changes);
  421. changes.length = 0;
  422. }
  423. }
  424. function forEachNode(callback) {
  425. if (typeof callback !== 'function') {
  426. throw new Error('Function is expected to iterate over graph nodes. You passed ' + callback);
  427. }
  428. var valuesIterator = nodes.values();
  429. var nextValue = valuesIterator.next();
  430. while (!nextValue.done) {
  431. if (callback(nextValue.value)) {
  432. return true; // client doesn't want to proceed. Return.
  433. }
  434. nextValue = valuesIterator.next();
  435. }
  436. }
  437. }
  438. // need this for old browsers. Should this be a separate module?
  439. function indexOfElementInArray(element, array) {
  440. if (!array) return -1;
  441. if (array.indexOf) {
  442. return array.indexOf(element);
  443. }
  444. var len = array.length,
  445. i;
  446. for (i = 0; i < len; i += 1) {
  447. if (array[i] === element) {
  448. return i;
  449. }
  450. }
  451. return -1;
  452. }
  453. /**
  454. * Internal structure to represent node;
  455. */
  456. function Node(id, data) {
  457. this.id = id;
  458. this.links = null;
  459. this.data = data;
  460. }
  461. function addLinkToNode(node, link) {
  462. if (node.links) {
  463. node.links.push(link);
  464. } else {
  465. node.links = [link];
  466. }
  467. }
  468. /**
  469. * Internal structure to represent links;
  470. */
  471. function Link(fromId, toId, data, id) {
  472. this.fromId = fromId;
  473. this.toId = toId;
  474. this.data = data;
  475. this.id = id;
  476. }
  477. function makeLinkId(fromId, toId) {
  478. return fromId.toString() + '👉 ' + toId.toString();
  479. }
  480. },{"ngraph.events":2}],2:[function(require,module,exports){
  481. module.exports = function eventify(subject) {
  482. validateSubject(subject);
  483. var eventsStorage = createEventsStorage(subject);
  484. subject.on = eventsStorage.on;
  485. subject.off = eventsStorage.off;
  486. subject.fire = eventsStorage.fire;
  487. return subject;
  488. };
  489. function createEventsStorage(subject) {
  490. // Store all event listeners to this hash. Key is event name, value is array
  491. // of callback records.
  492. //
  493. // A callback record consists of callback function and its optional context:
  494. // { 'eventName' => [{callback: function, ctx: object}] }
  495. var registeredEvents = Object.create(null);
  496. return {
  497. on: function (eventName, callback, ctx) {
  498. if (typeof callback !== 'function') {
  499. throw new Error('callback is expected to be a function');
  500. }
  501. var handlers = registeredEvents[eventName];
  502. if (!handlers) {
  503. handlers = registeredEvents[eventName] = [];
  504. }
  505. handlers.push({callback: callback, ctx: ctx});
  506. return subject;
  507. },
  508. off: function (eventName, callback) {
  509. var wantToRemoveAll = (typeof eventName === 'undefined');
  510. if (wantToRemoveAll) {
  511. // Killing old events storage should be enough in this case:
  512. registeredEvents = Object.create(null);
  513. return subject;
  514. }
  515. if (registeredEvents[eventName]) {
  516. var deleteAllCallbacksForEvent = (typeof callback !== 'function');
  517. if (deleteAllCallbacksForEvent) {
  518. delete registeredEvents[eventName];
  519. } else {
  520. var callbacks = registeredEvents[eventName];
  521. for (var i = 0; i < callbacks.length; ++i) {
  522. if (callbacks[i].callback === callback) {
  523. callbacks.splice(i, 1);
  524. }
  525. }
  526. }
  527. }
  528. return subject;
  529. },
  530. fire: function (eventName) {
  531. var callbacks = registeredEvents[eventName];
  532. if (!callbacks) {
  533. return subject;
  534. }
  535. var fireArguments;
  536. if (arguments.length > 1) {
  537. fireArguments = Array.prototype.splice.call(arguments, 1);
  538. }
  539. for(var i = 0; i < callbacks.length; ++i) {
  540. var callbackInfo = callbacks[i];
  541. callbackInfo.callback.apply(callbackInfo.ctx, fireArguments);
  542. }
  543. return subject;
  544. }
  545. };
  546. }
  547. function validateSubject(subject) {
  548. if (!subject) {
  549. throw new Error('Eventify cannot use falsy object as events subject');
  550. }
  551. var reservedWords = ['on', 'fire', 'off'];
  552. for (var i = 0; i < reservedWords.length; ++i) {
  553. if (subject.hasOwnProperty(reservedWords[i])) {
  554. throw new Error("Subject cannot be eventified, since it already has property '" + reservedWords[i] + "'");
  555. }
  556. }
  557. }
  558. },{}]},{},[1])(1)
  559. });