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.

724 lines
34 KiB

  1. var util = require('../../../util');
  2. var RepulsionMixin = require('./RepulsionMixin');
  3. var HierarchialRepulsionMixin = require('./HierarchialRepulsionMixin');
  4. var BarnesHutMixin = require('./BarnesHutMixin');
  5. /**
  6. * Toggling barnes Hut calculation on and off.
  7. *
  8. * @private
  9. */
  10. exports._toggleBarnesHut = function () {
  11. this.constants.physics.barnesHut.enabled = !this.constants.physics.barnesHut.enabled;
  12. this._loadSelectedForceSolver();
  13. this.moving = true;
  14. this.start();
  15. };
  16. /**
  17. * This loads the node force solver based on the barnes hut or repulsion algorithm
  18. *
  19. * @private
  20. */
  21. exports._loadSelectedForceSolver = function () {
  22. // this overloads the this._calculateNodeForces
  23. if (this.constants.physics.barnesHut.enabled == true) {
  24. this._clearMixin(RepulsionMixin);
  25. this._clearMixin(HierarchialRepulsionMixin);
  26. this.constants.physics.centralGravity = this.constants.physics.barnesHut.centralGravity;
  27. this.constants.physics.springLength = this.constants.physics.barnesHut.springLength;
  28. this.constants.physics.springConstant = this.constants.physics.barnesHut.springConstant;
  29. this.constants.physics.damping = this.constants.physics.barnesHut.damping;
  30. this._loadMixin(BarnesHutMixin);
  31. }
  32. else if (this.constants.physics.hierarchicalRepulsion.enabled == true) {
  33. this._clearMixin(BarnesHutMixin);
  34. this._clearMixin(RepulsionMixin);
  35. this.constants.physics.centralGravity = this.constants.physics.hierarchicalRepulsion.centralGravity;
  36. this.constants.physics.springLength = this.constants.physics.hierarchicalRepulsion.springLength;
  37. this.constants.physics.springConstant = this.constants.physics.hierarchicalRepulsion.springConstant;
  38. this.constants.physics.damping = this.constants.physics.hierarchicalRepulsion.damping;
  39. this._loadMixin(HierarchialRepulsionMixin);
  40. }
  41. else {
  42. this._clearMixin(BarnesHutMixin);
  43. this._clearMixin(HierarchialRepulsionMixin);
  44. this.barnesHutTree = undefined;
  45. this.constants.physics.centralGravity = this.constants.physics.repulsion.centralGravity;
  46. this.constants.physics.springLength = this.constants.physics.repulsion.springLength;
  47. this.constants.physics.springConstant = this.constants.physics.repulsion.springConstant;
  48. this.constants.physics.damping = this.constants.physics.repulsion.damping;
  49. this._loadMixin(RepulsionMixin);
  50. }
  51. };
  52. /**
  53. * Before calculating the forces, we check if we need to cluster to keep up performance and we check
  54. * if there is more than one node. If it is just one node, we dont calculate anything.
  55. *
  56. * @private
  57. */
  58. exports._initializeForceCalculation = function () {
  59. // stop calculation if there is only one node
  60. if (this.nodeIndices.length == 1) {
  61. this.nodes[this.nodeIndices[0]]._setForce(0, 0);
  62. }
  63. else {
  64. // if there are too many nodes on screen, we cluster without repositioning
  65. if (this.nodeIndices.length > this.constants.clustering.clusterThreshold && this.constants.clustering.enabled == true) {
  66. this.clusterToFit(this.constants.clustering.reduceToNodes, false);
  67. }
  68. // we now start the force calculation
  69. this._calculateForces();
  70. }
  71. };
  72. /**
  73. * Calculate the external forces acting on the nodes
  74. * Forces are caused by: edges, repulsing forces between nodes, gravity
  75. * @private
  76. */
  77. exports._calculateForces = function () {
  78. // Gravity is required to keep separated groups from floating off
  79. // the forces are reset to zero in this loop by using _setForce instead
  80. // of _addForce
  81. this._calculateGravitationalForces();
  82. this._calculateNodeForces();
  83. if (this.constants.physics.springConstant > 0) {
  84. if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
  85. this._calculateSpringForcesWithSupport();
  86. }
  87. else {
  88. if (this.constants.physics.hierarchicalRepulsion.enabled == true) {
  89. this._calculateHierarchicalSpringForces();
  90. }
  91. else {
  92. this._calculateSpringForces();
  93. }
  94. }
  95. }
  96. };
  97. /**
  98. * Smooth curves are created by adding invisible nodes in the center of the edges. These nodes are also
  99. * handled in the calculateForces function. We then use a quadratic curve with the center node as control.
  100. * This function joins the datanodes and invisible (called support) nodes into one object.
  101. * We do this so we do not contaminate this.nodes with the support nodes.
  102. *
  103. * @private
  104. */
  105. exports._updateCalculationNodes = function () {
  106. if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
  107. this.calculationNodes = {};
  108. this.calculationNodeIndices = [];
  109. for (var nodeId in this.nodes) {
  110. if (this.nodes.hasOwnProperty(nodeId)) {
  111. this.calculationNodes[nodeId] = this.nodes[nodeId];
  112. }
  113. }
  114. var supportNodes = this.sectors['support']['nodes'];
  115. for (var supportNodeId in supportNodes) {
  116. if (supportNodes.hasOwnProperty(supportNodeId)) {
  117. if (this.edges.hasOwnProperty(supportNodes[supportNodeId].parentEdgeId)) {
  118. this.calculationNodes[supportNodeId] = supportNodes[supportNodeId];
  119. }
  120. else {
  121. supportNodes[supportNodeId]._setForce(0, 0);
  122. }
  123. }
  124. }
  125. for (var idx in this.calculationNodes) {
  126. if (this.calculationNodes.hasOwnProperty(idx)) {
  127. this.calculationNodeIndices.push(idx);
  128. }
  129. }
  130. }
  131. else {
  132. this.calculationNodes = this.nodes;
  133. this.calculationNodeIndices = this.nodeIndices;
  134. }
  135. };
  136. /**
  137. * this function applies the central gravity effect to keep groups from floating off
  138. *
  139. * @private
  140. */
  141. exports._calculateGravitationalForces = function () {
  142. var dx, dy, distance, node, i;
  143. var nodes = this.calculationNodes;
  144. var gravity = this.constants.physics.centralGravity;
  145. var gravityForce = 0;
  146. for (i = 0; i < this.calculationNodeIndices.length; i++) {
  147. node = nodes[this.calculationNodeIndices[i]];
  148. node.damping = this.constants.physics.damping; // possibly add function to alter damping properties of clusters.
  149. // gravity does not apply when we are in a pocket sector
  150. if (this._sector() == "default" && gravity != 0) {
  151. dx = -node.x;
  152. dy = -node.y;
  153. distance = Math.sqrt(dx * dx + dy * dy);
  154. gravityForce = (distance == 0) ? 0 : (gravity / distance);
  155. node.fx = dx * gravityForce;
  156. node.fy = dy * gravityForce;
  157. }
  158. else {
  159. node.fx = 0;
  160. node.fy = 0;
  161. }
  162. }
  163. };
  164. /**
  165. * this function calculates the effects of the springs in the case of unsmooth curves.
  166. *
  167. * @private
  168. */
  169. exports._calculateSpringForces = function () {
  170. var edgeLength, edge, edgeId;
  171. var dx, dy, fx, fy, springForce, distance;
  172. var edges = this.edges;
  173. // forces caused by the edges, modelled as springs
  174. for (edgeId in edges) {
  175. if (edges.hasOwnProperty(edgeId)) {
  176. edge = edges[edgeId];
  177. if (edge.connected) {
  178. // only calculate forces if nodes are in the same sector
  179. if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) {
  180. edgeLength = edge.physics.springLength;
  181. // this implies that the edges between big clusters are longer
  182. edgeLength += (edge.to.clusterSize + edge.from.clusterSize - 2) * this.constants.clustering.edgeGrowth;
  183. dx = (edge.from.x - edge.to.x);
  184. dy = (edge.from.y - edge.to.y);
  185. distance = Math.sqrt(dx * dx + dy * dy);
  186. if (distance == 0) {
  187. distance = 0.01;
  188. }
  189. // the 1/distance is so the fx and fy can be calculated without sine or cosine.
  190. springForce = this.constants.physics.springConstant * (edgeLength - distance) / distance;
  191. fx = dx * springForce;
  192. fy = dy * springForce;
  193. edge.from.fx += fx;
  194. edge.from.fy += fy;
  195. edge.to.fx -= fx;
  196. edge.to.fy -= fy;
  197. }
  198. }
  199. }
  200. }
  201. };
  202. /**
  203. * This function calculates the springforces on the nodes, accounting for the support nodes.
  204. *
  205. * @private
  206. */
  207. exports._calculateSpringForcesWithSupport = function () {
  208. var edgeLength, edge, edgeId, combinedClusterSize;
  209. var edges = this.edges;
  210. // forces caused by the edges, modelled as springs
  211. for (edgeId in edges) {
  212. if (edges.hasOwnProperty(edgeId)) {
  213. edge = edges[edgeId];
  214. if (edge.connected) {
  215. // only calculate forces if nodes are in the same sector
  216. if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) {
  217. if (edge.via != null) {
  218. var node1 = edge.to;
  219. var node2 = edge.via;
  220. var node3 = edge.from;
  221. edgeLength = edge.physics.springLength;
  222. combinedClusterSize = node1.clusterSize + node3.clusterSize - 2;
  223. // this implies that the edges between big clusters are longer
  224. edgeLength += combinedClusterSize * this.constants.clustering.edgeGrowth;
  225. this._calculateSpringForce(node1, node2, 0.5 * edgeLength);
  226. this._calculateSpringForce(node2, node3, 0.5 * edgeLength);
  227. }
  228. }
  229. }
  230. }
  231. }
  232. };
  233. /**
  234. * This is the code actually performing the calculation for the function above. It is split out to avoid repetition.
  235. *
  236. * @param node1
  237. * @param node2
  238. * @param edgeLength
  239. * @private
  240. */
  241. exports._calculateSpringForce = function (node1, node2, edgeLength) {
  242. var dx, dy, fx, fy, springForce, distance;
  243. dx = (node1.x - node2.x);
  244. dy = (node1.y - node2.y);
  245. distance = Math.sqrt(dx * dx + dy * dy);
  246. if (distance == 0) {
  247. distance = 0.01;
  248. }
  249. // the 1/distance is so the fx and fy can be calculated without sine or cosine.
  250. springForce = this.constants.physics.springConstant * (edgeLength - distance) / distance;
  251. fx = dx * springForce;
  252. fy = dy * springForce;
  253. node1.fx += fx;
  254. node1.fy += fy;
  255. node2.fx -= fx;
  256. node2.fy -= fy;
  257. };
  258. exports._cleanupPhysicsConfiguration = function() {
  259. if (this.physicsConfiguration !== undefined) {
  260. while (this.physicsConfiguration.hasChildNodes()) {
  261. this.physicsConfiguration.removeChild(this.physicsConfiguration.firstChild);
  262. }
  263. this.physicsConfiguration.parentNode.removeChild(this.physicsConfiguration);
  264. this.physicsConfiguration = undefined;
  265. }
  266. }
  267. /**
  268. * Load the HTML for the physics config and bind it
  269. * @private
  270. */
  271. exports._loadPhysicsConfiguration = function () {
  272. if (this.physicsConfiguration === undefined) {
  273. this.backupConstants = {};
  274. util.deepExtend(this.backupConstants,this.constants);
  275. var maxGravitational = Math.max(20000, (-1 * this.constants.physics.barnesHut.gravitationalConstant) * 10);
  276. var maxSpring = Math.min(0.05, this.constants.physics.barnesHut.springConstant * 10)
  277. var hierarchicalLayoutDirections = ["LR", "RL", "UD", "DU"];
  278. this.physicsConfiguration = document.createElement('div');
  279. this.physicsConfiguration.className = "PhysicsConfiguration";
  280. this.physicsConfiguration.innerHTML = '' +
  281. '<table><tr><td><b>Simulation Mode:</b></td></tr>' +
  282. '<tr>' +
  283. '<td width="120px"><input type="radio" name="graph_physicsMethod" id="graph_physicsMethod1" value="BH" checked="checked">Barnes Hut</td>' +
  284. '<td width="120px"><input type="radio" name="graph_physicsMethod" id="graph_physicsMethod2" value="R">Repulsion</td>' +
  285. '<td width="120px"><input type="radio" name="graph_physicsMethod" id="graph_physicsMethod3" value="H">Hierarchical</td>' +
  286. '</tr>' +
  287. '</table>' +
  288. '<table id="graph_BH_table" style="display:none">' +
  289. '<tr><td><b>Barnes Hut</b></td></tr>' +
  290. '<tr>' +
  291. '<td width="150px">gravitationalConstant</td><td>0</td><td><input type="range" min="0" max="'+maxGravitational+'" value="' + (-1 * this.constants.physics.barnesHut.gravitationalConstant) + '" step="25" style="width:300px" id="graph_BH_gc"></td><td width="50px">-'+maxGravitational+'</td><td><input value="' + (this.constants.physics.barnesHut.gravitationalConstant) + '" id="graph_BH_gc_value" style="width:60px"></td>' +
  292. '</tr>' +
  293. '<tr>' +
  294. '<td width="150px">centralGravity</td><td>0</td><td><input type="range" min="0" max="6" value="' + this.constants.physics.barnesHut.centralGravity + '" step="0.05" style="width:300px" id="graph_BH_cg"></td><td>3</td><td><input value="' + this.constants.physics.barnesHut.centralGravity + '" id="graph_BH_cg_value" style="width:60px"></td>' +
  295. '</tr>' +
  296. '<tr>' +
  297. '<td width="150px">springLength</td><td>0</td><td><input type="range" min="0" max="500" value="' + this.constants.physics.barnesHut.springLength + '" step="1" style="width:300px" id="graph_BH_sl"></td><td>500</td><td><input value="' + this.constants.physics.barnesHut.springLength + '" id="graph_BH_sl_value" style="width:60px"></td>' +
  298. '</tr>' +
  299. '<tr>' +
  300. '<td width="150px">springConstant</td><td>0</td><td><input type="range" min="0" max="'+maxSpring+'" value="' + this.constants.physics.barnesHut.springConstant + '" step="0.0001" style="width:300px" id="graph_BH_sc"></td><td>'+maxSpring+'</td><td><input value="' + this.constants.physics.barnesHut.springConstant + '" id="graph_BH_sc_value" style="width:60px"></td>' +
  301. '</tr>' +
  302. '<tr>' +
  303. '<td width="150px">damping</td><td>0</td><td><input type="range" min="0" max="0.3" value="' + this.constants.physics.barnesHut.damping + '" step="0.005" style="width:300px" id="graph_BH_damp"></td><td>0.3</td><td><input value="' + this.constants.physics.barnesHut.damping + '" id="graph_BH_damp_value" style="width:60px"></td>' +
  304. '</tr>' +
  305. '</table>' +
  306. '<table id="graph_R_table" style="display:none">' +
  307. '<tr><td><b>Repulsion</b></td></tr>' +
  308. '<tr>' +
  309. '<td width="150px">nodeDistance</td><td>0</td><td><input type="range" min="0" max="300" value="' + this.constants.physics.repulsion.nodeDistance + '" step="1" style="width:300px" id="graph_R_nd"></td><td width="50px">300</td><td><input value="' + this.constants.physics.repulsion.nodeDistance + '" id="graph_R_nd_value" style="width:60px"></td>' +
  310. '</tr>' +
  311. '<tr>' +
  312. '<td width="150px">centralGravity</td><td>0</td><td><input type="range" min="0" max="3" value="' + this.constants.physics.repulsion.centralGravity + '" step="0.05" style="width:300px" id="graph_R_cg"></td><td>3</td><td><input value="' + this.constants.physics.repulsion.centralGravity + '" id="graph_R_cg_value" style="width:60px"></td>' +
  313. '</tr>' +
  314. '<tr>' +
  315. '<td width="150px">springLength</td><td>0</td><td><input type="range" min="0" max="500" value="' + this.constants.physics.repulsion.springLength + '" step="1" style="width:300px" id="graph_R_sl"></td><td>500</td><td><input value="' + this.constants.physics.repulsion.springLength + '" id="graph_R_sl_value" style="width:60px"></td>' +
  316. '</tr>' +
  317. '<tr>' +
  318. '<td width="150px">springConstant</td><td>0</td><td><input type="range" min="0" max="0.5" value="' + this.constants.physics.repulsion.springConstant + '" step="0.001" style="width:300px" id="graph_R_sc"></td><td>0.5</td><td><input value="' + this.constants.physics.repulsion.springConstant + '" id="graph_R_sc_value" style="width:60px"></td>' +
  319. '</tr>' +
  320. '<tr>' +
  321. '<td width="150px">damping</td><td>0</td><td><input type="range" min="0" max="0.3" value="' + this.constants.physics.repulsion.damping + '" step="0.005" style="width:300px" id="graph_R_damp"></td><td>0.3</td><td><input value="' + this.constants.physics.repulsion.damping + '" id="graph_R_damp_value" style="width:60px"></td>' +
  322. '</tr>' +
  323. '</table>' +
  324. '<table id="graph_H_table" style="display:none">' +
  325. '<tr><td width="150"><b>Hierarchical</b></td></tr>' +
  326. '<tr>' +
  327. '<td width="150px">nodeDistance</td><td>0</td><td><input type="range" min="0" max="300" value="' + this.constants.physics.hierarchicalRepulsion.nodeDistance + '" step="1" style="width:300px" id="graph_H_nd"></td><td width="50px">300</td><td><input value="' + this.constants.physics.hierarchicalRepulsion.nodeDistance + '" id="graph_H_nd_value" style="width:60px"></td>' +
  328. '</tr>' +
  329. '<tr>' +
  330. '<td width="150px">centralGravity</td><td>0</td><td><input type="range" min="0" max="3" value="' + this.constants.physics.hierarchicalRepulsion.centralGravity + '" step="0.05" style="width:300px" id="graph_H_cg"></td><td>3</td><td><input value="' + this.constants.physics.hierarchicalRepulsion.centralGravity + '" id="graph_H_cg_value" style="width:60px"></td>' +
  331. '</tr>' +
  332. '<tr>' +
  333. '<td width="150px">springLength</td><td>0</td><td><input type="range" min="0" max="500" value="' + this.constants.physics.hierarchicalRepulsion.springLength + '" step="1" style="width:300px" id="graph_H_sl"></td><td>500</td><td><input value="' + this.constants.physics.hierarchicalRepulsion.springLength + '" id="graph_H_sl_value" style="width:60px"></td>' +
  334. '</tr>' +
  335. '<tr>' +
  336. '<td width="150px">springConstant</td><td>0</td><td><input type="range" min="0" max="0.5" value="' + this.constants.physics.hierarchicalRepulsion.springConstant + '" step="0.001" style="width:300px" id="graph_H_sc"></td><td>0.5</td><td><input value="' + this.constants.physics.hierarchicalRepulsion.springConstant + '" id="graph_H_sc_value" style="width:60px"></td>' +
  337. '</tr>' +
  338. '<tr>' +
  339. '<td width="150px">damping</td><td>0</td><td><input type="range" min="0" max="0.3" value="' + this.constants.physics.hierarchicalRepulsion.damping + '" step="0.005" style="width:300px" id="graph_H_damp"></td><td>0.3</td><td><input value="' + this.constants.physics.hierarchicalRepulsion.damping + '" id="graph_H_damp_value" style="width:60px"></td>' +
  340. '</tr>' +
  341. '<tr>' +
  342. '<td width="150px">direction</td><td>1</td><td><input type="range" min="0" max="3" value="' + hierarchicalLayoutDirections.indexOf(this.constants.hierarchicalLayout.direction) + '" step="1" style="width:300px" id="graph_H_direction"></td><td>4</td><td><input value="' + this.constants.hierarchicalLayout.direction + '" id="graph_H_direction_value" style="width:60px"></td>' +
  343. '</tr>' +
  344. '<tr>' +
  345. '<td width="150px">levelSeparation</td><td>1</td><td><input type="range" min="0" max="500" value="' + this.constants.hierarchicalLayout.levelSeparation + '" step="1" style="width:300px" id="graph_H_levsep"></td><td>500</td><td><input value="' + this.constants.hierarchicalLayout.levelSeparation + '" id="graph_H_levsep_value" style="width:60px"></td>' +
  346. '</tr>' +
  347. '<tr>' +
  348. '<td width="150px">nodeSpacing</td><td>1</td><td><input type="range" min="0" max="500" value="' + this.constants.hierarchicalLayout.nodeSpacing + '" step="1" style="width:300px" id="graph_H_nspac"></td><td>500</td><td><input value="' + this.constants.hierarchicalLayout.nodeSpacing + '" id="graph_H_nspac_value" style="width:60px"></td>' +
  349. '</tr>' +
  350. '</table>' +
  351. '<table><tr><td><b>Options:</b></td></tr>' +
  352. '<tr>' +
  353. '<td width="180px"><input type="button" id="graph_toggleSmooth" value="Toggle smoothCurves" style="width:150px"></td>' +
  354. '<td width="180px"><input type="button" id="graph_repositionNodes" value="Reinitialize" style="width:150px"></td>' +
  355. '<td width="180px"><input type="button" id="graph_generateOptions" value="Generate Options" style="width:150px"></td>' +
  356. '</tr>' +
  357. '</table>'
  358. this.containerElement.parentElement.insertBefore(this.physicsConfiguration, this.containerElement);
  359. this.optionsDiv = document.createElement("div");
  360. this.optionsDiv.style.fontSize = "14px";
  361. this.optionsDiv.style.fontFamily = "verdana";
  362. this.containerElement.parentElement.insertBefore(this.optionsDiv, this.containerElement);
  363. var rangeElement;
  364. rangeElement = document.getElementById('graph_BH_gc');
  365. rangeElement.onchange = showValueOfRange.bind(this, 'graph_BH_gc', -1, "physics_barnesHut_gravitationalConstant");
  366. rangeElement = document.getElementById('graph_BH_cg');
  367. rangeElement.onchange = showValueOfRange.bind(this, 'graph_BH_cg', 1, "physics_centralGravity");
  368. rangeElement = document.getElementById('graph_BH_sc');
  369. rangeElement.onchange = showValueOfRange.bind(this, 'graph_BH_sc', 1, "physics_springConstant");
  370. rangeElement = document.getElementById('graph_BH_sl');
  371. rangeElement.onchange = showValueOfRange.bind(this, 'graph_BH_sl', 1, "physics_springLength");
  372. rangeElement = document.getElementById('graph_BH_damp');
  373. rangeElement.onchange = showValueOfRange.bind(this, 'graph_BH_damp', 1, "physics_damping");
  374. rangeElement = document.getElementById('graph_R_nd');
  375. rangeElement.onchange = showValueOfRange.bind(this, 'graph_R_nd', 1, "physics_repulsion_nodeDistance");
  376. rangeElement = document.getElementById('graph_R_cg');
  377. rangeElement.onchange = showValueOfRange.bind(this, 'graph_R_cg', 1, "physics_centralGravity");
  378. rangeElement = document.getElementById('graph_R_sc');
  379. rangeElement.onchange = showValueOfRange.bind(this, 'graph_R_sc', 1, "physics_springConstant");
  380. rangeElement = document.getElementById('graph_R_sl');
  381. rangeElement.onchange = showValueOfRange.bind(this, 'graph_R_sl', 1, "physics_springLength");
  382. rangeElement = document.getElementById('graph_R_damp');
  383. rangeElement.onchange = showValueOfRange.bind(this, 'graph_R_damp', 1, "physics_damping");
  384. rangeElement = document.getElementById('graph_H_nd');
  385. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_nd', 1, "physics_hierarchicalRepulsion_nodeDistance");
  386. rangeElement = document.getElementById('graph_H_cg');
  387. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_cg', 1, "physics_centralGravity");
  388. rangeElement = document.getElementById('graph_H_sc');
  389. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_sc', 1, "physics_springConstant");
  390. rangeElement = document.getElementById('graph_H_sl');
  391. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_sl', 1, "physics_springLength");
  392. rangeElement = document.getElementById('graph_H_damp');
  393. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_damp', 1, "physics_damping");
  394. rangeElement = document.getElementById('graph_H_direction');
  395. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_direction', hierarchicalLayoutDirections, "hierarchicalLayout_direction");
  396. rangeElement = document.getElementById('graph_H_levsep');
  397. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_levsep', 1, "hierarchicalLayout_levelSeparation");
  398. rangeElement = document.getElementById('graph_H_nspac');
  399. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_nspac', 1, "hierarchicalLayout_nodeSpacing");
  400. var radioButton1 = document.getElementById("graph_physicsMethod1");
  401. var radioButton2 = document.getElementById("graph_physicsMethod2");
  402. var radioButton3 = document.getElementById("graph_physicsMethod3");
  403. radioButton2.checked = true;
  404. if (this.constants.physics.barnesHut.enabled) {
  405. radioButton1.checked = true;
  406. }
  407. if (this.constants.hierarchicalLayout.enabled) {
  408. radioButton3.checked = true;
  409. }
  410. var graph_toggleSmooth = document.getElementById("graph_toggleSmooth");
  411. var graph_repositionNodes = document.getElementById("graph_repositionNodes");
  412. var graph_generateOptions = document.getElementById("graph_generateOptions");
  413. graph_toggleSmooth.onclick = graphToggleSmoothCurves.bind(this);
  414. graph_repositionNodes.onclick = graphRepositionNodes.bind(this);
  415. graph_generateOptions.onclick = graphGenerateOptions.bind(this);
  416. if (this.constants.smoothCurves == true && this.constants.dynamicSmoothCurves == false) {
  417. graph_toggleSmooth.style.background = "#A4FF56";
  418. }
  419. else {
  420. graph_toggleSmooth.style.background = "#FF8532";
  421. }
  422. switchConfigurations.apply(this);
  423. radioButton1.onchange = switchConfigurations.bind(this);
  424. radioButton2.onchange = switchConfigurations.bind(this);
  425. radioButton3.onchange = switchConfigurations.bind(this);
  426. }
  427. };
  428. /**
  429. * This overwrites the this.constants.
  430. *
  431. * @param constantsVariableName
  432. * @param value
  433. * @private
  434. */
  435. exports._overWriteGraphConstants = function (constantsVariableName, value) {
  436. var nameArray = constantsVariableName.split("_");
  437. if (nameArray.length == 1) {
  438. this.constants[nameArray[0]] = value;
  439. }
  440. else if (nameArray.length == 2) {
  441. this.constants[nameArray[0]][nameArray[1]] = value;
  442. }
  443. else if (nameArray.length == 3) {
  444. this.constants[nameArray[0]][nameArray[1]][nameArray[2]] = value;
  445. }
  446. };
  447. /**
  448. * this function is bound to the toggle smooth curves button. That is also why it is not in the prototype.
  449. */
  450. function graphToggleSmoothCurves () {
  451. this.constants.smoothCurves.enabled = !this.constants.smoothCurves.enabled;
  452. var graph_toggleSmooth = document.getElementById("graph_toggleSmooth");
  453. if (this.constants.smoothCurves.enabled == true) {graph_toggleSmooth.style.background = "#A4FF56";}
  454. else {graph_toggleSmooth.style.background = "#FF8532";}
  455. this._configureSmoothCurves(false);
  456. }
  457. /**
  458. * this function is used to scramble the nodes
  459. *
  460. */
  461. function graphRepositionNodes () {
  462. for (var nodeId in this.calculationNodes) {
  463. if (this.calculationNodes.hasOwnProperty(nodeId)) {
  464. this.calculationNodes[nodeId].vx = 0; this.calculationNodes[nodeId].vy = 0;
  465. this.calculationNodes[nodeId].fx = 0; this.calculationNodes[nodeId].fy = 0;
  466. }
  467. }
  468. if (this.constants.hierarchicalLayout.enabled == true) {
  469. this._setupHierarchicalLayout();
  470. showValueOfRange.call(this, 'graph_H_nd', 1, "physics_hierarchicalRepulsion_nodeDistance");
  471. showValueOfRange.call(this, 'graph_H_cg', 1, "physics_centralGravity");
  472. showValueOfRange.call(this, 'graph_H_sc', 1, "physics_springConstant");
  473. showValueOfRange.call(this, 'graph_H_sl', 1, "physics_springLength");
  474. showValueOfRange.call(this, 'graph_H_damp', 1, "physics_damping");
  475. }
  476. else {
  477. this.repositionNodes();
  478. }
  479. this.moving = true;
  480. this.start();
  481. }
  482. /**
  483. * this is used to generate an options file from the playing with physics system.
  484. */
  485. function graphGenerateOptions () {
  486. var options = "No options are required, default values used.";
  487. var optionsSpecific = [];
  488. var radioButton1 = document.getElementById("graph_physicsMethod1");
  489. var radioButton2 = document.getElementById("graph_physicsMethod2");
  490. if (radioButton1.checked == true) {
  491. if (this.constants.physics.barnesHut.gravitationalConstant != this.backupConstants.physics.barnesHut.gravitationalConstant) {optionsSpecific.push("gravitationalConstant: " + this.constants.physics.barnesHut.gravitationalConstant);}
  492. if (this.constants.physics.centralGravity != this.backupConstants.physics.barnesHut.centralGravity) {optionsSpecific.push("centralGravity: " + this.constants.physics.centralGravity);}
  493. if (this.constants.physics.springLength != this.backupConstants.physics.barnesHut.springLength) {optionsSpecific.push("springLength: " + this.constants.physics.springLength);}
  494. if (this.constants.physics.springConstant != this.backupConstants.physics.barnesHut.springConstant) {optionsSpecific.push("springConstant: " + this.constants.physics.springConstant);}
  495. if (this.constants.physics.damping != this.backupConstants.physics.barnesHut.damping) {optionsSpecific.push("damping: " + this.constants.physics.damping);}
  496. if (optionsSpecific.length != 0) {
  497. options = "var options = {";
  498. options += "physics: {barnesHut: {";
  499. for (var i = 0; i < optionsSpecific.length; i++) {
  500. options += optionsSpecific[i];
  501. if (i < optionsSpecific.length - 1) {
  502. options += ", "
  503. }
  504. }
  505. options += '}}'
  506. }
  507. if (this.constants.smoothCurves.enabled != this.backupConstants.smoothCurves.enabled) {
  508. if (optionsSpecific.length == 0) {options = "var options = {";}
  509. else {options += ", "}
  510. options += "smoothCurves: " + this.constants.smoothCurves.enabled;
  511. }
  512. if (options != "No options are required, default values used.") {
  513. options += '};'
  514. }
  515. }
  516. else if (radioButton2.checked == true) {
  517. options = "var options = {";
  518. options += "physics: {barnesHut: {enabled: false}";
  519. if (this.constants.physics.repulsion.nodeDistance != this.backupConstants.physics.repulsion.nodeDistance) {optionsSpecific.push("nodeDistance: " + this.constants.physics.repulsion.nodeDistance);}
  520. if (this.constants.physics.centralGravity != this.backupConstants.physics.repulsion.centralGravity) {optionsSpecific.push("centralGravity: " + this.constants.physics.centralGravity);}
  521. if (this.constants.physics.springLength != this.backupConstants.physics.repulsion.springLength) {optionsSpecific.push("springLength: " + this.constants.physics.springLength);}
  522. if (this.constants.physics.springConstant != this.backupConstants.physics.repulsion.springConstant) {optionsSpecific.push("springConstant: " + this.constants.physics.springConstant);}
  523. if (this.constants.physics.damping != this.backupConstants.physics.repulsion.damping) {optionsSpecific.push("damping: " + this.constants.physics.damping);}
  524. if (optionsSpecific.length != 0) {
  525. options += ", repulsion: {";
  526. for (var i = 0; i < optionsSpecific.length; i++) {
  527. options += optionsSpecific[i];
  528. if (i < optionsSpecific.length - 1) {
  529. options += ", "
  530. }
  531. }
  532. options += '}}'
  533. }
  534. if (optionsSpecific.length == 0) {options += "}"}
  535. if (this.constants.smoothCurves != this.backupConstants.smoothCurves) {
  536. options += ", smoothCurves: " + this.constants.smoothCurves;
  537. }
  538. options += '};'
  539. }
  540. else {
  541. options = "var options = {";
  542. if (this.constants.physics.hierarchicalRepulsion.nodeDistance != this.backupConstants.physics.hierarchicalRepulsion.nodeDistance) {optionsSpecific.push("nodeDistance: " + this.constants.physics.hierarchicalRepulsion.nodeDistance);}
  543. if (this.constants.physics.centralGravity != this.backupConstants.physics.hierarchicalRepulsion.centralGravity) {optionsSpecific.push("centralGravity: " + this.constants.physics.centralGravity);}
  544. if (this.constants.physics.springLength != this.backupConstants.physics.hierarchicalRepulsion.springLength) {optionsSpecific.push("springLength: " + this.constants.physics.springLength);}
  545. if (this.constants.physics.springConstant != this.backupConstants.physics.hierarchicalRepulsion.springConstant) {optionsSpecific.push("springConstant: " + this.constants.physics.springConstant);}
  546. if (this.constants.physics.damping != this.backupConstants.physics.hierarchicalRepulsion.damping) {optionsSpecific.push("damping: " + this.constants.physics.damping);}
  547. if (optionsSpecific.length != 0) {
  548. options += "physics: {hierarchicalRepulsion: {";
  549. for (var i = 0; i < optionsSpecific.length; i++) {
  550. options += optionsSpecific[i];
  551. if (i < optionsSpecific.length - 1) {
  552. options += ", ";
  553. }
  554. }
  555. options += '}},';
  556. }
  557. options += 'hierarchicalLayout: {';
  558. optionsSpecific = [];
  559. if (this.constants.hierarchicalLayout.direction != this.backupConstants.hierarchicalLayout.direction) {optionsSpecific.push("direction: " + this.constants.hierarchicalLayout.direction);}
  560. if (Math.abs(this.constants.hierarchicalLayout.levelSeparation) != this.backupConstants.hierarchicalLayout.levelSeparation) {optionsSpecific.push("levelSeparation: " + this.constants.hierarchicalLayout.levelSeparation);}
  561. if (this.constants.hierarchicalLayout.nodeSpacing != this.backupConstants.hierarchicalLayout.nodeSpacing) {optionsSpecific.push("nodeSpacing: " + this.constants.hierarchicalLayout.nodeSpacing);}
  562. if (optionsSpecific.length != 0) {
  563. for (var i = 0; i < optionsSpecific.length; i++) {
  564. options += optionsSpecific[i];
  565. if (i < optionsSpecific.length - 1) {
  566. options += ", "
  567. }
  568. }
  569. options += '}'
  570. }
  571. else {
  572. options += "enabled:true}";
  573. }
  574. options += '};'
  575. }
  576. this.optionsDiv.innerHTML = options;
  577. }
  578. /**
  579. * this is used to switch between barnesHut, repulsion and hierarchical.
  580. *
  581. */
  582. function switchConfigurations () {
  583. var ids = ["graph_BH_table", "graph_R_table", "graph_H_table"];
  584. var radioButton = document.querySelector('input[name="graph_physicsMethod"]:checked').value;
  585. var tableId = "graph_" + radioButton + "_table";
  586. var table = document.getElementById(tableId);
  587. table.style.display = "block";
  588. for (var i = 0; i < ids.length; i++) {
  589. if (ids[i] != tableId) {
  590. table = document.getElementById(ids[i]);
  591. table.style.display = "none";
  592. }
  593. }
  594. this._restoreNodes();
  595. if (radioButton == "R") {
  596. this.constants.hierarchicalLayout.enabled = false;
  597. this.constants.physics.hierarchicalRepulsion.enabled = false;
  598. this.constants.physics.barnesHut.enabled = false;
  599. }
  600. else if (radioButton == "H") {
  601. if (this.constants.hierarchicalLayout.enabled == false) {
  602. this.constants.hierarchicalLayout.enabled = true;
  603. this.constants.physics.hierarchicalRepulsion.enabled = true;
  604. this.constants.physics.barnesHut.enabled = false;
  605. this.constants.smoothCurves.enabled = false;
  606. this._setupHierarchicalLayout();
  607. }
  608. }
  609. else {
  610. this.constants.hierarchicalLayout.enabled = false;
  611. this.constants.physics.hierarchicalRepulsion.enabled = false;
  612. this.constants.physics.barnesHut.enabled = true;
  613. }
  614. this._loadSelectedForceSolver();
  615. var graph_toggleSmooth = document.getElementById("graph_toggleSmooth");
  616. if (this.constants.smoothCurves.enabled == true) {graph_toggleSmooth.style.background = "#A4FF56";}
  617. else {graph_toggleSmooth.style.background = "#FF8532";}
  618. this.moving = true;
  619. this.start();
  620. }
  621. /**
  622. * this generates the ranges depending on the iniital values.
  623. *
  624. * @param id
  625. * @param map
  626. * @param constantsVariableName
  627. */
  628. function showValueOfRange (id,map,constantsVariableName) {
  629. var valueId = id + "_value";
  630. var rangeValue = document.getElementById(id).value;
  631. if (Array.isArray(map)) {
  632. document.getElementById(valueId).value = map[parseInt(rangeValue)];
  633. this._overWriteGraphConstants(constantsVariableName,map[parseInt(rangeValue)]);
  634. }
  635. else {
  636. document.getElementById(valueId).value = parseInt(map) * parseFloat(rangeValue);
  637. this._overWriteGraphConstants(constantsVariableName, parseInt(map) * parseFloat(rangeValue));
  638. }
  639. if (constantsVariableName == "hierarchicalLayout_direction" ||
  640. constantsVariableName == "hierarchicalLayout_levelSeparation" ||
  641. constantsVariableName == "hierarchicalLayout_nodeSpacing") {
  642. this._setupHierarchicalLayout();
  643. }
  644. this.moving = true;
  645. this.start();
  646. }