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.

576 lines
20 KiB

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