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.

611 lines
14 KiB

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