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.

708 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.body.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.body.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.body.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.body.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.edges.length; i++) {
  322. var edge = node.edges[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.edges.length; i++) {
  335. var edge = node.edges[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.edges.length; i++) {
  348. var edge = node.edges[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, overrideSelectable) {
  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. // selectable allows the object to be selected. Override can be used if needed to bypass this.
  373. if (object.selected == false && (this.constants.selectable == true || overrideSelectable)) {
  374. object.select();
  375. this._addToSelection(object);
  376. if (object instanceof Node && this.blockConnectingEdgeSelection == false && highlightEdges == true) {
  377. this._selectConnectedEdges(object);
  378. }
  379. }
  380. // do not select the object if selectable is false, only add it to selection to allow drag to work
  381. else if (object.selected == false) {
  382. this._addToSelection(object);
  383. doNotTrigger = true;
  384. }
  385. else {
  386. object.unselect();
  387. this._removeFromSelection(object);
  388. }
  389. if (doNotTrigger == false) {
  390. this.emit('select', this.getSelection());
  391. }
  392. };
  393. /**
  394. * This is called when someone clicks on a node. either select or deselect it.
  395. * If there is an existing selection and we don't want to append to it, clear the existing selection
  396. *
  397. * @param {Node || Edge} object
  398. * @private
  399. */
  400. exports._blurObject = function(object) {
  401. if (object.hover == true) {
  402. object.hover = false;
  403. this.emit("blurNode",{node:object.id});
  404. }
  405. };
  406. /**
  407. * This is called when someone clicks on a node. either select or deselect it.
  408. * If there is an existing selection and we don't want to append to it, clear the existing selection
  409. *
  410. * @param {Node || Edge} object
  411. * @private
  412. */
  413. exports._hoverObject = function(object) {
  414. if (object.hover == false) {
  415. object.hover = true;
  416. this._addToHover(object);
  417. if (object instanceof Node) {
  418. this.emit("hoverNode",{node:object.id});
  419. }
  420. }
  421. if (object instanceof Node) {
  422. this._hoverConnectedEdges(object);
  423. }
  424. };
  425. /**
  426. * handles the selection part of the touch, only for navigation controls elements;
  427. * Touch is triggered before tap, also before hold. Hold triggers after a while.
  428. * This is the most responsive solution
  429. *
  430. * @param {Object} pointer
  431. * @private
  432. */
  433. exports._handleTouch = function(pointer) {
  434. };
  435. /**
  436. * handles the selection part of the tap;
  437. *
  438. * @param {Object} pointer
  439. * @private
  440. */
  441. exports._handleTap = function(pointer) {
  442. var node = this._getNodeAt(pointer);
  443. if (node != null) {
  444. this._selectObject(node, false);
  445. }
  446. else {
  447. var edge = this._getEdgeAt(pointer);
  448. if (edge != null) {
  449. this._selectObject(edge, false);
  450. }
  451. else {
  452. this._unselectAll();
  453. }
  454. }
  455. var properties = this.getSelection();
  456. properties['pointer'] = {
  457. DOM: {x: pointer.x, y: pointer.y},
  458. canvas: {x: this._XconvertDOMtoCanvas(pointer.x), y: this._YconvertDOMtoCanvas(pointer.y)}
  459. }
  460. this.emit("click", properties);
  461. this._requestRedraw();
  462. };
  463. /**
  464. * handles the selection part of the double tap and opens a cluster if needed
  465. *
  466. * @param {Object} pointer
  467. * @private
  468. */
  469. exports._handleDoubleTap = function(pointer) {
  470. var node = this._getNodeAt(pointer);
  471. if (node != null && node !== undefined) {
  472. // we reset the areaCenter here so the opening of the node will occur
  473. this.areaCenter = {"x" : this._XconvertDOMtoCanvas(pointer.x),
  474. "y" : this._YconvertDOMtoCanvas(pointer.y)};
  475. this.openCluster(node);
  476. }
  477. var properties = this.getSelection();
  478. properties['pointer'] = {
  479. DOM: {x: pointer.x, y: pointer.y},
  480. canvas: {x: this._XconvertDOMtoCanvas(pointer.x), y: this._YconvertDOMtoCanvas(pointer.y)}
  481. }
  482. this.emit("doubleClick", properties);
  483. };
  484. /**
  485. * Handle the onHold selection part
  486. *
  487. * @param pointer
  488. * @private
  489. */
  490. exports._handleOnHold = function(pointer) {
  491. var node = this._getNodeAt(pointer);
  492. if (node != null) {
  493. this._selectObject(node,true);
  494. }
  495. else {
  496. var edge = this._getEdgeAt(pointer);
  497. if (edge != null) {
  498. this._selectObject(edge,true);
  499. }
  500. }
  501. this._requestRedraw();
  502. };
  503. /**
  504. * handle the onRelease event. These functions are here for the navigation controls module
  505. * and data manipulation module.
  506. *
  507. * @private
  508. */
  509. exports._handleOnRelease = function(pointer) {
  510. this._manipulationReleaseOverload(pointer);
  511. this._navigationReleaseOverload(pointer);
  512. };
  513. exports._manipulationReleaseOverload = function (pointer) {};
  514. exports._navigationReleaseOverload = function (pointer) {};
  515. /**
  516. *
  517. * retrieve the currently selected objects
  518. * @return {{nodes: Array.<String>, edges: Array.<String>}} selection
  519. */
  520. exports.getSelection = function() {
  521. var nodeIds = this.getSelectedNodes();
  522. var edgeIds = this.getSelectedEdges();
  523. return {nodes:nodeIds, edges:edgeIds};
  524. };
  525. /**
  526. *
  527. * retrieve the currently selected nodes
  528. * @return {String[]} selection An array with the ids of the
  529. * selected nodes.
  530. */
  531. exports.getSelectedNodes = function() {
  532. var idArray = [];
  533. if (this.constants.selectable == true) {
  534. for (var nodeId in this.selectionObj.nodes) {
  535. if (this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  536. idArray.push(nodeId);
  537. }
  538. }
  539. }
  540. return idArray
  541. };
  542. /**
  543. *
  544. * retrieve the currently selected edges
  545. * @return {Array} selection An array with the ids of the
  546. * selected nodes.
  547. */
  548. exports.getSelectedEdges = function() {
  549. var idArray = [];
  550. if (this.constants.selectable == true) {
  551. for (var edgeId in this.selectionObj.edges) {
  552. if (this.selectionObj.edges.hasOwnProperty(edgeId)) {
  553. idArray.push(edgeId);
  554. }
  555. }
  556. }
  557. return idArray;
  558. };
  559. /**
  560. * select zero or more nodes DEPRICATED
  561. * @param {Number[] | String[]} selection An array with the ids of the
  562. * selected nodes.
  563. */
  564. exports.setSelection = function() {
  565. console.log("setSelection is deprecated. Please use selectNodes instead.")
  566. };
  567. /**
  568. * select zero or more nodes with the option to highlight edges
  569. * @param {Number[] | String[]} selection An array with the ids of the
  570. * selected nodes.
  571. * @param {boolean} [highlightEdges]
  572. */
  573. exports.selectNodes = function(selection, highlightEdges) {
  574. var i, iMax, id;
  575. if (!selection || (selection.length == undefined))
  576. throw 'Selection must be an array with ids';
  577. // first unselect any selected node
  578. this._unselectAll(true);
  579. for (i = 0, iMax = selection.length; i < iMax; i++) {
  580. id = selection[i];
  581. var node = this.body.nodes[id];
  582. if (!node) {
  583. throw new RangeError('Node with id "' + id + '" not found');
  584. }
  585. this._selectObject(node,true,true,highlightEdges,true);
  586. }
  587. this.redraw();
  588. };
  589. /**
  590. * select zero or more edges
  591. * @param {Number[] | String[]} selection An array with the ids of the
  592. * selected nodes.
  593. */
  594. exports.selectEdges = function(selection) {
  595. var i, iMax, id;
  596. if (!selection || (selection.length == undefined))
  597. throw 'Selection must be an array with ids';
  598. // first unselect any selected node
  599. this._unselectAll(true);
  600. for (i = 0, iMax = selection.length; i < iMax; i++) {
  601. id = selection[i];
  602. var edge = this.body.edges[id];
  603. if (!edge) {
  604. throw new RangeError('Edge with id "' + id + '" not found');
  605. }
  606. this._selectObject(edge,true,true,false,true);
  607. }
  608. this.redraw();
  609. };
  610. /**
  611. * Validate the selection: remove ids of nodes which no longer exist
  612. * @private
  613. */
  614. exports._updateSelection = function () {
  615. for(var nodeId in this.selectionObj.nodes) {
  616. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  617. if (!this.body.nodes.hasOwnProperty(nodeId)) {
  618. delete this.selectionObj.nodes[nodeId];
  619. }
  620. }
  621. }
  622. for(var edgeId in this.selectionObj.edges) {
  623. if(this.selectionObj.edges.hasOwnProperty(edgeId)) {
  624. if (!this.body.edges.hasOwnProperty(edgeId)) {
  625. delete this.selectionObj.edges[edgeId];
  626. }
  627. }
  628. }
  629. };