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.

674 lines
19 KiB

  1. ;(function(undefined) {
  2. 'use strict';
  3. /**
  4. * Sigma Quadtree Module
  5. * =====================
  6. *
  7. * Author: Guillaume Plique (Yomguithereal)
  8. * Version: 0.2
  9. */
  10. /**
  11. * Quad Geometric Operations
  12. * -------------------------
  13. *
  14. * A useful batch of geometric operations used by the quadtree.
  15. */
  16. var _geom = {
  17. /**
  18. * Transforms a graph node with x, y and size into an
  19. * axis-aligned square.
  20. *
  21. * @param {object} A graph node with at least a point (x, y) and a size.
  22. * @return {object} A square: two points (x1, y1), (x2, y2) and height.
  23. */
  24. pointToSquare: function(n) {
  25. return {
  26. x1: n.x - n.size,
  27. y1: n.y - n.size,
  28. x2: n.x + n.size,
  29. y2: n.y - n.size,
  30. height: n.size * 2
  31. };
  32. },
  33. /**
  34. * Checks whether a rectangle is axis-aligned.
  35. *
  36. * @param {object} A rectangle defined by two points
  37. * (x1, y1) and (x2, y2).
  38. * @return {boolean} True if the rectangle is axis-aligned.
  39. */
  40. isAxisAligned: function(r) {
  41. return r.x1 === r.x2 || r.y1 === r.y2;
  42. },
  43. /**
  44. * Compute top points of an axis-aligned rectangle. This is useful in
  45. * cases when the rectangle has been rotated (left, right or bottom up) and
  46. * later operations need to know the top points.
  47. *
  48. * @param {object} An axis-aligned rectangle defined by two points
  49. * (x1, y1), (x2, y2) and height.
  50. * @return {object} A rectangle: two points (x1, y1), (x2, y2) and height.
  51. */
  52. axisAlignedTopPoints: function(r) {
  53. // Basic
  54. if (r.y1 === r.y2 && r.x1 < r.x2)
  55. return r;
  56. // Rotated to right
  57. if (r.x1 === r.x2 && r.y2 > r.y1)
  58. return {
  59. x1: r.x1 - r.height, y1: r.y1,
  60. x2: r.x1, y2: r.y1,
  61. height: r.height
  62. };
  63. // Rotated to left
  64. if (r.x1 === r.x2 && r.y2 < r.y1)
  65. return {
  66. x1: r.x1, y1: r.y2,
  67. x2: r.x2 + r.height, y2: r.y2,
  68. height: r.height
  69. };
  70. // Bottom's up
  71. return {
  72. x1: r.x2, y1: r.y1 - r.height,
  73. x2: r.x1, y2: r.y1 - r.height,
  74. height: r.height
  75. };
  76. },
  77. /**
  78. * Get coordinates of a rectangle's lower left corner from its top points.
  79. *
  80. * @param {object} A rectangle defined by two points (x1, y1) and (x2, y2).
  81. * @return {object} Coordinates of the corner (x, y).
  82. */
  83. lowerLeftCoor: function(r) {
  84. var width = (
  85. Math.sqrt(
  86. Math.pow(r.x2 - r.x1, 2) +
  87. Math.pow(r.y2 - r.y1, 2)
  88. )
  89. );
  90. return {
  91. x: r.x1 - (r.y2 - r.y1) * r.height / width,
  92. y: r.y1 + (r.x2 - r.x1) * r.height / width
  93. };
  94. },
  95. /**
  96. * Get coordinates of a rectangle's lower right corner from its top points
  97. * and its lower left corner.
  98. *
  99. * @param {object} A rectangle defined by two points (x1, y1) and (x2, y2).
  100. * @param {object} A corner's coordinates (x, y).
  101. * @return {object} Coordinates of the corner (x, y).
  102. */
  103. lowerRightCoor: function(r, llc) {
  104. return {
  105. x: llc.x - r.x1 + r.x2,
  106. y: llc.y - r.y1 + r.y2
  107. };
  108. },
  109. /**
  110. * Get the coordinates of all the corners of a rectangle from its top point.
  111. *
  112. * @param {object} A rectangle defined by two points (x1, y1) and (x2, y2).
  113. * @return {array} An array of the four corners' coordinates (x, y).
  114. */
  115. rectangleCorners: function(r) {
  116. var llc = this.lowerLeftCoor(r),
  117. lrc = this.lowerRightCoor(r, llc);
  118. return [
  119. {x: r.x1, y: r.y1},
  120. {x: r.x2, y: r.y2},
  121. {x: llc.x, y: llc.y},
  122. {x: lrc.x, y: lrc.y}
  123. ];
  124. },
  125. /**
  126. * Split a square defined by its boundaries into four.
  127. *
  128. * @param {object} Boundaries of the square (x, y, width, height).
  129. * @return {array} An array containing the four new squares, themselves
  130. * defined by an array of their four corners (x, y).
  131. */
  132. splitSquare: function(b) {
  133. return [
  134. [
  135. {x: b.x, y: b.y},
  136. {x: b.x + b.width / 2, y: b.y},
  137. {x: b.x, y: b.y + b.height / 2},
  138. {x: b.x + b.width / 2, y: b.y + b.height / 2}
  139. ],
  140. [
  141. {x: b.x + b.width / 2, y: b.y},
  142. {x: b.x + b.width, y: b.y},
  143. {x: b.x + b.width / 2, y: b.y + b.height / 2},
  144. {x: b.x + b.width, y: b.y + b.height / 2}
  145. ],
  146. [
  147. {x: b.x, y: b.y + b.height / 2},
  148. {x: b.x + b.width / 2, y: b.y + b.height / 2},
  149. {x: b.x, y: b.y + b.height},
  150. {x: b.x + b.width / 2, y: b.y + b.height}
  151. ],
  152. [
  153. {x: b.x + b.width / 2, y: b.y + b.height / 2},
  154. {x: b.x + b.width, y: b.y + b.height / 2},
  155. {x: b.x + b.width / 2, y: b.y + b.height},
  156. {x: b.x + b.width, y: b.y + b.height}
  157. ]
  158. ];
  159. },
  160. /**
  161. * Compute the four axis between corners of rectangle A and corners of
  162. * rectangle B. This is needed later to check an eventual collision.
  163. *
  164. * @param {array} An array of rectangle A's four corners (x, y).
  165. * @param {array} An array of rectangle B's four corners (x, y).
  166. * @return {array} An array of four axis defined by their coordinates (x,y).
  167. */
  168. axis: function(c1, c2) {
  169. return [
  170. {x: c1[1].x - c1[0].x, y: c1[1].y - c1[0].y},
  171. {x: c1[1].x - c1[3].x, y: c1[1].y - c1[3].y},
  172. {x: c2[0].x - c2[2].x, y: c2[0].y - c2[2].y},
  173. {x: c2[0].x - c2[1].x, y: c2[0].y - c2[1].y}
  174. ];
  175. },
  176. /**
  177. * Project a rectangle's corner on an axis.
  178. *
  179. * @param {object} Coordinates of a corner (x, y).
  180. * @param {object} Coordinates of an axis (x, y).
  181. * @return {object} The projection defined by coordinates (x, y).
  182. */
  183. projection: function(c, a) {
  184. var l = (
  185. (c.x * a.x + c.y * a.y) /
  186. (Math.pow(a.x, 2) + Math.pow(a.y, 2))
  187. );
  188. return {
  189. x: l * a.x,
  190. y: l * a.y
  191. };
  192. },
  193. /**
  194. * Check whether two rectangles collide on one particular axis.
  195. *
  196. * @param {object} An axis' coordinates (x, y).
  197. * @param {array} Rectangle A's corners.
  198. * @param {array} Rectangle B's corners.
  199. * @return {boolean} True if the rectangles collide on the axis.
  200. */
  201. axisCollision: function(a, c1, c2) {
  202. var sc1 = [],
  203. sc2 = [];
  204. for (var ci = 0; ci < 4; ci++) {
  205. var p1 = this.projection(c1[ci], a),
  206. p2 = this.projection(c2[ci], a);
  207. sc1.push(p1.x * a.x + p1.y * a.y);
  208. sc2.push(p2.x * a.x + p2.y * a.y);
  209. }
  210. var maxc1 = Math.max.apply(Math, sc1),
  211. maxc2 = Math.max.apply(Math, sc2),
  212. minc1 = Math.min.apply(Math, sc1),
  213. minc2 = Math.min.apply(Math, sc2);
  214. return (minc2 <= maxc1 && maxc2 >= minc1);
  215. },
  216. /**
  217. * Check whether two rectangles collide on each one of their four axis. If
  218. * all axis collide, then the two rectangles do collide on the plane.
  219. *
  220. * @param {array} Rectangle A's corners.
  221. * @param {array} Rectangle B's corners.
  222. * @return {boolean} True if the rectangles collide.
  223. */
  224. collision: function(c1, c2) {
  225. var axis = this.axis(c1, c2),
  226. col = true;
  227. for (var i = 0; i < 4; i++)
  228. col = col && this.axisCollision(axis[i], c1, c2);
  229. return col;
  230. }
  231. };
  232. /**
  233. * Quad Functions
  234. * ------------
  235. *
  236. * The Quadtree functions themselves.
  237. * For each of those functions, we consider that in a splitted quad, the
  238. * index of each node is the following:
  239. * 0: top left
  240. * 1: top right
  241. * 2: bottom left
  242. * 3: bottom right
  243. *
  244. * Moreover, the hereafter quad's philosophy is to consider that if an element
  245. * collides with more than one nodes, this element belongs to each of the
  246. * nodes it collides with where other would let it lie on a higher node.
  247. */
  248. /**
  249. * Get the index of the node containing the point in the quad
  250. *
  251. * @param {object} point A point defined by coordinates (x, y).
  252. * @param {object} quadBounds Boundaries of the quad (x, y, width, heigth).
  253. * @return {integer} The index of the node containing the point.
  254. */
  255. function _quadIndex(point, quadBounds) {
  256. var xmp = quadBounds.x + quadBounds.width / 2,
  257. ymp = quadBounds.y + quadBounds.height / 2,
  258. top = (point.y < ymp),
  259. left = (point.x < xmp);
  260. if (top) {
  261. if (left)
  262. return 0;
  263. else
  264. return 1;
  265. }
  266. else {
  267. if (left)
  268. return 2;
  269. else
  270. return 3;
  271. }
  272. }
  273. /**
  274. * Get a list of indexes of nodes containing an axis-aligned rectangle
  275. *
  276. * @param {object} rectangle A rectangle defined by two points (x1, y1),
  277. * (x2, y2) and height.
  278. * @param {array} quadCorners An array of the quad nodes' corners.
  279. * @return {array} An array of indexes containing one to
  280. * four integers.
  281. */
  282. function _quadIndexes(rectangle, quadCorners) {
  283. var indexes = [];
  284. // Iterating through quads
  285. for (var i = 0; i < 4; i++)
  286. if ((rectangle.x2 >= quadCorners[i][0].x) &&
  287. (rectangle.x1 <= quadCorners[i][1].x) &&
  288. (rectangle.y1 + rectangle.height >= quadCorners[i][0].y) &&
  289. (rectangle.y1 <= quadCorners[i][2].y))
  290. indexes.push(i);
  291. return indexes;
  292. }
  293. /**
  294. * Get a list of indexes of nodes containing a non-axis-aligned rectangle
  295. *
  296. * @param {array} corners An array containing each corner of the
  297. * rectangle defined by its coordinates (x, y).
  298. * @param {array} quadCorners An array of the quad nodes' corners.
  299. * @return {array} An array of indexes containing one to
  300. * four integers.
  301. */
  302. function _quadCollision(corners, quadCorners) {
  303. var indexes = [];
  304. // Iterating through quads
  305. for (var i = 0; i < 4; i++)
  306. if (_geom.collision(corners, quadCorners[i]))
  307. indexes.push(i);
  308. return indexes;
  309. }
  310. /**
  311. * Subdivide a quad by creating a node at a precise index. The function does
  312. * not generate all four nodes not to potentially create unused nodes.
  313. *
  314. * @param {integer} index The index of the node to create.
  315. * @param {object} quad The quad object to subdivide.
  316. * @return {object} A new quad representing the node created.
  317. */
  318. function _quadSubdivide(index, quad) {
  319. var next = quad.level + 1,
  320. subw = Math.round(quad.bounds.width / 2),
  321. subh = Math.round(quad.bounds.height / 2),
  322. qx = Math.round(quad.bounds.x),
  323. qy = Math.round(quad.bounds.y),
  324. x,
  325. y;
  326. switch (index) {
  327. case 0:
  328. x = qx;
  329. y = qy;
  330. break;
  331. case 1:
  332. x = qx + subw;
  333. y = qy;
  334. break;
  335. case 2:
  336. x = qx;
  337. y = qy + subh;
  338. break;
  339. case 3:
  340. x = qx + subw;
  341. y = qy + subh;
  342. break;
  343. }
  344. return _quadTree(
  345. {x: x, y: y, width: subw, height: subh},
  346. next,
  347. quad.maxElements,
  348. quad.maxLevel
  349. );
  350. }
  351. /**
  352. * Recursively insert an element into the quadtree. Only points
  353. * with size, i.e. axis-aligned squares, may be inserted with this
  354. * method.
  355. *
  356. * @param {object} el The element to insert in the quadtree.
  357. * @param {object} sizedPoint A sized point defined by two top points
  358. * (x1, y1), (x2, y2) and height.
  359. * @param {object} quad The quad in which to insert the element.
  360. * @return {undefined} The function does not return anything.
  361. */
  362. function _quadInsert(el, sizedPoint, quad) {
  363. if (quad.level < quad.maxLevel) {
  364. // Searching appropriate quads
  365. var indexes = _quadIndexes(sizedPoint, quad.corners);
  366. // Iterating
  367. for (var i = 0, l = indexes.length; i < l; i++) {
  368. // Subdividing if necessary
  369. if (quad.nodes[indexes[i]] === undefined)
  370. quad.nodes[indexes[i]] = _quadSubdivide(indexes[i], quad);
  371. // Recursion
  372. _quadInsert(el, sizedPoint, quad.nodes[indexes[i]]);
  373. }
  374. }
  375. else {
  376. // Pushing the element in a leaf node
  377. quad.elements.push(el);
  378. }
  379. }
  380. /**
  381. * Recursively retrieve every elements held by the node containing the
  382. * searched point.
  383. *
  384. * @param {object} point The searched point (x, y).
  385. * @param {object} quad The searched quad.
  386. * @return {array} An array of elements contained in the relevant
  387. * node.
  388. */
  389. function _quadRetrievePoint(point, quad) {
  390. if (quad.level < quad.maxLevel) {
  391. var index = _quadIndex(point, quad.bounds);
  392. // If node does not exist we return an empty list
  393. if (quad.nodes[index] !== undefined) {
  394. return _quadRetrievePoint(point, quad.nodes[index]);
  395. }
  396. else {
  397. return [];
  398. }
  399. }
  400. else {
  401. return quad.elements;
  402. }
  403. }
  404. /**
  405. * Recursively retrieve every elements contained within an rectangular area
  406. * that may or may not be axis-aligned.
  407. *
  408. * @param {object|array} rectData The searched area defined either by
  409. * an array of four corners (x, y) in
  410. * the case of a non-axis-aligned
  411. * rectangle or an object with two top
  412. * points (x1, y1), (x2, y2) and height.
  413. * @param {object} quad The searched quad.
  414. * @param {function} collisionFunc The collision function used to search
  415. * for node indexes.
  416. * @param {array?} els The retrieved elements.
  417. * @return {array} An array of elements contained in the
  418. * area.
  419. */
  420. function _quadRetrieveArea(rectData, quad, collisionFunc, els) {
  421. els = els || {};
  422. if (quad.level < quad.maxLevel) {
  423. var indexes = collisionFunc(rectData, quad.corners);
  424. for (var i = 0, l = indexes.length; i < l; i++)
  425. if (quad.nodes[indexes[i]] !== undefined)
  426. _quadRetrieveArea(
  427. rectData,
  428. quad.nodes[indexes[i]],
  429. collisionFunc,
  430. els
  431. );
  432. } else
  433. for (var j = 0, m = quad.elements.length; j < m; j++)
  434. if (els[quad.elements[j].id] === undefined)
  435. els[quad.elements[j].id] = quad.elements[j];
  436. return els;
  437. }
  438. /**
  439. * Creates the quadtree object itself.
  440. *
  441. * @param {object} bounds The boundaries of the quad defined by an
  442. * origin (x, y), width and heigth.
  443. * @param {integer} level The level of the quad in the tree.
  444. * @param {integer} maxElements The max number of element in a leaf node.
  445. * @param {integer} maxLevel The max recursion level of the tree.
  446. * @return {object} The quadtree object.
  447. */
  448. function _quadTree(bounds, level, maxElements, maxLevel) {
  449. return {
  450. level: level || 0,
  451. bounds: bounds,
  452. corners: _geom.splitSquare(bounds),
  453. maxElements: maxElements || 20,
  454. maxLevel: maxLevel || 4,
  455. elements: [],
  456. nodes: []
  457. };
  458. }
  459. /**
  460. * Sigma Quad Constructor
  461. * ----------------------
  462. *
  463. * The quad API as exposed to sigma.
  464. */
  465. /**
  466. * The quad core that will become the sigma interface with the quadtree.
  467. *
  468. * property {object} _tree Property holding the quadtree object.
  469. * property {object} _geom Exposition of the _geom namespace for testing.
  470. * property {object} _cache Cache for the area method.
  471. */
  472. var quad = function() {
  473. this._geom = _geom;
  474. this._tree = null;
  475. this._cache = {
  476. query: false,
  477. result: false
  478. };
  479. };
  480. /**
  481. * Index a graph by inserting its nodes into the quadtree.
  482. *
  483. * @param {array} nodes An array of nodes to index.
  484. * @param {object} params An object of parameters with at least the quad
  485. * bounds.
  486. * @return {object} The quadtree object.
  487. *
  488. * Parameters:
  489. * ----------
  490. * bounds: {object} boundaries of the quad defined by its origin (x, y)
  491. * width and heigth.
  492. * prefix: {string?} a prefix for node geometric attributes.
  493. * maxElements: {integer?} the max number of elements in a leaf node.
  494. * maxLevel: {integer?} the max recursion level of the tree.
  495. */
  496. quad.prototype.index = function(nodes, params) {
  497. // Enforcing presence of boundaries
  498. if (!params.bounds)
  499. throw 'sigma.classes.quad.index: bounds information not given.';
  500. // Prefix
  501. var prefix = params.prefix || '';
  502. // Building the tree
  503. this._tree = _quadTree(
  504. params.bounds,
  505. 0,
  506. params.maxElements,
  507. params.maxLevel
  508. );
  509. // Inserting graph nodes into the tree
  510. for (var i = 0, l = nodes.length; i < l; i++) {
  511. // Inserting node
  512. _quadInsert(
  513. nodes[i],
  514. _geom.pointToSquare({
  515. x: nodes[i][prefix + 'x'],
  516. y: nodes[i][prefix + 'y'],
  517. size: nodes[i][prefix + 'size']
  518. }),
  519. this._tree
  520. );
  521. }
  522. // Reset cache:
  523. this._cache = {
  524. query: false,
  525. result: false
  526. };
  527. // remove?
  528. return this._tree;
  529. };
  530. /**
  531. * Retrieve every graph nodes held by the quadtree node containing the
  532. * searched point.
  533. *
  534. * @param {number} x of the point.
  535. * @param {number} y of the point.
  536. * @return {array} An array of nodes retrieved.
  537. */
  538. quad.prototype.point = function(x, y) {
  539. return this._tree ?
  540. _quadRetrievePoint({x: x, y: y}, this._tree) || [] :
  541. [];
  542. };
  543. /**
  544. * Retrieve every graph nodes within a rectangular area. The methods keep the
  545. * last area queried in cache for optimization reason and will act differently
  546. * for the same reason if the area is axis-aligned or not.
  547. *
  548. * @param {object} A rectangle defined by two top points (x1, y1), (x2, y2)
  549. * and height.
  550. * @return {array} An array of nodes retrieved.
  551. */
  552. quad.prototype.area = function(rect) {
  553. var serialized = JSON.stringify(rect),
  554. collisionFunc,
  555. rectData;
  556. // Returning cache?
  557. if (this._cache.query === serialized)
  558. return this._cache.result;
  559. // Axis aligned ?
  560. if (_geom.isAxisAligned(rect)) {
  561. collisionFunc = _quadIndexes;
  562. rectData = _geom.axisAlignedTopPoints(rect);
  563. }
  564. else {
  565. collisionFunc = _quadCollision;
  566. rectData = _geom.rectangleCorners(rect);
  567. }
  568. // Retrieving nodes
  569. var nodes = this._tree ?
  570. _quadRetrieveArea(
  571. rectData,
  572. this._tree,
  573. collisionFunc
  574. ) :
  575. [];
  576. // Object to array
  577. var nodesArray = [];
  578. for (var i in nodes)
  579. nodesArray.push(nodes[i]);
  580. // Caching
  581. this._cache.query = serialized;
  582. this._cache.result = nodesArray;
  583. return nodesArray;
  584. };
  585. /**
  586. * EXPORT:
  587. * *******
  588. */
  589. if (typeof this.sigma !== 'undefined') {
  590. this.sigma.classes = this.sigma.classes || {};
  591. this.sigma.classes.quad = quad;
  592. } else if (typeof exports !== 'undefined') {
  593. if (typeof module !== 'undefined' && module.exports)
  594. exports = module.exports = quad;
  595. exports.quad = quad;
  596. } else
  597. this.quad = quad;
  598. }).call(this);