Integrations.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. /* *
  2. *
  3. * Networkgraph series
  4. *
  5. * (c) 2010-2020 Paweł Fus
  6. *
  7. * License: www.highcharts.com/license
  8. *
  9. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  10. *
  11. * */
  12. 'use strict';
  13. import H from '../../Core/Globals.js';
  14. /* eslint-disable no-invalid-this, valid-jsdoc */
  15. H.networkgraphIntegrations = {
  16. verlet: {
  17. /**
  18. * Attractive force funtion. Can be replaced by API's
  19. * `layoutAlgorithm.attractiveForce`
  20. *
  21. * @private
  22. * @param {number} d current distance between two nodes
  23. * @param {number} k expected distance between two nodes
  24. * @return {number} force
  25. */
  26. attractiveForceFunction: function (d, k) {
  27. // Used in API:
  28. return (k - d) / d;
  29. },
  30. /**
  31. * Repulsive force funtion. Can be replaced by API's
  32. * `layoutAlgorithm.repulsiveForce`
  33. *
  34. * @private
  35. * @param {number} d current distance between two nodes
  36. * @param {number} k expected distance between two nodes
  37. * @return {number} force
  38. */
  39. repulsiveForceFunction: function (d, k) {
  40. // Used in API:
  41. return (k - d) / d * (k > d ? 1 : 0); // Force only for close nodes
  42. },
  43. /**
  44. * Barycenter force. Calculate and applys barycenter forces on the
  45. * nodes. Making them closer to the center of their barycenter point.
  46. *
  47. * In Verlet integration, force is applied on a node immidatelly to it's
  48. * `plotX` and `plotY` position.
  49. *
  50. * @private
  51. * @return {void}
  52. */
  53. barycenter: function () {
  54. var gravitationalConstant = this.options.gravitationalConstant, xFactor = this.barycenter.xFactor, yFactor = this.barycenter.yFactor;
  55. // To consider:
  56. xFactor = (xFactor - (this.box.left + this.box.width) / 2) *
  57. gravitationalConstant;
  58. yFactor = (yFactor - (this.box.top + this.box.height) / 2) *
  59. gravitationalConstant;
  60. this.nodes.forEach(function (node) {
  61. if (!node.fixedPosition) {
  62. node.plotX -=
  63. xFactor / node.mass / node.degree;
  64. node.plotY -=
  65. yFactor / node.mass / node.degree;
  66. }
  67. });
  68. },
  69. /**
  70. * Repulsive force.
  71. *
  72. * In Verlet integration, force is applied on a node immidatelly to it's
  73. * `plotX` and `plotY` position.
  74. *
  75. * @private
  76. * @param {Highcharts.Point} node
  77. * Node that should be translated by force.
  78. * @param {number} force
  79. * Force calcualated in `repulsiveForceFunction`
  80. * @param {Highcharts.PositionObject} distance
  81. * Distance between two nodes e.g. `{x, y}`
  82. * @return {void}
  83. */
  84. repulsive: function (node, force, distanceXY) {
  85. var factor = force * this.diffTemperature / node.mass / node.degree;
  86. if (!node.fixedPosition) {
  87. node.plotX += distanceXY.x * factor;
  88. node.plotY += distanceXY.y * factor;
  89. }
  90. },
  91. /**
  92. * Attractive force.
  93. *
  94. * In Verlet integration, force is applied on a node immidatelly to it's
  95. * `plotX` and `plotY` position.
  96. *
  97. * @private
  98. * @param {Highcharts.Point} link
  99. * Link that connects two nodes
  100. * @param {number} force
  101. * Force calcualated in `repulsiveForceFunction`
  102. * @param {Highcharts.PositionObject} distance
  103. * Distance between two nodes e.g. `{x, y}`
  104. * @return {void}
  105. */
  106. attractive: function (link, force, distanceXY) {
  107. var massFactor = link.getMass(), translatedX = -distanceXY.x * force * this.diffTemperature, translatedY = -distanceXY.y * force * this.diffTemperature;
  108. if (!link.fromNode.fixedPosition) {
  109. link.fromNode.plotX -=
  110. translatedX * massFactor.fromNode / link.fromNode.degree;
  111. link.fromNode.plotY -=
  112. translatedY * massFactor.fromNode / link.fromNode.degree;
  113. }
  114. if (!link.toNode.fixedPosition) {
  115. link.toNode.plotX +=
  116. translatedX * massFactor.toNode / link.toNode.degree;
  117. link.toNode.plotY +=
  118. translatedY * massFactor.toNode / link.toNode.degree;
  119. }
  120. },
  121. /**
  122. * Integration method.
  123. *
  124. * In Verlet integration, forces are applied on node immidatelly to it's
  125. * `plotX` and `plotY` position.
  126. *
  127. * Verlet without velocity:
  128. *
  129. * x(n+1) = 2 * x(n) - x(n-1) + A(T) * deltaT ^ 2
  130. *
  131. * where:
  132. * - x(n+1) - new position
  133. * - x(n) - current position
  134. * - x(n-1) - previous position
  135. *
  136. * Assuming A(t) = 0 (no acceleration) and (deltaT = 1) we get:
  137. *
  138. * x(n+1) = x(n) + (x(n) - x(n-1))
  139. *
  140. * where:
  141. * - (x(n) - x(n-1)) - position change
  142. *
  143. * TO DO:
  144. * Consider Verlet with velocity to support additional
  145. * forces. Or even Time-Corrected Verlet by Jonathan
  146. * "lonesock" Dummer
  147. *
  148. * @private
  149. * @param {Highcharts.NetworkgraphLayout} layout layout object
  150. * @param {Highcharts.Point} node node that should be translated
  151. * @return {void}
  152. */
  153. integrate: function (layout, node) {
  154. var friction = -layout.options.friction, maxSpeed = layout.options.maxSpeed, prevX = node.prevX, prevY = node.prevY,
  155. // Apply friciton:
  156. diffX = ((node.plotX + node.dispX -
  157. prevX) * friction), diffY = ((node.plotY + node.dispY -
  158. prevY) * friction), abs = Math.abs, signX = abs(diffX) / (diffX || 1), // need to deal with 0
  159. signY = abs(diffY) / (diffY || 1);
  160. // Apply max speed:
  161. diffX = signX * Math.min(maxSpeed, Math.abs(diffX));
  162. diffY = signY * Math.min(maxSpeed, Math.abs(diffY));
  163. // Store for the next iteration:
  164. node.prevX = node.plotX + node.dispX;
  165. node.prevY = node.plotY + node.dispY;
  166. // Update positions:
  167. node.plotX += diffX;
  168. node.plotY += diffY;
  169. node.temperature = layout.vectorLength({
  170. x: diffX,
  171. y: diffY
  172. });
  173. },
  174. /**
  175. * Estiamte the best possible distance between two nodes, making graph
  176. * readable.
  177. *
  178. * @private
  179. * @param {Highcharts.NetworkgraphLayout} layout layout object
  180. * @return {number}
  181. */
  182. getK: function (layout) {
  183. return Math.pow(layout.box.width * layout.box.height / layout.nodes.length, 0.5);
  184. }
  185. },
  186. euler: {
  187. /**
  188. * Attractive force funtion. Can be replaced by API's
  189. * `layoutAlgorithm.attractiveForce`
  190. *
  191. * Other forces that can be used:
  192. *
  193. * basic, not recommended:
  194. * `function (d, k) { return d / k }`
  195. *
  196. * @private
  197. * @param {number} d current distance between two nodes
  198. * @param {number} k expected distance between two nodes
  199. * @return {number} force
  200. */
  201. attractiveForceFunction: function (d, k) {
  202. return d * d / k;
  203. },
  204. /**
  205. * Repulsive force funtion. Can be replaced by API's
  206. * `layoutAlgorithm.repulsiveForce`.
  207. *
  208. * Other forces that can be used:
  209. *
  210. * basic, not recommended:
  211. * `function (d, k) { return k / d }`
  212. *
  213. * standard:
  214. * `function (d, k) { return k * k / d }`
  215. *
  216. * grid-variant:
  217. * `function (d, k) { return k * k / d * (2 * k - d > 0 ? 1 : 0) }`
  218. *
  219. * @private
  220. * @param {number} d current distance between two nodes
  221. * @param {number} k expected distance between two nodes
  222. * @return {number} force
  223. */
  224. repulsiveForceFunction: function (d, k) {
  225. return k * k / d;
  226. },
  227. /**
  228. * Barycenter force. Calculate and applys barycenter forces on the
  229. * nodes. Making them closer to the center of their barycenter point.
  230. *
  231. * In Euler integration, force is stored in a node, not changing it's
  232. * position. Later, in `integrate()` forces are applied on nodes.
  233. *
  234. * @private
  235. * @return {void}
  236. */
  237. barycenter: function () {
  238. var gravitationalConstant = this.options.gravitationalConstant, xFactor = this.barycenter.xFactor, yFactor = this.barycenter.yFactor;
  239. this.nodes.forEach(function (node) {
  240. if (!node.fixedPosition) {
  241. var degree = node.getDegree(), phi = degree * (1 + degree / 2);
  242. node.dispX += ((xFactor - node.plotX) *
  243. gravitationalConstant *
  244. phi / node.degree);
  245. node.dispY += ((yFactor - node.plotY) *
  246. gravitationalConstant *
  247. phi / node.degree);
  248. }
  249. });
  250. },
  251. /**
  252. * Repulsive force.
  253. *
  254. * @private
  255. * @param {Highcharts.Point} node
  256. * Node that should be translated by force.
  257. * @param {number} force
  258. * Force calcualated in `repulsiveForceFunction`
  259. * @param {Highcharts.PositionObject} distanceXY
  260. * Distance between two nodes e.g. `{x, y}`
  261. * @return {void}
  262. */
  263. repulsive: function (node, force, distanceXY, distanceR) {
  264. node.dispX +=
  265. (distanceXY.x / distanceR) * force / node.degree;
  266. node.dispY +=
  267. (distanceXY.y / distanceR) * force / node.degree;
  268. },
  269. /**
  270. * Attractive force.
  271. *
  272. * In Euler integration, force is stored in a node, not changing it's
  273. * position. Later, in `integrate()` forces are applied on nodes.
  274. *
  275. * @private
  276. * @param {Highcharts.Point} link
  277. * Link that connects two nodes
  278. * @param {number} force
  279. * Force calcualated in `repulsiveForceFunction`
  280. * @param {Highcharts.PositionObject} distanceXY
  281. * Distance between two nodes e.g. `{x, y}`
  282. * @param {number} distanceR
  283. * @return {void}
  284. */
  285. attractive: function (link, force, distanceXY, distanceR) {
  286. var massFactor = link.getMass(), translatedX = (distanceXY.x / distanceR) * force, translatedY = (distanceXY.y / distanceR) * force;
  287. if (!link.fromNode.fixedPosition) {
  288. link.fromNode.dispX -=
  289. translatedX * massFactor.fromNode / link.fromNode.degree;
  290. link.fromNode.dispY -=
  291. translatedY * massFactor.fromNode / link.fromNode.degree;
  292. }
  293. if (!link.toNode.fixedPosition) {
  294. link.toNode.dispX +=
  295. translatedX * massFactor.toNode / link.toNode.degree;
  296. link.toNode.dispY +=
  297. translatedY * massFactor.toNode / link.toNode.degree;
  298. }
  299. },
  300. /**
  301. * Integration method.
  302. *
  303. * In Euler integration, force were stored in a node, not changing it's
  304. * position. Now, in the integrator method, we apply changes.
  305. *
  306. * Euler:
  307. *
  308. * Basic form: `x(n+1) = x(n) + v(n)`
  309. *
  310. * With Rengoild-Fruchterman we get:
  311. * `x(n+1) = x(n) + v(n) / length(v(n)) * min(v(n), temperature(n))`
  312. * where:
  313. * - `x(n+1)`: next position
  314. * - `x(n)`: current position
  315. * - `v(n)`: velocity (comes from net force)
  316. * - `temperature(n)`: current temperature
  317. *
  318. * Known issues:
  319. * Oscillations when force vector has the same magnitude but opposite
  320. * direction in the next step. Potentially solved by decreasing force by
  321. * `v * (1 / node.degree)`
  322. *
  323. * Note:
  324. * Actually `min(v(n), temperature(n))` replaces simulated annealing.
  325. *
  326. * @private
  327. * @param {Highcharts.NetworkgraphLayout} layout
  328. * Layout object
  329. * @param {Highcharts.Point} node
  330. * Node that should be translated
  331. * @return {void}
  332. */
  333. integrate: function (layout, node) {
  334. var distanceR;
  335. node.dispX +=
  336. node.dispX * layout.options.friction;
  337. node.dispY +=
  338. node.dispY * layout.options.friction;
  339. distanceR = node.temperature = layout.vectorLength({
  340. x: node.dispX,
  341. y: node.dispY
  342. });
  343. if (distanceR !== 0) {
  344. node.plotX += (node.dispX / distanceR *
  345. Math.min(Math.abs(node.dispX), layout.temperature));
  346. node.plotY += (node.dispY / distanceR *
  347. Math.min(Math.abs(node.dispY), layout.temperature));
  348. }
  349. },
  350. /**
  351. * Estiamte the best possible distance between two nodes, making graph
  352. * readable.
  353. *
  354. * @private
  355. * @param {object} layout layout object
  356. * @return {number}
  357. */
  358. getK: function (layout) {
  359. return Math.pow(layout.box.width * layout.box.height / layout.nodes.length, 0.3);
  360. }
  361. }
  362. };