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.

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