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.

571 lines
19 KiB

  1. var util = require('../../util');
  2. /**
  3. * clears the toolbar div element of children
  4. *
  5. * @private
  6. */
  7. exports._clearManipulatorBar = function() {
  8. while (this.manipulationDiv.hasChildNodes()) {
  9. this.manipulationDiv.removeChild(this.manipulationDiv.firstChild);
  10. }
  11. };
  12. /**
  13. * Manipulation UI temporarily overloads certain functions to extend or replace them. To be able to restore
  14. * these functions to their original functionality, we saved them in this.cachedFunctions.
  15. * This function restores these functions to their original function.
  16. *
  17. * @private
  18. */
  19. exports._restoreOverloadedFunctions = function() {
  20. for (var functionName in this.cachedFunctions) {
  21. if (this.cachedFunctions.hasOwnProperty(functionName)) {
  22. this[functionName] = this.cachedFunctions[functionName];
  23. }
  24. }
  25. };
  26. /**
  27. * Enable or disable edit-mode.
  28. *
  29. * @private
  30. */
  31. exports._toggleEditMode = function() {
  32. this.editMode = !this.editMode;
  33. var toolbar = document.getElementById("network-manipulationDiv");
  34. var closeDiv = document.getElementById("network-manipulation-closeDiv");
  35. var editModeDiv = document.getElementById("network-manipulation-editMode");
  36. if (this.editMode == true) {
  37. toolbar.style.display="block";
  38. closeDiv.style.display="block";
  39. editModeDiv.style.display="none";
  40. closeDiv.onclick = this._toggleEditMode.bind(this);
  41. }
  42. else {
  43. toolbar.style.display="none";
  44. closeDiv.style.display="none";
  45. editModeDiv.style.display="block";
  46. closeDiv.onclick = null;
  47. }
  48. this._createManipulatorBar()
  49. };
  50. /**
  51. * main function, creates the main toolbar. Removes functions bound to the select event. Binds all the buttons of the toolbar.
  52. *
  53. * @private
  54. */
  55. exports._createManipulatorBar = function() {
  56. // remove bound functions
  57. if (this.boundFunction) {
  58. this.off('select', this.boundFunction);
  59. }
  60. if (this.edgeBeingEdited !== undefined) {
  61. this.edgeBeingEdited._disableControlNodes();
  62. this.edgeBeingEdited = undefined;
  63. this.selectedControlNode = null;
  64. }
  65. // restore overloaded functions
  66. this._restoreOverloadedFunctions();
  67. // resume calculation
  68. this.freezeSimulation = false;
  69. // reset global variables
  70. this.blockConnectingEdgeSelection = false;
  71. this.forceAppendSelection = false;
  72. if (this.editMode == true) {
  73. while (this.manipulationDiv.hasChildNodes()) {
  74. this.manipulationDiv.removeChild(this.manipulationDiv.firstChild);
  75. }
  76. // add the icons to the manipulator div
  77. this.manipulationDiv.innerHTML = "" +
  78. "<span class='network-manipulationUI add' id='network-manipulate-addNode'>" +
  79. "<span class='network-manipulationLabel'>"+this.constants.labels['add'] +"</span></span>" +
  80. "<div class='network-seperatorLine'></div>" +
  81. "<span class='network-manipulationUI connect' id='network-manipulate-connectNode'>" +
  82. "<span class='network-manipulationLabel'>"+this.constants.labels['link'] +"</span></span>";
  83. if (this._getSelectedNodeCount() == 1 && this.triggerFunctions.edit) {
  84. this.manipulationDiv.innerHTML += "" +
  85. "<div class='network-seperatorLine'></div>" +
  86. "<span class='network-manipulationUI edit' id='network-manipulate-editNode'>" +
  87. "<span class='network-manipulationLabel'>"+this.constants.labels['editNode'] +"</span></span>";
  88. }
  89. else if (this._getSelectedEdgeCount() == 1 && this._getSelectedNodeCount() == 0) {
  90. this.manipulationDiv.innerHTML += "" +
  91. "<div class='network-seperatorLine'></div>" +
  92. "<span class='network-manipulationUI edit' id='network-manipulate-editEdge'>" +
  93. "<span class='network-manipulationLabel'>"+this.constants.labels['editEdge'] +"</span></span>";
  94. }
  95. if (this._selectionIsEmpty() == false) {
  96. this.manipulationDiv.innerHTML += "" +
  97. "<div class='network-seperatorLine'></div>" +
  98. "<span class='network-manipulationUI delete' id='network-manipulate-delete'>" +
  99. "<span class='network-manipulationLabel'>"+this.constants.labels['del'] +"</span></span>";
  100. }
  101. // bind the icons
  102. var addNodeButton = document.getElementById("network-manipulate-addNode");
  103. addNodeButton.onclick = this._createAddNodeToolbar.bind(this);
  104. var addEdgeButton = document.getElementById("network-manipulate-connectNode");
  105. addEdgeButton.onclick = this._createAddEdgeToolbar.bind(this);
  106. if (this._getSelectedNodeCount() == 1 && this.triggerFunctions.edit) {
  107. var editButton = document.getElementById("network-manipulate-editNode");
  108. editButton.onclick = this._editNode.bind(this);
  109. }
  110. else if (this._getSelectedEdgeCount() == 1 && this._getSelectedNodeCount() == 0) {
  111. var editButton = document.getElementById("network-manipulate-editEdge");
  112. editButton.onclick = this._createEditEdgeToolbar.bind(this);
  113. }
  114. if (this._selectionIsEmpty() == false) {
  115. var deleteButton = document.getElementById("network-manipulate-delete");
  116. deleteButton.onclick = this._deleteSelected.bind(this);
  117. }
  118. var closeDiv = document.getElementById("network-manipulation-closeDiv");
  119. closeDiv.onclick = this._toggleEditMode.bind(this);
  120. this.boundFunction = this._createManipulatorBar.bind(this);
  121. this.on('select', this.boundFunction);
  122. }
  123. else {
  124. this.editModeDiv.innerHTML = "" +
  125. "<span class='network-manipulationUI edit editmode' id='network-manipulate-editModeButton'>" +
  126. "<span class='network-manipulationLabel'>" + this.constants.labels['edit'] + "</span></span>";
  127. var editModeButton = document.getElementById("network-manipulate-editModeButton");
  128. editModeButton.onclick = this._toggleEditMode.bind(this);
  129. }
  130. };
  131. /**
  132. * Create the toolbar for adding Nodes
  133. *
  134. * @private
  135. */
  136. exports._createAddNodeToolbar = function() {
  137. // clear the toolbar
  138. this._clearManipulatorBar();
  139. if (this.boundFunction) {
  140. this.off('select', this.boundFunction);
  141. }
  142. // create the toolbar contents
  143. this.manipulationDiv.innerHTML = "" +
  144. "<span class='network-manipulationUI back' id='network-manipulate-back'>" +
  145. "<span class='network-manipulationLabel'>" + this.constants.labels['back'] + " </span></span>" +
  146. "<div class='network-seperatorLine'></div>" +
  147. "<span class='network-manipulationUI none' id='network-manipulate-back'>" +
  148. "<span id='network-manipulatorLabel' class='network-manipulationLabel'>" + this.constants.labels['addDescription'] + "</span></span>";
  149. // bind the icon
  150. var backButton = document.getElementById("network-manipulate-back");
  151. backButton.onclick = this._createManipulatorBar.bind(this);
  152. // we use the boundFunction so we can reference it when we unbind it from the "select" event.
  153. this.boundFunction = this._addNode.bind(this);
  154. this.on('select', this.boundFunction);
  155. };
  156. /**
  157. * create the toolbar to connect nodes
  158. *
  159. * @private
  160. */
  161. exports._createAddEdgeToolbar = function() {
  162. // clear the toolbar
  163. this._clearManipulatorBar();
  164. this._unselectAll(true);
  165. this.freezeSimulation = true;
  166. if (this.boundFunction) {
  167. this.off('select', this.boundFunction);
  168. }
  169. this._unselectAll();
  170. this.forceAppendSelection = false;
  171. this.blockConnectingEdgeSelection = true;
  172. this.manipulationDiv.innerHTML = "" +
  173. "<span class='network-manipulationUI back' id='network-manipulate-back'>" +
  174. "<span class='network-manipulationLabel'>" + this.constants.labels['back'] + " </span></span>" +
  175. "<div class='network-seperatorLine'></div>" +
  176. "<span class='network-manipulationUI none' id='network-manipulate-back'>" +
  177. "<span id='network-manipulatorLabel' class='network-manipulationLabel'>" + this.constants.labels['linkDescription'] + "</span></span>";
  178. // bind the icon
  179. var backButton = document.getElementById("network-manipulate-back");
  180. backButton.onclick = this._createManipulatorBar.bind(this);
  181. // we use the boundFunction so we can reference it when we unbind it from the "select" event.
  182. this.boundFunction = this._handleConnect.bind(this);
  183. this.on('select', this.boundFunction);
  184. // temporarily overload functions
  185. this.cachedFunctions["_handleTouch"] = this._handleTouch;
  186. this.cachedFunctions["_handleOnRelease"] = this._handleOnRelease;
  187. this._handleTouch = this._handleConnect;
  188. this._handleOnRelease = this._finishConnect;
  189. // redraw to show the unselect
  190. this._redraw();
  191. };
  192. /**
  193. * create the toolbar to edit edges
  194. *
  195. * @private
  196. */
  197. exports._createEditEdgeToolbar = function() {
  198. // clear the toolbar
  199. this._clearManipulatorBar();
  200. if (this.boundFunction) {
  201. this.off('select', this.boundFunction);
  202. }
  203. this.edgeBeingEdited = this._getSelectedEdge();
  204. this.edgeBeingEdited._enableControlNodes();
  205. this.manipulationDiv.innerHTML = "" +
  206. "<span class='network-manipulationUI back' id='network-manipulate-back'>" +
  207. "<span class='network-manipulationLabel'>" + this.constants.labels['back'] + " </span></span>" +
  208. "<div class='network-seperatorLine'></div>" +
  209. "<span class='network-manipulationUI none' id='network-manipulate-back'>" +
  210. "<span id='network-manipulatorLabel' class='network-manipulationLabel'>" + this.constants.labels['editEdgeDescription'] + "</span></span>";
  211. // bind the icon
  212. var backButton = document.getElementById("network-manipulate-back");
  213. backButton.onclick = this._createManipulatorBar.bind(this);
  214. // temporarily overload functions
  215. this.cachedFunctions["_handleTouch"] = this._handleTouch;
  216. this.cachedFunctions["_handleOnRelease"] = this._handleOnRelease;
  217. this.cachedFunctions["_handleTap"] = this._handleTap;
  218. this.cachedFunctions["_handleDragStart"] = this._handleDragStart;
  219. this.cachedFunctions["_handleOnDrag"] = this._handleOnDrag;
  220. this._handleTouch = this._selectControlNode;
  221. this._handleTap = function () {};
  222. this._handleOnDrag = this._controlNodeDrag;
  223. this._handleDragStart = function () {}
  224. this._handleOnRelease = this._releaseControlNode;
  225. // redraw to show the unselect
  226. this._redraw();
  227. };
  228. /**
  229. * the function bound to the selection event. It checks if you want to connect a cluster and changes the description
  230. * to walk the user through the process.
  231. *
  232. * @private
  233. */
  234. exports._selectControlNode = function(pointer) {
  235. this.edgeBeingEdited.controlNodes.from.unselect();
  236. this.edgeBeingEdited.controlNodes.to.unselect();
  237. this.selectedControlNode = this.edgeBeingEdited._getSelectedControlNode(this._XconvertDOMtoCanvas(pointer.x),this._YconvertDOMtoCanvas(pointer.y));
  238. if (this.selectedControlNode !== null) {
  239. this.selectedControlNode.select();
  240. this.freezeSimulation = true;
  241. }
  242. this._redraw();
  243. };
  244. /**
  245. * the function bound to the selection event. It checks if you want to connect a cluster and changes the description
  246. * to walk the user through the process.
  247. *
  248. * @private
  249. */
  250. exports._controlNodeDrag = function(event) {
  251. var pointer = this._getPointer(event.gesture.center);
  252. if (this.selectedControlNode !== null && this.selectedControlNode !== undefined) {
  253. this.selectedControlNode.x = this._XconvertDOMtoCanvas(pointer.x);
  254. this.selectedControlNode.y = this._YconvertDOMtoCanvas(pointer.y);
  255. }
  256. this._redraw();
  257. };
  258. exports._releaseControlNode = function(pointer) {
  259. var newNode = this._getNodeAt(pointer);
  260. if (newNode != null) {
  261. if (this.edgeBeingEdited.controlNodes.from.selected == true) {
  262. this._editEdge(newNode.id, this.edgeBeingEdited.to.id);
  263. this.edgeBeingEdited.controlNodes.from.unselect();
  264. }
  265. if (this.edgeBeingEdited.controlNodes.to.selected == true) {
  266. this._editEdge(this.edgeBeingEdited.from.id, newNode.id);
  267. this.edgeBeingEdited.controlNodes.to.unselect();
  268. }
  269. }
  270. else {
  271. this.edgeBeingEdited._restoreControlNodes();
  272. }
  273. this.freezeSimulation = false;
  274. this._redraw();
  275. };
  276. /**
  277. * the function bound to the selection event. It checks if you want to connect a cluster and changes the description
  278. * to walk the user through the process.
  279. *
  280. * @private
  281. */
  282. exports._handleConnect = function(pointer) {
  283. if (this._getSelectedNodeCount() == 0) {
  284. var node = this._getNodeAt(pointer);
  285. if (node != null) {
  286. if (node.clusterSize > 1) {
  287. alert("Cannot create edges to a cluster.")
  288. }
  289. else {
  290. this._selectObject(node,false);
  291. // create a node the temporary line can look at
  292. this.sectors['support']['nodes']['targetNode'] = new Node({id:'targetNode'},{},{},this.constants);
  293. this.sectors['support']['nodes']['targetNode'].x = node.x;
  294. this.sectors['support']['nodes']['targetNode'].y = node.y;
  295. this.sectors['support']['nodes']['targetViaNode'] = new Node({id:'targetViaNode'},{},{},this.constants);
  296. this.sectors['support']['nodes']['targetViaNode'].x = node.x;
  297. this.sectors['support']['nodes']['targetViaNode'].y = node.y;
  298. this.sectors['support']['nodes']['targetViaNode'].parentEdgeId = "connectionEdge";
  299. // create a temporary edge
  300. this.edges['connectionEdge'] = new Edge({id:"connectionEdge",from:node.id,to:this.sectors['support']['nodes']['targetNode'].id}, this, this.constants);
  301. this.edges['connectionEdge'].from = node;
  302. this.edges['connectionEdge'].connected = true;
  303. this.edges['connectionEdge'].smooth = true;
  304. this.edges['connectionEdge'].selected = true;
  305. this.edges['connectionEdge'].to = this.sectors['support']['nodes']['targetNode'];
  306. this.edges['connectionEdge'].via = this.sectors['support']['nodes']['targetViaNode'];
  307. this.cachedFunctions["_handleOnDrag"] = this._handleOnDrag;
  308. this._handleOnDrag = function(event) {
  309. var pointer = this._getPointer(event.gesture.center);
  310. this.sectors['support']['nodes']['targetNode'].x = this._XconvertDOMtoCanvas(pointer.x);
  311. this.sectors['support']['nodes']['targetNode'].y = this._YconvertDOMtoCanvas(pointer.y);
  312. this.sectors['support']['nodes']['targetViaNode'].x = 0.5 * (this._XconvertDOMtoCanvas(pointer.x) + this.edges['connectionEdge'].from.x);
  313. this.sectors['support']['nodes']['targetViaNode'].y = this._YconvertDOMtoCanvas(pointer.y);
  314. };
  315. this.moving = true;
  316. this.start();
  317. }
  318. }
  319. }
  320. };
  321. exports._finishConnect = function(pointer) {
  322. if (this._getSelectedNodeCount() == 1) {
  323. // restore the drag function
  324. this._handleOnDrag = this.cachedFunctions["_handleOnDrag"];
  325. delete this.cachedFunctions["_handleOnDrag"];
  326. // remember the edge id
  327. var connectFromId = this.edges['connectionEdge'].fromId;
  328. // remove the temporary nodes and edge
  329. delete this.edges['connectionEdge'];
  330. delete this.sectors['support']['nodes']['targetNode'];
  331. delete this.sectors['support']['nodes']['targetViaNode'];
  332. var node = this._getNodeAt(pointer);
  333. if (node != null) {
  334. if (node.clusterSize > 1) {
  335. alert("Cannot create edges to a cluster.")
  336. }
  337. else {
  338. this._createEdge(connectFromId,node.id);
  339. this._createManipulatorBar();
  340. }
  341. }
  342. this._unselectAll();
  343. }
  344. };
  345. /**
  346. * Adds a node on the specified location
  347. */
  348. exports._addNode = function() {
  349. if (this._selectionIsEmpty() && this.editMode == true) {
  350. var positionObject = this._pointerToPositionObject(this.pointerPosition);
  351. var defaultData = {id:util.randomUUID(),x:positionObject.left,y:positionObject.top,label:"new",allowedToMoveX:true,allowedToMoveY:true};
  352. if (this.triggerFunctions.add) {
  353. if (this.triggerFunctions.add.length == 2) {
  354. var me = this;
  355. this.triggerFunctions.add(defaultData, function(finalizedData) {
  356. me.nodesData.add(finalizedData);
  357. me._createManipulatorBar();
  358. me.moving = true;
  359. me.start();
  360. });
  361. }
  362. else {
  363. alert(this.constants.labels['addError']);
  364. this._createManipulatorBar();
  365. this.moving = true;
  366. this.start();
  367. }
  368. }
  369. else {
  370. this.nodesData.add(defaultData);
  371. this._createManipulatorBar();
  372. this.moving = true;
  373. this.start();
  374. }
  375. }
  376. };
  377. /**
  378. * connect two nodes with a new edge.
  379. *
  380. * @private
  381. */
  382. exports._createEdge = function(sourceNodeId,targetNodeId) {
  383. if (this.editMode == true) {
  384. var defaultData = {from:sourceNodeId, to:targetNodeId};
  385. if (this.triggerFunctions.connect) {
  386. if (this.triggerFunctions.connect.length == 2) {
  387. var me = this;
  388. this.triggerFunctions.connect(defaultData, function(finalizedData) {
  389. me.edgesData.add(finalizedData);
  390. me.moving = true;
  391. me.start();
  392. });
  393. }
  394. else {
  395. alert(this.constants.labels["linkError"]);
  396. this.moving = true;
  397. this.start();
  398. }
  399. }
  400. else {
  401. this.edgesData.add(defaultData);
  402. this.moving = true;
  403. this.start();
  404. }
  405. }
  406. };
  407. /**
  408. * connect two nodes with a new edge.
  409. *
  410. * @private
  411. */
  412. exports._editEdge = function(sourceNodeId,targetNodeId) {
  413. if (this.editMode == true) {
  414. var defaultData = {id: this.edgeBeingEdited.id, from:sourceNodeId, to:targetNodeId};
  415. if (this.triggerFunctions.editEdge) {
  416. if (this.triggerFunctions.editEdge.length == 2) {
  417. var me = this;
  418. this.triggerFunctions.editEdge(defaultData, function(finalizedData) {
  419. me.edgesData.update(finalizedData);
  420. me.moving = true;
  421. me.start();
  422. });
  423. }
  424. else {
  425. alert(this.constants.labels["linkError"]);
  426. this.moving = true;
  427. this.start();
  428. }
  429. }
  430. else {
  431. this.edgesData.update(defaultData);
  432. this.moving = true;
  433. this.start();
  434. }
  435. }
  436. };
  437. /**
  438. * Create the toolbar to edit the selected node. The label and the color can be changed. Other colors are derived from the chosen color.
  439. *
  440. * @private
  441. */
  442. exports._editNode = function() {
  443. if (this.triggerFunctions.edit && this.editMode == true) {
  444. var node = this._getSelectedNode();
  445. var data = {id:node.id,
  446. label: node.label,
  447. group: node.group,
  448. shape: node.shape,
  449. color: {
  450. background:node.color.background,
  451. border:node.color.border,
  452. highlight: {
  453. background:node.color.highlight.background,
  454. border:node.color.highlight.border
  455. }
  456. }};
  457. if (this.triggerFunctions.edit.length == 2) {
  458. var me = this;
  459. this.triggerFunctions.edit(data, function (finalizedData) {
  460. me.nodesData.update(finalizedData);
  461. me._createManipulatorBar();
  462. me.moving = true;
  463. me.start();
  464. });
  465. }
  466. else {
  467. alert(this.constants.labels["editError"]);
  468. }
  469. }
  470. else {
  471. alert(this.constants.labels["editBoundError"]);
  472. }
  473. };
  474. /**
  475. * delete everything in the selection
  476. *
  477. * @private
  478. */
  479. exports._deleteSelected = function() {
  480. if (!this._selectionIsEmpty() && this.editMode == true) {
  481. if (!this._clusterInSelection()) {
  482. var selectedNodes = this.getSelectedNodes();
  483. var selectedEdges = this.getSelectedEdges();
  484. if (this.triggerFunctions.del) {
  485. var me = this;
  486. var data = {nodes: selectedNodes, edges: selectedEdges};
  487. if (this.triggerFunctions.del.length = 2) {
  488. this.triggerFunctions.del(data, function (finalizedData) {
  489. me.edgesData.remove(finalizedData.edges);
  490. me.nodesData.remove(finalizedData.nodes);
  491. me._unselectAll();
  492. me.moving = true;
  493. me.start();
  494. });
  495. }
  496. else {
  497. alert(this.constants.labels["deleteError"])
  498. }
  499. }
  500. else {
  501. this.edgesData.remove(selectedEdges);
  502. this.nodesData.remove(selectedNodes);
  503. this._unselectAll();
  504. this.moving = true;
  505. this.start();
  506. }
  507. }
  508. else {
  509. alert(this.constants.labels["deleteClusterError"]);
  510. }
  511. }
  512. };