Graph database Analysis of the Steam Network
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

859 lines
26 KiB

  1. ;(function(undefined) {
  2. 'use strict';
  3. var _methods = Object.create(null),
  4. _indexes = Object.create(null),
  5. _initBindings = Object.create(null),
  6. _methodBindings = Object.create(null),
  7. _methodBeforeBindings = Object.create(null),
  8. _defaultSettings = {
  9. immutable: true,
  10. clone: true
  11. },
  12. _defaultSettingsFunction = function(key) {
  13. return _defaultSettings[key];
  14. };
  15. /**
  16. * The graph constructor. It initializes the data and the indexes, and binds
  17. * the custom indexes and methods to its own scope.
  18. *
  19. * Recognized parameters:
  20. * **********************
  21. * Here is the exhaustive list of every accepted parameters in the settings
  22. * object:
  23. *
  24. * {boolean} clone Indicates if the data have to be cloned in methods
  25. * to add nodes or edges.
  26. * {boolean} immutable Indicates if nodes "id" values and edges "id",
  27. * "source" and "target" values must be set as
  28. * immutable.
  29. *
  30. * @param {?configurable} settings Eventually a settings function.
  31. * @return {graph} The new graph instance.
  32. */
  33. var graph = function(settings) {
  34. var k,
  35. fn,
  36. data;
  37. /**
  38. * DATA:
  39. * *****
  40. * Every data that is callable from graph methods are stored in this "data"
  41. * object. This object will be served as context for all these methods,
  42. * and it is possible to add other type of data in it.
  43. */
  44. data = {
  45. /**
  46. * SETTINGS FUNCTION:
  47. * ******************
  48. */
  49. settings: settings || _defaultSettingsFunction,
  50. /**
  51. * MAIN DATA:
  52. * **********
  53. */
  54. nodesArray: [],
  55. edgesArray: [],
  56. /**
  57. * GLOBAL INDEXES:
  58. * ***************
  59. * These indexes just index data by ids.
  60. */
  61. nodesIndex: Object.create(null),
  62. edgesIndex: Object.create(null),
  63. /**
  64. * LOCAL INDEXES:
  65. * **************
  66. * These indexes refer from node to nodes. Each key is an id, and each
  67. * value is the array of the ids of related nodes.
  68. */
  69. inNeighborsIndex: Object.create(null),
  70. outNeighborsIndex: Object.create(null),
  71. allNeighborsIndex: Object.create(null),
  72. inNeighborsCount: Object.create(null),
  73. outNeighborsCount: Object.create(null),
  74. allNeighborsCount: Object.create(null)
  75. };
  76. // Execute bindings:
  77. for (k in _initBindings)
  78. _initBindings[k].call(data);
  79. // Add methods to both the scope and the data objects:
  80. for (k in _methods) {
  81. fn = __bindGraphMethod(k, data, _methods[k]);
  82. this[k] = fn;
  83. data[k] = fn;
  84. }
  85. };
  86. /**
  87. * A custom tool to bind methods such that function that are bound to it will
  88. * be executed anytime the method is called.
  89. *
  90. * @param {string} methodName The name of the method to bind.
  91. * @param {object} scope The scope where the method must be executed.
  92. * @param {function} fn The method itself.
  93. * @return {function} The new method.
  94. */
  95. function __bindGraphMethod(methodName, scope, fn) {
  96. var result = function() {
  97. var k,
  98. res;
  99. // Execute "before" bound functions:
  100. for (k in _methodBeforeBindings[methodName])
  101. _methodBeforeBindings[methodName][k].apply(scope, arguments);
  102. // Apply the method:
  103. res = fn.apply(scope, arguments);
  104. // Execute bound functions:
  105. for (k in _methodBindings[methodName])
  106. _methodBindings[methodName][k].apply(scope, arguments);
  107. // Return res:
  108. return res;
  109. };
  110. return result;
  111. }
  112. /**
  113. * This custom tool function removes every pair key/value from an hash. The
  114. * goal is to avoid creating a new object while some other references are
  115. * still hanging in some scopes...
  116. *
  117. * @param {object} obj The object to empty.
  118. * @return {object} The empty object.
  119. */
  120. function __emptyObject(obj) {
  121. var k;
  122. for (k in obj)
  123. if (!('hasOwnProperty' in obj) || obj.hasOwnProperty(k))
  124. delete obj[k];
  125. return obj;
  126. }
  127. /**
  128. * This global method adds a method that will be bound to the futurly created
  129. * graph instances.
  130. *
  131. * Since these methods will be bound to their scope when the instances are
  132. * created, it does not use the prototype. Because of that, methods have to
  133. * be added before instances are created to make them available.
  134. *
  135. * Here is an example:
  136. *
  137. * > graph.addMethod('getNodesCount', function() {
  138. * > return this.nodesArray.length;
  139. * > });
  140. * >
  141. * > var myGraph = new graph();
  142. * > console.log(myGraph.getNodesCount()); // outputs 0
  143. *
  144. * @param {string} methodName The name of the method.
  145. * @param {function} fn The method itself.
  146. * @return {object} The global graph constructor.
  147. */
  148. graph.addMethod = function(methodName, fn) {
  149. if (
  150. typeof methodName !== 'string' ||
  151. typeof fn !== 'function' ||
  152. arguments.length !== 2
  153. )
  154. throw 'addMethod: Wrong arguments.';
  155. if (_methods[methodName] || graph[methodName])
  156. throw 'The method "' + methodName + '" already exists.';
  157. _methods[methodName] = fn;
  158. _methodBindings[methodName] = Object.create(null);
  159. _methodBeforeBindings[methodName] = Object.create(null);
  160. return this;
  161. };
  162. /**
  163. * This global method returns true if the method has already been added, and
  164. * false else.
  165. *
  166. * Here are some examples:
  167. *
  168. * > graph.hasMethod('addNode'); // returns true
  169. * > graph.hasMethod('hasMethod'); // returns true
  170. * > graph.hasMethod('unexistingMethod'); // returns false
  171. *
  172. * @param {string} methodName The name of the method.
  173. * @return {boolean} The result.
  174. */
  175. graph.hasMethod = function(methodName) {
  176. return !!(_methods[methodName] || graph[methodName]);
  177. };
  178. /**
  179. * This global methods attaches a function to a method. Anytime the specified
  180. * method is called, the attached function is called right after, with the
  181. * same arguments and in the same scope. The attached function is called
  182. * right before if the last argument is true, unless the method is the graph
  183. * constructor.
  184. *
  185. * To attach a function to the graph constructor, use 'constructor' as the
  186. * method name (first argument).
  187. *
  188. * The main idea is to have a clean way to keep custom indexes up to date,
  189. * for instance:
  190. *
  191. * > var timesAddNodeCalled = 0;
  192. * > graph.attach('addNode', 'timesAddNodeCalledInc', function() {
  193. * > timesAddNodeCalled++;
  194. * > });
  195. * >
  196. * > var myGraph = new graph();
  197. * > console.log(timesAddNodeCalled); // outputs 0
  198. * >
  199. * > myGraph.addNode({ id: '1' }).addNode({ id: '2' });
  200. * > console.log(timesAddNodeCalled); // outputs 2
  201. *
  202. * The idea for calling a function before is to provide pre-processors, for
  203. * instance:
  204. *
  205. * > var colorPalette = { Person: '#C3CBE1', Place: '#9BDEBD' };
  206. * > graph.attach('addNode', 'applyNodeColorPalette', function(n) {
  207. * > n.color = colorPalette[n.category];
  208. * > }, true);
  209. * >
  210. * > var myGraph = new graph();
  211. * > myGraph.addNode({ id: 'n0', category: 'Person' });
  212. * > console.log(myGraph.nodes('n0').color); // outputs '#C3CBE1'
  213. *
  214. * @param {string} methodName The name of the related method or
  215. * "constructor".
  216. * @param {string} key The key to identify the function to attach.
  217. * @param {function} fn The function to bind.
  218. * @param {boolean} before If true the function is called right before.
  219. * @return {object} The global graph constructor.
  220. */
  221. graph.attach = function(methodName, key, fn, before) {
  222. if (
  223. typeof methodName !== 'string' ||
  224. typeof key !== 'string' ||
  225. typeof fn !== 'function' ||
  226. arguments.length < 3 ||
  227. arguments.length > 4
  228. )
  229. throw 'attach: Wrong arguments.';
  230. var bindings;
  231. if (methodName === 'constructor')
  232. bindings = _initBindings;
  233. else {
  234. if (before) {
  235. if (!_methodBeforeBindings[methodName])
  236. throw 'The method "' + methodName + '" does not exist.';
  237. bindings = _methodBeforeBindings[methodName];
  238. }
  239. else {
  240. if (!_methodBindings[methodName])
  241. throw 'The method "' + methodName + '" does not exist.';
  242. bindings = _methodBindings[methodName];
  243. }
  244. }
  245. if (bindings[key])
  246. throw 'A function "' + key + '" is already attached ' +
  247. 'to the method "' + methodName + '".';
  248. bindings[key] = fn;
  249. return this;
  250. };
  251. /**
  252. * Alias of attach(methodName, key, fn, true).
  253. */
  254. graph.attachBefore = function(methodName, key, fn) {
  255. return this.attach(methodName, key, fn, true);
  256. };
  257. /**
  258. * This methods is just an helper to deal with custom indexes. It takes as
  259. * arguments the name of the index and an object containing all the different
  260. * functions to bind to the methods.
  261. *
  262. * Here is a basic example, that creates an index to keep the number of nodes
  263. * in the current graph. It also adds a method to provide a getter on that
  264. * new index:
  265. *
  266. * > sigma.classes.graph.addIndex('nodesCount', {
  267. * > constructor: function() {
  268. * > this.nodesCount = 0;
  269. * > },
  270. * > addNode: function() {
  271. * > this.nodesCount++;
  272. * > },
  273. * > dropNode: function() {
  274. * > this.nodesCount--;
  275. * > }
  276. * > });
  277. * >
  278. * > sigma.classes.graph.addMethod('getNodesCount', function() {
  279. * > return this.nodesCount;
  280. * > });
  281. * >
  282. * > var myGraph = new sigma.classes.graph();
  283. * > console.log(myGraph.getNodesCount()); // outputs 0
  284. * >
  285. * > myGraph.addNode({ id: '1' }).addNode({ id: '2' });
  286. * > console.log(myGraph.getNodesCount()); // outputs 2
  287. *
  288. * @param {string} name The name of the index.
  289. * @param {object} bindings The object containing the functions to bind.
  290. * @return {object} The global graph constructor.
  291. */
  292. graph.addIndex = function(name, bindings) {
  293. if (
  294. typeof name !== 'string' ||
  295. Object(bindings) !== bindings ||
  296. arguments.length !== 2
  297. )
  298. throw 'addIndex: Wrong arguments.';
  299. if (_indexes[name])
  300. throw 'The index "' + name + '" already exists.';
  301. var k;
  302. // Store the bindings:
  303. _indexes[name] = bindings;
  304. // Attach the bindings:
  305. for (k in bindings)
  306. if (typeof bindings[k] !== 'function')
  307. throw 'The bindings must be functions.';
  308. else
  309. graph.attach(k, name, bindings[k]);
  310. return this;
  311. };
  312. /**
  313. * This method adds a node to the graph. The node must be an object, with a
  314. * string under the key "id". Except for this, it is possible to add any
  315. * other attribute, that will be preserved all along the manipulations.
  316. *
  317. * If the graph option "clone" has a truthy value, the node will be cloned
  318. * when added to the graph. Also, if the graph option "immutable" has a
  319. * truthy value, its id will be defined as immutable.
  320. *
  321. * @param {object} node The node to add.
  322. * @return {object} The graph instance.
  323. */
  324. graph.addMethod('addNode', function(node) {
  325. // Check that the node is an object and has an id:
  326. if (Object(node) !== node || arguments.length !== 1)
  327. throw 'addNode: Wrong arguments.';
  328. if (typeof node.id !== 'string' && typeof node.id !== 'number')
  329. throw 'The node must have a string or number id.';
  330. if (this.nodesIndex[node.id])
  331. throw 'The node "' + node.id + '" already exists.';
  332. var k,
  333. id = node.id,
  334. validNode = Object.create(null);
  335. // Check the "clone" option:
  336. if (this.settings('clone')) {
  337. for (k in node)
  338. if (k !== 'id')
  339. validNode[k] = node[k];
  340. } else
  341. validNode = node;
  342. // Check the "immutable" option:
  343. if (this.settings('immutable'))
  344. Object.defineProperty(validNode, 'id', {
  345. value: id,
  346. enumerable: true
  347. });
  348. else
  349. validNode.id = id;
  350. // Add empty containers for edges indexes:
  351. this.inNeighborsIndex[id] = Object.create(null);
  352. this.outNeighborsIndex[id] = Object.create(null);
  353. this.allNeighborsIndex[id] = Object.create(null);
  354. this.inNeighborsCount[id] = 0;
  355. this.outNeighborsCount[id] = 0;
  356. this.allNeighborsCount[id] = 0;
  357. // Add the node to indexes:
  358. this.nodesArray.push(validNode);
  359. this.nodesIndex[validNode.id] = validNode;
  360. // Return the current instance:
  361. return this;
  362. });
  363. /**
  364. * This method adds an edge to the graph. The edge must be an object, with a
  365. * string under the key "id", and strings under the keys "source" and
  366. * "target" that design existing nodes. Except for this, it is possible to
  367. * add any other attribute, that will be preserved all along the
  368. * manipulations.
  369. *
  370. * If the graph option "clone" has a truthy value, the edge will be cloned
  371. * when added to the graph. Also, if the graph option "immutable" has a
  372. * truthy value, its id, source and target will be defined as immutable.
  373. *
  374. * @param {object} edge The edge to add.
  375. * @return {object} The graph instance.
  376. */
  377. graph.addMethod('addEdge', function(edge) {
  378. // Check that the edge is an object and has an id:
  379. if (Object(edge) !== edge || arguments.length !== 1)
  380. throw 'addEdge: Wrong arguments.';
  381. if (typeof edge.id !== 'string' && typeof edge.id !== 'number')
  382. throw 'The edge must have a string or number id.';
  383. if ((typeof edge.source !== 'string' && typeof edge.source !== 'number') ||
  384. !this.nodesIndex[edge.source])
  385. throw 'The edge source must have an existing node id.';
  386. if ((typeof edge.target !== 'string' && typeof edge.target !== 'number') ||
  387. !this.nodesIndex[edge.target])
  388. throw 'The edge target must have an existing node id.';
  389. if (this.edgesIndex[edge.id])
  390. throw 'The edge "' + edge.id + '" already exists.';
  391. var k,
  392. validEdge = Object.create(null);
  393. // Check the "clone" option:
  394. if (this.settings('clone')) {
  395. for (k in edge)
  396. if (k !== 'id' && k !== 'source' && k !== 'target')
  397. validEdge[k] = edge[k];
  398. } else
  399. validEdge = edge;
  400. // Check the "immutable" option:
  401. if (this.settings('immutable')) {
  402. Object.defineProperty(validEdge, 'id', {
  403. value: edge.id,
  404. enumerable: true
  405. });
  406. Object.defineProperty(validEdge, 'source', {
  407. value: edge.source,
  408. enumerable: true
  409. });
  410. Object.defineProperty(validEdge, 'target', {
  411. value: edge.target,
  412. enumerable: true
  413. });
  414. } else {
  415. validEdge.id = edge.id;
  416. validEdge.source = edge.source;
  417. validEdge.target = edge.target;
  418. }
  419. // Add the edge to indexes:
  420. this.edgesArray.push(validEdge);
  421. this.edgesIndex[validEdge.id] = validEdge;
  422. if (!this.inNeighborsIndex[validEdge.target][validEdge.source])
  423. this.inNeighborsIndex[validEdge.target][validEdge.source] =
  424. Object.create(null);
  425. this.inNeighborsIndex[validEdge.target][validEdge.source][validEdge.id] =
  426. validEdge;
  427. if (!this.outNeighborsIndex[validEdge.source][validEdge.target])
  428. this.outNeighborsIndex[validEdge.source][validEdge.target] =
  429. Object.create(null);
  430. this.outNeighborsIndex[validEdge.source][validEdge.target][validEdge.id] =
  431. validEdge;
  432. if (!this.allNeighborsIndex[validEdge.source][validEdge.target])
  433. this.allNeighborsIndex[validEdge.source][validEdge.target] =
  434. Object.create(null);
  435. this.allNeighborsIndex[validEdge.source][validEdge.target][validEdge.id] =
  436. validEdge;
  437. if (validEdge.target !== validEdge.source) {
  438. if (!this.allNeighborsIndex[validEdge.target][validEdge.source])
  439. this.allNeighborsIndex[validEdge.target][validEdge.source] =
  440. Object.create(null);
  441. this.allNeighborsIndex[validEdge.target][validEdge.source][validEdge.id] =
  442. validEdge;
  443. }
  444. // Keep counts up to date:
  445. this.inNeighborsCount[validEdge.target]++;
  446. this.outNeighborsCount[validEdge.source]++;
  447. this.allNeighborsCount[validEdge.target]++;
  448. this.allNeighborsCount[validEdge.source]++;
  449. return this;
  450. });
  451. /**
  452. * This method drops a node from the graph. It also removes each edge that is
  453. * bound to it, through the dropEdge method. An error is thrown if the node
  454. * does not exist.
  455. *
  456. * @param {string} id The node id.
  457. * @return {object} The graph instance.
  458. */
  459. graph.addMethod('dropNode', function(id) {
  460. // Check that the arguments are valid:
  461. if ((typeof id !== 'string' && typeof id !== 'number') ||
  462. arguments.length !== 1)
  463. throw 'dropNode: Wrong arguments.';
  464. if (!this.nodesIndex[id])
  465. throw 'The node "' + id + '" does not exist.';
  466. var i, k, l;
  467. // Remove the node from indexes:
  468. delete this.nodesIndex[id];
  469. for (i = 0, l = this.nodesArray.length; i < l; i++)
  470. if (this.nodesArray[i].id === id) {
  471. this.nodesArray.splice(i, 1);
  472. break;
  473. }
  474. // Remove related edges:
  475. for (i = this.edgesArray.length - 1; i >= 0; i--)
  476. if (this.edgesArray[i].source === id || this.edgesArray[i].target === id)
  477. this.dropEdge(this.edgesArray[i].id);
  478. // Remove related edge indexes:
  479. delete this.inNeighborsIndex[id];
  480. delete this.outNeighborsIndex[id];
  481. delete this.allNeighborsIndex[id];
  482. delete this.inNeighborsCount[id];
  483. delete this.outNeighborsCount[id];
  484. delete this.allNeighborsCount[id];
  485. for (k in this.nodesIndex) {
  486. delete this.inNeighborsIndex[k][id];
  487. delete this.outNeighborsIndex[k][id];
  488. delete this.allNeighborsIndex[k][id];
  489. }
  490. return this;
  491. });
  492. /**
  493. * This method drops an edge from the graph. An error is thrown if the edge
  494. * does not exist.
  495. *
  496. * @param {string} id The edge id.
  497. * @return {object} The graph instance.
  498. */
  499. graph.addMethod('dropEdge', function(id) {
  500. // Check that the arguments are valid:
  501. if ((typeof id !== 'string' && typeof id !== 'number') ||
  502. arguments.length !== 1)
  503. throw 'dropEdge: Wrong arguments.';
  504. if (!this.edgesIndex[id])
  505. throw 'The edge "' + id + '" does not exist.';
  506. var i, l, edge;
  507. // Remove the edge from indexes:
  508. edge = this.edgesIndex[id];
  509. delete this.edgesIndex[id];
  510. for (i = 0, l = this.edgesArray.length; i < l; i++)
  511. if (this.edgesArray[i].id === id) {
  512. this.edgesArray.splice(i, 1);
  513. break;
  514. }
  515. delete this.inNeighborsIndex[edge.target][edge.source][edge.id];
  516. if (!Object.keys(this.inNeighborsIndex[edge.target][edge.source]).length)
  517. delete this.inNeighborsIndex[edge.target][edge.source];
  518. delete this.outNeighborsIndex[edge.source][edge.target][edge.id];
  519. if (!Object.keys(this.outNeighborsIndex[edge.source][edge.target]).length)
  520. delete this.outNeighborsIndex[edge.source][edge.target];
  521. delete this.allNeighborsIndex[edge.source][edge.target][edge.id];
  522. if (!Object.keys(this.allNeighborsIndex[edge.source][edge.target]).length)
  523. delete this.allNeighborsIndex[edge.source][edge.target];
  524. if (edge.target !== edge.source) {
  525. delete this.allNeighborsIndex[edge.target][edge.source][edge.id];
  526. if (!Object.keys(this.allNeighborsIndex[edge.target][edge.source]).length)
  527. delete this.allNeighborsIndex[edge.target][edge.source];
  528. }
  529. this.inNeighborsCount[edge.target]--;
  530. this.outNeighborsCount[edge.source]--;
  531. this.allNeighborsCount[edge.source]--;
  532. this.allNeighborsCount[edge.target]--;
  533. return this;
  534. });
  535. /**
  536. * This method destroys the current instance. It basically empties each index
  537. * and methods attached to the graph.
  538. */
  539. graph.addMethod('kill', function() {
  540. // Delete arrays:
  541. this.nodesArray.length = 0;
  542. this.edgesArray.length = 0;
  543. delete this.nodesArray;
  544. delete this.edgesArray;
  545. // Delete indexes:
  546. delete this.nodesIndex;
  547. delete this.edgesIndex;
  548. delete this.inNeighborsIndex;
  549. delete this.outNeighborsIndex;
  550. delete this.allNeighborsIndex;
  551. delete this.inNeighborsCount;
  552. delete this.outNeighborsCount;
  553. delete this.allNeighborsCount;
  554. });
  555. /**
  556. * This method empties the nodes and edges arrays, as well as the different
  557. * indexes.
  558. *
  559. * @return {object} The graph instance.
  560. */
  561. graph.addMethod('clear', function() {
  562. this.nodesArray.length = 0;
  563. this.edgesArray.length = 0;
  564. // Due to GC issues, I prefer not to create new object. These objects are
  565. // only available from the methods and attached functions, but still, it is
  566. // better to prevent ghost references to unrelevant data...
  567. __emptyObject(this.nodesIndex);
  568. __emptyObject(this.edgesIndex);
  569. __emptyObject(this.nodesIndex);
  570. __emptyObject(this.inNeighborsIndex);
  571. __emptyObject(this.outNeighborsIndex);
  572. __emptyObject(this.allNeighborsIndex);
  573. __emptyObject(this.inNeighborsCount);
  574. __emptyObject(this.outNeighborsCount);
  575. __emptyObject(this.allNeighborsCount);
  576. return this;
  577. });
  578. /**
  579. * This method reads an object and adds the nodes and edges, through the
  580. * proper methods "addNode" and "addEdge".
  581. *
  582. * Here is an example:
  583. *
  584. * > var myGraph = new graph();
  585. * > myGraph.read({
  586. * > nodes: [
  587. * > { id: 'n0' },
  588. * > { id: 'n1' }
  589. * > ],
  590. * > edges: [
  591. * > {
  592. * > id: 'e0',
  593. * > source: 'n0',
  594. * > target: 'n1'
  595. * > }
  596. * > ]
  597. * > });
  598. * >
  599. * > console.log(
  600. * > myGraph.nodes().length,
  601. * > myGraph.edges().length
  602. * > ); // outputs 2 1
  603. *
  604. * @param {object} g The graph object.
  605. * @return {object} The graph instance.
  606. */
  607. graph.addMethod('read', function(g) {
  608. var i,
  609. a,
  610. l;
  611. a = g.nodes || [];
  612. for (i = 0, l = a.length; i < l; i++)
  613. this.addNode(a[i]);
  614. a = g.edges || [];
  615. for (i = 0, l = a.length; i < l; i++)
  616. this.addEdge(a[i]);
  617. return this;
  618. });
  619. /**
  620. * This methods returns one or several nodes, depending on how it is called.
  621. *
  622. * To get the array of nodes, call "nodes" without argument. To get a
  623. * specific node, call it with the id of the node. The get multiple node,
  624. * call it with an array of ids, and it will return the array of nodes, in
  625. * the same order.
  626. *
  627. * @param {?(string|array)} v Eventually one id, an array of ids.
  628. * @return {object|array} The related node or array of nodes.
  629. */
  630. graph.addMethod('nodes', function(v) {
  631. // Clone the array of nodes and return it:
  632. if (!arguments.length)
  633. return this.nodesArray.slice(0);
  634. // Return the related node:
  635. if (arguments.length === 1 &&
  636. (typeof v === 'string' || typeof v === 'number'))
  637. return this.nodesIndex[v];
  638. // Return an array of the related node:
  639. if (
  640. arguments.length === 1 &&
  641. Object.prototype.toString.call(v) === '[object Array]'
  642. ) {
  643. var i,
  644. l,
  645. a = [];
  646. for (i = 0, l = v.length; i < l; i++)
  647. if (typeof v[i] === 'string' || typeof v[i] === 'number')
  648. a.push(this.nodesIndex[v[i]]);
  649. else
  650. throw 'nodes: Wrong arguments.';
  651. return a;
  652. }
  653. throw 'nodes: Wrong arguments.';
  654. });
  655. /**
  656. * This methods returns the degree of one or several nodes, depending on how
  657. * it is called. It is also possible to get incoming or outcoming degrees
  658. * instead by specifying 'in' or 'out' as a second argument.
  659. *
  660. * @param {string|array} v One id, an array of ids.
  661. * @param {?string} which Which degree is required. Values are 'in',
  662. * 'out', and by default the normal degree.
  663. * @return {number|array} The related degree or array of degrees.
  664. */
  665. graph.addMethod('degree', function(v, which) {
  666. // Check which degree is required:
  667. which = {
  668. 'in': this.inNeighborsCount,
  669. 'out': this.outNeighborsCount
  670. }[which || ''] || this.allNeighborsCount;
  671. // Return the related node:
  672. if (typeof v === 'string' || typeof v === 'number')
  673. return which[v];
  674. // Return an array of the related node:
  675. if (Object.prototype.toString.call(v) === '[object Array]') {
  676. var i,
  677. l,
  678. a = [];
  679. for (i = 0, l = v.length; i < l; i++)
  680. if (typeof v[i] === 'string' || typeof v[i] === 'number')
  681. a.push(which[v[i]]);
  682. else
  683. throw 'degree: Wrong arguments.';
  684. return a;
  685. }
  686. throw 'degree: Wrong arguments.';
  687. });
  688. /**
  689. * This methods returns one or several edges, depending on how it is called.
  690. *
  691. * To get the array of edges, call "edges" without argument. To get a
  692. * specific edge, call it with the id of the edge. The get multiple edge,
  693. * call it with an array of ids, and it will return the array of edges, in
  694. * the same order.
  695. *
  696. * @param {?(string|array)} v Eventually one id, an array of ids.
  697. * @return {object|array} The related edge or array of edges.
  698. */
  699. graph.addMethod('edges', function(v) {
  700. // Clone the array of edges and return it:
  701. if (!arguments.length)
  702. return this.edgesArray.slice(0);
  703. // Return the related edge:
  704. if (arguments.length === 1 &&
  705. (typeof v === 'string' || typeof v === 'number'))
  706. return this.edgesIndex[v];
  707. // Return an array of the related edge:
  708. if (
  709. arguments.length === 1 &&
  710. Object.prototype.toString.call(v) === '[object Array]'
  711. ) {
  712. var i,
  713. l,
  714. a = [];
  715. for (i = 0, l = v.length; i < l; i++)
  716. if (typeof v[i] === 'string' || typeof v[i] === 'number')
  717. a.push(this.edgesIndex[v[i]]);
  718. else
  719. throw 'edges: Wrong arguments.';
  720. return a;
  721. }
  722. throw 'edges: Wrong arguments.';
  723. });
  724. /**
  725. * EXPORT:
  726. * *******
  727. */
  728. if (typeof sigma !== 'undefined') {
  729. sigma.classes = sigma.classes || Object.create(null);
  730. sigma.classes.graph = graph;
  731. } else if (typeof exports !== 'undefined') {
  732. if (typeof module !== 'undefined' && module.exports)
  733. exports = module.exports = graph;
  734. exports.graph = graph;
  735. } else
  736. this.graph = graph;
  737. }).call(this);