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.

985 lines
31 KiB

  1. let util = require('../../util');
  2. let Hammer = require('../../module/hammer');
  3. let hammerUtil = require('../../hammerUtil');
  4. let locales = require('../locales');
  5. /**
  6. * clears the toolbar div element of children
  7. *
  8. * @private
  9. */
  10. class ManipulationSystem {
  11. constructor(body, canvas, selectionHandler) {
  12. this.body = body;
  13. this.canvas = canvas;
  14. this.selectionHandler = selectionHandler;
  15. this.editMode = false;
  16. this.manipulationDiv = undefined;
  17. this.editModeDiv = undefined;
  18. this.closeDiv = undefined;
  19. this.boundFunction = undefined;
  20. this.manipulationHammers = [];
  21. this.cachedFunctions = {};
  22. this.touchTime = 0;
  23. this.temporaryIds = {nodes: [], edges:[]};
  24. this.guiEnabled = false;
  25. this.selectedControlNode = undefined;
  26. this.options = {};
  27. this.defaultOptions = {
  28. enabled: false,
  29. initiallyVisible: false,
  30. locale: 'en',
  31. locales: locales,
  32. functionality:{
  33. addNode: true,
  34. addEdge: true,
  35. editNode: true,
  36. editEdge: true,
  37. deleteNode: true,
  38. deleteEdge: true
  39. },
  40. handlerFunctions: {
  41. addNode: undefined,
  42. addEdge: undefined,
  43. editNode: undefined,
  44. editEdge: undefined,
  45. deleteNode: undefined,
  46. deleteEdge: undefined
  47. }
  48. }
  49. util.extend(this.options, this.defaultOptions);
  50. }
  51. setOptions(options) {
  52. if (options !== undefined) {
  53. if (typeof options == 'boolean') {
  54. this.options.enabled = options;
  55. }
  56. else {
  57. this.options.enabled = true;
  58. for (let prop in options) {
  59. if (options.hasOwnProperty(prop)) {
  60. this.options[prop] = options[prop];
  61. }
  62. }
  63. }
  64. if (this.options.initiallyVisible === true) {
  65. this.editMode = true;
  66. }
  67. this.init();
  68. }
  69. }
  70. init() {
  71. if (this.options.enabled === true) {
  72. // Enable the GUI
  73. this.guiEnabled = true;
  74. // remove override
  75. this.selectionHandler.forceSelectEdges = true;
  76. this.createWrappers();
  77. if (this.editMode === false) {
  78. this.createEditButton();
  79. }
  80. else {
  81. this.createManipulatorBar();
  82. }
  83. }
  84. else {
  85. this.removeManipulationDOM();
  86. // disable the gui
  87. this.guiEnabled = false;
  88. }
  89. }
  90. createWrappers() {
  91. // load the manipulator HTML elements. All styling done in css.
  92. if (this.manipulationDiv === undefined) {
  93. this.manipulationDiv = document.createElement('div');
  94. this.manipulationDiv.className = 'network-manipulationDiv';
  95. if (this.editMode === true) {
  96. this.manipulationDiv.style.display = "block";
  97. }
  98. else {
  99. this.manipulationDiv.style.display = "none";
  100. }
  101. this.canvas.frame.appendChild(this.manipulationDiv);
  102. }
  103. if (this.editModeDiv === undefined) {
  104. this.editModeDiv = document.createElement('div');
  105. this.editModeDiv.className = 'network-manipulation-editMode';
  106. if (this.editMode === true) {
  107. this.editModeDiv.style.display = "none";
  108. }
  109. else {
  110. this.editModeDiv.style.display = "block";
  111. }
  112. this.canvas.frame.appendChild(this.editModeDiv);
  113. }
  114. if (this.closeDiv === undefined) {
  115. this.closeDiv = document.createElement('div');
  116. this.closeDiv.className = 'network-manipulation-closeDiv';
  117. this.closeDiv.style.display = this.manipulationDiv.style.display;
  118. this.canvas.frame.appendChild(this.closeDiv);
  119. }
  120. }
  121. /**
  122. * Create the edit button
  123. */
  124. createEditButton() {
  125. // restore everything to it's original state (if applicable)
  126. this._clean();
  127. // reset the manipulationDOM
  128. this.manipulationDOM = {};
  129. // empty the editModeDiv
  130. util.recursiveDOMDelete(this.editModeDiv);
  131. // create the contents for the editMode button
  132. let locale = this.options.locales[this.options.locale];
  133. let button = this.createButton('editMode', 'network-manipulationUI edit editmode', locale['edit']);
  134. this.editModeDiv.appendChild(button);
  135. // bind a hammer listener to the button, calling the function toggleEditMode.
  136. this.bindHammerToDiv(button, 'toggleEditMode');
  137. }
  138. removeManipulationDOM() {
  139. // removes all the bindings and overloads
  140. this._clean();
  141. // empty the manipulation divs
  142. util.recursiveDOMDelete(this.manipulationDiv);
  143. util.recursiveDOMDelete(this.editModeDiv);
  144. util.recursiveDOMDelete(this.closeDiv);
  145. // remove the manipulation divs
  146. this.canvas.frame.removeChild(this.manipulationDiv);
  147. this.canvas.frame.removeChild(this.editModeDiv);
  148. this.canvas.frame.removeChild(this.closeDiv);
  149. // set the references to undefined
  150. this.manipulationDiv = undefined;
  151. this.editModeDiv = undefined;
  152. this.closeDiv = undefined;
  153. // remove override
  154. this.selectionHandler.forceSelectEdges = false;
  155. }
  156. //clearManipulatorBar() {
  157. // util._recursiveDOMDelete(this.manipulationDiv);
  158. // this.manipulationDOM = {};
  159. // this._cleanManipulatorHammers();
  160. // this._manipulationReleaseOverload();
  161. //}
  162. _cleanManipulatorHammers() {
  163. // _clean hammer bindings
  164. if (this.manipulationHammers.length != 0) {
  165. for (let i = 0; i < this.manipulationHammers.length; i++) {
  166. this.manipulationHammers[i].destroy();
  167. }
  168. this.manipulationHammers = [];
  169. }
  170. }
  171. /**
  172. * Manipulation UI temporarily overloads certain functions to extend or replace them. To be able to restore
  173. * these functions to their original functionality, we saved them in this.cachedFunctions.
  174. * This function restores these functions to their original function.
  175. *
  176. * @private
  177. */
  178. _restoreOverloadedFunctions() {
  179. for (let functionName in this.cachedFunctions) {
  180. if (this.cachedFunctions.hasOwnProperty(functionName)) {
  181. this.body.eventListeners[functionName] = this.cachedFunctions[functionName];
  182. delete this.cachedFunctions[functionName];
  183. }
  184. }
  185. this.cachedFunctions = {};
  186. }
  187. /**
  188. * Enable or disable edit-mode.
  189. *
  190. * @private
  191. */
  192. toggleEditMode() {
  193. this.editMode = !this.editMode;
  194. let toolbar = this.manipulationDiv;
  195. let closeDiv = this.closeDiv;
  196. let editModeDiv = this.editModeDiv;
  197. if (this.editMode === true) {
  198. toolbar.style.display = "block";
  199. closeDiv.style.display = "block";
  200. editModeDiv.style.display = "none";
  201. this.bindHammerToDiv(closeDiv, 'toggleEditMode');
  202. this.createManipulatorBar();
  203. }
  204. else {
  205. toolbar.style.display = "none";
  206. closeDiv.style.display = "none";
  207. editModeDiv.style.display = "block";
  208. this.createEditButton();
  209. }
  210. }
  211. _clean() {
  212. // _clean the divs
  213. if (this.guiEnabled === true) {
  214. util.recursiveDOMDelete(this.editModeDiv);
  215. util.recursiveDOMDelete(this.manipulationDiv);
  216. // removes all the bindings and overloads
  217. this._cleanManipulatorHammers();
  218. }
  219. // remove temporary nodes and edges
  220. this._cleanupTemporaryNodesAndEdges();
  221. // restore overloaded UI functions
  222. this._restoreOverloadedFunctions();
  223. // remove the boundFunction
  224. if (this.boundFunction !== undefined) {
  225. this.body.emitter.off(this.boundFunction.event, this.boundFunction.fn);
  226. }
  227. this.boundFunction = undefined;
  228. }
  229. createSeperator(index = 1) {
  230. this.manipulationDOM['seperatorLineDiv' + index] = document.createElement('div');
  231. this.manipulationDOM['seperatorLineDiv' + index].className = 'network-seperatorLine';
  232. this.manipulationDiv.appendChild(this.manipulationDOM['seperatorLineDiv' + index]);
  233. }
  234. createAddNodeButton(locale) {
  235. let button = this.createButton('addNode', 'network-manipulationUI add', locale['addNode']);
  236. this.manipulationDiv.appendChild(button);
  237. this.bindHammerToDiv(button, 'addNodeMode');
  238. }
  239. createAddEdgeButton(locale) {
  240. let button = this.createButton('addEdge', 'network-manipulationUI connect', locale['addEdge']);
  241. this.manipulationDiv.appendChild(button);
  242. this.bindHammerToDiv(button, 'addEdgeMode');
  243. }
  244. createEditNodeButton(locale) {
  245. let button = this.createButton('editNode', 'network-manipulationUI edit', locale['editNode']);
  246. this.manipulationDiv.appendChild(button);
  247. this.bindHammerToDiv(button, '_editNode');
  248. }
  249. createEditEdgeButton(locale) {
  250. let button = this.createButton('editEdge', 'network-manipulationUI edit', locale['editEdge']);
  251. this.manipulationDiv.appendChild(button);
  252. this.bindHammerToDiv(button, 'editEdgeMode');
  253. }
  254. createDeleteButton(locale) {
  255. let button = this.createButton('delete', 'network-manipulationUI delete', locale['del']);
  256. this.manipulationDiv.appendChild(button);
  257. this.bindHammerToDiv(button, 'deleteSelected');
  258. }
  259. createBackButton(locale) {
  260. let button = this.createButton('back', 'network-manipulationUI back', locale['back']);
  261. this.manipulationDiv.appendChild(button);
  262. this.bindHammerToDiv(button, 'createManipulatorBar');
  263. }
  264. createDescription(label) {
  265. this.manipulationDiv.appendChild(
  266. this.createButton('description', 'network-manipulationUI none', label)
  267. );
  268. }
  269. createButton(id, className, label, labelClassName = 'network-manipulationLabel') {
  270. this.manipulationDOM[id+"Div"] = document.createElement('div');
  271. this.manipulationDOM[id+"Div"].className = className;
  272. this.manipulationDOM[id+"Label"] = document.createElement('div');
  273. this.manipulationDOM[id+"Label"].className = labelClassName;
  274. this.manipulationDOM[id+"Label"].innerHTML = label;
  275. this.manipulationDOM[id+"Div"].appendChild(this.manipulationDOM[id+'Label']);
  276. return this.manipulationDOM[id+"Div"];
  277. }
  278. temporaryBind(fn, event) {
  279. this.boundFunction = {fn:fn.bind(this), event};
  280. this.body.emitter.on(event, this.boundFunction.fn);
  281. }
  282. /**
  283. * main function, creates the main toolbar. Removes functions bound to the select event. Binds all the buttons of the toolbar.
  284. *
  285. * @private
  286. */
  287. createManipulatorBar() {
  288. this._clean();
  289. // resume calculation
  290. this.body.emitter.emit("restorePhysics");
  291. // reset global letiables
  292. this.manipulationDOM = {};
  293. let selectedNodeCount = this.selectionHandler._getSelectedNodeCount();
  294. let selectedEdgeCount = this.selectionHandler._getSelectedEdgeCount();
  295. let selectedTotalCount = selectedNodeCount + selectedEdgeCount;
  296. let locale = this.options.locales[this.options.locale];
  297. let needSeperator = false;
  298. if (this.options.functionality.addNode === true) {
  299. this.createAddNodeButton(locale);
  300. needSeperator = true;
  301. }
  302. if (this.options.functionality.addEdge === true) {
  303. if (needSeperator === true) {this.createSeperator(1);} else {needSeperator = true;}
  304. this.createAddEdgeButton(locale);
  305. }
  306. if (selectedNodeCount === 1 && typeof this.options.handlerFunctions.editNode === 'function' && this.options.functionality.editNode === true) {
  307. if (needSeperator === true) {this.createSeperator(2);} else {needSeperator = true;}
  308. this.createEditNodeButton(locale);
  309. }
  310. else if (selectedEdgeCount === 1 && selectedNodeCount === 0 && this.options.functionality.editEdge === true) {
  311. if (needSeperator === true) {this.createSeperator(3);} else {needSeperator = true;}
  312. this.createEditEdgeButton(locale);
  313. }
  314. // remove buttons
  315. if (selectedTotalCount !== 0) {
  316. if (selectedNodeCount === 1 && this.options.functionality.deleteNode === true) {
  317. if (needSeperator === true) {this.createSeperator(4);}
  318. this.createDeleteButton(locale);
  319. }
  320. else if (selectedNodeCount === 0 && this.options.functionality.deleteEdge === true) {
  321. if (needSeperator === true) {this.createSeperator(4);}
  322. this.createDeleteButton(locale);
  323. }
  324. }
  325. // bind the close button
  326. this.bindHammerToDiv(this.closeDiv, 'toggleEditMode');
  327. // refresh this bar based on what has been selected
  328. this.temporaryBind(this.createManipulatorBar,'select');
  329. }
  330. /**
  331. * Bind an hammer instance to a DOM element. TODO: remove the double check.
  332. * @param domElement
  333. * @param funct
  334. */
  335. bindHammerToDiv(domElement, funct) {
  336. let hammer = new Hammer(domElement, {});
  337. hammerUtil.onTouch(hammer, this[funct].bind(this));
  338. this.manipulationHammers.push(hammer);
  339. }
  340. /**
  341. * Create the toolbar for adding Nodes
  342. *
  343. * @private
  344. */
  345. addNodeMode() {
  346. // clear the toolbar
  347. this._clean();
  348. if (this.guiEnabled === true) {
  349. let locale = this.options.locales[this.options.locale];
  350. this.manipulationDOM = {};
  351. this.createBackButton(locale);
  352. this.createSeperator();
  353. this.createDescription(locale['addDescription'])
  354. // bind the close button
  355. this.bindHammerToDiv(this.closeDiv, 'toggleEditMode');
  356. }
  357. this.temporaryBind(this._addNode,'click');
  358. }
  359. /**
  360. * create the toolbar to connect nodes
  361. *
  362. * @private
  363. */
  364. addEdgeMode() {
  365. // _clean the system
  366. this._clean();
  367. if (this.guiEnabled === true) {
  368. let locale = this.options.locales[this.options.locale];
  369. this.manipulationDOM = {};
  370. this.createBackButton(locale);
  371. this.createSeperator();
  372. this.createDescription(locale['edgeDescription']);
  373. // bind the close button
  374. this.bindHammerToDiv(this.closeDiv, 'toggleEditMode');
  375. }
  376. // temporarily overload functions
  377. this.cachedFunctions["onTouch"] = this.body.eventListeners.onTouch;
  378. this.cachedFunctions["onDragEnd"] = this.body.eventListeners.onDragEnd;
  379. this.cachedFunctions["onHold"] = this.body.eventListeners.onHold;
  380. this.body.eventListeners.onTouch = this._handleConnect.bind(this);
  381. this.body.eventListeners.onDragEnd = this._finishConnect.bind(this);
  382. this.body.eventListeners.onHold = function () {};
  383. }
  384. /**
  385. * create the toolbar to edit edges
  386. *
  387. * @private
  388. */
  389. editEdgeMode() {
  390. // clear the system
  391. this._clean();
  392. if (this.guiEnabled === true) {
  393. let locale = this.options.locales[this.options.locale];
  394. this.manipulationDOM = {};
  395. this.createBackButton(locale);
  396. this.createSeperator();
  397. this.createDescription(locale['editEdgeDescription']);
  398. // bind the close button
  399. this.bindHammerToDiv(this.closeDiv, 'toggleEditMode');
  400. }
  401. this.edgeBeingEditedId = this.selectionHandler.getSelectedEdges()[0];
  402. let edge = this.body.edges[this.edgeBeingEditedId];
  403. // create control nodes
  404. let controlNodeFrom = this.body.functions.createNode(this.getTargetNodeProperties(edge.from.x, edge.from.y));
  405. let controlNodeTo = this.body.functions.createNode(this.getTargetNodeProperties(edge.to.x, edge.to.y));
  406. this.temporaryIds.nodes.push(controlNodeFrom.id);
  407. this.temporaryIds.nodes.push(controlNodeTo.id);
  408. this.body.nodes[controlNodeFrom.id] = controlNodeFrom;
  409. this.body.nodeIndices.push(controlNodeFrom.id);
  410. this.body.nodes[controlNodeTo.id] = controlNodeTo;
  411. this.body.nodeIndices.push(controlNodeTo.id);
  412. // temporarily overload functions
  413. this.cachedFunctions['onTouch'] = this.body.eventListeners.onTouch;
  414. this.cachedFunctions['onTap'] = this.body.eventListeners.onTap;
  415. this.cachedFunctions['onHold'] = this.body.eventListeners.onHold;
  416. this.cachedFunctions['onDragStart'] = this.body.eventListeners.onDragStart;
  417. this.cachedFunctions['onDrag'] = this.body.eventListeners.onDrag;
  418. this.cachedFunctions['onDragEnd'] = this.body.eventListeners.onDragEnd;
  419. this.cachedFunctions['onMouseOver'] = this.body.eventListeners.onMouseOver;
  420. this.body.eventListeners.onTouch = this._controlNodeTouch.bind(this);
  421. this.body.eventListeners.onTap = function() {};
  422. this.body.eventListeners.onHold = function() {};
  423. this.body.eventListeners.onDragStart= this._controlNodeDragStart.bind(this);
  424. this.body.eventListeners.onDrag = this._controlNodeDrag.bind(this);
  425. this.body.eventListeners.onDragEnd = this._controlNodeDragEnd.bind(this);
  426. this.body.eventListeners.onMouseOver= function() {}
  427. // create function to position control nodes correctly on movement
  428. let positionControlNodes = (ctx) => {
  429. let positions = edge.edgeType.findBorderPositions(ctx);
  430. if (controlNodeFrom.selected === false) {
  431. controlNodeFrom.x = positions.from.x;
  432. controlNodeFrom.y = positions.from.y;
  433. }
  434. if (controlNodeTo.selected === false) {
  435. controlNodeTo.x = positions.to.x;
  436. controlNodeTo.y = positions.to.y;
  437. }
  438. }
  439. this.temporaryBind(positionControlNodes, "beforeDrawing");
  440. this.body.emitter.emit("_redraw");
  441. }
  442. _controlNodeTouch(event) {
  443. this.lastTouch = this.body.functions.getPointer(event.center);
  444. this.lastTouch.translation = util.extend({},this.body.view.translation); // copy the object
  445. }
  446. _controlNodeDragStart(event) {
  447. let pointer = this.lastTouch;
  448. let pointerObj = this.selectionHandler._pointerToPositionObject(pointer);
  449. let from = this.body.nodes[this.temporaryIds.nodes[0]];
  450. let to = this.body.nodes[this.temporaryIds.nodes[1]];
  451. let edge = this.body.edges[this.edgeBeingEditedId];
  452. this.selectedControlNode = undefined;
  453. let fromSelect = from.isOverlappingWith(pointerObj);
  454. let toSelect = to.isOverlappingWith(pointerObj);
  455. if (fromSelect === true) {
  456. this.selectedControlNode = from;
  457. edge.edgeType.from = from;
  458. }
  459. else if (toSelect === true) {
  460. this.selectedControlNode = to;
  461. edge.edgeType.to = to;
  462. }
  463. this.body.emitter.emit("_redraw");
  464. }
  465. _controlNodeDrag(event) {
  466. this.body.emitter.emit("disablePhysics");
  467. let pointer = this.body.functions.getPointer(event.center);
  468. let pos = this.canvas.DOMtoCanvas(pointer);
  469. if (this.selectedControlNode !== undefined) {
  470. this.selectedControlNode.x = pos.x;
  471. this.selectedControlNode.y = pos.y;
  472. }
  473. else {
  474. // if the drag was not started properly because the click started outside the network div, start it now.
  475. let diffX = pointer.x - this.lastTouch.x;
  476. let diffY = pointer.y - this.lastTouch.y;
  477. this.body.view.translation = {x:this.lastTouch.translation.x + diffX, y:this.lastTouch.translation.y + diffY};
  478. }
  479. this.body.emitter.emit("_redraw");
  480. }
  481. _controlNodeDragEnd(event) {
  482. let pointer = this.body.functions.getPointer(event.center);
  483. let pointerObj = this.selectionHandler._pointerToPositionObject(pointer);
  484. let edge = this.body.edges[this.edgeBeingEditedId];
  485. let overlappingNodeIds = this.selectionHandler._getAllNodesOverlappingWith(pointerObj);
  486. let node = undefined;
  487. for (let i = overlappingNodeIds.length-1; i >= 0; i--) {
  488. if (overlappingNodeIds[i] !== this.selectedControlNode.id) {
  489. node = this.body.nodes[overlappingNodeIds[i]];
  490. break;
  491. }
  492. }
  493. // perform the connection
  494. if (node !== undefined && this.selectedControlNode !== undefined) {
  495. if (node.isCluster === true) {
  496. alert(this.options.locales[this.options.locale]["createEdgeError"])
  497. }
  498. else {
  499. let from = this.body.nodes[this.temporaryIds.nodes[0]];
  500. if (this.selectedControlNode.id == from.id) {
  501. this._editEdge(node.id, edge.to.id);
  502. }
  503. else {
  504. this._editEdge(edge.from.id, node.id);
  505. }
  506. }
  507. }
  508. else {
  509. edge.updateEdgeType();
  510. this.body.emitter.emit("restorePhysics");
  511. }
  512. this.body.emitter.emit("_redraw");
  513. }
  514. /**
  515. * the function bound to the selection event. It checks if you want to connect a cluster and changes the description
  516. * to walk the user through the process.
  517. *
  518. * @private
  519. */
  520. _selectControlNode(event) {
  521. }
  522. /**
  523. *
  524. * @param pointer
  525. * @private
  526. */
  527. _releaseControlNode(pointer) {
  528. if (new Date().valueOf() - this.touchTime > 100) {
  529. console.log("release")
  530. // perform the connection
  531. let node = this.selectionHandler.getNodeAt(pointer);
  532. if (node !== undefined) {
  533. if (node.isCluster === true) {
  534. alert(this.options.locales[this.options.locale]["createEdgeError"])
  535. }
  536. else {
  537. let edge = this.body.edges[this.edgeBeingEditedId];
  538. let targetNodeId = undefined;
  539. if (edge.to.selected === true) {
  540. targetNodeId = edge.toId;
  541. }
  542. else if (edge.from.selected === true) {
  543. targetNodeId = edge.fromId;
  544. }
  545. //this.body.eventListeners.onDrag = this.cachedFunctions["onDrag"];
  546. //this.body.eventListeners.onRelease = this.cachedFunctions["onRelease"];
  547. //delete this.cachedFunctions["onRelease"];
  548. //delete this.cachedFunctions["onDrag"];
  549. ////
  550. //
  551. //
  552. //
  553. //
  554. //
  555. //
  556. //if (this.body.nodes[connectFromId] !== undefined && this.body.nodes[node.id] !== undefined) {
  557. // this._createEdge(connectFromId, node.id);
  558. //}
  559. }
  560. }
  561. this.body.emitter.emit("_redraw");
  562. //this.body.emitter.emit("_redraw");
  563. //let newNode = this.getNodeAt(pointer);
  564. //if (newNode !== undefined) {
  565. // if (this.edgeBeingEditedId.controlNodes.from.selected == true) {
  566. // this.edgeBeingEditedId._restoreControlNodes();
  567. // this._editEdge(newNode.id, this.edgeBeingEditedId.to.id);
  568. // this.edgeBeingEditedId.controlNodes.from.unselect();
  569. // }
  570. // if (this.edgeBeingEditedId.controlNodes.to.selected == true) {
  571. // this.edgeBeingEditedId._restoreControlNodes();
  572. // this._editEdge(this.edgeBeingEditedId.from.id, newNode.id);
  573. // this.edgeBeingEditedId.controlNodes.to.unselect();
  574. // }
  575. //}
  576. //else {
  577. // this.edgeBeingEditedId._restoreControlNodes();
  578. //}
  579. this.touchTime = new Date().valueOf();
  580. }
  581. }
  582. /**
  583. * the function bound to the selection event. It checks if you want to connect a cluster and changes the description
  584. * to walk the user through the process.
  585. *
  586. * @private
  587. */
  588. _handleConnect(event) {
  589. // check to avoid double fireing of this function.
  590. if (new Date().valueOf() - this.touchTime > 100) {
  591. let pointer = this.body.functions.getPointer(event.center);
  592. let node = this.selectionHandler.getNodeAt(pointer);
  593. if (node !== undefined) {
  594. if (node.isCluster === true) {
  595. alert(this.options.locales[this.options.locale]['createEdgeError'])
  596. }
  597. else {
  598. // create a node the temporary line can look at
  599. let targetNode = this.body.functions.createNode(this.getTargetNodeProperties(node.x,node.y));
  600. let targetNodeId = targetNode.id;
  601. this.body.nodes[targetNode.id] = targetNode;
  602. this.body.nodeIndices.push(targetNode.id);
  603. // create a temporary edge
  604. let connectionEdge = this.body.functions.createEdge({
  605. id: "connectionEdge" + util.randomUUID(),
  606. from: node.id,
  607. to: targetNode.id,
  608. physics:false,
  609. smooth: {
  610. enabled: true,
  611. dynamic: false,
  612. type: "continuous",
  613. roundness: 0.5
  614. }
  615. });
  616. this.body.edges[connectionEdge.id] = connectionEdge;
  617. this.body.edgeIndices.push(connectionEdge.id);
  618. this.temporaryIds.nodes.push(targetNode.id);
  619. this.temporaryIds.edges.push(connectionEdge.id);
  620. this.cachedFunctions["onDrag"] = this.body.eventListeners.onDrag;
  621. this.body.eventListeners.onDrag = (event) => {
  622. let pointer = this.body.functions.getPointer(event.center);
  623. let targetNode = this.body.nodes[targetNodeId];
  624. targetNode.x = this.canvas._XconvertDOMtoCanvas(pointer.x);
  625. targetNode.y = this.canvas._YconvertDOMtoCanvas(pointer.y);
  626. this.body.emitter.emit("_redraw");
  627. }
  628. }
  629. }
  630. this.touchTime = new Date().valueOf();
  631. // do the original touch events
  632. this.cachedFunctions["onTouch"](event);
  633. }
  634. }
  635. _finishConnect(event) {
  636. let pointer = this.body.functions.getPointer(event.center);
  637. let pointerObj = this.selectionHandler._pointerToPositionObject(pointer);
  638. // remember the edge id
  639. let connectFromId = undefined;
  640. if (this.temporaryIds.edges[0] !== undefined) {
  641. connectFromId = this.body.edges[this.temporaryIds.edges[0]].fromId;
  642. }
  643. //restore the drag function
  644. if (this.cachedFunctions["onDrag"] !== undefined) {
  645. this.body.eventListeners.onDrag = this.cachedFunctions["onDrag"];
  646. delete this.cachedFunctions["onDrag"];
  647. }
  648. // get the overlapping node but NOT the temporary node;
  649. let overlappingNodeIds = this.selectionHandler._getAllNodesOverlappingWith(pointerObj);
  650. let node = undefined;
  651. for (let i = overlappingNodeIds.length-1; i >= 0; i--) {
  652. if (this.temporaryIds.nodes.indexOf(overlappingNodeIds[i]) !== -1) {
  653. node = this.body.nodes[overlappingNodeIds[i]];
  654. break;
  655. }
  656. }
  657. // clean temporary nodes and edges.
  658. this._cleanupTemporaryNodesAndEdges();
  659. // perform the connection
  660. if (node !== undefined) {
  661. if (node.isCluster === true) {
  662. alert(this.options.locales[this.options.locale]["createEdgeError"])
  663. }
  664. else {
  665. if (this.body.nodes[connectFromId] !== undefined && this.body.nodes[node.id] !== undefined) {
  666. this._createEdge(connectFromId, node.id);
  667. }
  668. }
  669. }
  670. this.body.emitter.emit("_redraw");
  671. }
  672. _cleanupTemporaryNodesAndEdges() {
  673. // _clean temporary edges
  674. for (let i = 0; i < this.temporaryIds.edges.length; i++) {
  675. this.body.edges[this.temporaryIds.edges[i]].disconnect();
  676. delete this.body.edges[this.temporaryIds.edges[i]];
  677. let indexTempEdge = this.body.edgeIndices.indexOf(this.temporaryIds.edges[i]);
  678. if (indexTempEdge !== -1) {this.body.edgeIndices.splice(indexTempEdge,1);}
  679. }
  680. // _clean temporary nodes
  681. for (let i = 0; i < this.temporaryIds.nodes.length; i++) {
  682. delete this.body.nodes[this.temporaryIds.nodes[i]];
  683. let indexTempNode = this.body.nodeIndices.indexOf(this.temporaryIds.nodes[i]);
  684. if (indexTempNode !== -1) {this.body.nodeIndices.splice(indexTempNode,1);}
  685. }
  686. this.temporaryIds = {nodes: [], edges: []};
  687. }
  688. /**
  689. * Adds a node on the specified location
  690. */
  691. _addNode(clickData) {
  692. let defaultData = {
  693. id: util.randomUUID(),
  694. x: clickData.pointer.canvas.x,
  695. y: clickData.pointer.canvas.y,
  696. label: "new"
  697. };
  698. if (typeof this.options.handlerFunctions.addNode === 'function') {
  699. if (this.options.handlerFunctions.addNode.length == 2) {
  700. this.options.handlerFunctions.addNode(defaultData, (finalizedData) => {
  701. this.body.data.nodes.add(finalizedData);
  702. this.createManipulatorBar();
  703. });
  704. }
  705. else {
  706. throw new Error('The function for add does not support two arguments (data,callback)');
  707. this.createManipulatorBar();
  708. }
  709. }
  710. else {
  711. this.body.data.nodes.add(defaultData);
  712. this.createManipulatorBar();
  713. }
  714. }
  715. /**
  716. * connect two nodes with a new edge.
  717. *
  718. * @private
  719. */
  720. _createEdge(sourceNodeId, targetNodeId) {
  721. let defaultData = {from: sourceNodeId, to: targetNodeId};
  722. if (this.options.handlerFunctions.addEdge) {
  723. if (this.options.handlerFunctions.addEdge.length == 2) {
  724. this.options.handlerFunctions.addEdge(defaultData, (finalizedData) => {
  725. this.body.data.edges.add(finalizedData);
  726. this.selectionHandler.unselectAll();
  727. this.createManipulatorBar();
  728. });
  729. }
  730. else {
  731. throw new Error('The function for connect does not support two arguments (data,callback)');
  732. }
  733. }
  734. else {
  735. this.body.data.edges.add(defaultData);
  736. this.selectionHandler.unselectAll();
  737. this.createManipulatorBar();
  738. }
  739. }
  740. /**
  741. * connect two nodes with a new edge.
  742. *
  743. * @private
  744. */
  745. _editEdge(sourceNodeId, targetNodeId) {
  746. let defaultData = {id: this.edgeBeingEditedId, from: sourceNodeId, to: targetNodeId};
  747. console.log(defaultData)
  748. if (this.options.handlerFunctions.editEdge) {
  749. if (this.options.handlerFunctions.editEdge.length == 2) {
  750. this.options.handlerFunctions.editEdge(defaultData, (finalizedData) => {
  751. this.body.data.edges.update(finalizedData);
  752. this.selectionHandler.unselectAll();
  753. this.createManipulatorBar();
  754. });
  755. }
  756. else {
  757. throw new Error('The function for edit does not support two arguments (data, callback)');
  758. }
  759. }
  760. else {
  761. this.body.data.edges.update(defaultData);
  762. this.selectionHandler.unselectAll();
  763. this.createManipulatorBar();
  764. }
  765. }
  766. /**
  767. * Create the toolbar to edit the selected node. The label and the color can be changed. Other colors are derived from the chosen color.
  768. *
  769. * @private
  770. */
  771. _editNode() {
  772. if (this.options.handlerFunctions.edit && this.editMode == true) {
  773. let node = this._getSelectedNode();
  774. let data = {
  775. id: node.id,
  776. label: node.label,
  777. group: node.options.group,
  778. shape: node.options.shape,
  779. color: {
  780. background: node.options.color.background,
  781. border: node.options.color.border,
  782. highlight: {
  783. background: node.options.color.highlight.background,
  784. border: node.options.color.highlight.border
  785. }
  786. }
  787. };
  788. if (this.options.handlerFunctions.edit.length == 2) {
  789. let me = this;
  790. this.options.handlerFunctions.edit(data, function (finalizedData) {
  791. me.body.data.nodes.update(finalizedData);
  792. me.createManipulatorBar();
  793. me.moving = true;
  794. me.start();
  795. });
  796. }
  797. else {
  798. throw new Error('The function for edit does not support two arguments (data, callback)');
  799. }
  800. }
  801. else {
  802. throw new Error('No edit function has been bound to this button');
  803. }
  804. }
  805. /**
  806. * delete everything in the selection
  807. *
  808. * @private
  809. */
  810. deleteSelected() {
  811. let selectedNodes = this.selectionHandler.getSelectedNodes();
  812. let selectedEdges = this.selectionHandler.getSelectedEdges();
  813. let deleteFunction = undefined;
  814. if (selectedNodes.length > 0) {
  815. for (let i = 0; i < selectedNodes.length; i++) {
  816. if (this.body.nodes[selectedNodes[i]].isCluster === true) {
  817. alert("You cannot delete a cluster.");
  818. return;
  819. }
  820. }
  821. if (typeof this.options.handlerFunctions.deleteNode === 'function') {
  822. deleteFunction = this.options.handlerFunctions.deleteNode;
  823. }
  824. }
  825. else if (selectedEdges.length > 0) {
  826. if (typeof this.options.handlerFunctions.deleteEdge === 'function') {
  827. deleteFunction = this.options.handlerFunctions.deleteEdge;
  828. }
  829. }
  830. if (typeof deleteFunction === 'function') {
  831. let data = {nodes: selectedNodes, edges: selectedEdges};
  832. if (deleteFunction.length == 2) {
  833. deleteFunction(data, (finalizedData) => {
  834. this.body.data.edges.remove(finalizedData.edges);
  835. this.body.data.nodes.remove(finalizedData.nodes);
  836. this.body.emitter.emit("startSimulation");
  837. });
  838. }
  839. else {
  840. throw new Error('The function for delete does not support two arguments (data, callback)')
  841. }
  842. }
  843. else {
  844. this.body.data.edges.remove(selectedEdges);
  845. this.body.data.nodes.remove(selectedNodes);
  846. this.body.emitter.emit("startSimulation");
  847. }
  848. }
  849. getTargetNodeProperties(x,y) {
  850. return {
  851. id: 'targetNode' + util.randomUUID(),
  852. hidden: false,
  853. physics: false,
  854. shape:'dot',
  855. size:6,
  856. x:x,
  857. y:y,
  858. color: {background: '#ff0000', border: '#3c3c3c', highlight: {background: '#07f968'}},
  859. borderWidth: 2,
  860. borderWidthSelected: 2
  861. }
  862. }
  863. }
  864. export default ManipulationSystem;