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.

509 lines
14 KiB

  1. ;(function(undefined) {
  2. 'use strict';
  3. if (typeof sigma === 'undefined')
  4. throw 'sigma is not declared';
  5. // Initialize packages:
  6. sigma.utils.pkg('sigma.misc');
  7. /**
  8. * This helper will bind any no-DOM renderer (for instance canvas or WebGL)
  9. * to its captors, to properly dispatch the good events to the sigma instance
  10. * to manage clicking, hovering etc...
  11. *
  12. * It has to be called in the scope of the related renderer.
  13. */
  14. sigma.misc.bindEvents = function(prefix) {
  15. var i,
  16. l,
  17. mX,
  18. mY,
  19. captor,
  20. self = this;
  21. function getNodes(e) {
  22. if (e) {
  23. mX = 'x' in e.data ? e.data.x : mX;
  24. mY = 'y' in e.data ? e.data.y : mY;
  25. }
  26. var i,
  27. j,
  28. l,
  29. n,
  30. x,
  31. y,
  32. s,
  33. inserted,
  34. selected = [],
  35. modifiedX = mX + self.width / 2,
  36. modifiedY = mY + self.height / 2,
  37. point = self.camera.cameraPosition(
  38. mX,
  39. mY
  40. ),
  41. nodes = self.camera.quadtree.point(
  42. point.x,
  43. point.y
  44. );
  45. if (nodes.length)
  46. for (i = 0, l = nodes.length; i < l; i++) {
  47. n = nodes[i];
  48. x = n[prefix + 'x'];
  49. y = n[prefix + 'y'];
  50. s = n[prefix + 'size'];
  51. if (
  52. !n.hidden &&
  53. modifiedX > x - s &&
  54. modifiedX < x + s &&
  55. modifiedY > y - s &&
  56. modifiedY < y + s &&
  57. Math.sqrt(
  58. Math.pow(modifiedX - x, 2) +
  59. Math.pow(modifiedY - y, 2)
  60. ) < s
  61. ) {
  62. // Insert the node:
  63. inserted = false;
  64. for (j = 0; j < selected.length; j++)
  65. if (n.size > selected[j].size) {
  66. selected.splice(j, 0, n);
  67. inserted = true;
  68. break;
  69. }
  70. if (!inserted)
  71. selected.push(n);
  72. }
  73. }
  74. return selected;
  75. }
  76. function getEdges(e) {
  77. if (!self.settings('enableEdgeHovering')) {
  78. // No event if the setting is off:
  79. return [];
  80. }
  81. var isCanvas = (
  82. sigma.renderers.canvas && self instanceof sigma.renderers.canvas);
  83. if (!isCanvas) {
  84. // A quick hardcoded rule to prevent people from using this feature
  85. // with the WebGL renderer (which is not good enough at the moment):
  86. throw new Error(
  87. 'The edge events feature is not compatible with the WebGL renderer'
  88. );
  89. }
  90. if (e) {
  91. mX = 'x' in e.data ? e.data.x : mX;
  92. mY = 'y' in e.data ? e.data.y : mY;
  93. }
  94. var i,
  95. j,
  96. l,
  97. a,
  98. edge,
  99. s,
  100. maxEpsilon = self.settings('edgeHoverPrecision'),
  101. source,
  102. target,
  103. cp,
  104. nodeIndex = {},
  105. inserted,
  106. selected = [],
  107. modifiedX = mX + self.width / 2,
  108. modifiedY = mY + self.height / 2,
  109. point = self.camera.cameraPosition(
  110. mX,
  111. mY
  112. ),
  113. edges = [];
  114. if (isCanvas) {
  115. var nodesOnScreen = self.camera.quadtree.area(
  116. self.camera.getRectangle(self.width, self.height)
  117. );
  118. for (a = nodesOnScreen, i = 0, l = a.length; i < l; i++)
  119. nodeIndex[a[i].id] = a[i];
  120. }
  121. if (self.camera.edgequadtree !== undefined) {
  122. edges = self.camera.edgequadtree.point(
  123. point.x,
  124. point.y
  125. );
  126. }
  127. function insertEdge(selected, edge) {
  128. inserted = false;
  129. for (j = 0; j < selected.length; j++)
  130. if (edge.size > selected[j].size) {
  131. selected.splice(j, 0, edge);
  132. inserted = true;
  133. break;
  134. }
  135. if (!inserted)
  136. selected.push(edge);
  137. }
  138. if (edges.length)
  139. for (i = 0, l = edges.length; i < l; i++) {
  140. edge = edges[i];
  141. source = self.graph.nodes(edge.source);
  142. target = self.graph.nodes(edge.target);
  143. // (HACK) we can't get edge[prefix + 'size'] on WebGL renderer:
  144. s = edge[prefix + 'size'] ||
  145. edge['read_' + prefix + 'size'];
  146. // First, let's identify which edges are drawn. To do this, we keep
  147. // every edges that have at least one extremity displayed according to
  148. // the quadtree and the "hidden" attribute. We also do not keep hidden
  149. // edges.
  150. // Then, let's check if the mouse is on the edge (we suppose that it
  151. // is a line segment).
  152. if (
  153. !edge.hidden &&
  154. !source.hidden && !target.hidden &&
  155. (!isCanvas ||
  156. (nodeIndex[edge.source] || nodeIndex[edge.target])) &&
  157. sigma.utils.getDistance(
  158. source[prefix + 'x'],
  159. source[prefix + 'y'],
  160. modifiedX,
  161. modifiedY) > source[prefix + 'size'] &&
  162. sigma.utils.getDistance(
  163. target[prefix + 'x'],
  164. target[prefix + 'y'],
  165. modifiedX,
  166. modifiedY) > target[prefix + 'size']
  167. ) {
  168. if (edge.type == 'curve' || edge.type == 'curvedArrow') {
  169. if (source.id === target.id) {
  170. cp = sigma.utils.getSelfLoopControlPoints(
  171. source[prefix + 'x'],
  172. source[prefix + 'y'],
  173. source[prefix + 'size']
  174. );
  175. if (
  176. sigma.utils.isPointOnBezierCurve(
  177. modifiedX,
  178. modifiedY,
  179. source[prefix + 'x'],
  180. source[prefix + 'y'],
  181. target[prefix + 'x'],
  182. target[prefix + 'y'],
  183. cp.x1,
  184. cp.y1,
  185. cp.x2,
  186. cp.y2,
  187. Math.max(s, maxEpsilon)
  188. )) {
  189. insertEdge(selected, edge);
  190. }
  191. }
  192. else {
  193. cp = sigma.utils.getQuadraticControlPoint(
  194. source[prefix + 'x'],
  195. source[prefix + 'y'],
  196. target[prefix + 'x'],
  197. target[prefix + 'y']);
  198. if (
  199. sigma.utils.isPointOnQuadraticCurve(
  200. modifiedX,
  201. modifiedY,
  202. source[prefix + 'x'],
  203. source[prefix + 'y'],
  204. target[prefix + 'x'],
  205. target[prefix + 'y'],
  206. cp.x,
  207. cp.y,
  208. Math.max(s, maxEpsilon)
  209. )) {
  210. insertEdge(selected, edge);
  211. }
  212. }
  213. } else if (
  214. sigma.utils.isPointOnSegment(
  215. modifiedX,
  216. modifiedY,
  217. source[prefix + 'x'],
  218. source[prefix + 'y'],
  219. target[prefix + 'x'],
  220. target[prefix + 'y'],
  221. Math.max(s, maxEpsilon)
  222. )) {
  223. insertEdge(selected, edge);
  224. }
  225. }
  226. }
  227. return selected;
  228. }
  229. function bindCaptor(captor) {
  230. var nodes,
  231. edges,
  232. overNodes = {},
  233. overEdges = {};
  234. function onClick(e) {
  235. if (!self.settings('eventsEnabled'))
  236. return;
  237. self.dispatchEvent('click', e.data);
  238. nodes = getNodes(e);
  239. edges = getEdges(e);
  240. if (nodes.length) {
  241. self.dispatchEvent('clickNode', {
  242. node: nodes[0],
  243. captor: e.data
  244. });
  245. self.dispatchEvent('clickNodes', {
  246. node: nodes,
  247. captor: e.data
  248. });
  249. } else if (edges.length) {
  250. self.dispatchEvent('clickEdge', {
  251. edge: edges[0],
  252. captor: e.data
  253. });
  254. self.dispatchEvent('clickEdges', {
  255. edge: edges,
  256. captor: e.data
  257. });
  258. } else
  259. self.dispatchEvent('clickStage', {captor: e.data});
  260. }
  261. function onDoubleClick(e) {
  262. if (!self.settings('eventsEnabled'))
  263. return;
  264. self.dispatchEvent('doubleClick', e.data);
  265. nodes = getNodes(e);
  266. edges = getEdges(e);
  267. if (nodes.length) {
  268. self.dispatchEvent('doubleClickNode', {
  269. node: nodes[0],
  270. captor: e.data
  271. });
  272. self.dispatchEvent('doubleClickNodes', {
  273. node: nodes,
  274. captor: e.data
  275. });
  276. } else if (edges.length) {
  277. self.dispatchEvent('doubleClickEdge', {
  278. edge: edges[0],
  279. captor: e.data
  280. });
  281. self.dispatchEvent('doubleClickEdges', {
  282. edge: edges,
  283. captor: e.data
  284. });
  285. } else
  286. self.dispatchEvent('doubleClickStage', {captor: e.data});
  287. }
  288. function onRightClick(e) {
  289. if (!self.settings('eventsEnabled'))
  290. return;
  291. self.dispatchEvent('rightClick', e.data);
  292. nodes = getNodes(e);
  293. edges = getEdges(e);
  294. if (nodes.length) {
  295. self.dispatchEvent('rightClickNode', {
  296. node: nodes[0],
  297. captor: e.data
  298. });
  299. self.dispatchEvent('rightClickNodes', {
  300. node: nodes,
  301. captor: e.data
  302. });
  303. } else if (edges.length) {
  304. self.dispatchEvent('rightClickEdge', {
  305. edge: edges[0],
  306. captor: e.data
  307. });
  308. self.dispatchEvent('rightClickEdges', {
  309. edge: edges,
  310. captor: e.data
  311. });
  312. } else
  313. self.dispatchEvent('rightClickStage', {captor: e.data});
  314. }
  315. function onOut(e) {
  316. if (!self.settings('eventsEnabled'))
  317. return;
  318. var k,
  319. i,
  320. l,
  321. le,
  322. outNodes = [],
  323. outEdges = [];
  324. for (k in overNodes)
  325. outNodes.push(overNodes[k]);
  326. overNodes = {};
  327. // Dispatch both single and multi events:
  328. for (i = 0, l = outNodes.length; i < l; i++)
  329. self.dispatchEvent('outNode', {
  330. node: outNodes[i],
  331. captor: e.data
  332. });
  333. if (outNodes.length)
  334. self.dispatchEvent('outNodes', {
  335. nodes: outNodes,
  336. captor: e.data
  337. });
  338. overEdges = {};
  339. // Dispatch both single and multi events:
  340. for (i = 0, le = outEdges.length; i < le; i++)
  341. self.dispatchEvent('outEdge', {
  342. edge: outEdges[i],
  343. captor: e.data
  344. });
  345. if (outEdges.length)
  346. self.dispatchEvent('outEdges', {
  347. edges: outEdges,
  348. captor: e.data
  349. });
  350. }
  351. function onMove(e) {
  352. if (!self.settings('eventsEnabled'))
  353. return;
  354. nodes = getNodes(e);
  355. edges = getEdges(e);
  356. var i,
  357. k,
  358. node,
  359. edge,
  360. newOutNodes = [],
  361. newOverNodes = [],
  362. currentOverNodes = {},
  363. l = nodes.length,
  364. newOutEdges = [],
  365. newOverEdges = [],
  366. currentOverEdges = {},
  367. le = edges.length;
  368. // Check newly overred nodes:
  369. for (i = 0; i < l; i++) {
  370. node = nodes[i];
  371. currentOverNodes[node.id] = node;
  372. if (!overNodes[node.id]) {
  373. newOverNodes.push(node);
  374. overNodes[node.id] = node;
  375. }
  376. }
  377. // Check no more overred nodes:
  378. for (k in overNodes)
  379. if (!currentOverNodes[k]) {
  380. newOutNodes.push(overNodes[k]);
  381. delete overNodes[k];
  382. }
  383. // Dispatch both single and multi events:
  384. for (i = 0, l = newOverNodes.length; i < l; i++)
  385. self.dispatchEvent('overNode', {
  386. node: newOverNodes[i],
  387. captor: e.data
  388. });
  389. for (i = 0, l = newOutNodes.length; i < l; i++)
  390. self.dispatchEvent('outNode', {
  391. node: newOutNodes[i],
  392. captor: e.data
  393. });
  394. if (newOverNodes.length)
  395. self.dispatchEvent('overNodes', {
  396. nodes: newOverNodes,
  397. captor: e.data
  398. });
  399. if (newOutNodes.length)
  400. self.dispatchEvent('outNodes', {
  401. nodes: newOutNodes,
  402. captor: e.data
  403. });
  404. // Check newly overred edges:
  405. for (i = 0; i < le; i++) {
  406. edge = edges[i];
  407. currentOverEdges[edge.id] = edge;
  408. if (!overEdges[edge.id]) {
  409. newOverEdges.push(edge);
  410. overEdges[edge.id] = edge;
  411. }
  412. }
  413. // Check no more overred edges:
  414. for (k in overEdges)
  415. if (!currentOverEdges[k]) {
  416. newOutEdges.push(overEdges[k]);
  417. delete overEdges[k];
  418. }
  419. // Dispatch both single and multi events:
  420. for (i = 0, le = newOverEdges.length; i < le; i++)
  421. self.dispatchEvent('overEdge', {
  422. edge: newOverEdges[i],
  423. captor: e.data
  424. });
  425. for (i = 0, le = newOutEdges.length; i < le; i++)
  426. self.dispatchEvent('outEdge', {
  427. edge: newOutEdges[i],
  428. captor: e.data
  429. });
  430. if (newOverEdges.length)
  431. self.dispatchEvent('overEdges', {
  432. edges: newOverEdges,
  433. captor: e.data
  434. });
  435. if (newOutEdges.length)
  436. self.dispatchEvent('outEdges', {
  437. edges: newOutEdges,
  438. captor: e.data
  439. });
  440. }
  441. // Bind events:
  442. captor.bind('click', onClick);
  443. captor.bind('mousedown', onMove);
  444. captor.bind('mouseup', onMove);
  445. captor.bind('mousemove', onMove);
  446. captor.bind('mouseout', onOut);
  447. captor.bind('doubleclick', onDoubleClick);
  448. captor.bind('rightclick', onRightClick);
  449. self.bind('render', onMove);
  450. }
  451. for (i = 0, l = this.captors.length; i < l; i++)
  452. bindCaptor(this.captors[i]);
  453. };
  454. }).call(this);