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.

719 lines
23 KiB

9 years ago
9 years ago
9 years ago
9 years ago
  1. let util = require('../../util');
  2. import NavigationHandler from './components/NavigationHandler'
  3. import Popup from './components/Popup'
  4. class InteractionHandler {
  5. constructor(body, canvas, selectionHandler) {
  6. this.body = body;
  7. this.canvas = canvas;
  8. this.selectionHandler = selectionHandler;
  9. this.navigationHandler = new NavigationHandler(body,canvas);
  10. // bind the events from hammer to functions in this object
  11. this.body.eventListeners.onTap = this.onTap.bind(this);
  12. this.body.eventListeners.onTouch = this.onTouch.bind(this);
  13. this.body.eventListeners.onDoubleTap = this.onDoubleTap.bind(this);
  14. this.body.eventListeners.onHold = this.onHold.bind(this);
  15. this.body.eventListeners.onDragStart = this.onDragStart.bind(this);
  16. this.body.eventListeners.onDrag = this.onDrag.bind(this);
  17. this.body.eventListeners.onDragEnd = this.onDragEnd.bind(this);
  18. this.body.eventListeners.onMouseWheel = this.onMouseWheel.bind(this);
  19. this.body.eventListeners.onPinch = this.onPinch.bind(this);
  20. this.body.eventListeners.onMouseMove = this.onMouseMove.bind(this);
  21. this.body.eventListeners.onRelease = this.onRelease.bind(this);
  22. this.body.eventListeners.onContext = this.onContext.bind(this);
  23. this.touchTime = 0;
  24. this.drag = {};
  25. this.pinch = {};
  26. this.popup = undefined;
  27. this.popupObj = undefined;
  28. this.popupTimer = undefined;
  29. this.body.functions.getPointer = this.getPointer.bind(this);
  30. this.options = {};
  31. this.defaultOptions = {
  32. dragNodes:true,
  33. dragView: true,
  34. hover: false,
  35. keyboard: {
  36. enabled: false,
  37. speed: {x: 10, y: 10, zoom: 0.02},
  38. bindToWindow: true
  39. },
  40. navigationButtons: false,
  41. tooltipDelay: 300,
  42. zoomView: true
  43. };
  44. util.extend(this.options,this.defaultOptions);
  45. this.bindEventListeners()
  46. }
  47. bindEventListeners() {
  48. this.body.emitter.on('destroy', () => {
  49. clearTimeout(this.popupTimer);
  50. delete this.body.functions.getPointer;
  51. })
  52. }
  53. setOptions(options) {
  54. if (options !== undefined) {
  55. // extend all but the values in fields
  56. let fields = ['hideEdgesOnDrag','hideNodesOnDrag','keyboard','multiselect','selectable','selectConnectedEdges'];
  57. util.selectiveNotDeepExtend(fields, this.options, options);
  58. // merge the keyboard options in.
  59. util.mergeOptions(this.options, options, 'keyboard');
  60. if (options.tooltip) {
  61. util.extend(this.options.tooltip, options.tooltip);
  62. if (options.tooltip.color) {
  63. this.options.tooltip.color = util.parseColor(options.tooltip.color);
  64. }
  65. }
  66. }
  67. this.navigationHandler.setOptions(this.options);
  68. }
  69. /**
  70. * Get the pointer location from a touch location
  71. * @param {{x: Number, y: Number}} touch
  72. * @return {{x: Number, y: Number}} pointer
  73. * @private
  74. */
  75. getPointer(touch) {
  76. return {
  77. x: touch.x - util.getAbsoluteLeft(this.canvas.frame.canvas),
  78. y: touch.y - util.getAbsoluteTop(this.canvas.frame.canvas)
  79. };
  80. }
  81. /**
  82. * On start of a touch gesture, store the pointer
  83. * @param event
  84. * @private
  85. */
  86. onTouch(event) {
  87. if (new Date().valueOf() - this.touchTime > 50) {
  88. this.drag.pointer = this.getPointer(event.center);
  89. this.drag.pinched = false;
  90. this.pinch.scale = this.body.view.scale;
  91. // to avoid double fireing of this event because we have two hammer instances. (on canvas and on frame)
  92. this.touchTime = new Date().valueOf();
  93. }
  94. }
  95. /**
  96. * handle tap/click event: select/unselect a node
  97. * @private
  98. */
  99. onTap(event) {
  100. let pointer = this.getPointer(event.center);
  101. let multiselect = this.selectionHandler.options.multiselect &&
  102. (event.changedPointers[0].ctrlKey || event.changedPointers[0].metaKey);
  103. this.checkSelectionChanges(pointer, event, multiselect);
  104. this.selectionHandler._generateClickEvent('click', event, pointer);
  105. }
  106. /**
  107. * handle doubletap event
  108. * @private
  109. */
  110. onDoubleTap(event) {
  111. let pointer = this.getPointer(event.center);
  112. this.selectionHandler._generateClickEvent('doubleClick', event, pointer);
  113. }
  114. /**
  115. * handle long tap event: multi select nodes
  116. * @private
  117. */
  118. onHold(event) {
  119. let pointer = this.getPointer(event.center);
  120. let multiselect = this.selectionHandler.options.multiselect;
  121. this.checkSelectionChanges(pointer, event, multiselect);
  122. this.selectionHandler._generateClickEvent('click', event, pointer);
  123. this.selectionHandler._generateClickEvent('hold', event, pointer);
  124. }
  125. /**
  126. * handle the release of the screen
  127. *
  128. * @private
  129. */
  130. onRelease(event) {
  131. if (new Date().valueOf() - this.touchTime > 10) {
  132. let pointer = this.getPointer(event.center);
  133. this.selectionHandler._generateClickEvent('release', event, pointer);
  134. // to avoid double fireing of this event because we have two hammer instances. (on canvas and on frame)
  135. this.touchTime = new Date().valueOf();
  136. }
  137. }
  138. onContext(event) {
  139. let pointer = this.getPointer({x:event.clientX, y:event.clientY});
  140. this.selectionHandler._generateClickEvent('oncontext', event, pointer);
  141. }
  142. /**
  143. *
  144. * @param pointer
  145. * @param add
  146. */
  147. checkSelectionChanges(pointer, event, add = false) {
  148. let previouslySelectedEdgeCount = this.selectionHandler._getSelectedEdgeCount();
  149. let previouslySelectedNodeCount = this.selectionHandler._getSelectedNodeCount();
  150. let previousSelection = this.selectionHandler.getSelection();
  151. let selected;
  152. if (add === true) {
  153. selected = this.selectionHandler.selectAdditionalOnPoint(pointer);
  154. }
  155. else {
  156. selected = this.selectionHandler.selectOnPoint(pointer);
  157. }
  158. let selectedEdgesCount = this.selectionHandler._getSelectedEdgeCount();
  159. let selectedNodesCount = this.selectionHandler._getSelectedNodeCount();
  160. let currentSelection = this.selectionHandler.getSelection();
  161. let {nodesChanged, edgesChanged} = this._determineIfDifferent(previousSelection, currentSelection);
  162. let nodeSelected = false;
  163. if (selectedNodesCount - previouslySelectedNodeCount > 0) { // node was selected
  164. this.selectionHandler._generateClickEvent('selectNode', event, pointer);
  165. selected = true;
  166. nodeSelected = true;
  167. }
  168. else if (nodesChanged === true && selectedNodesCount > 0) {
  169. this.selectionHandler._generateClickEvent('deselectNode', event, pointer, previousSelection);
  170. this.selectionHandler._generateClickEvent('selectNode', event, pointer);
  171. nodeSelected = true;
  172. selected = true;
  173. }
  174. else if (selectedNodesCount - previouslySelectedNodeCount < 0) { // node was deselected
  175. this.selectionHandler._generateClickEvent('deselectNode', event, pointer, previousSelection);
  176. selected = true;
  177. }
  178. // handle the selected edges
  179. if (selectedEdgesCount - previouslySelectedEdgeCount > 0 && nodeSelected === false) { // edge was selected
  180. this.selectionHandler._generateClickEvent('selectEdge', event, pointer);
  181. selected = true;
  182. }
  183. else if (selectedEdgesCount > 0 && edgesChanged === true) {
  184. this.selectionHandler._generateClickEvent('deselectEdge', event, pointer, previousSelection);
  185. this.selectionHandler._generateClickEvent('selectEdge', event, pointer);
  186. selected = true;
  187. }
  188. else if (selectedEdgesCount - previouslySelectedEdgeCount < 0) { // edge was deselected
  189. this.selectionHandler._generateClickEvent('deselectEdge', event, pointer, previousSelection);
  190. selected = true;
  191. }
  192. // fire the select event if anything has been selected or deselected
  193. if (selected === true) { // select or unselect
  194. this.selectionHandler._generateClickEvent('select', event, pointer);
  195. }
  196. }
  197. /**
  198. * This function checks if the nodes and edges previously selected have changed.
  199. * @param previousSelection
  200. * @param currentSelection
  201. * @returns {{nodesChanged: boolean, edgesChanged: boolean}}
  202. * @private
  203. */
  204. _determineIfDifferent(previousSelection,currentSelection) {
  205. let nodesChanged = false;
  206. let edgesChanged = false;
  207. for (let i = 0; i < previousSelection.nodes.length; i++) {
  208. if (currentSelection.nodes.indexOf(previousSelection.nodes[i]) === -1) {
  209. nodesChanged = true;
  210. }
  211. }
  212. for (let i = 0; i < currentSelection.nodes.length; i++) {
  213. if (previousSelection.nodes.indexOf(previousSelection.nodes[i]) === -1) {
  214. nodesChanged = true;
  215. }
  216. }
  217. for (let i = 0; i < previousSelection.edges.length; i++) {
  218. if (currentSelection.edges.indexOf(previousSelection.edges[i]) === -1) {
  219. edgesChanged = true;
  220. }
  221. }
  222. for (let i = 0; i < currentSelection.edges.length; i++) {
  223. if (previousSelection.edges.indexOf(previousSelection.edges[i]) === -1) {
  224. edgesChanged = true;
  225. }
  226. }
  227. return {nodesChanged, edgesChanged};
  228. }
  229. /**
  230. * This function is called by onDragStart.
  231. * It is separated out because we can then overload it for the datamanipulation system.
  232. *
  233. * @private
  234. */
  235. onDragStart(event) {
  236. //in case the touch event was triggered on an external div, do the initial touch now.
  237. if (this.drag.pointer === undefined) {
  238. this.onTouch(event);
  239. }
  240. // note: drag.pointer is set in onTouch to get the initial touch location
  241. let node = this.selectionHandler.getNodeAt(this.drag.pointer);
  242. this.drag.dragging = true;
  243. this.drag.selection = [];
  244. this.drag.translation = util.extend({},this.body.view.translation); // copy the object
  245. this.drag.nodeId = undefined;
  246. if (node !== undefined && this.options.dragNodes === true) {
  247. this.drag.nodeId = node.id;
  248. // select the clicked node if not yet selected
  249. if (node.isSelected() === false) {
  250. this.selectionHandler.unselectAll();
  251. this.selectionHandler.selectObject(node);
  252. }
  253. // after select to contain the node
  254. this.selectionHandler._generateClickEvent('dragStart', event, this.drag.pointer);
  255. let selection = this.selectionHandler.selectionObj.nodes;
  256. // create an array with the selected nodes and their original location and status
  257. for (let nodeId in selection) {
  258. if (selection.hasOwnProperty(nodeId)) {
  259. let object = selection[nodeId];
  260. let s = {
  261. id: object.id,
  262. node: object,
  263. // store original x, y, xFixed and yFixed, make the node temporarily Fixed
  264. x: object.x,
  265. y: object.y,
  266. xFixed: object.options.fixed.x,
  267. yFixed: object.options.fixed.y
  268. };
  269. object.options.fixed.x = true;
  270. object.options.fixed.y = true;
  271. this.drag.selection.push(s);
  272. }
  273. }
  274. }
  275. else {
  276. // fallback if no node is selected and thus the view is dragged.
  277. this.selectionHandler._generateClickEvent('dragStart', event, this.drag.pointer, undefined, true);
  278. }
  279. }
  280. /**
  281. * handle drag event
  282. * @private
  283. */
  284. onDrag(event) {
  285. if (this.drag.pinched === true) {
  286. return;
  287. }
  288. // remove the focus on node if it is focussed on by the focusOnNode
  289. this.body.emitter.emit('unlockNode');
  290. let pointer = this.getPointer(event.center);
  291. let selection = this.drag.selection;
  292. if (selection && selection.length && this.options.dragNodes === true) {
  293. this.selectionHandler._generateClickEvent('dragging', event, pointer);
  294. // calculate delta's and new location
  295. let deltaX = pointer.x - this.drag.pointer.x;
  296. let deltaY = pointer.y - this.drag.pointer.y;
  297. // update position of all selected nodes
  298. selection.forEach((selection) => {
  299. let node = selection.node;
  300. // only move the node if it was not fixed initially
  301. if (selection.xFixed === false) {
  302. node.x = this.canvas._XconvertDOMtoCanvas(this.canvas._XconvertCanvasToDOM(selection.x) + deltaX);
  303. }
  304. // only move the node if it was not fixed initially
  305. if (selection.yFixed === false) {
  306. node.y = this.canvas._YconvertDOMtoCanvas(this.canvas._YconvertCanvasToDOM(selection.y) + deltaY);
  307. }
  308. });
  309. // start the simulation of the physics
  310. this.body.emitter.emit('startSimulation');
  311. }
  312. else {
  313. // move the network
  314. if (this.options.dragView === true) {
  315. this.selectionHandler._generateClickEvent('dragging', event, pointer, undefined, true);
  316. // if the drag was not started properly because the click started outside the network div, start it now.
  317. if (this.drag.pointer === undefined) {
  318. this.onDragStart(event);
  319. return;
  320. }
  321. let diffX = pointer.x - this.drag.pointer.x;
  322. let diffY = pointer.y - this.drag.pointer.y;
  323. this.body.view.translation = {x:this.drag.translation.x + diffX, y:this.drag.translation.y + diffY};
  324. this.body.emitter.emit('_redraw');
  325. }
  326. }
  327. }
  328. /**
  329. * handle drag start event
  330. * @private
  331. */
  332. onDragEnd(event) {
  333. this.drag.dragging = false;
  334. let selection = this.drag.selection;
  335. if (selection && selection.length) {
  336. selection.forEach(function (s) {
  337. // restore original xFixed and yFixed
  338. s.node.options.fixed.x = s.xFixed;
  339. s.node.options.fixed.y = s.yFixed;
  340. });
  341. this.selectionHandler._generateClickEvent('dragEnd', event, this.getPointer(event.center));
  342. this.body.emitter.emit('startSimulation');
  343. }
  344. else {
  345. this.selectionHandler._generateClickEvent('dragEnd', event, this.getPointer(event.center), undefined, true);
  346. this.body.emitter.emit('_requestRedraw');
  347. }
  348. }
  349. /**
  350. * Handle pinch event
  351. * @param event
  352. * @private
  353. */
  354. onPinch(event) {
  355. let pointer = this.getPointer(event.center);
  356. this.drag.pinched = true;
  357. if (this.pinch['scale'] === undefined) {
  358. this.pinch.scale = 1;
  359. }
  360. // TODO: enabled moving while pinching?
  361. let scale = this.pinch.scale * event.scale;
  362. this.zoom(scale, pointer)
  363. }
  364. /**
  365. * Zoom the network in or out
  366. * @param {Number} scale a number around 1, and between 0.01 and 10
  367. * @param {{x: Number, y: Number}} pointer Position on screen
  368. * @return {Number} appliedScale scale is limited within the boundaries
  369. * @private
  370. */
  371. zoom(scale, pointer) {
  372. if (this.options.zoomView === true) {
  373. let scaleOld = this.body.view.scale;
  374. if (scale < 0.00001) {
  375. scale = 0.00001;
  376. }
  377. if (scale > 10) {
  378. scale = 10;
  379. }
  380. let preScaleDragPointer = undefined;
  381. if (this.drag !== undefined) {
  382. if (this.drag.dragging === true) {
  383. preScaleDragPointer = this.canvas.DOMtoCanvas(this.drag.pointer);
  384. }
  385. }
  386. // + this.canvas.frame.canvas.clientHeight / 2
  387. let translation = this.body.view.translation;
  388. let scaleFrac = scale / scaleOld;
  389. let tx = (1 - scaleFrac) * pointer.x + translation.x * scaleFrac;
  390. let ty = (1 - scaleFrac) * pointer.y + translation.y * scaleFrac;
  391. this.body.view.scale = scale;
  392. this.body.view.translation = {x:tx, y:ty};
  393. if (preScaleDragPointer != undefined) {
  394. let postScaleDragPointer = this.canvas.canvasToDOM(preScaleDragPointer);
  395. this.drag.pointer.x = postScaleDragPointer.x;
  396. this.drag.pointer.y = postScaleDragPointer.y;
  397. }
  398. this.body.emitter.emit('_requestRedraw');
  399. if (scaleOld < scale) {
  400. this.body.emitter.emit('zoom', {direction: '+', scale: this.body.view.scale, pointer: pointer});
  401. }
  402. else {
  403. this.body.emitter.emit('zoom', {direction: '-', scale: this.body.view.scale, pointer: pointer});
  404. }
  405. }
  406. }
  407. /**
  408. * Event handler for mouse wheel event, used to zoom the timeline
  409. * See http://adomas.org/javascript-mouse-wheel/
  410. * https://github.com/EightMedia/hammer.js/issues/256
  411. * @param {MouseEvent} event
  412. * @private
  413. */
  414. onMouseWheel(event) {
  415. if (this.options.zoomView === true) {
  416. // retrieve delta
  417. let delta = 0;
  418. if (event.wheelDelta) { /* IE/Opera. */
  419. delta = event.wheelDelta / 120;
  420. }
  421. else if (event.detail) { /* Mozilla case. */
  422. // In Mozilla, sign of delta is different than in IE.
  423. // Also, delta is multiple of 3.
  424. delta = -event.detail / 3;
  425. }
  426. // If delta is nonzero, handle it.
  427. // Basically, delta is now positive if wheel was scrolled up,
  428. // and negative, if wheel was scrolled down.
  429. if (delta !== 0) {
  430. // calculate the new scale
  431. let scale = this.body.view.scale;
  432. let zoom = delta / 10;
  433. if (delta < 0) {
  434. zoom = zoom / (1 - zoom);
  435. }
  436. scale *= (1 + zoom);
  437. // calculate the pointer location
  438. let pointer = this.getPointer({x: event.clientX, y: event.clientY});
  439. // apply the new scale
  440. this.zoom(scale, pointer);
  441. }
  442. // Prevent default actions caused by mouse wheel.
  443. event.preventDefault();
  444. }
  445. }
  446. /**
  447. * Mouse move handler for checking whether the title moves over a node with a title.
  448. * @param {Event} event
  449. * @private
  450. */
  451. onMouseMove(event) {
  452. let pointer = this.getPointer({x:event.clientX, y:event.clientY});
  453. let popupVisible = false;
  454. // check if the previously selected node is still selected
  455. if (this.popup !== undefined) {
  456. if (this.popup.hidden === false) {
  457. this._checkHidePopup(pointer);
  458. }
  459. // if the popup was not hidden above
  460. if (this.popup.hidden === false) {
  461. popupVisible = true;
  462. this.popup.setPosition(pointer.x + 3, pointer.y - 5);
  463. this.popup.show();
  464. }
  465. }
  466. // if we bind the keyboard to the div, we have to highlight it to use it. This highlights it on mouse over.
  467. if (this.options.keyboard.bindToWindow === false && this.options.keyboard.enabled === true) {
  468. this.canvas.frame.focus();
  469. }
  470. // start a timeout that will check if the mouse is positioned above an element
  471. if (popupVisible === false) {
  472. if (this.popupTimer !== undefined) {
  473. clearInterval(this.popupTimer); // stop any running calculationTimer
  474. this.popupTimer = undefined;
  475. }
  476. if (!this.drag.dragging) {
  477. this.popupTimer = setTimeout(() => this._checkShowPopup(pointer), this.options.tooltipDelay);
  478. }
  479. }
  480. /**
  481. * Adding hover highlights
  482. */
  483. if (this.options.hover === true) {
  484. // adding hover highlights
  485. let obj = this.selectionHandler.getNodeAt(pointer);
  486. if (obj === undefined) {
  487. obj = this.selectionHandler.getEdgeAt(pointer);
  488. }
  489. this.selectionHandler.hoverObject(obj);
  490. }
  491. }
  492. /**
  493. * Check if there is an element on the given position in the network
  494. * (a node or edge). If so, and if this element has a title,
  495. * show a popup window with its title.
  496. *
  497. * @param {{x:Number, y:Number}} pointer
  498. * @private
  499. */
  500. _checkShowPopup(pointer) {
  501. let x = this.canvas._XconvertDOMtoCanvas(pointer.x);
  502. let y = this.canvas._YconvertDOMtoCanvas(pointer.y);
  503. let pointerObj = {
  504. left: x,
  505. top: y,
  506. right: x,
  507. bottom: y
  508. };
  509. let previousPopupObjId = this.popupObj === undefined ? undefined : this.popupObj.id;
  510. let nodeUnderCursor = false;
  511. let popupType = 'node';
  512. // check if a node is under the cursor.
  513. if (this.popupObj === undefined) {
  514. // search the nodes for overlap, select the top one in case of multiple nodes
  515. let nodeIndices = this.body.nodeIndices;
  516. let nodes = this.body.nodes;
  517. let node;
  518. let overlappingNodes = [];
  519. for (let i = 0; i < nodeIndices.length; i++) {
  520. node = nodes[nodeIndices[i]];
  521. if (node.isOverlappingWith(pointerObj) === true) {
  522. if (node.getTitle() !== undefined) {
  523. overlappingNodes.push(nodeIndices[i]);
  524. }
  525. }
  526. }
  527. if (overlappingNodes.length > 0) {
  528. // if there are overlapping nodes, select the last one, this is the one which is drawn on top of the others
  529. this.popupObj = nodes[overlappingNodes[overlappingNodes.length - 1]];
  530. // if you hover over a node, the title of the edge is not supposed to be shown.
  531. nodeUnderCursor = true;
  532. }
  533. }
  534. if (this.popupObj === undefined && nodeUnderCursor === false) {
  535. // search the edges for overlap
  536. let edgeIndices = this.body.edgeIndices;
  537. let edges = this.body.edges;
  538. let edge;
  539. let overlappingEdges = [];
  540. for (let i = 0; i < edgeIndices.length; i++) {
  541. edge = edges[edgeIndices[i]];
  542. if (edge.isOverlappingWith(pointerObj) === true) {
  543. if (edge.connected === true && edge.getTitle() !== undefined) {
  544. overlappingEdges.push(edgeIndices[i]);
  545. }
  546. }
  547. }
  548. if (overlappingEdges.length > 0) {
  549. this.popupObj = edges[overlappingEdges[overlappingEdges.length - 1]];
  550. popupType = 'edge';
  551. }
  552. }
  553. if (this.popupObj !== undefined) {
  554. // show popup message window
  555. if (this.popupObj.id !== previousPopupObjId) {
  556. if (this.popup === undefined) {
  557. this.popup = new Popup(this.canvas.frame);
  558. }
  559. this.popup.popupTargetType = popupType;
  560. this.popup.popupTargetId = this.popupObj.id;
  561. // adjust a small offset such that the mouse cursor is located in the
  562. // bottom left location of the popup, and you can easily move over the
  563. // popup area
  564. this.popup.setPosition(pointer.x + 3, pointer.y - 5);
  565. this.popup.setText(this.popupObj.getTitle());
  566. this.popup.show();
  567. this.body.emitter.emit('showPopup',this.popupObj.id);
  568. }
  569. }
  570. else {
  571. if (this.popup !== undefined) {
  572. this.popup.hide();
  573. this.body.emitter.emit('hidePopup');
  574. }
  575. }
  576. }
  577. /**
  578. * Check if the popup must be hidden, which is the case when the mouse is no
  579. * longer hovering on the object
  580. * @param {{x:Number, y:Number}} pointer
  581. * @private
  582. */
  583. _checkHidePopup(pointer) {
  584. let pointerObj = this.selectionHandler._pointerToPositionObject(pointer);
  585. let stillOnObj = false;
  586. if (this.popup.popupTargetType === 'node') {
  587. if (this.body.nodes[this.popup.popupTargetId] !== undefined) {
  588. stillOnObj = this.body.nodes[this.popup.popupTargetId].isOverlappingWith(pointerObj);
  589. // if the mouse is still one the node, we have to check if it is not also on one that is drawn on top of it.
  590. // we initially only check stillOnObj because this is much faster.
  591. if (stillOnObj === true) {
  592. let overNode = this.selectionHandler.getNodeAt(pointer);
  593. stillOnObj = overNode.id === this.popup.popupTargetId;
  594. }
  595. }
  596. }
  597. else {
  598. if (this.selectionHandler.getNodeAt(pointer) === undefined) {
  599. if (this.body.edges[this.popup.popupTargetId] !== undefined) {
  600. stillOnObj = this.body.edges[this.popup.popupTargetId].isOverlappingWith(pointerObj);
  601. }
  602. }
  603. }
  604. if (stillOnObj === false) {
  605. this.popupObj = undefined;
  606. this.popup.hide();
  607. this.body.emitter.emit('hidePopup');
  608. }
  609. }
  610. }
  611. export default InteractionHandler;