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.

617 lines
14 KiB

  1. /**
  2. * Created by Alex on 2/27/2015.
  3. */
  4. var Node = require("./components/nodes/NodeMain");
  5. var util = require('../../util');
  6. class SelectionHandler {
  7. constructor(body, canvas) {
  8. this.body = body;
  9. this.canvas = canvas;
  10. this.selectionObj = {nodes: [], edges: []};
  11. this.options = {};
  12. this.defaultOptions = {
  13. select: true,
  14. selectConnectedEdges: true
  15. }
  16. util.extend(this.options, this.defaultOptions);
  17. this.body.emitter.on("_dataChanged", () => {
  18. this.updateSelection()
  19. });
  20. }
  21. setOptions(options) {
  22. if (options !== undefined) {
  23. util.deepExtend(this.options, options);
  24. }
  25. }
  26. /**
  27. * handles the selection part of the tap;
  28. *
  29. * @param {Object} pointer
  30. * @private
  31. */
  32. selectOnPoint(pointer) {
  33. var selected = false;
  34. if (this.options.select === true) {
  35. this.unselectAll();
  36. var obj = this.getNodeAt(pointer) || this.getEdgeAt(pointer);;
  37. if (obj !== undefined) {
  38. selected = this.selectObject(obj);
  39. }
  40. this.body.emitter.emit("_requestRedraw");
  41. }
  42. return selected;
  43. }
  44. selectAdditionalOnPoint(pointer) {
  45. var selectionChanged = false;
  46. if (this.options.select === true) {
  47. var obj = this.getNodeAt(pointer) || this.getEdgeAt(pointer);;
  48. if (obj !== undefined) {
  49. selectionChanged = true;
  50. if (obj.isSelected() === true) {
  51. this.deselectObject(obj);
  52. }
  53. else {
  54. this.selectObject(obj);
  55. }
  56. this.body.emitter.emit("_requestRedraw");
  57. }
  58. }
  59. return selectionChanged;
  60. }
  61. _generateClickEvent(eventType,pointer) {
  62. var properties = this.getSelection();
  63. properties['pointer'] = {
  64. DOM: {x: pointer.x, y: pointer.y},
  65. canvas: this.canvas.DOMtoCanvas(pointer)
  66. }
  67. this.body.emitter.emit(eventType, properties);
  68. }
  69. selectObject(obj) {
  70. if (obj !== undefined) {
  71. if (obj instanceof Node) {
  72. if (this.options.selectConnectedEdges === true) {
  73. this._selectConnectedEdges(obj);
  74. }
  75. }
  76. obj.select();
  77. this._addToSelection(obj);
  78. return true;
  79. }
  80. return false;
  81. }
  82. deselectObject(obj) {
  83. if (obj.isSelected() === true) {
  84. obj.selected = false;
  85. this._removeFromSelection(obj);
  86. }
  87. }
  88. /**
  89. * retrieve all nodes overlapping with given object
  90. * @param {Object} object An object with parameters left, top, right, bottom
  91. * @return {Number[]} An array with id's of the overlapping nodes
  92. * @private
  93. */
  94. _getAllNodesOverlappingWith(object) {
  95. var overlappingNodes = [];
  96. var nodes = this.body.nodes;
  97. for (var nodeId in nodes) {
  98. if (nodes.hasOwnProperty(nodeId)) {
  99. if (nodes[nodeId].isOverlappingWith(object)) {
  100. overlappingNodes.push(nodeId);
  101. }
  102. }
  103. }
  104. return overlappingNodes;
  105. }
  106. /**
  107. * Return a position object in canvasspace from a single point in screenspace
  108. *
  109. * @param pointer
  110. * @returns {{left: number, top: number, right: number, bottom: number}}
  111. * @private
  112. */
  113. _pointerToPositionObject(pointer) {
  114. var canvasPos = this.canvas.DOMtoCanvas(pointer);
  115. return {
  116. left: canvasPos.x,
  117. top: canvasPos.y,
  118. right: canvasPos.x,
  119. bottom: canvasPos.y
  120. };
  121. }
  122. /**
  123. * Get the top node at the a specific point (like a click)
  124. *
  125. * @param {{x: Number, y: Number}} pointer
  126. * @return {Node | undefined} node
  127. * @private
  128. */
  129. getNodeAt(pointer) {
  130. // we first check if this is an navigation controls element
  131. var positionObject = this._pointerToPositionObject(pointer);
  132. var overlappingNodes = this._getAllNodesOverlappingWith(positionObject);
  133. // if there are overlapping nodes, select the last one, this is the
  134. // one which is drawn on top of the others
  135. if (overlappingNodes.length > 0) {
  136. return this.body.nodes[overlappingNodes[overlappingNodes.length - 1]];
  137. }
  138. else {
  139. return undefined;
  140. }
  141. }
  142. /**
  143. * retrieve all edges overlapping with given object, selector is around center
  144. * @param {Object} object An object with parameters left, top, right, bottom
  145. * @return {Number[]} An array with id's of the overlapping nodes
  146. * @private
  147. */
  148. _getEdgesOverlappingWith(object, overlappingEdges) {
  149. var edges = this.body.edges;
  150. for (var edgeId in edges) {
  151. if (edges.hasOwnProperty(edgeId)) {
  152. if (edges[edgeId].isOverlappingWith(object)) {
  153. overlappingEdges.push(edgeId);
  154. }
  155. }
  156. }
  157. }
  158. /**
  159. * retrieve all nodes overlapping with given object
  160. * @param {Object} object An object with parameters left, top, right, bottom
  161. * @return {Number[]} An array with id's of the overlapping nodes
  162. * @private
  163. */
  164. _getAllEdgesOverlappingWith(object) {
  165. var overlappingEdges = [];
  166. this._getEdgesOverlappingWith(object,overlappingEdges);
  167. return overlappingEdges;
  168. }
  169. /**
  170. * Place holder. To implement change the getNodeAt to a _getObjectAt. Have the _getObjectAt call
  171. * getNodeAt and _getEdgesAt, then priortize the selection to user preferences.
  172. *
  173. * @param pointer
  174. * @returns {undefined}
  175. * @private
  176. */
  177. getEdgeAt(pointer) {
  178. var positionObject = this._pointerToPositionObject(pointer);
  179. var overlappingEdges = this._getAllEdgesOverlappingWith(positionObject);
  180. if (overlappingEdges.length > 0) {
  181. return this.body.edges[overlappingEdges[overlappingEdges.length - 1]];
  182. }
  183. else {
  184. return undefined;
  185. }
  186. }
  187. /**
  188. * Add object to the selection array.
  189. *
  190. * @param obj
  191. * @private
  192. */
  193. _addToSelection(obj) {
  194. if (obj instanceof Node) {
  195. this.selectionObj.nodes[obj.id] = obj;
  196. }
  197. else {
  198. this.selectionObj.edges[obj.id] = obj;
  199. }
  200. }
  201. /**
  202. * Add object to the selection array.
  203. *
  204. * @param obj
  205. * @private
  206. */
  207. _addToHover(obj) {
  208. if (obj instanceof Node) {
  209. this.hoverObj.nodes[obj.id] = obj;
  210. }
  211. else {
  212. this.hoverObj.edges[obj.id] = obj;
  213. }
  214. }
  215. /**
  216. * Remove a single option from selection.
  217. *
  218. * @param {Object} obj
  219. * @private
  220. */
  221. _removeFromSelection(obj) {
  222. if (obj instanceof Node) {
  223. delete this.selectionObj.nodes[obj.id];
  224. }
  225. else {
  226. delete this.selectionObj.edges[obj.id];
  227. }
  228. }
  229. /**
  230. * Unselect all. The selectionObj is useful for this.
  231. *
  232. * @private
  233. */
  234. unselectAll() {
  235. for(var nodeId in this.selectionObj.nodes) {
  236. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  237. this.selectionObj.nodes[nodeId].unselect();
  238. }
  239. }
  240. for(var edgeId in this.selectionObj.edges) {
  241. if(this.selectionObj.edges.hasOwnProperty(edgeId)) {
  242. this.selectionObj.edges[edgeId].unselect();
  243. }
  244. }
  245. this.selectionObj = {nodes:{},edges:{}};
  246. }
  247. /**
  248. * return the number of selected nodes
  249. *
  250. * @returns {number}
  251. * @private
  252. */
  253. _getSelectedNodeCount() {
  254. var count = 0;
  255. for (var nodeId in this.selectionObj.nodes) {
  256. if (this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  257. count += 1;
  258. }
  259. }
  260. return count;
  261. }
  262. /**
  263. * return the selected node
  264. *
  265. * @returns {number}
  266. * @private
  267. */
  268. _getSelectedNode() {
  269. for (var nodeId in this.selectionObj.nodes) {
  270. if (this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  271. return this.selectionObj.nodes[nodeId];
  272. }
  273. }
  274. return undefined;
  275. }
  276. /**
  277. * return the selected edge
  278. *
  279. * @returns {number}
  280. * @private
  281. */
  282. _getSelectedEdge() {
  283. for (var edgeId in this.selectionObj.edges) {
  284. if (this.selectionObj.edges.hasOwnProperty(edgeId)) {
  285. return this.selectionObj.edges[edgeId];
  286. }
  287. }
  288. return undefined;
  289. }
  290. /**
  291. * return the number of selected edges
  292. *
  293. * @returns {number}
  294. * @private
  295. */
  296. _getSelectedEdgeCount() {
  297. var count = 0;
  298. for (var edgeId in this.selectionObj.edges) {
  299. if (this.selectionObj.edges.hasOwnProperty(edgeId)) {
  300. count += 1;
  301. }
  302. }
  303. return count;
  304. }
  305. /**
  306. * return the number of selected objects.
  307. *
  308. * @returns {number}
  309. * @private
  310. */
  311. _getSelectedObjectCount() {
  312. var count = 0;
  313. for(var nodeId in this.selectionObj.nodes) {
  314. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  315. count += 1;
  316. }
  317. }
  318. for(var edgeId in this.selectionObj.edges) {
  319. if(this.selectionObj.edges.hasOwnProperty(edgeId)) {
  320. count += 1;
  321. }
  322. }
  323. return count;
  324. }
  325. /**
  326. * Check if anything is selected
  327. *
  328. * @returns {boolean}
  329. * @private
  330. */
  331. _selectionIsEmpty() {
  332. for(var nodeId in this.selectionObj.nodes) {
  333. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  334. return false;
  335. }
  336. }
  337. for(var edgeId in this.selectionObj.edges) {
  338. if(this.selectionObj.edges.hasOwnProperty(edgeId)) {
  339. return false;
  340. }
  341. }
  342. return true;
  343. }
  344. /**
  345. * check if one of the selected nodes is a cluster.
  346. *
  347. * @returns {boolean}
  348. * @private
  349. */
  350. _clusterInSelection() {
  351. for(var nodeId in this.selectionObj.nodes) {
  352. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  353. if (this.selectionObj.nodes[nodeId].clusterSize > 1) {
  354. return true;
  355. }
  356. }
  357. }
  358. return false;
  359. }
  360. /**
  361. * select the edges connected to the node that is being selected
  362. *
  363. * @param {Node} node
  364. * @private
  365. */
  366. _selectConnectedEdges(node) {
  367. for (var i = 0; i < node.edges.length; i++) {
  368. var edge = node.edges[i];
  369. edge.select();
  370. this._addToSelection(edge);
  371. }
  372. }
  373. /**
  374. * select the edges connected to the node that is being selected
  375. *
  376. * @param {Node} node
  377. * @private
  378. */
  379. _hoverConnectedEdges(node) {
  380. for (var i = 0; i < node.edges.length; i++) {
  381. var edge = node.edges[i];
  382. edge.hover = true;
  383. this._addToHover(edge);
  384. }
  385. }
  386. /**
  387. * unselect the edges connected to the node that is being selected
  388. *
  389. * @param {Node} node
  390. * @private
  391. */
  392. _unselectConnectedEdges(node) {
  393. for (var i = 0; i < node.edges.length; i++) {
  394. var edge = node.edges[i];
  395. edge.unselect();
  396. this._removeFromSelection(edge);
  397. }
  398. }
  399. /**
  400. * This is called when someone clicks on a node. either select or deselect it.
  401. * If there is an existing selection and we don't want to append to it, clear the existing selection
  402. *
  403. * @param {Node || Edge} object
  404. * @private
  405. */
  406. _blurObject(object) {
  407. if (object.hover == true) {
  408. object.hover = false;
  409. this.body.emitter.emit("blurNode",{node:object.id});
  410. }
  411. }
  412. /**
  413. * This is called when someone clicks on a node. either select or deselect it.
  414. * If there is an existing selection and we don't want to append to it, clear the existing selection
  415. *
  416. * @param {Node || Edge} object
  417. * @private
  418. */
  419. _hoverObject(object) {
  420. if (object.hover == false) {
  421. object.hover = true;
  422. this._addToHover(object);
  423. if (object instanceof Node) {
  424. this.body.emitter.emit("hoverNode",{node:object.id});
  425. }
  426. }
  427. if (object instanceof Node) {
  428. this._hoverConnectedEdges(object);
  429. }
  430. }
  431. /**
  432. *
  433. * retrieve the currently selected objects
  434. * @return {{nodes: Array.<String>, edges: Array.<String>}} selection
  435. */
  436. getSelection() {
  437. var nodeIds = this.getSelectedNodes();
  438. var edgeIds = this.getSelectedEdges();
  439. return {nodes:nodeIds, edges:edgeIds};
  440. }
  441. /**
  442. *
  443. * retrieve the currently selected nodes
  444. * @return {String[]} selection An array with the ids of the
  445. * selected nodes.
  446. */
  447. getSelectedNodes() {
  448. var idArray = [];
  449. if (this.options.select == true) {
  450. for (var nodeId in this.selectionObj.nodes) {
  451. if (this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  452. idArray.push(nodeId);
  453. }
  454. }
  455. }
  456. return idArray
  457. }
  458. /**
  459. *
  460. * retrieve the currently selected edges
  461. * @return {Array} selection An array with the ids of the
  462. * selected nodes.
  463. */
  464. getSelectedEdges() {
  465. var idArray = [];
  466. if (this.options.select == true) {
  467. for (var edgeId in this.selectionObj.edges) {
  468. if (this.selectionObj.edges.hasOwnProperty(edgeId)) {
  469. idArray.push(edgeId);
  470. }
  471. }
  472. }
  473. return idArray;
  474. }
  475. /**
  476. * select zero or more nodes with the option to highlight edges
  477. * @param {Number[] | String[]} selection An array with the ids of the
  478. * selected nodes.
  479. * @param {boolean} [highlightEdges]
  480. */
  481. selectNodes(selection, highlightEdges) {
  482. var i, iMax, id;
  483. if (!selection || (selection.length == undefined))
  484. throw 'Selection must be an array with ids';
  485. // first unselect any selected node
  486. this.unselectAll(true);
  487. for (i = 0, iMax = selection.length; i < iMax; i++) {
  488. id = selection[i];
  489. var node = this.body.nodes[id];
  490. if (!node) {
  491. throw new RangeError('Node with id "' + id + '" not found');
  492. }
  493. this._selectObject(node,true,true,highlightEdges,true);
  494. }
  495. this.redraw();
  496. }
  497. /**
  498. * select zero or more edges
  499. * @param {Number[] | String[]} selection An array with the ids of the
  500. * selected nodes.
  501. */
  502. selectEdges(selection) {
  503. var i, iMax, id;
  504. if (!selection || (selection.length == undefined))
  505. throw 'Selection must be an array with ids';
  506. // first unselect any selected node
  507. this.unselectAll(true);
  508. for (i = 0, iMax = selection.length; i < iMax; i++) {
  509. id = selection[i];
  510. var edge = this.body.edges[id];
  511. if (!edge) {
  512. throw new RangeError('Edge with id "' + id + '" not found');
  513. }
  514. this._selectObject(edge,true,true,false,true);
  515. }
  516. this.redraw();
  517. }
  518. /**
  519. * Validate the selection: remove ids of nodes which no longer exist
  520. * @private
  521. */
  522. updateSelection() {
  523. for (var nodeId in this.selectionObj.nodes) {
  524. if (this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  525. if (!this.body.nodes.hasOwnProperty(nodeId)) {
  526. delete this.selectionObj.nodes[nodeId];
  527. }
  528. }
  529. }
  530. for (var edgeId in this.selectionObj.edges) {
  531. if (this.selectionObj.edges.hasOwnProperty(edgeId)) {
  532. if (!this.body.edges.hasOwnProperty(edgeId)) {
  533. delete this.selectionObj.edges[edgeId];
  534. }
  535. }
  536. }
  537. }
  538. }
  539. export default SelectionHandler;