vis.js is a dynamic, browser-based visualization library
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.

710 lines
17 KiB

  1. var Node = require('../Node');
  2. /**
  3. * This function can be called from the _doInAllSectors function
  4. *
  5. * @param object
  6. * @param overlappingNodes
  7. * @private
  8. */
  9. exports._getNodesOverlappingWith = function(object, overlappingNodes) {
  10. var nodes = this.nodes;
  11. for (var nodeId in nodes) {
  12. if (nodes.hasOwnProperty(nodeId)) {
  13. if (nodes[nodeId].isOverlappingWith(object)) {
  14. overlappingNodes.push(nodeId);
  15. }
  16. }
  17. }
  18. };
  19. /**
  20. * retrieve all nodes overlapping with given object
  21. * @param {Object} object An object with parameters left, top, right, bottom
  22. * @return {Number[]} An array with id's of the overlapping nodes
  23. * @private
  24. */
  25. exports._getAllNodesOverlappingWith = function (object) {
  26. var overlappingNodes = [];
  27. this._doInAllActiveSectors("_getNodesOverlappingWith",object,overlappingNodes);
  28. return overlappingNodes;
  29. };
  30. /**
  31. * Return a position object in canvasspace from a single point in screenspace
  32. *
  33. * @param pointer
  34. * @returns {{left: number, top: number, right: number, bottom: number}}
  35. * @private
  36. */
  37. exports._pointerToPositionObject = function(pointer) {
  38. var x = this._XconvertDOMtoCanvas(pointer.x);
  39. var y = this._YconvertDOMtoCanvas(pointer.y);
  40. return {
  41. left: x,
  42. top: y,
  43. right: x,
  44. bottom: y
  45. };
  46. };
  47. /**
  48. * Get the top node at the a specific point (like a click)
  49. *
  50. * @param {{x: Number, y: Number}} pointer
  51. * @return {Node | null} node
  52. * @private
  53. */
  54. exports._getNodeAt = function (pointer) {
  55. // we first check if this is an navigation controls element
  56. var positionObject = this._pointerToPositionObject(pointer);
  57. var overlappingNodes = this._getAllNodesOverlappingWith(positionObject);
  58. // if there are overlapping nodes, select the last one, this is the
  59. // one which is drawn on top of the others
  60. if (overlappingNodes.length > 0) {
  61. return this.nodes[overlappingNodes[overlappingNodes.length - 1]];
  62. }
  63. else {
  64. return null;
  65. }
  66. };
  67. /**
  68. * retrieve all edges overlapping with given object, selector is around center
  69. * @param {Object} object An object with parameters left, top, right, bottom
  70. * @return {Number[]} An array with id's of the overlapping nodes
  71. * @private
  72. */
  73. exports._getEdgesOverlappingWith = function (object, overlappingEdges) {
  74. var edges = this.edges;
  75. for (var edgeId in edges) {
  76. if (edges.hasOwnProperty(edgeId)) {
  77. if (edges[edgeId].isOverlappingWith(object)) {
  78. overlappingEdges.push(edgeId);
  79. }
  80. }
  81. }
  82. };
  83. /**
  84. * retrieve all nodes overlapping with given object
  85. * @param {Object} object An object with parameters left, top, right, bottom
  86. * @return {Number[]} An array with id's of the overlapping nodes
  87. * @private
  88. */
  89. exports._getAllEdgesOverlappingWith = function (object) {
  90. var overlappingEdges = [];
  91. this._doInAllActiveSectors("_getEdgesOverlappingWith",object,overlappingEdges);
  92. return overlappingEdges;
  93. };
  94. /**
  95. * Place holder. To implement change the _getNodeAt to a _getObjectAt. Have the _getObjectAt call
  96. * _getNodeAt and _getEdgesAt, then priortize the selection to user preferences.
  97. *
  98. * @param pointer
  99. * @returns {null}
  100. * @private
  101. */
  102. exports._getEdgeAt = function(pointer) {
  103. var positionObject = this._pointerToPositionObject(pointer);
  104. var overlappingEdges = this._getAllEdgesOverlappingWith(positionObject);
  105. if (overlappingEdges.length > 0) {
  106. return this.edges[overlappingEdges[overlappingEdges.length - 1]];
  107. }
  108. else {
  109. return null;
  110. }
  111. };
  112. /**
  113. * Add object to the selection array.
  114. *
  115. * @param obj
  116. * @private
  117. */
  118. exports._addToSelection = function(obj) {
  119. if (obj instanceof Node) {
  120. this.selectionObj.nodes[obj.id] = obj;
  121. }
  122. else {
  123. this.selectionObj.edges[obj.id] = obj;
  124. }
  125. };
  126. /**
  127. * Add object to the selection array.
  128. *
  129. * @param obj
  130. * @private
  131. */
  132. exports._addToHover = function(obj) {
  133. if (obj instanceof Node) {
  134. this.hoverObj.nodes[obj.id] = obj;
  135. }
  136. else {
  137. this.hoverObj.edges[obj.id] = obj;
  138. }
  139. };
  140. /**
  141. * Remove a single option from selection.
  142. *
  143. * @param {Object} obj
  144. * @private
  145. */
  146. exports._removeFromSelection = function(obj) {
  147. if (obj instanceof Node) {
  148. delete this.selectionObj.nodes[obj.id];
  149. }
  150. else {
  151. delete this.selectionObj.edges[obj.id];
  152. }
  153. };
  154. /**
  155. * Unselect all. The selectionObj is useful for this.
  156. *
  157. * @param {Boolean} [doNotTrigger] | ignore trigger
  158. * @private
  159. */
  160. exports._unselectAll = function(doNotTrigger) {
  161. if (doNotTrigger === undefined) {
  162. doNotTrigger = false;
  163. }
  164. for(var nodeId in this.selectionObj.nodes) {
  165. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  166. this.selectionObj.nodes[nodeId].unselect();
  167. }
  168. }
  169. for(var edgeId in this.selectionObj.edges) {
  170. if(this.selectionObj.edges.hasOwnProperty(edgeId)) {
  171. this.selectionObj.edges[edgeId].unselect();
  172. }
  173. }
  174. this.selectionObj = {nodes:{},edges:{}};
  175. if (doNotTrigger == false) {
  176. this.emit('select', this.getSelection());
  177. }
  178. };
  179. /**
  180. * Unselect all clusters. The selectionObj is useful for this.
  181. *
  182. * @param {Boolean} [doNotTrigger] | ignore trigger
  183. * @private
  184. */
  185. exports._unselectClusters = function(doNotTrigger) {
  186. if (doNotTrigger === undefined) {
  187. doNotTrigger = false;
  188. }
  189. for (var nodeId in this.selectionObj.nodes) {
  190. if (this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  191. if (this.selectionObj.nodes[nodeId].clusterSize > 1) {
  192. this.selectionObj.nodes[nodeId].unselect();
  193. this._removeFromSelection(this.selectionObj.nodes[nodeId]);
  194. }
  195. }
  196. }
  197. if (doNotTrigger == false) {
  198. this.emit('select', this.getSelection());
  199. }
  200. };
  201. /**
  202. * return the number of selected nodes
  203. *
  204. * @returns {number}
  205. * @private
  206. */
  207. exports._getSelectedNodeCount = function() {
  208. var count = 0;
  209. for (var nodeId in this.selectionObj.nodes) {
  210. if (this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  211. count += 1;
  212. }
  213. }
  214. return count;
  215. };
  216. /**
  217. * return the selected node
  218. *
  219. * @returns {number}
  220. * @private
  221. */
  222. exports._getSelectedNode = function() {
  223. for (var nodeId in this.selectionObj.nodes) {
  224. if (this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  225. return this.selectionObj.nodes[nodeId];
  226. }
  227. }
  228. return null;
  229. };
  230. /**
  231. * return the selected edge
  232. *
  233. * @returns {number}
  234. * @private
  235. */
  236. exports._getSelectedEdge = function() {
  237. for (var edgeId in this.selectionObj.edges) {
  238. if (this.selectionObj.edges.hasOwnProperty(edgeId)) {
  239. return this.selectionObj.edges[edgeId];
  240. }
  241. }
  242. return null;
  243. };
  244. /**
  245. * return the number of selected edges
  246. *
  247. * @returns {number}
  248. * @private
  249. */
  250. exports._getSelectedEdgeCount = function() {
  251. var count = 0;
  252. for (var edgeId in this.selectionObj.edges) {
  253. if (this.selectionObj.edges.hasOwnProperty(edgeId)) {
  254. count += 1;
  255. }
  256. }
  257. return count;
  258. };
  259. /**
  260. * return the number of selected objects.
  261. *
  262. * @returns {number}
  263. * @private
  264. */
  265. exports._getSelectedObjectCount = function() {
  266. var count = 0;
  267. for(var nodeId in this.selectionObj.nodes) {
  268. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  269. count += 1;
  270. }
  271. }
  272. for(var edgeId in this.selectionObj.edges) {
  273. if(this.selectionObj.edges.hasOwnProperty(edgeId)) {
  274. count += 1;
  275. }
  276. }
  277. return count;
  278. };
  279. /**
  280. * Check if anything is selected
  281. *
  282. * @returns {boolean}
  283. * @private
  284. */
  285. exports._selectionIsEmpty = function() {
  286. for(var nodeId in this.selectionObj.nodes) {
  287. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  288. return false;
  289. }
  290. }
  291. for(var edgeId in this.selectionObj.edges) {
  292. if(this.selectionObj.edges.hasOwnProperty(edgeId)) {
  293. return false;
  294. }
  295. }
  296. return true;
  297. };
  298. /**
  299. * check if one of the selected nodes is a cluster.
  300. *
  301. * @returns {boolean}
  302. * @private
  303. */
  304. exports._clusterInSelection = function() {
  305. for(var nodeId in this.selectionObj.nodes) {
  306. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  307. if (this.selectionObj.nodes[nodeId].clusterSize > 1) {
  308. return true;
  309. }
  310. }
  311. }
  312. return false;
  313. };
  314. /**
  315. * select the edges connected to the node that is being selected
  316. *
  317. * @param {Node} node
  318. * @private
  319. */
  320. exports._selectConnectedEdges = function(node) {
  321. for (var i = 0; i < node.dynamicEdges.length; i++) {
  322. var edge = node.dynamicEdges[i];
  323. edge.select();
  324. this._addToSelection(edge);
  325. }
  326. };
  327. /**
  328. * select the edges connected to the node that is being selected
  329. *
  330. * @param {Node} node
  331. * @private
  332. */
  333. exports._hoverConnectedEdges = function(node) {
  334. for (var i = 0; i < node.dynamicEdges.length; i++) {
  335. var edge = node.dynamicEdges[i];
  336. edge.hover = true;
  337. this._addToHover(edge);
  338. }
  339. };
  340. /**
  341. * unselect the edges connected to the node that is being selected
  342. *
  343. * @param {Node} node
  344. * @private
  345. */
  346. exports._unselectConnectedEdges = function(node) {
  347. for (var i = 0; i < node.dynamicEdges.length; i++) {
  348. var edge = node.dynamicEdges[i];
  349. edge.unselect();
  350. this._removeFromSelection(edge);
  351. }
  352. };
  353. /**
  354. * This is called when someone clicks on a node. either select or deselect it.
  355. * If there is an existing selection and we don't want to append to it, clear the existing selection
  356. *
  357. * @param {Node || Edge} object
  358. * @param {Boolean} append
  359. * @param {Boolean} [doNotTrigger] | ignore trigger
  360. * @private
  361. */
  362. exports._selectObject = function(object, append, doNotTrigger, highlightEdges) {
  363. if (doNotTrigger === undefined) {
  364. doNotTrigger = false;
  365. }
  366. if (highlightEdges === undefined) {
  367. highlightEdges = true;
  368. }
  369. if (this._selectionIsEmpty() == false && append == false && this.forceAppendSelection == false) {
  370. this._unselectAll(true);
  371. }
  372. if (object.selected == false) {
  373. object.select();
  374. this._addToSelection(object);
  375. if (object instanceof Node && this.blockConnectingEdgeSelection == false && highlightEdges == true) {
  376. this._selectConnectedEdges(object);
  377. }
  378. }
  379. else {
  380. object.unselect();
  381. this._removeFromSelection(object);
  382. }
  383. if (doNotTrigger == false) {
  384. this.emit('select', this.getSelection());
  385. }
  386. };
  387. /**
  388. * This is called when someone clicks on a node. either select or deselect it.
  389. * If there is an existing selection and we don't want to append to it, clear the existing selection
  390. *
  391. * @param {Node || Edge} object
  392. * @private
  393. */
  394. exports._blurObject = function(object) {
  395. if (object.hover == true) {
  396. object.hover = false;
  397. this.emit("blurNode",{node:object.id});
  398. }
  399. };
  400. /**
  401. * This is called when someone clicks on a node. either select or deselect it.
  402. * If there is an existing selection and we don't want to append to it, clear the existing selection
  403. *
  404. * @param {Node || Edge} object
  405. * @private
  406. */
  407. exports._hoverObject = function(object) {
  408. if (object.hover == false) {
  409. object.hover = true;
  410. this._addToHover(object);
  411. if (object instanceof Node) {
  412. this.emit("hoverNode",{node:object.id});
  413. }
  414. }
  415. if (object instanceof Node) {
  416. this._hoverConnectedEdges(object);
  417. }
  418. };
  419. /**
  420. * handles the selection part of the touch, only for navigation controls elements;
  421. * Touch is triggered before tap, also before hold. Hold triggers after a while.
  422. * This is the most responsive solution
  423. *
  424. * @param {Object} pointer
  425. * @private
  426. */
  427. exports._handleTouch = function(pointer) {
  428. };
  429. /**
  430. * handles the selection part of the tap;
  431. *
  432. * @param {Object} pointer
  433. * @private
  434. */
  435. exports._handleTap = function(pointer) {
  436. if (this.constants.selectable == true) {
  437. var node = this._getNodeAt(pointer);
  438. if (node != null) {
  439. this._selectObject(node, false);
  440. }
  441. else {
  442. var edge = this._getEdgeAt(pointer);
  443. if (edge != null) {
  444. this._selectObject(edge, false);
  445. }
  446. else {
  447. this._unselectAll();
  448. }
  449. }
  450. this.emit("click", this.getSelection());
  451. this._redraw();
  452. }
  453. };
  454. /**
  455. * handles the selection part of the double tap and opens a cluster if needed
  456. *
  457. * @param {Object} pointer
  458. * @private
  459. */
  460. exports._handleDoubleTap = function(pointer) {
  461. var node = this._getNodeAt(pointer);
  462. if (node != null && node !== undefined) {
  463. // we reset the areaCenter here so the opening of the node will occur
  464. this.areaCenter = {"x" : this._XconvertDOMtoCanvas(pointer.x),
  465. "y" : this._YconvertDOMtoCanvas(pointer.y)};
  466. this.openCluster(node);
  467. }
  468. this.emit("doubleClick", this.getSelection());
  469. };
  470. /**
  471. * Handle the onHold selection part
  472. *
  473. * @param pointer
  474. * @private
  475. */
  476. exports._handleOnHold = function(pointer) {
  477. var node = this._getNodeAt(pointer);
  478. if (node != null) {
  479. this._selectObject(node,true);
  480. }
  481. else {
  482. var edge = this._getEdgeAt(pointer);
  483. if (edge != null) {
  484. this._selectObject(edge,true);
  485. }
  486. }
  487. this._redraw();
  488. };
  489. /**
  490. * handle the onRelease event. These functions are here for the navigation controls module
  491. * and data manipulation module.
  492. *
  493. * @private
  494. */
  495. exports._handleOnRelease = function(pointer) {
  496. this._manipulationReleaseOverload(pointer);
  497. this._navigationReleaseOverload(pointer);
  498. };
  499. exports._manipulationReleaseOverload = function (pointer) {};
  500. exports._navigationReleaseOverload = function (pointer) {};
  501. /**
  502. *
  503. * retrieve the currently selected objects
  504. * @return {{nodes: Array.<String>, edges: Array.<String>}} selection
  505. */
  506. exports.getSelection = function() {
  507. var nodeIds = this.getSelectedNodes();
  508. var edgeIds = this.getSelectedEdges();
  509. return {nodes:nodeIds, edges:edgeIds};
  510. };
  511. /**
  512. *
  513. * retrieve the currently selected nodes
  514. * @return {String[]} selection An array with the ids of the
  515. * selected nodes.
  516. */
  517. exports.getSelectedNodes = function() {
  518. var idArray = [];
  519. for(var nodeId in this.selectionObj.nodes) {
  520. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  521. idArray.push(nodeId);
  522. }
  523. }
  524. return idArray
  525. };
  526. /**
  527. *
  528. * retrieve the currently selected edges
  529. * @return {Array} selection An array with the ids of the
  530. * selected nodes.
  531. */
  532. exports.getSelectedEdges = function() {
  533. var idArray = [];
  534. for(var edgeId in this.selectionObj.edges) {
  535. if(this.selectionObj.edges.hasOwnProperty(edgeId)) {
  536. idArray.push(edgeId);
  537. }
  538. }
  539. return idArray;
  540. };
  541. /**
  542. * select zero or more nodes
  543. * @param {Number[] | String[]} selection An array with the ids of the
  544. * selected nodes.
  545. */
  546. exports.setSelection = function(selection) {
  547. var i, iMax, id;
  548. if (!selection || (selection.length == undefined))
  549. throw 'Selection must be an array with ids';
  550. // first unselect any selected node
  551. this._unselectAll(true);
  552. for (i = 0, iMax = selection.length; i < iMax; i++) {
  553. id = selection[i];
  554. var node = this.nodes[id];
  555. if (!node) {
  556. throw new RangeError('Node with id "' + id + '" not found');
  557. }
  558. this._selectObject(node,true,true);
  559. }
  560. console.log("setSelection is deprecated. Please use selectNodes instead.")
  561. this.redraw();
  562. };
  563. /**
  564. * select zero or more nodes with the option to highlight edges
  565. * @param {Number[] | String[]} selection An array with the ids of the
  566. * selected nodes.
  567. * @param {boolean} [highlightEdges]
  568. */
  569. exports.selectNodes = function(selection, highlightEdges) {
  570. var i, iMax, id;
  571. if (!selection || (selection.length == undefined))
  572. throw 'Selection must be an array with ids';
  573. // first unselect any selected node
  574. this._unselectAll(true);
  575. for (i = 0, iMax = selection.length; i < iMax; i++) {
  576. id = selection[i];
  577. var node = this.nodes[id];
  578. if (!node) {
  579. throw new RangeError('Node with id "' + id + '" not found');
  580. }
  581. this._selectObject(node,true,true,highlightEdges);
  582. }
  583. this.redraw();
  584. };
  585. /**
  586. * select zero or more edges
  587. * @param {Number[] | String[]} selection An array with the ids of the
  588. * selected nodes.
  589. */
  590. exports.selectEdges = function(selection) {
  591. var i, iMax, id;
  592. if (!selection || (selection.length == undefined))
  593. throw 'Selection must be an array with ids';
  594. // first unselect any selected node
  595. this._unselectAll(true);
  596. for (i = 0, iMax = selection.length; i < iMax; i++) {
  597. id = selection[i];
  598. var edge = this.edges[id];
  599. if (!edge) {
  600. throw new RangeError('Edge with id "' + id + '" not found');
  601. }
  602. this._selectObject(edge,true,true,highlightEdges);
  603. }
  604. this.redraw();
  605. };
  606. /**
  607. * Validate the selection: remove ids of nodes which no longer exist
  608. * @private
  609. */
  610. exports._updateSelection = function () {
  611. for(var nodeId in this.selectionObj.nodes) {
  612. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  613. if (!this.nodes.hasOwnProperty(nodeId)) {
  614. delete this.selectionObj.nodes[nodeId];
  615. }
  616. }
  617. }
  618. for(var edgeId in this.selectionObj.edges) {
  619. if(this.selectionObj.edges.hasOwnProperty(edgeId)) {
  620. if (!this.edges.hasOwnProperty(edgeId)) {
  621. delete this.selectionObj.edges[edgeId];
  622. }
  623. }
  624. }
  625. };