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.

707 lines
17 KiB

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