networkgraph.src.js 114 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695
  1. /**
  2. * @license Highcharts JS v8.1.2 (2020-06-16)
  3. *
  4. * Force directed graph module
  5. *
  6. * (c) 2010-2019 Torstein Honsi
  7. *
  8. * License: www.highcharts.com/license
  9. */
  10. 'use strict';
  11. (function (factory) {
  12. if (typeof module === 'object' && module.exports) {
  13. factory['default'] = factory;
  14. module.exports = factory;
  15. } else if (typeof define === 'function' && define.amd) {
  16. define('highcharts/modules/networkgraph', ['highcharts'], function (Highcharts) {
  17. factory(Highcharts);
  18. factory.Highcharts = Highcharts;
  19. return factory;
  20. });
  21. } else {
  22. factory(typeof Highcharts !== 'undefined' ? Highcharts : undefined);
  23. }
  24. }(function (Highcharts) {
  25. var _modules = Highcharts ? Highcharts._modules : {};
  26. function _registerModule(obj, path, args, fn) {
  27. if (!obj.hasOwnProperty(path)) {
  28. obj[path] = fn.apply(null, args);
  29. }
  30. }
  31. _registerModule(_modules, 'mixins/nodes.js', [_modules['parts/Globals.js'], _modules['parts/Point.js'], _modules['parts/Utilities.js']], function (H, Point, U) {
  32. /* *
  33. *
  34. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  35. *
  36. * */
  37. var defined = U.defined, extend = U.extend, find = U.find, pick = U.pick;
  38. H.NodesMixin = {
  39. /* eslint-disable valid-jsdoc */
  40. /**
  41. * Create a single node that holds information on incoming and outgoing
  42. * links.
  43. * @private
  44. */
  45. createNode: function (id) {
  46. /**
  47. * @private
  48. */
  49. function findById(nodes, id) {
  50. return find(nodes, function (node) {
  51. return node.id === id;
  52. });
  53. }
  54. var node = findById(this.nodes, id), PointClass = this.pointClass, options;
  55. if (!node) {
  56. options = this.options.nodes && findById(this.options.nodes, id);
  57. node = (new PointClass()).init(this, extend({
  58. className: 'highcharts-node',
  59. isNode: true,
  60. id: id,
  61. y: 1 // Pass isNull test
  62. }, options));
  63. node.linksTo = [];
  64. node.linksFrom = [];
  65. node.formatPrefix = 'node';
  66. node.name = node.name || node.options.id || ''; // for use in formats
  67. // Mass is used in networkgraph:
  68. node.mass = pick(
  69. // Node:
  70. node.options.mass, node.options.marker && node.options.marker.radius,
  71. // Series:
  72. this.options.marker && this.options.marker.radius,
  73. // Default:
  74. 4);
  75. /**
  76. * Return the largest sum of either the incoming or outgoing links.
  77. * @private
  78. */
  79. node.getSum = function () {
  80. var sumTo = 0, sumFrom = 0;
  81. node.linksTo.forEach(function (link) {
  82. sumTo += link.weight;
  83. });
  84. node.linksFrom.forEach(function (link) {
  85. sumFrom += link.weight;
  86. });
  87. return Math.max(sumTo, sumFrom);
  88. };
  89. /**
  90. * Get the offset in weight values of a point/link.
  91. * @private
  92. */
  93. node.offset = function (point, coll) {
  94. var offset = 0;
  95. for (var i = 0; i < node[coll].length; i++) {
  96. if (node[coll][i] === point) {
  97. return offset;
  98. }
  99. offset += node[coll][i].weight;
  100. }
  101. };
  102. // Return true if the node has a shape, otherwise all links are
  103. // outgoing.
  104. node.hasShape = function () {
  105. var outgoing = 0;
  106. node.linksTo.forEach(function (link) {
  107. if (link.outgoing) {
  108. outgoing++;
  109. }
  110. });
  111. return (!node.linksTo.length ||
  112. outgoing !== node.linksTo.length);
  113. };
  114. this.nodes.push(node);
  115. }
  116. return node;
  117. },
  118. /**
  119. * Extend generatePoints by adding the nodes, which are Point objects
  120. * but pushed to the this.nodes array.
  121. */
  122. generatePoints: function () {
  123. var chart = this.chart, nodeLookup = {};
  124. H.Series.prototype.generatePoints.call(this);
  125. if (!this.nodes) {
  126. this.nodes = []; // List of Point-like node items
  127. }
  128. this.colorCounter = 0;
  129. // Reset links from previous run
  130. this.nodes.forEach(function (node) {
  131. node.linksFrom.length = 0;
  132. node.linksTo.length = 0;
  133. node.level = node.options.level;
  134. });
  135. // Create the node list and set up links
  136. this.points.forEach(function (point) {
  137. if (defined(point.from)) {
  138. if (!nodeLookup[point.from]) {
  139. nodeLookup[point.from] = this.createNode(point.from);
  140. }
  141. nodeLookup[point.from].linksFrom.push(point);
  142. point.fromNode = nodeLookup[point.from];
  143. // Point color defaults to the fromNode's color
  144. if (chart.styledMode) {
  145. point.colorIndex = pick(point.options.colorIndex, nodeLookup[point.from].colorIndex);
  146. }
  147. else {
  148. point.color =
  149. point.options.color || nodeLookup[point.from].color;
  150. }
  151. }
  152. if (defined(point.to)) {
  153. if (!nodeLookup[point.to]) {
  154. nodeLookup[point.to] = this.createNode(point.to);
  155. }
  156. nodeLookup[point.to].linksTo.push(point);
  157. point.toNode = nodeLookup[point.to];
  158. }
  159. point.name = point.name || point.id; // for use in formats
  160. }, this);
  161. // Store lookup table for later use
  162. this.nodeLookup = nodeLookup;
  163. },
  164. // Destroy all nodes on setting new data
  165. setData: function () {
  166. if (this.nodes) {
  167. this.nodes.forEach(function (node) {
  168. node.destroy();
  169. });
  170. this.nodes.length = 0;
  171. }
  172. H.Series.prototype.setData.apply(this, arguments);
  173. },
  174. // Destroy alll nodes and links
  175. destroy: function () {
  176. // Nodes must also be destroyed (#8682, #9300)
  177. this.data = []
  178. .concat(this.points || [], this.nodes);
  179. return H.Series.prototype.destroy.apply(this, arguments);
  180. },
  181. /**
  182. * When hovering node, highlight all connected links. When hovering a link,
  183. * highlight all connected nodes.
  184. */
  185. setNodeState: function (state) {
  186. var args = arguments, others = this.isNode ? this.linksTo.concat(this.linksFrom) :
  187. [this.fromNode, this.toNode];
  188. if (state !== 'select') {
  189. others.forEach(function (linkOrNode) {
  190. if (linkOrNode && linkOrNode.series) {
  191. Point.prototype.setState.apply(linkOrNode, args);
  192. if (!linkOrNode.isNode) {
  193. if (linkOrNode.fromNode.graphic) {
  194. Point.prototype.setState.apply(linkOrNode.fromNode, args);
  195. }
  196. if (linkOrNode.toNode && linkOrNode.toNode.graphic) {
  197. Point.prototype.setState.apply(linkOrNode.toNode, args);
  198. }
  199. }
  200. }
  201. });
  202. }
  203. Point.prototype.setState.apply(this, args);
  204. }
  205. /* eslint-enable valid-jsdoc */
  206. };
  207. });
  208. _registerModule(_modules, 'modules/networkgraph/integrations.js', [_modules['parts/Globals.js']], function (H) {
  209. /* *
  210. *
  211. * Networkgraph series
  212. *
  213. * (c) 2010-2020 Paweł Fus
  214. *
  215. * License: www.highcharts.com/license
  216. *
  217. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  218. *
  219. * */
  220. /* eslint-disable no-invalid-this, valid-jsdoc */
  221. H.networkgraphIntegrations = {
  222. verlet: {
  223. /**
  224. * Attractive force funtion. Can be replaced by API's
  225. * `layoutAlgorithm.attractiveForce`
  226. *
  227. * @private
  228. * @param {number} d current distance between two nodes
  229. * @param {number} k expected distance between two nodes
  230. * @return {number} force
  231. */
  232. attractiveForceFunction: function (d, k) {
  233. // Used in API:
  234. return (k - d) / d;
  235. },
  236. /**
  237. * Repulsive force funtion. Can be replaced by API's
  238. * `layoutAlgorithm.repulsiveForce`
  239. *
  240. * @private
  241. * @param {number} d current distance between two nodes
  242. * @param {number} k expected distance between two nodes
  243. * @return {number} force
  244. */
  245. repulsiveForceFunction: function (d, k) {
  246. // Used in API:
  247. return (k - d) / d * (k > d ? 1 : 0); // Force only for close nodes
  248. },
  249. /**
  250. * Barycenter force. Calculate and applys barycenter forces on the
  251. * nodes. Making them closer to the center of their barycenter point.
  252. *
  253. * In Verlet integration, force is applied on a node immidatelly to it's
  254. * `plotX` and `plotY` position.
  255. *
  256. * @private
  257. * @return {void}
  258. */
  259. barycenter: function () {
  260. var gravitationalConstant = this.options.gravitationalConstant, xFactor = this.barycenter.xFactor, yFactor = this.barycenter.yFactor;
  261. // To consider:
  262. xFactor = (xFactor - (this.box.left + this.box.width) / 2) *
  263. gravitationalConstant;
  264. yFactor = (yFactor - (this.box.top + this.box.height) / 2) *
  265. gravitationalConstant;
  266. this.nodes.forEach(function (node) {
  267. if (!node.fixedPosition) {
  268. node.plotX -=
  269. xFactor / node.mass / node.degree;
  270. node.plotY -=
  271. yFactor / node.mass / node.degree;
  272. }
  273. });
  274. },
  275. /**
  276. * Repulsive force.
  277. *
  278. * In Verlet integration, force is applied on a node immidatelly to it's
  279. * `plotX` and `plotY` position.
  280. *
  281. * @private
  282. * @param {Highcharts.Point} node
  283. * Node that should be translated by force.
  284. * @param {number} force
  285. * Force calcualated in `repulsiveForceFunction`
  286. * @param {Highcharts.PositionObject} distance
  287. * Distance between two nodes e.g. `{x, y}`
  288. * @return {void}
  289. */
  290. repulsive: function (node, force, distanceXY) {
  291. var factor = force * this.diffTemperature / node.mass / node.degree;
  292. if (!node.fixedPosition) {
  293. node.plotX += distanceXY.x * factor;
  294. node.plotY += distanceXY.y * factor;
  295. }
  296. },
  297. /**
  298. * Attractive force.
  299. *
  300. * In Verlet integration, force is applied on a node immidatelly to it's
  301. * `plotX` and `plotY` position.
  302. *
  303. * @private
  304. * @param {Highcharts.Point} link
  305. * Link that connects two nodes
  306. * @param {number} force
  307. * Force calcualated in `repulsiveForceFunction`
  308. * @param {Highcharts.PositionObject} distance
  309. * Distance between two nodes e.g. `{x, y}`
  310. * @return {void}
  311. */
  312. attractive: function (link, force, distanceXY) {
  313. var massFactor = link.getMass(), translatedX = -distanceXY.x * force * this.diffTemperature, translatedY = -distanceXY.y * force * this.diffTemperature;
  314. if (!link.fromNode.fixedPosition) {
  315. link.fromNode.plotX -=
  316. translatedX * massFactor.fromNode / link.fromNode.degree;
  317. link.fromNode.plotY -=
  318. translatedY * massFactor.fromNode / link.fromNode.degree;
  319. }
  320. if (!link.toNode.fixedPosition) {
  321. link.toNode.plotX +=
  322. translatedX * massFactor.toNode / link.toNode.degree;
  323. link.toNode.plotY +=
  324. translatedY * massFactor.toNode / link.toNode.degree;
  325. }
  326. },
  327. /**
  328. * Integration method.
  329. *
  330. * In Verlet integration, forces are applied on node immidatelly to it's
  331. * `plotX` and `plotY` position.
  332. *
  333. * Verlet without velocity:
  334. *
  335. * x(n+1) = 2 * x(n) - x(n-1) + A(T) * deltaT ^ 2
  336. *
  337. * where:
  338. * - x(n+1) - new position
  339. * - x(n) - current position
  340. * - x(n-1) - previous position
  341. *
  342. * Assuming A(t) = 0 (no acceleration) and (deltaT = 1) we get:
  343. *
  344. * x(n+1) = x(n) + (x(n) - x(n-1))
  345. *
  346. * where:
  347. * - (x(n) - x(n-1)) - position change
  348. *
  349. * TO DO:
  350. * Consider Verlet with velocity to support additional
  351. * forces. Or even Time-Corrected Verlet by Jonathan
  352. * "lonesock" Dummer
  353. *
  354. * @private
  355. * @param {Highcharts.NetworkgraphLayout} layout layout object
  356. * @param {Highcharts.Point} node node that should be translated
  357. * @return {void}
  358. */
  359. integrate: function (layout, node) {
  360. var friction = -layout.options.friction, maxSpeed = layout.options.maxSpeed, prevX = node.prevX, prevY = node.prevY,
  361. // Apply friciton:
  362. diffX = ((node.plotX + node.dispX -
  363. prevX) * friction), diffY = ((node.plotY + node.dispY -
  364. prevY) * friction), abs = Math.abs, signX = abs(diffX) / (diffX || 1), // need to deal with 0
  365. signY = abs(diffY) / (diffY || 1);
  366. // Apply max speed:
  367. diffX = signX * Math.min(maxSpeed, Math.abs(diffX));
  368. diffY = signY * Math.min(maxSpeed, Math.abs(diffY));
  369. // Store for the next iteration:
  370. node.prevX = node.plotX + node.dispX;
  371. node.prevY = node.plotY + node.dispY;
  372. // Update positions:
  373. node.plotX += diffX;
  374. node.plotY += diffY;
  375. node.temperature = layout.vectorLength({
  376. x: diffX,
  377. y: diffY
  378. });
  379. },
  380. /**
  381. * Estiamte the best possible distance between two nodes, making graph
  382. * readable.
  383. *
  384. * @private
  385. * @param {Highcharts.NetworkgraphLayout} layout layout object
  386. * @return {number}
  387. */
  388. getK: function (layout) {
  389. return Math.pow(layout.box.width * layout.box.height / layout.nodes.length, 0.5);
  390. }
  391. },
  392. euler: {
  393. /**
  394. * Attractive force funtion. Can be replaced by API's
  395. * `layoutAlgorithm.attractiveForce`
  396. *
  397. * Other forces that can be used:
  398. *
  399. * basic, not recommended:
  400. * `function (d, k) { return d / k }`
  401. *
  402. * @private
  403. * @param {number} d current distance between two nodes
  404. * @param {number} k expected distance between two nodes
  405. * @return {number} force
  406. */
  407. attractiveForceFunction: function (d, k) {
  408. return d * d / k;
  409. },
  410. /**
  411. * Repulsive force funtion. Can be replaced by API's
  412. * `layoutAlgorithm.repulsiveForce`.
  413. *
  414. * Other forces that can be used:
  415. *
  416. * basic, not recommended:
  417. * `function (d, k) { return k / d }`
  418. *
  419. * standard:
  420. * `function (d, k) { return k * k / d }`
  421. *
  422. * grid-variant:
  423. * `function (d, k) { return k * k / d * (2 * k - d > 0 ? 1 : 0) }`
  424. *
  425. * @private
  426. * @param {number} d current distance between two nodes
  427. * @param {number} k expected distance between two nodes
  428. * @return {number} force
  429. */
  430. repulsiveForceFunction: function (d, k) {
  431. return k * k / d;
  432. },
  433. /**
  434. * Barycenter force. Calculate and applys barycenter forces on the
  435. * nodes. Making them closer to the center of their barycenter point.
  436. *
  437. * In Euler integration, force is stored in a node, not changing it's
  438. * position. Later, in `integrate()` forces are applied on nodes.
  439. *
  440. * @private
  441. * @return {void}
  442. */
  443. barycenter: function () {
  444. var gravitationalConstant = this.options.gravitationalConstant, xFactor = this.barycenter.xFactor, yFactor = this.barycenter.yFactor;
  445. this.nodes.forEach(function (node) {
  446. if (!node.fixedPosition) {
  447. var degree = node.getDegree(), phi = degree * (1 + degree / 2);
  448. node.dispX += ((xFactor - node.plotX) *
  449. gravitationalConstant *
  450. phi / node.degree);
  451. node.dispY += ((yFactor - node.plotY) *
  452. gravitationalConstant *
  453. phi / node.degree);
  454. }
  455. });
  456. },
  457. /**
  458. * Repulsive force.
  459. *
  460. * @private
  461. * @param {Highcharts.Point} node
  462. * Node that should be translated by force.
  463. * @param {number} force
  464. * Force calcualated in `repulsiveForceFunction`
  465. * @param {Highcharts.PositionObject} distanceXY
  466. * Distance between two nodes e.g. `{x, y}`
  467. * @return {void}
  468. */
  469. repulsive: function (node, force, distanceXY, distanceR) {
  470. node.dispX +=
  471. (distanceXY.x / distanceR) * force / node.degree;
  472. node.dispY +=
  473. (distanceXY.y / distanceR) * force / node.degree;
  474. },
  475. /**
  476. * Attractive force.
  477. *
  478. * In Euler integration, force is stored in a node, not changing it's
  479. * position. Later, in `integrate()` forces are applied on nodes.
  480. *
  481. * @private
  482. * @param {Highcharts.Point} link
  483. * Link that connects two nodes
  484. * @param {number} force
  485. * Force calcualated in `repulsiveForceFunction`
  486. * @param {Highcharts.PositionObject} distanceXY
  487. * Distance between two nodes e.g. `{x, y}`
  488. * @param {number} distanceR
  489. * @return {void}
  490. */
  491. attractive: function (link, force, distanceXY, distanceR) {
  492. var massFactor = link.getMass(), translatedX = (distanceXY.x / distanceR) * force, translatedY = (distanceXY.y / distanceR) * force;
  493. if (!link.fromNode.fixedPosition) {
  494. link.fromNode.dispX -=
  495. translatedX * massFactor.fromNode / link.fromNode.degree;
  496. link.fromNode.dispY -=
  497. translatedY * massFactor.fromNode / link.fromNode.degree;
  498. }
  499. if (!link.toNode.fixedPosition) {
  500. link.toNode.dispX +=
  501. translatedX * massFactor.toNode / link.toNode.degree;
  502. link.toNode.dispY +=
  503. translatedY * massFactor.toNode / link.toNode.degree;
  504. }
  505. },
  506. /**
  507. * Integration method.
  508. *
  509. * In Euler integration, force were stored in a node, not changing it's
  510. * position. Now, in the integrator method, we apply changes.
  511. *
  512. * Euler:
  513. *
  514. * Basic form: `x(n+1) = x(n) + v(n)`
  515. *
  516. * With Rengoild-Fruchterman we get:
  517. * `x(n+1) = x(n) + v(n) / length(v(n)) * min(v(n), temperature(n))`
  518. * where:
  519. * - `x(n+1)`: next position
  520. * - `x(n)`: current position
  521. * - `v(n)`: velocity (comes from net force)
  522. * - `temperature(n)`: current temperature
  523. *
  524. * Known issues:
  525. * Oscillations when force vector has the same magnitude but opposite
  526. * direction in the next step. Potentially solved by decreasing force by
  527. * `v * (1 / node.degree)`
  528. *
  529. * Note:
  530. * Actually `min(v(n), temperature(n))` replaces simulated annealing.
  531. *
  532. * @private
  533. * @param {Highcharts.NetworkgraphLayout} layout
  534. * Layout object
  535. * @param {Highcharts.Point} node
  536. * Node that should be translated
  537. * @return {void}
  538. */
  539. integrate: function (layout, node) {
  540. var distanceR;
  541. node.dispX +=
  542. node.dispX * layout.options.friction;
  543. node.dispY +=
  544. node.dispY * layout.options.friction;
  545. distanceR = node.temperature = layout.vectorLength({
  546. x: node.dispX,
  547. y: node.dispY
  548. });
  549. if (distanceR !== 0) {
  550. node.plotX += (node.dispX / distanceR *
  551. Math.min(Math.abs(node.dispX), layout.temperature));
  552. node.plotY += (node.dispY / distanceR *
  553. Math.min(Math.abs(node.dispY), layout.temperature));
  554. }
  555. },
  556. /**
  557. * Estiamte the best possible distance between two nodes, making graph
  558. * readable.
  559. *
  560. * @private
  561. * @param {object} layout layout object
  562. * @return {number}
  563. */
  564. getK: function (layout) {
  565. return Math.pow(layout.box.width * layout.box.height / layout.nodes.length, 0.3);
  566. }
  567. }
  568. };
  569. });
  570. _registerModule(_modules, 'modules/networkgraph/QuadTree.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js']], function (H, U) {
  571. /* *
  572. *
  573. * Networkgraph series
  574. *
  575. * (c) 2010-2020 Paweł Fus
  576. *
  577. * License: www.highcharts.com/license
  578. *
  579. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  580. *
  581. * */
  582. var extend = U.extend;
  583. /* eslint-disable no-invalid-this, valid-jsdoc */
  584. /**
  585. * The QuadTree node class. Used in Networkgraph chart as a base for Barnes-Hut
  586. * approximation.
  587. *
  588. * @private
  589. * @class
  590. * @name Highcharts.QuadTreeNode
  591. *
  592. * @param {Highcharts.Dictionary<number>} box Available space for the node
  593. */
  594. var QuadTreeNode = H.QuadTreeNode = function (box) {
  595. /**
  596. * Read only. The available space for node.
  597. *
  598. * @name Highcharts.QuadTreeNode#box
  599. * @type {Highcharts.Dictionary<number>}
  600. */
  601. this.box = box;
  602. /**
  603. * Read only. The minium of width and height values.
  604. *
  605. * @name Highcharts.QuadTreeNode#boxSize
  606. * @type {number}
  607. */
  608. this.boxSize = Math.min(box.width, box.height);
  609. /**
  610. * Read only. Array of subnodes. Empty if QuadTreeNode has just one Point.
  611. * When added another Point to this QuadTreeNode, array is filled with four
  612. * subnodes.
  613. *
  614. * @name Highcharts.QuadTreeNode#nodes
  615. * @type {Array<Highcharts.QuadTreeNode>}
  616. */
  617. this.nodes = [];
  618. /**
  619. * Read only. Flag to determine if QuadTreeNode is internal (and has
  620. * subnodes with mass and central position) or external (bound to Point).
  621. *
  622. * @name Highcharts.QuadTreeNode#isInternal
  623. * @type {boolean}
  624. */
  625. this.isInternal = false;
  626. /**
  627. * Read only. If QuadTreeNode is an external node, Point is stored in
  628. * `this.body`.
  629. *
  630. * @name Highcharts.QuadTreeNode#body
  631. * @type {boolean|Highcharts.Point}
  632. */
  633. this.body = false;
  634. /**
  635. * Read only. Internal nodes when created are empty to reserve the space. If
  636. * Point is added to this QuadTreeNode, QuadTreeNode is no longer empty.
  637. *
  638. * @name Highcharts.QuadTreeNode#isEmpty
  639. * @type {boolean}
  640. */
  641. this.isEmpty = true;
  642. };
  643. extend(QuadTreeNode.prototype,
  644. /** @lends Highcharts.QuadTreeNode.prototype */
  645. {
  646. /**
  647. * Insert recursively point(node) into the QuadTree. If the given
  648. * quadrant is already occupied, divide it into smaller quadrants.
  649. *
  650. * @param {Highcharts.Point} point
  651. * Point/node to be inserted
  652. * @param {number} depth
  653. * Max depth of the QuadTree
  654. */
  655. insert: function (point, depth) {
  656. var newQuadTreeNode;
  657. if (this.isInternal) {
  658. // Internal node:
  659. this.nodes[this.getBoxPosition(point)].insert(point, depth - 1);
  660. }
  661. else {
  662. this.isEmpty = false;
  663. if (!this.body) {
  664. // First body in a quadrant:
  665. this.isInternal = false;
  666. this.body = point;
  667. }
  668. else {
  669. if (depth) {
  670. // Every other body in a quadrant:
  671. this.isInternal = true;
  672. this.divideBox();
  673. // Reinsert main body only once:
  674. if (this.body !== true) {
  675. this.nodes[this.getBoxPosition(this.body)]
  676. .insert(this.body, depth - 1);
  677. this.body = true;
  678. }
  679. // Add second body:
  680. this.nodes[this.getBoxPosition(point)]
  681. .insert(point, depth - 1);
  682. }
  683. else {
  684. // We are below max allowed depth. That means either:
  685. // - really huge number of points
  686. // - falling two points into exactly the same position
  687. // In this case, create another node in the QuadTree.
  688. //
  689. // Alternatively we could add some noise to the
  690. // position, but that could result in different
  691. // rendered chart in exporting.
  692. newQuadTreeNode = new QuadTreeNode({
  693. top: point.plotX,
  694. left: point.plotY,
  695. // Width/height below 1px
  696. width: 0.1,
  697. height: 0.1
  698. });
  699. newQuadTreeNode.body = point;
  700. newQuadTreeNode.isInternal = false;
  701. this.nodes.push(newQuadTreeNode);
  702. }
  703. }
  704. }
  705. },
  706. /**
  707. * Each quad node requires it's mass and center position. That mass and
  708. * position is used to imitate real node in the layout by approximation.
  709. */
  710. updateMassAndCenter: function () {
  711. var mass = 0, plotX = 0, plotY = 0;
  712. if (this.isInternal) {
  713. // Calcualte weightened mass of the quad node:
  714. this.nodes.forEach(function (pointMass) {
  715. if (!pointMass.isEmpty) {
  716. mass += pointMass.mass;
  717. plotX +=
  718. pointMass.plotX * pointMass.mass;
  719. plotY +=
  720. pointMass.plotY * pointMass.mass;
  721. }
  722. });
  723. plotX /= mass;
  724. plotY /= mass;
  725. }
  726. else if (this.body) {
  727. // Just one node, use coordinates directly:
  728. mass = this.body.mass;
  729. plotX = this.body.plotX;
  730. plotY = this.body.plotY;
  731. }
  732. // Store details:
  733. this.mass = mass;
  734. this.plotX = plotX;
  735. this.plotY = plotY;
  736. },
  737. /**
  738. * When inserting another node into the box, that already hove one node,
  739. * divide the available space into another four quadrants.
  740. *
  741. * Indexes of quadrants are:
  742. * ```
  743. * ------------- -------------
  744. * | | | | |
  745. * | | | 0 | 1 |
  746. * | | divide() | | |
  747. * | 1 | -----------> -------------
  748. * | | | | |
  749. * | | | 3 | 2 |
  750. * | | | | |
  751. * ------------- -------------
  752. * ```
  753. */
  754. divideBox: function () {
  755. var halfWidth = this.box.width / 2, halfHeight = this.box.height / 2;
  756. // Top left
  757. this.nodes[0] = new QuadTreeNode({
  758. left: this.box.left,
  759. top: this.box.top,
  760. width: halfWidth,
  761. height: halfHeight
  762. });
  763. // Top right
  764. this.nodes[1] = new QuadTreeNode({
  765. left: this.box.left + halfWidth,
  766. top: this.box.top,
  767. width: halfWidth,
  768. height: halfHeight
  769. });
  770. // Bottom right
  771. this.nodes[2] = new QuadTreeNode({
  772. left: this.box.left + halfWidth,
  773. top: this.box.top + halfHeight,
  774. width: halfWidth,
  775. height: halfHeight
  776. });
  777. // Bottom left
  778. this.nodes[3] = new QuadTreeNode({
  779. left: this.box.left,
  780. top: this.box.top + halfHeight,
  781. width: halfWidth,
  782. height: halfHeight
  783. });
  784. },
  785. /**
  786. * Determine which of the quadrants should be used when placing node in
  787. * the QuadTree. Returned index is always in range `< 0 , 3 >`.
  788. *
  789. * @param {Highcharts.Point} point
  790. * @return {number}
  791. */
  792. getBoxPosition: function (point) {
  793. var left = point.plotX < this.box.left + this.box.width / 2, top = point.plotY < this.box.top + this.box.height / 2, index;
  794. if (left) {
  795. if (top) {
  796. // Top left
  797. index = 0;
  798. }
  799. else {
  800. // Bottom left
  801. index = 3;
  802. }
  803. }
  804. else {
  805. if (top) {
  806. // Top right
  807. index = 1;
  808. }
  809. else {
  810. // Bottom right
  811. index = 2;
  812. }
  813. }
  814. return index;
  815. }
  816. });
  817. /**
  818. * The QuadTree class. Used in Networkgraph chart as a base for Barnes-Hut
  819. * approximation.
  820. *
  821. * @private
  822. * @class
  823. * @name Highcharts.QuadTree
  824. *
  825. * @param {number} x left position of the plotting area
  826. * @param {number} y top position of the plotting area
  827. * @param {number} width width of the plotting area
  828. * @param {number} height height of the plotting area
  829. */
  830. var QuadTree = H.QuadTree = function (x, y, width, height) {
  831. // Boundary rectangle:
  832. this.box = {
  833. left: x,
  834. top: y,
  835. width: width,
  836. height: height
  837. };
  838. this.maxDepth = 25;
  839. this.root = new QuadTreeNode(this.box, '0');
  840. this.root.isInternal = true;
  841. this.root.isRoot = true;
  842. this.root.divideBox();
  843. };
  844. extend(QuadTree.prototype,
  845. /** @lends Highcharts.QuadTree.prototype */
  846. {
  847. /**
  848. * Insert nodes into the QuadTree
  849. *
  850. * @param {Array<Highcharts.Point>} points
  851. */
  852. insertNodes: function (points) {
  853. points.forEach(function (point) {
  854. this.root.insert(point, this.maxDepth);
  855. }, this);
  856. },
  857. /**
  858. * Depfth first treversal (DFS). Using `before` and `after` callbacks,
  859. * we can get two results: preorder and postorder traversals, reminder:
  860. *
  861. * ```
  862. * (a)
  863. * / \
  864. * (b) (c)
  865. * / \
  866. * (d) (e)
  867. * ```
  868. *
  869. * DFS (preorder): `a -> b -> d -> e -> c`
  870. *
  871. * DFS (postorder): `d -> e -> b -> c -> a`
  872. *
  873. * @param {Highcharts.QuadTreeNode|null} node
  874. * @param {Function} [beforeCallback] function to be called before
  875. * visiting children nodes
  876. * @param {Function} [afterCallback] function to be called after
  877. * visiting children nodes
  878. */
  879. visitNodeRecursive: function (node, beforeCallback, afterCallback) {
  880. var goFurther;
  881. if (!node) {
  882. node = this.root;
  883. }
  884. if (node === this.root && beforeCallback) {
  885. goFurther = beforeCallback(node);
  886. }
  887. if (goFurther === false) {
  888. return;
  889. }
  890. node.nodes.forEach(function (qtNode) {
  891. if (qtNode.isInternal) {
  892. if (beforeCallback) {
  893. goFurther = beforeCallback(qtNode);
  894. }
  895. if (goFurther === false) {
  896. return;
  897. }
  898. this.visitNodeRecursive(qtNode, beforeCallback, afterCallback);
  899. }
  900. else if (qtNode.body) {
  901. if (beforeCallback) {
  902. beforeCallback(qtNode.body);
  903. }
  904. }
  905. if (afterCallback) {
  906. afterCallback(qtNode);
  907. }
  908. }, this);
  909. if (node === this.root && afterCallback) {
  910. afterCallback(node);
  911. }
  912. },
  913. /**
  914. * Calculate mass of the each QuadNode in the tree.
  915. */
  916. calculateMassAndCenter: function () {
  917. this.visitNodeRecursive(null, null, function (node) {
  918. node.updateMassAndCenter();
  919. });
  920. }
  921. });
  922. });
  923. _registerModule(_modules, 'modules/networkgraph/layouts.js', [_modules['parts/Chart.js'], _modules['parts/Globals.js'], _modules['parts/Utilities.js']], function (Chart, H, U) {
  924. /* *
  925. *
  926. * Networkgraph series
  927. *
  928. * (c) 2010-2020 Paweł Fus
  929. *
  930. * License: www.highcharts.com/license
  931. *
  932. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  933. *
  934. * */
  935. var addEvent = U.addEvent, clamp = U.clamp, defined = U.defined, extend = U.extend, isFunction = U.isFunction, pick = U.pick, setAnimation = U.setAnimation;
  936. /* eslint-disable no-invalid-this, valid-jsdoc */
  937. H.layouts = {
  938. 'reingold-fruchterman': function () {
  939. }
  940. };
  941. extend(
  942. /**
  943. * Reingold-Fruchterman algorithm from
  944. * "Graph Drawing by Force-directed Placement" paper.
  945. * @private
  946. */
  947. H.layouts['reingold-fruchterman'].prototype, {
  948. init: function (options) {
  949. this.options = options;
  950. this.nodes = [];
  951. this.links = [];
  952. this.series = [];
  953. this.box = {
  954. x: 0,
  955. y: 0,
  956. width: 0,
  957. height: 0
  958. };
  959. this.setInitialRendering(true);
  960. this.integration =
  961. H.networkgraphIntegrations[options.integration];
  962. this.enableSimulation = options.enableSimulation;
  963. this.attractiveForce = pick(options.attractiveForce, this.integration.attractiveForceFunction);
  964. this.repulsiveForce = pick(options.repulsiveForce, this.integration.repulsiveForceFunction);
  965. this.approximation = options.approximation;
  966. },
  967. updateSimulation: function (enable) {
  968. this.enableSimulation = pick(enable, this.options.enableSimulation);
  969. },
  970. start: function () {
  971. var layout = this, series = this.series, options = this.options;
  972. layout.currentStep = 0;
  973. layout.forces = series[0] && series[0].forces || [];
  974. layout.chart = series[0] && series[0].chart;
  975. if (layout.initialRendering) {
  976. layout.initPositions();
  977. // Render elements in initial positions:
  978. series.forEach(function (s) {
  979. s.finishedAnimating = true; // #13169
  980. s.render();
  981. });
  982. }
  983. layout.setK();
  984. layout.resetSimulation(options);
  985. if (layout.enableSimulation) {
  986. layout.step();
  987. }
  988. },
  989. step: function () {
  990. var layout = this, series = this.series, options = this.options;
  991. // Algorithm:
  992. layout.currentStep++;
  993. if (layout.approximation === 'barnes-hut') {
  994. layout.createQuadTree();
  995. layout.quadTree.calculateMassAndCenter();
  996. }
  997. layout.forces.forEach(function (forceName) {
  998. layout[forceName + 'Forces'](layout.temperature);
  999. });
  1000. // Limit to the plotting area and cool down:
  1001. layout.applyLimits(layout.temperature);
  1002. // Cool down the system:
  1003. layout.temperature = layout.coolDown(layout.startTemperature, layout.diffTemperature, layout.currentStep);
  1004. layout.prevSystemTemperature = layout.systemTemperature;
  1005. layout.systemTemperature = layout.getSystemTemperature();
  1006. if (layout.enableSimulation) {
  1007. series.forEach(function (s) {
  1008. // Chart could be destroyed during the simulation
  1009. if (s.chart) {
  1010. s.render();
  1011. }
  1012. });
  1013. if (layout.maxIterations-- &&
  1014. isFinite(layout.temperature) &&
  1015. !layout.isStable()) {
  1016. if (layout.simulation) {
  1017. H.win.cancelAnimationFrame(layout.simulation);
  1018. }
  1019. layout.simulation = H.win.requestAnimationFrame(function () {
  1020. layout.step();
  1021. });
  1022. }
  1023. else {
  1024. layout.simulation = false;
  1025. }
  1026. }
  1027. },
  1028. stop: function () {
  1029. if (this.simulation) {
  1030. H.win.cancelAnimationFrame(this.simulation);
  1031. }
  1032. },
  1033. setArea: function (x, y, w, h) {
  1034. this.box = {
  1035. left: x,
  1036. top: y,
  1037. width: w,
  1038. height: h
  1039. };
  1040. },
  1041. setK: function () {
  1042. // Optimal distance between nodes,
  1043. // available space around the node:
  1044. this.k = this.options.linkLength || this.integration.getK(this);
  1045. },
  1046. addElementsToCollection: function (elements, collection) {
  1047. elements.forEach(function (elem) {
  1048. if (collection.indexOf(elem) === -1) {
  1049. collection.push(elem);
  1050. }
  1051. });
  1052. },
  1053. removeElementFromCollection: function (element, collection) {
  1054. var index = collection.indexOf(element);
  1055. if (index !== -1) {
  1056. collection.splice(index, 1);
  1057. }
  1058. },
  1059. clear: function () {
  1060. this.nodes.length = 0;
  1061. this.links.length = 0;
  1062. this.series.length = 0;
  1063. this.resetSimulation();
  1064. },
  1065. resetSimulation: function () {
  1066. this.forcedStop = false;
  1067. this.systemTemperature = 0;
  1068. this.setMaxIterations();
  1069. this.setTemperature();
  1070. this.setDiffTemperature();
  1071. },
  1072. restartSimulation: function () {
  1073. if (!this.simulation) {
  1074. // When dragging nodes, we don't need to calculate
  1075. // initial positions and rendering nodes:
  1076. this.setInitialRendering(false);
  1077. // Start new simulation:
  1078. if (!this.enableSimulation) {
  1079. // Run only one iteration to speed things up:
  1080. this.setMaxIterations(1);
  1081. }
  1082. else {
  1083. this.start();
  1084. }
  1085. if (this.chart) {
  1086. this.chart.redraw();
  1087. }
  1088. // Restore defaults:
  1089. this.setInitialRendering(true);
  1090. }
  1091. else {
  1092. // Extend current simulation:
  1093. this.resetSimulation();
  1094. }
  1095. },
  1096. setMaxIterations: function (maxIterations) {
  1097. this.maxIterations = pick(maxIterations, this.options.maxIterations);
  1098. },
  1099. setTemperature: function () {
  1100. this.temperature = this.startTemperature =
  1101. Math.sqrt(this.nodes.length);
  1102. },
  1103. setDiffTemperature: function () {
  1104. this.diffTemperature = this.startTemperature /
  1105. (this.options.maxIterations + 1);
  1106. },
  1107. setInitialRendering: function (enable) {
  1108. this.initialRendering = enable;
  1109. },
  1110. createQuadTree: function () {
  1111. this.quadTree = new H.QuadTree(this.box.left, this.box.top, this.box.width, this.box.height);
  1112. this.quadTree.insertNodes(this.nodes);
  1113. },
  1114. initPositions: function () {
  1115. var initialPositions = this.options.initialPositions;
  1116. if (isFunction(initialPositions)) {
  1117. initialPositions.call(this);
  1118. this.nodes.forEach(function (node) {
  1119. if (!defined(node.prevX)) {
  1120. node.prevX = node.plotX;
  1121. }
  1122. if (!defined(node.prevY)) {
  1123. node.prevY = node.plotY;
  1124. }
  1125. node.dispX = 0;
  1126. node.dispY = 0;
  1127. });
  1128. }
  1129. else if (initialPositions === 'circle') {
  1130. this.setCircularPositions();
  1131. }
  1132. else {
  1133. this.setRandomPositions();
  1134. }
  1135. },
  1136. setCircularPositions: function () {
  1137. var box = this.box, nodes = this.nodes, nodesLength = nodes.length + 1, angle = 2 * Math.PI / nodesLength, rootNodes = nodes.filter(function (node) {
  1138. return node.linksTo.length === 0;
  1139. }), sortedNodes = [], visitedNodes = {}, radius = this.options.initialPositionRadius;
  1140. /**
  1141. * @private
  1142. */
  1143. function addToNodes(node) {
  1144. node.linksFrom.forEach(function (link) {
  1145. if (!visitedNodes[link.toNode.id]) {
  1146. visitedNodes[link.toNode.id] = true;
  1147. sortedNodes.push(link.toNode);
  1148. addToNodes(link.toNode);
  1149. }
  1150. });
  1151. }
  1152. // Start with identified root nodes an sort the nodes by their
  1153. // hierarchy. In trees, this ensures that branches don't cross
  1154. // eachother.
  1155. rootNodes.forEach(function (rootNode) {
  1156. sortedNodes.push(rootNode);
  1157. addToNodes(rootNode);
  1158. });
  1159. // Cyclic tree, no root node found
  1160. if (!sortedNodes.length) {
  1161. sortedNodes = nodes;
  1162. // Dangling, cyclic trees
  1163. }
  1164. else {
  1165. nodes.forEach(function (node) {
  1166. if (sortedNodes.indexOf(node) === -1) {
  1167. sortedNodes.push(node);
  1168. }
  1169. });
  1170. }
  1171. // Initial positions are laid out along a small circle, appearing
  1172. // as a cluster in the middle
  1173. sortedNodes.forEach(function (node, index) {
  1174. node.plotX = node.prevX = pick(node.plotX, box.width / 2 + radius * Math.cos(index * angle));
  1175. node.plotY = node.prevY = pick(node.plotY, box.height / 2 + radius * Math.sin(index * angle));
  1176. node.dispX = 0;
  1177. node.dispY = 0;
  1178. });
  1179. },
  1180. setRandomPositions: function () {
  1181. var box = this.box, nodes = this.nodes, nodesLength = nodes.length + 1;
  1182. /**
  1183. * Return a repeatable, quasi-random number based on an integer
  1184. * input. For the initial positions
  1185. * @private
  1186. */
  1187. function unrandom(n) {
  1188. var rand = n * n / Math.PI;
  1189. rand = rand - Math.floor(rand);
  1190. return rand;
  1191. }
  1192. // Initial positions:
  1193. nodes.forEach(function (node, index) {
  1194. node.plotX = node.prevX = pick(node.plotX, box.width * unrandom(index));
  1195. node.plotY = node.prevY = pick(node.plotY, box.height * unrandom(nodesLength + index));
  1196. node.dispX = 0;
  1197. node.dispY = 0;
  1198. });
  1199. },
  1200. force: function (name) {
  1201. this.integration[name].apply(this, Array.prototype.slice.call(arguments, 1));
  1202. },
  1203. barycenterForces: function () {
  1204. this.getBarycenter();
  1205. this.force('barycenter');
  1206. },
  1207. getBarycenter: function () {
  1208. var systemMass = 0, cx = 0, cy = 0;
  1209. this.nodes.forEach(function (node) {
  1210. cx += node.plotX * node.mass;
  1211. cy += node.plotY * node.mass;
  1212. systemMass += node.mass;
  1213. });
  1214. this.barycenter = {
  1215. x: cx,
  1216. y: cy,
  1217. xFactor: cx / systemMass,
  1218. yFactor: cy / systemMass
  1219. };
  1220. return this.barycenter;
  1221. },
  1222. barnesHutApproximation: function (node, quadNode) {
  1223. var layout = this, distanceXY = layout.getDistXY(node, quadNode), distanceR = layout.vectorLength(distanceXY), goDeeper, force;
  1224. if (node !== quadNode && distanceR !== 0) {
  1225. if (quadNode.isInternal) {
  1226. // Internal node:
  1227. if (quadNode.boxSize / distanceR <
  1228. layout.options.theta &&
  1229. distanceR !== 0) {
  1230. // Treat as an external node:
  1231. force = layout.repulsiveForce(distanceR, layout.k);
  1232. layout.force('repulsive', node, force * quadNode.mass, distanceXY, distanceR);
  1233. goDeeper = false;
  1234. }
  1235. else {
  1236. // Go deeper:
  1237. goDeeper = true;
  1238. }
  1239. }
  1240. else {
  1241. // External node, direct force:
  1242. force = layout.repulsiveForce(distanceR, layout.k);
  1243. layout.force('repulsive', node, force * quadNode.mass, distanceXY, distanceR);
  1244. }
  1245. }
  1246. return goDeeper;
  1247. },
  1248. repulsiveForces: function () {
  1249. var layout = this;
  1250. if (layout.approximation === 'barnes-hut') {
  1251. layout.nodes.forEach(function (node) {
  1252. layout.quadTree.visitNodeRecursive(null, function (quadNode) {
  1253. return layout.barnesHutApproximation(node, quadNode);
  1254. });
  1255. });
  1256. }
  1257. else {
  1258. layout.nodes.forEach(function (node) {
  1259. layout.nodes.forEach(function (repNode) {
  1260. var force, distanceR, distanceXY;
  1261. if (
  1262. // Node can not repulse itself:
  1263. node !== repNode &&
  1264. // Only close nodes affect each other:
  1265. // layout.getDistR(node, repNode) < 2 * k &&
  1266. // Not dragged:
  1267. !node.fixedPosition) {
  1268. distanceXY = layout.getDistXY(node, repNode);
  1269. distanceR = layout.vectorLength(distanceXY);
  1270. if (distanceR !== 0) {
  1271. force = layout.repulsiveForce(distanceR, layout.k);
  1272. layout.force('repulsive', node, force * repNode.mass, distanceXY, distanceR);
  1273. }
  1274. }
  1275. });
  1276. });
  1277. }
  1278. },
  1279. attractiveForces: function () {
  1280. var layout = this, distanceXY, distanceR, force;
  1281. layout.links.forEach(function (link) {
  1282. if (link.fromNode && link.toNode) {
  1283. distanceXY = layout.getDistXY(link.fromNode, link.toNode);
  1284. distanceR = layout.vectorLength(distanceXY);
  1285. if (distanceR !== 0) {
  1286. force = layout.attractiveForce(distanceR, layout.k);
  1287. layout.force('attractive', link, force, distanceXY, distanceR);
  1288. }
  1289. }
  1290. });
  1291. },
  1292. applyLimits: function () {
  1293. var layout = this, nodes = layout.nodes;
  1294. nodes.forEach(function (node) {
  1295. if (node.fixedPosition) {
  1296. return;
  1297. }
  1298. layout.integration.integrate(layout, node);
  1299. layout.applyLimitBox(node, layout.box);
  1300. // Reset displacement:
  1301. node.dispX = 0;
  1302. node.dispY = 0;
  1303. });
  1304. },
  1305. /**
  1306. * External box that nodes should fall. When hitting an edge, node
  1307. * should stop or bounce.
  1308. * @private
  1309. */
  1310. applyLimitBox: function (node, box) {
  1311. var radius = node.radius;
  1312. /*
  1313. TO DO: Consider elastic collision instead of stopping.
  1314. o' means end position when hitting plotting area edge:
  1315. - "inelastic":
  1316. o
  1317. \
  1318. ______
  1319. | o'
  1320. | \
  1321. | \
  1322. - "elastic"/"bounced":
  1323. o
  1324. \
  1325. ______
  1326. | ^
  1327. | / \
  1328. |o' \
  1329. Euler sample:
  1330. if (plotX < 0) {
  1331. plotX = 0;
  1332. dispX *= -1;
  1333. }
  1334. if (plotX > box.width) {
  1335. plotX = box.width;
  1336. dispX *= -1;
  1337. }
  1338. */
  1339. // Limit X-coordinates:
  1340. node.plotX = clamp(node.plotX, box.left + radius, box.width - radius);
  1341. // Limit Y-coordinates:
  1342. node.plotY = clamp(node.plotY, box.top + radius, box.height - radius);
  1343. },
  1344. /**
  1345. * From "A comparison of simulated annealing cooling strategies" by
  1346. * Nourani and Andresen work.
  1347. * @private
  1348. */
  1349. coolDown: function (temperature, temperatureStep, currentStep) {
  1350. // Logarithmic:
  1351. /*
  1352. return Math.sqrt(this.nodes.length) -
  1353. Math.log(
  1354. currentStep * layout.diffTemperature
  1355. );
  1356. */
  1357. // Exponential:
  1358. /*
  1359. var alpha = 0.1;
  1360. layout.temperature = Math.sqrt(layout.nodes.length) *
  1361. Math.pow(alpha, layout.diffTemperature);
  1362. */
  1363. // Linear:
  1364. return temperature - temperatureStep * currentStep;
  1365. },
  1366. isStable: function () {
  1367. return Math.abs(this.systemTemperature -
  1368. this.prevSystemTemperature) < 0.00001 || this.temperature <= 0;
  1369. },
  1370. getSystemTemperature: function () {
  1371. return this.nodes.reduce(function (value, node) {
  1372. return value + node.temperature;
  1373. }, 0);
  1374. },
  1375. vectorLength: function (vector) {
  1376. return Math.sqrt(vector.x * vector.x + vector.y * vector.y);
  1377. },
  1378. getDistR: function (nodeA, nodeB) {
  1379. var distance = this.getDistXY(nodeA, nodeB);
  1380. return this.vectorLength(distance);
  1381. },
  1382. getDistXY: function (nodeA, nodeB) {
  1383. var xDist = nodeA.plotX - nodeB.plotX, yDist = nodeA.plotY - nodeB.plotY;
  1384. return {
  1385. x: xDist,
  1386. y: yDist,
  1387. absX: Math.abs(xDist),
  1388. absY: Math.abs(yDist)
  1389. };
  1390. }
  1391. });
  1392. /* ************************************************************************** *
  1393. * Multiple series support:
  1394. * ************************************************************************** */
  1395. // Clear previous layouts
  1396. addEvent(Chart, 'predraw', function () {
  1397. if (this.graphLayoutsLookup) {
  1398. this.graphLayoutsLookup.forEach(function (layout) {
  1399. layout.stop();
  1400. });
  1401. }
  1402. });
  1403. addEvent(Chart, 'render', function () {
  1404. var systemsStable, afterRender = false;
  1405. /**
  1406. * @private
  1407. */
  1408. function layoutStep(layout) {
  1409. if (layout.maxIterations-- &&
  1410. isFinite(layout.temperature) &&
  1411. !layout.isStable() &&
  1412. !layout.enableSimulation) {
  1413. // Hook similar to build-in addEvent, but instead of
  1414. // creating whole events logic, use just a function.
  1415. // It's faster which is important for rAF code.
  1416. // Used e.g. in packed-bubble series for bubble radius
  1417. // calculations
  1418. if (layout.beforeStep) {
  1419. layout.beforeStep();
  1420. }
  1421. layout.step();
  1422. systemsStable = false;
  1423. afterRender = true;
  1424. }
  1425. }
  1426. if (this.graphLayoutsLookup) {
  1427. setAnimation(false, this);
  1428. // Start simulation
  1429. this.graphLayoutsLookup.forEach(function (layout) {
  1430. layout.start();
  1431. });
  1432. // Just one sync step, to run different layouts similar to
  1433. // async mode.
  1434. while (!systemsStable) {
  1435. systemsStable = true;
  1436. this.graphLayoutsLookup.forEach(layoutStep);
  1437. }
  1438. if (afterRender) {
  1439. this.series.forEach(function (s) {
  1440. if (s && s.layout) {
  1441. s.render();
  1442. }
  1443. });
  1444. }
  1445. }
  1446. });
  1447. // disable simulation before print if enabled
  1448. addEvent(Chart, 'beforePrint', function () {
  1449. if (this.graphLayoutsLookup) {
  1450. this.graphLayoutsLookup.forEach(function (layout) {
  1451. layout.updateSimulation(false);
  1452. });
  1453. this.redraw();
  1454. }
  1455. });
  1456. // re-enable simulation after print
  1457. addEvent(Chart, 'afterPrint', function () {
  1458. if (this.graphLayoutsLookup) {
  1459. this.graphLayoutsLookup.forEach(function (layout) {
  1460. // return to default simulation
  1461. layout.updateSimulation();
  1462. });
  1463. }
  1464. this.redraw();
  1465. });
  1466. });
  1467. _registerModule(_modules, 'modules/networkgraph/draggable-nodes.js', [_modules['parts/Chart.js'], _modules['parts/Globals.js'], _modules['parts/Utilities.js']], function (Chart, H, U) {
  1468. /* *
  1469. *
  1470. * Networkgraph series
  1471. *
  1472. * (c) 2010-2020 Paweł Fus
  1473. *
  1474. * License: www.highcharts.com/license
  1475. *
  1476. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  1477. *
  1478. * */
  1479. var addEvent = U.addEvent;
  1480. /* eslint-disable no-invalid-this, valid-jsdoc */
  1481. H.dragNodesMixin = {
  1482. /**
  1483. * Mouse down action, initializing drag&drop mode.
  1484. *
  1485. * @private
  1486. * @param {Highcharts.Point} point The point that event occured.
  1487. * @param {Highcharts.PointerEventObject} event Browser event, before normalization.
  1488. * @return {void}
  1489. */
  1490. onMouseDown: function (point, event) {
  1491. var normalizedEvent = this.chart.pointer.normalize(event);
  1492. point.fixedPosition = {
  1493. chartX: normalizedEvent.chartX,
  1494. chartY: normalizedEvent.chartY,
  1495. plotX: point.plotX,
  1496. plotY: point.plotY
  1497. };
  1498. point.inDragMode = true;
  1499. },
  1500. /**
  1501. * Mouse move action during drag&drop.
  1502. *
  1503. * @private
  1504. *
  1505. * @param {global.Event} event Browser event, before normalization.
  1506. * @param {Highcharts.Point} point The point that event occured.
  1507. *
  1508. * @return {void}
  1509. */
  1510. onMouseMove: function (point, event) {
  1511. if (point.fixedPosition && point.inDragMode) {
  1512. var series = this, chart = series.chart, normalizedEvent = chart.pointer.normalize(event), diffX = point.fixedPosition.chartX - normalizedEvent.chartX, diffY = point.fixedPosition.chartY - normalizedEvent.chartY, newPlotX, newPlotY, graphLayoutsLookup = chart.graphLayoutsLookup;
  1513. // At least 5px to apply change (avoids simple click):
  1514. if (Math.abs(diffX) > 5 || Math.abs(diffY) > 5) {
  1515. newPlotX = point.fixedPosition.plotX - diffX;
  1516. newPlotY = point.fixedPosition.plotY - diffY;
  1517. if (chart.isInsidePlot(newPlotX, newPlotY)) {
  1518. point.plotX = newPlotX;
  1519. point.plotY = newPlotY;
  1520. point.hasDragged = true;
  1521. this.redrawHalo(point);
  1522. graphLayoutsLookup.forEach(function (layout) {
  1523. layout.restartSimulation();
  1524. });
  1525. }
  1526. }
  1527. }
  1528. },
  1529. /**
  1530. * Mouse up action, finalizing drag&drop.
  1531. *
  1532. * @private
  1533. * @param {Highcharts.Point} point The point that event occured.
  1534. * @return {void}
  1535. */
  1536. onMouseUp: function (point, event) {
  1537. if (point.fixedPosition && point.hasDragged) {
  1538. if (this.layout.enableSimulation) {
  1539. this.layout.start();
  1540. }
  1541. else {
  1542. this.chart.redraw();
  1543. }
  1544. point.inDragMode = point.hasDragged = false;
  1545. if (!this.options.fixedDraggable) {
  1546. delete point.fixedPosition;
  1547. }
  1548. }
  1549. },
  1550. // Draggable mode:
  1551. /**
  1552. * Redraw halo on mousemove during the drag&drop action.
  1553. *
  1554. * @private
  1555. * @param {Highcharts.Point} point The point that should show halo.
  1556. * @return {void}
  1557. */
  1558. redrawHalo: function (point) {
  1559. if (point && this.halo) {
  1560. this.halo.attr({
  1561. d: point.haloPath(this.options.states.hover.halo.size)
  1562. });
  1563. }
  1564. }
  1565. };
  1566. /*
  1567. * Draggable mode:
  1568. */
  1569. addEvent(Chart, 'load', function () {
  1570. var chart = this, mousedownUnbinder, mousemoveUnbinder, mouseupUnbinder;
  1571. if (chart.container) {
  1572. mousedownUnbinder = addEvent(chart.container, 'mousedown', function (event) {
  1573. var point = chart.hoverPoint;
  1574. if (point &&
  1575. point.series &&
  1576. point.series.hasDraggableNodes &&
  1577. point.series.options.draggable) {
  1578. point.series.onMouseDown(point, event);
  1579. mousemoveUnbinder = addEvent(chart.container, 'mousemove', function (e) {
  1580. return point &&
  1581. point.series &&
  1582. point.series.onMouseMove(point, e);
  1583. });
  1584. mouseupUnbinder = addEvent(chart.container.ownerDocument, 'mouseup', function (e) {
  1585. mousemoveUnbinder();
  1586. mouseupUnbinder();
  1587. return point &&
  1588. point.series &&
  1589. point.series.onMouseUp(point, e);
  1590. });
  1591. }
  1592. });
  1593. }
  1594. addEvent(chart, 'destroy', function () {
  1595. mousedownUnbinder();
  1596. });
  1597. });
  1598. });
  1599. _registerModule(_modules, 'modules/networkgraph/networkgraph.src.js', [_modules['parts/Globals.js'], _modules['parts/Point.js'], _modules['parts/Utilities.js']], function (H, Point, U) {
  1600. /* *
  1601. *
  1602. * Networkgraph series
  1603. *
  1604. * (c) 2010-2020 Paweł Fus
  1605. *
  1606. * License: www.highcharts.com/license
  1607. *
  1608. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  1609. *
  1610. * */
  1611. var addEvent = U.addEvent, css = U.css, defined = U.defined, pick = U.pick, seriesType = U.seriesType;
  1612. /**
  1613. * Formatter callback function.
  1614. *
  1615. * @callback Highcharts.SeriesNetworkgraphDataLabelsFormatterCallbackFunction
  1616. *
  1617. * @param {Highcharts.SeriesNetworkgraphDataLabelsFormatterContextObject|Highcharts.PointLabelObject} this
  1618. * Data label context to format
  1619. *
  1620. * @return {string}
  1621. * Formatted data label text
  1622. */
  1623. /**
  1624. * Context for the formatter function.
  1625. *
  1626. * @interface Highcharts.SeriesNetworkgraphDataLabelsFormatterContextObject
  1627. * @extends Highcharts.PointLabelObject
  1628. * @since 7.0.0
  1629. */ /**
  1630. * The color of the node.
  1631. * @name Highcharts.SeriesNetworkgraphDataLabelsFormatterContextObject#color
  1632. * @type {Highcharts.ColorString}
  1633. * @since 7.0.0
  1634. */ /**
  1635. * The point (node) object. The node name, if defined, is available through
  1636. * `this.point.name`. Arrays: `this.point.linksFrom` and `this.point.linksTo`
  1637. * contains all nodes connected to this point.
  1638. * @name Highcharts.SeriesNetworkgraphDataLabelsFormatterContextObject#point
  1639. * @type {Highcharts.Point}
  1640. * @since 7.0.0
  1641. */ /**
  1642. * The ID of the node.
  1643. * @name Highcharts.SeriesNetworkgraphDataLabelsFormatterContextObject#key
  1644. * @type {string}
  1645. * @since 7.0.0
  1646. */
  1647. ''; // detach doclets above
  1648. var seriesTypes = H.seriesTypes, Series = H.Series, dragNodesMixin = H.dragNodesMixin;
  1649. /**
  1650. * @private
  1651. * @class
  1652. * @name Highcharts.seriesTypes.networkgraph
  1653. *
  1654. * @extends Highcharts.Series
  1655. */
  1656. seriesType('networkgraph', 'line',
  1657. /**
  1658. * A networkgraph is a type of relationship chart, where connnections
  1659. * (links) attracts nodes (points) and other nodes repulse each other.
  1660. *
  1661. * @extends plotOptions.line
  1662. * @product highcharts
  1663. * @sample highcharts/demo/network-graph/
  1664. * Networkgraph
  1665. * @since 7.0.0
  1666. * @excluding boostThreshold, animation, animationLimit, connectEnds,
  1667. * colorAxis, colorKey, connectNulls, dragDrop,
  1668. * getExtremesFromAll, label, linecap, negativeColor,
  1669. * pointInterval, pointIntervalUnit, pointPlacement,
  1670. * pointStart, softThreshold, stack, stacking, step,
  1671. * threshold, xAxis, yAxis, zoneAxis, dataSorting
  1672. * @requires modules/networkgraph
  1673. * @optionparent plotOptions.networkgraph
  1674. */
  1675. {
  1676. stickyTracking: false,
  1677. /**
  1678. * @ignore-option
  1679. * @private
  1680. */
  1681. inactiveOtherPoints: true,
  1682. marker: {
  1683. enabled: true,
  1684. states: {
  1685. /**
  1686. * The opposite state of a hover for a single point node.
  1687. * Applied to all not connected nodes to the hovered one.
  1688. *
  1689. * @declare Highcharts.PointStatesInactiveOptionsObject
  1690. */
  1691. inactive: {
  1692. /**
  1693. * Opacity of inactive markers.
  1694. */
  1695. opacity: 0.3,
  1696. /**
  1697. * Animation when not hovering over the node.
  1698. *
  1699. * @type {boolean|Highcharts.AnimationOptionsObject}
  1700. */
  1701. animation: {
  1702. /** @internal */
  1703. duration: 50
  1704. }
  1705. }
  1706. }
  1707. },
  1708. states: {
  1709. /**
  1710. * The opposite state of a hover for a single point link. Applied
  1711. * to all links that are not comming from the hovered node.
  1712. *
  1713. * @declare Highcharts.SeriesStatesInactiveOptionsObject
  1714. */
  1715. inactive: {
  1716. /**
  1717. * Opacity of inactive links.
  1718. */
  1719. linkOpacity: 0.3,
  1720. /**
  1721. * Animation when not hovering over the node.
  1722. *
  1723. * @type {boolean|Highcharts.AnimationOptionsObject}
  1724. */
  1725. animation: {
  1726. /** @internal */
  1727. duration: 50
  1728. }
  1729. }
  1730. },
  1731. /**
  1732. * @sample highcharts/series-networkgraph/link-datalabels
  1733. * Networkgraph with labels on links
  1734. * @sample highcharts/series-networkgraph/textpath-datalabels
  1735. * Networkgraph with labels around nodes
  1736. * @sample highcharts/series-networkgraph/link-datalabels
  1737. * Data labels moved into the nodes
  1738. * @sample highcharts/series-networkgraph/link-datalabels
  1739. * Data labels moved under the links
  1740. *
  1741. * @declare Highcharts.SeriesNetworkgraphDataLabelsOptionsObject
  1742. *
  1743. * @private
  1744. */
  1745. dataLabels: {
  1746. /**
  1747. * The
  1748. * [format string](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting)
  1749. * specifying what to show for _node_ in the networkgraph. In v7.0
  1750. * defaults to `{key}`, since v7.1 defaults to `undefined` and
  1751. * `formatter` is used instead.
  1752. *
  1753. * @type {string}
  1754. * @since 7.0.0
  1755. * @apioption plotOptions.networkgraph.dataLabels.format
  1756. */
  1757. // eslint-disable-next-line valid-jsdoc
  1758. /**
  1759. * Callback JavaScript function to format the data label for a node.
  1760. * Note that if a `format` is defined, the format takes precedence
  1761. * and the formatter is ignored.
  1762. *
  1763. * @type {Highcharts.SeriesNetworkgraphDataLabelsFormatterCallbackFunction}
  1764. * @since 7.0.0
  1765. */
  1766. formatter: function () {
  1767. return this.key;
  1768. },
  1769. /**
  1770. * The
  1771. * [format string](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting)
  1772. * specifying what to show for _links_ in the networkgraph.
  1773. * (Default: `undefined`)
  1774. *
  1775. * @type {string}
  1776. * @since 7.1.0
  1777. * @apioption plotOptions.networkgraph.dataLabels.linkFormat
  1778. */
  1779. // eslint-disable-next-line valid-jsdoc
  1780. /**
  1781. * Callback to format data labels for _links_ in the sankey diagram.
  1782. * The `linkFormat` option takes precedence over the
  1783. * `linkFormatter`.
  1784. *
  1785. * @type {Highcharts.SeriesNetworkgraphDataLabelsFormatterCallbackFunction}
  1786. * @since 7.1.0
  1787. */
  1788. linkFormatter: function () {
  1789. return (this.point.fromNode.name +
  1790. '<br>' +
  1791. this.point.toNode.name);
  1792. },
  1793. /**
  1794. * Options for a _link_ label text which should follow link
  1795. * connection. Border and background are disabled for a label that
  1796. * follows a path.
  1797. *
  1798. * **Note:** Only SVG-based renderer supports this option. Setting
  1799. * `useHTML` to true will disable this option.
  1800. *
  1801. * @extends plotOptions.networkgraph.dataLabels.textPath
  1802. * @since 7.1.0
  1803. */
  1804. linkTextPath: {
  1805. enabled: true
  1806. },
  1807. textPath: {
  1808. enabled: false
  1809. },
  1810. style: {
  1811. transition: 'opacity 2000ms'
  1812. }
  1813. },
  1814. /**
  1815. * Link style options
  1816. * @private
  1817. */
  1818. link: {
  1819. /**
  1820. * A name for the dash style to use for links.
  1821. *
  1822. * @type {string}
  1823. * @apioption plotOptions.networkgraph.link.dashStyle
  1824. */
  1825. /**
  1826. * Color of the link between two nodes.
  1827. */
  1828. color: 'rgba(100, 100, 100, 0.5)',
  1829. /**
  1830. * Width (px) of the link between two nodes.
  1831. */
  1832. width: 1
  1833. },
  1834. /**
  1835. * Flag to determine if nodes are draggable or not.
  1836. * @private
  1837. */
  1838. draggable: true,
  1839. layoutAlgorithm: {
  1840. /**
  1841. * Repulsive force applied on a node. Passed are two arguments:
  1842. * - `d` - which is current distance between two nodes
  1843. * - `k` - which is desired distance between two nodes
  1844. *
  1845. * In `verlet` integration, defaults to:
  1846. * `function (d, k) { return (k - d) / d * (k > d ? 1 : 0) }`
  1847. *
  1848. * @see [layoutAlgorithm.integration](#series.networkgraph.layoutAlgorithm.integration)
  1849. *
  1850. * @sample highcharts/series-networkgraph/forces/
  1851. * Custom forces with Euler integration
  1852. * @sample highcharts/series-networkgraph/cuboids/
  1853. * Custom forces with Verlet integration
  1854. *
  1855. * @type {Function}
  1856. * @default function (d, k) { return k * k / d; }
  1857. * @apioption plotOptions.networkgraph.layoutAlgorithm.repulsiveForce
  1858. */
  1859. /**
  1860. * Attraction force applied on a node which is conected to another
  1861. * node by a link. Passed are two arguments:
  1862. * - `d` - which is current distance between two nodes
  1863. * - `k` - which is desired distance between two nodes
  1864. *
  1865. * In `verlet` integration, defaults to:
  1866. * `function (d, k) { return (k - d) / d; }`
  1867. *
  1868. * @see [layoutAlgorithm.integration](#series.networkgraph.layoutAlgorithm.integration)
  1869. *
  1870. * @sample highcharts/series-networkgraph/forces/
  1871. * Custom forces with Euler integration
  1872. * @sample highcharts/series-networkgraph/cuboids/
  1873. * Custom forces with Verlet integration
  1874. *
  1875. * @type {Function}
  1876. * @default function (d, k) { return k * k / d; }
  1877. * @apioption plotOptions.networkgraph.layoutAlgorithm.attractiveForce
  1878. */
  1879. /**
  1880. * Ideal length (px) of the link between two nodes. When not
  1881. * defined, length is calculated as:
  1882. * `Math.pow(availableWidth * availableHeight / nodesLength, 0.4);`
  1883. *
  1884. * Note: Because of the algorithm specification, length of each link
  1885. * might be not exactly as specified.
  1886. *
  1887. * @sample highcharts/series-networkgraph/styled-links/
  1888. * Numerical values
  1889. *
  1890. * @type {number}
  1891. * @apioption plotOptions.networkgraph.layoutAlgorithm.linkLength
  1892. */
  1893. /**
  1894. * Initial layout algorithm for positioning nodes. Can be one of
  1895. * built-in options ("circle", "random") or a function where
  1896. * positions should be set on each node (`this.nodes`) as
  1897. * `node.plotX` and `node.plotY`
  1898. *
  1899. * @sample highcharts/series-networkgraph/initial-positions/
  1900. * Initial positions with callback
  1901. *
  1902. * @type {"circle"|"random"|Function}
  1903. */
  1904. initialPositions: 'circle',
  1905. /**
  1906. * When `initialPositions` are set to 'circle',
  1907. * `initialPositionRadius` is a distance from the center of circle,
  1908. * in which nodes are created.
  1909. *
  1910. * @type {number}
  1911. * @default 1
  1912. * @since 7.1.0
  1913. */
  1914. initialPositionRadius: 1,
  1915. /**
  1916. * Experimental. Enables live simulation of the algorithm
  1917. * implementation. All nodes are animated as the forces applies on
  1918. * them.
  1919. *
  1920. * @sample highcharts/demo/network-graph/
  1921. * Live simulation enabled
  1922. */
  1923. enableSimulation: false,
  1924. /**
  1925. * Barnes-Hut approximation only.
  1926. * Deteremines when distance between cell and node is small enough
  1927. * to caculate forces. Value of `theta` is compared directly with
  1928. * quotient `s / d`, where `s` is the size of the cell, and `d` is
  1929. * distance between center of cell's mass and currently compared
  1930. * node.
  1931. *
  1932. * @see [layoutAlgorithm.approximation](#series.networkgraph.layoutAlgorithm.approximation)
  1933. *
  1934. * @since 7.1.0
  1935. */
  1936. theta: 0.5,
  1937. /**
  1938. * Verlet integration only.
  1939. * Max speed that node can get in one iteration. In terms of
  1940. * simulation, it's a maximum translation (in pixels) that node can
  1941. * move (in both, x and y, dimensions). While `friction` is applied
  1942. * on all nodes, max speed is applied only for nodes that move very
  1943. * fast, for example small or disconnected ones.
  1944. *
  1945. * @see [layoutAlgorithm.integration](#series.networkgraph.layoutAlgorithm.integration)
  1946. * @see [layoutAlgorithm.friction](#series.networkgraph.layoutAlgorithm.friction)
  1947. *
  1948. * @since 7.1.0
  1949. */
  1950. maxSpeed: 10,
  1951. /**
  1952. * Approximation used to calculate repulsive forces affecting nodes.
  1953. * By default, when calculateing net force, nodes are compared
  1954. * against each other, which gives O(N^2) complexity. Using
  1955. * Barnes-Hut approximation, we decrease this to O(N log N), but the
  1956. * resulting graph will have different layout. Barnes-Hut
  1957. * approximation divides space into rectangles via quad tree, where
  1958. * forces exerted on nodes are calculated directly for nearby cells,
  1959. * and for all others, cells are treated as a separate node with
  1960. * center of mass.
  1961. *
  1962. * @see [layoutAlgorithm.theta](#series.networkgraph.layoutAlgorithm.theta)
  1963. *
  1964. * @sample highcharts/series-networkgraph/barnes-hut-approximation/
  1965. * A graph with Barnes-Hut approximation
  1966. *
  1967. * @type {string}
  1968. * @validvalue ["barnes-hut", "none"]
  1969. * @since 7.1.0
  1970. */
  1971. approximation: 'none',
  1972. /**
  1973. * Type of the algorithm used when positioning nodes.
  1974. *
  1975. * @type {string}
  1976. * @validvalue ["reingold-fruchterman"]
  1977. */
  1978. type: 'reingold-fruchterman',
  1979. /**
  1980. * Integration type. Available options are `'euler'` and `'verlet'`.
  1981. * Integration determines how forces are applied on particles. In
  1982. * Euler integration, force is applied direct as
  1983. * `newPosition += velocity;`.
  1984. * In Verlet integration, new position is based on a previous
  1985. * posittion without velocity:
  1986. * `newPosition += previousPosition - newPosition`.
  1987. *
  1988. * Note that different integrations give different results as forces
  1989. * are different.
  1990. *
  1991. * In Highcharts v7.0.x only `'euler'` integration was supported.
  1992. *
  1993. * @sample highcharts/series-networkgraph/integration-comparison/
  1994. * Comparison of Verlet and Euler integrations
  1995. *
  1996. * @type {string}
  1997. * @validvalue ["euler", "verlet"]
  1998. * @since 7.1.0
  1999. */
  2000. integration: 'euler',
  2001. /**
  2002. * Max number of iterations before algorithm will stop. In general,
  2003. * algorithm should find positions sooner, but when rendering huge
  2004. * number of nodes, it is recommended to increase this value as
  2005. * finding perfect graph positions can require more time.
  2006. */
  2007. maxIterations: 1000,
  2008. /**
  2009. * Gravitational const used in the barycenter force of the
  2010. * algorithm.
  2011. *
  2012. * @sample highcharts/series-networkgraph/forces/
  2013. * Custom forces with Euler integration
  2014. */
  2015. gravitationalConstant: 0.0625,
  2016. /**
  2017. * Friction applied on forces to prevent nodes rushing to fast to
  2018. * the desired positions.
  2019. */
  2020. friction: -0.981
  2021. },
  2022. showInLegend: false
  2023. }, {
  2024. /**
  2025. * Array of internal forces. Each force should be later defined in
  2026. * integrations.js.
  2027. * @private
  2028. */
  2029. forces: ['barycenter', 'repulsive', 'attractive'],
  2030. hasDraggableNodes: true,
  2031. drawGraph: null,
  2032. isCartesian: false,
  2033. requireSorting: false,
  2034. directTouch: true,
  2035. noSharedTooltip: true,
  2036. pointArrayMap: ['from', 'to'],
  2037. trackerGroups: ['group', 'markerGroup', 'dataLabelsGroup'],
  2038. drawTracker: H.TrackerMixin.drawTrackerPoint,
  2039. // Animation is run in `series.simulation`.
  2040. animate: null,
  2041. buildKDTree: H.noop,
  2042. /**
  2043. * Create a single node that holds information on incoming and outgoing
  2044. * links.
  2045. * @private
  2046. */
  2047. createNode: H.NodesMixin.createNode,
  2048. destroy: function () {
  2049. this.layout.removeElementFromCollection(this, this.layout.series);
  2050. H.NodesMixin.destroy.call(this);
  2051. },
  2052. /* eslint-disable no-invalid-this, valid-jsdoc */
  2053. /**
  2054. * Extend init with base event, which should stop simulation during
  2055. * update. After data is updated, `chart.render` resumes the simulation.
  2056. * @private
  2057. */
  2058. init: function () {
  2059. Series.prototype.init.apply(this, arguments);
  2060. addEvent(this, 'updatedData', function () {
  2061. if (this.layout) {
  2062. this.layout.stop();
  2063. }
  2064. });
  2065. return this;
  2066. },
  2067. /**
  2068. * Extend generatePoints by adding the nodes, which are Point objects
  2069. * but pushed to the this.nodes array.
  2070. * @private
  2071. */
  2072. generatePoints: function () {
  2073. var node, i;
  2074. H.NodesMixin.generatePoints.apply(this, arguments);
  2075. // In networkgraph, it's fine to define stanalone nodes, create
  2076. // them:
  2077. if (this.options.nodes) {
  2078. this.options.nodes.forEach(function (nodeOptions) {
  2079. if (!this.nodeLookup[nodeOptions.id]) {
  2080. this.nodeLookup[nodeOptions.id] =
  2081. this.createNode(nodeOptions.id);
  2082. }
  2083. }, this);
  2084. }
  2085. for (i = this.nodes.length - 1; i >= 0; i--) {
  2086. node = this.nodes[i];
  2087. node.degree = node.getDegree();
  2088. node.radius = pick(node.marker && node.marker.radius, this.options.marker && this.options.marker.radius, 0);
  2089. // If node exists, but it's not available in nodeLookup,
  2090. // then it's leftover from previous runs (e.g. setData)
  2091. if (!this.nodeLookup[node.id]) {
  2092. node.remove();
  2093. }
  2094. }
  2095. this.data.forEach(function (link) {
  2096. link.formatPrefix = 'link';
  2097. });
  2098. this.indexateNodes();
  2099. },
  2100. /**
  2101. * In networkgraph, series.points refers to links,
  2102. * but series.nodes refers to actual points.
  2103. * @private
  2104. */
  2105. getPointsCollection: function () {
  2106. return this.nodes || [];
  2107. },
  2108. /**
  2109. * Set index for each node. Required for proper `node.update()`.
  2110. * Note that links are indexated out of the box in `generatePoints()`.
  2111. *
  2112. * @private
  2113. */
  2114. indexateNodes: function () {
  2115. this.nodes.forEach(function (node, index) {
  2116. node.index = index;
  2117. });
  2118. },
  2119. /**
  2120. * Extend the default marker attribs by using a non-rounded X position,
  2121. * otherwise the nodes will jump from pixel to pixel which looks a bit
  2122. * jaggy when approaching equilibrium.
  2123. * @private
  2124. */
  2125. markerAttribs: function (point, state) {
  2126. var attribs = Series.prototype.markerAttribs.call(this, point, state);
  2127. // series.render() is called before initial positions are set:
  2128. if (!defined(point.plotY)) {
  2129. attribs.y = 0;
  2130. }
  2131. attribs.x = (point.plotX || 0) - (attribs.width / 2 || 0);
  2132. return attribs;
  2133. },
  2134. /**
  2135. * Run pre-translation and register nodes&links to the deffered layout.
  2136. * @private
  2137. */
  2138. translate: function () {
  2139. if (!this.processedXData) {
  2140. this.processData();
  2141. }
  2142. this.generatePoints();
  2143. this.deferLayout();
  2144. this.nodes.forEach(function (node) {
  2145. // Draw the links from this node
  2146. node.isInside = true;
  2147. node.linksFrom.forEach(function (point) {
  2148. point.shapeType = 'path';
  2149. // Pass test in drawPoints
  2150. point.y = 1;
  2151. });
  2152. });
  2153. },
  2154. /**
  2155. * Defer the layout.
  2156. * Each series first registers all nodes and links, then layout
  2157. * calculates all nodes positions and calls `series.render()` in every
  2158. * simulation step.
  2159. *
  2160. * Note:
  2161. * Animation is done through `requestAnimationFrame` directly, without
  2162. * `Highcharts.animate()` use.
  2163. * @private
  2164. */
  2165. deferLayout: function () {
  2166. var layoutOptions = this.options.layoutAlgorithm, graphLayoutsStorage = this.chart.graphLayoutsStorage, graphLayoutsLookup = this.chart.graphLayoutsLookup, chartOptions = this.chart.options.chart, layout;
  2167. if (!this.visible) {
  2168. return;
  2169. }
  2170. if (!graphLayoutsStorage) {
  2171. this.chart.graphLayoutsStorage = graphLayoutsStorage = {};
  2172. this.chart.graphLayoutsLookup = graphLayoutsLookup = [];
  2173. }
  2174. layout = graphLayoutsStorage[layoutOptions.type];
  2175. if (!layout) {
  2176. layoutOptions.enableSimulation =
  2177. !defined(chartOptions.forExport) ?
  2178. layoutOptions.enableSimulation :
  2179. !chartOptions.forExport;
  2180. graphLayoutsStorage[layoutOptions.type] = layout =
  2181. new H.layouts[layoutOptions.type]();
  2182. layout.init(layoutOptions);
  2183. graphLayoutsLookup.splice(layout.index, 0, layout);
  2184. }
  2185. this.layout = layout;
  2186. layout.setArea(0, 0, this.chart.plotWidth, this.chart.plotHeight);
  2187. layout.addElementsToCollection([this], layout.series);
  2188. layout.addElementsToCollection(this.nodes, layout.nodes);
  2189. layout.addElementsToCollection(this.points, layout.links);
  2190. },
  2191. /**
  2192. * Extend the render function to also render this.nodes together with
  2193. * the points.
  2194. * @private
  2195. */
  2196. render: function () {
  2197. var series = this, points = series.points, hoverPoint = series.chart.hoverPoint, dataLabels = [];
  2198. // Render markers:
  2199. series.points = series.nodes;
  2200. seriesTypes.line.prototype.render.call(this);
  2201. series.points = points;
  2202. points.forEach(function (point) {
  2203. if (point.fromNode && point.toNode) {
  2204. point.renderLink();
  2205. point.redrawLink();
  2206. }
  2207. });
  2208. if (hoverPoint && hoverPoint.series === series) {
  2209. series.redrawHalo(hoverPoint);
  2210. }
  2211. if (series.chart.hasRendered &&
  2212. !series.options.dataLabels.allowOverlap) {
  2213. series.nodes.concat(series.points).forEach(function (node) {
  2214. if (node.dataLabel) {
  2215. dataLabels.push(node.dataLabel);
  2216. }
  2217. });
  2218. series.chart.hideOverlappingLabels(dataLabels);
  2219. }
  2220. },
  2221. // Networkgraph has two separate collecions of nodes and lines, render
  2222. // dataLabels for both sets:
  2223. drawDataLabels: function () {
  2224. var textPath = this.options.dataLabels.textPath;
  2225. // Render node labels:
  2226. Series.prototype.drawDataLabels.apply(this, arguments);
  2227. // Render link labels:
  2228. this.points = this.data;
  2229. this.options.dataLabels.textPath =
  2230. this.options.dataLabels.linkTextPath;
  2231. Series.prototype.drawDataLabels.apply(this, arguments);
  2232. // Restore nodes
  2233. this.points = this.nodes;
  2234. this.options.dataLabels.textPath = textPath;
  2235. },
  2236. // Return the presentational attributes.
  2237. pointAttribs: function (point, state) {
  2238. // By default, only `selected` state is passed on
  2239. var pointState = state || point && point.state || 'normal', attribs = Series.prototype.pointAttribs.call(this, point, pointState), stateOptions = this.options.states[pointState];
  2240. if (point && !point.isNode) {
  2241. attribs = point.getLinkAttributes();
  2242. // For link, get prefixed names:
  2243. if (stateOptions) {
  2244. attribs = {
  2245. // TO DO: API?
  2246. stroke: stateOptions.linkColor || attribs.stroke,
  2247. dashstyle: (stateOptions.linkDashStyle || attribs.dashstyle),
  2248. opacity: pick(stateOptions.linkOpacity, attribs.opacity),
  2249. 'stroke-width': stateOptions.linkColor ||
  2250. attribs['stroke-width']
  2251. };
  2252. }
  2253. }
  2254. return attribs;
  2255. },
  2256. // Draggable mode:
  2257. /**
  2258. * Redraw halo on mousemove during the drag&drop action.
  2259. * @private
  2260. * @param {Highcharts.Point} point The point that should show halo.
  2261. */
  2262. redrawHalo: dragNodesMixin.redrawHalo,
  2263. /**
  2264. * Mouse down action, initializing drag&drop mode.
  2265. * @private
  2266. * @param {global.Event} event Browser event, before normalization.
  2267. * @param {Highcharts.Point} point The point that event occured.
  2268. */
  2269. onMouseDown: dragNodesMixin.onMouseDown,
  2270. /**
  2271. * Mouse move action during drag&drop.
  2272. * @private
  2273. * @param {global.Event} event Browser event, before normalization.
  2274. * @param {Highcharts.Point} point The point that event occured.
  2275. */
  2276. onMouseMove: dragNodesMixin.onMouseMove,
  2277. /**
  2278. * Mouse up action, finalizing drag&drop.
  2279. * @private
  2280. * @param {Highcharts.Point} point The point that event occured.
  2281. */
  2282. onMouseUp: dragNodesMixin.onMouseUp,
  2283. /**
  2284. * When state should be passed down to all points, concat nodes and
  2285. * links and apply this state to all of them.
  2286. * @private
  2287. */
  2288. setState: function (state, inherit) {
  2289. if (inherit) {
  2290. this.points = this.nodes.concat(this.data);
  2291. Series.prototype.setState.apply(this, arguments);
  2292. this.points = this.data;
  2293. }
  2294. else {
  2295. Series.prototype.setState.apply(this, arguments);
  2296. }
  2297. // If simulation is done, re-render points with new states:
  2298. if (!this.layout.simulation && !state) {
  2299. this.render();
  2300. }
  2301. }
  2302. }, {
  2303. setState: H.NodesMixin.setNodeState,
  2304. /**
  2305. * Basic `point.init()` and additional styles applied when
  2306. * `series.draggable` is enabled.
  2307. * @private
  2308. */
  2309. init: function () {
  2310. Point.prototype.init.apply(this, arguments);
  2311. if (this.series.options.draggable &&
  2312. !this.series.chart.styledMode) {
  2313. addEvent(this, 'mouseOver', function () {
  2314. css(this.series.chart.container, { cursor: 'move' });
  2315. });
  2316. addEvent(this, 'mouseOut', function () {
  2317. css(this.series.chart.container, { cursor: 'default' });
  2318. });
  2319. }
  2320. return this;
  2321. },
  2322. /**
  2323. * Return degree of a node. If node has no connections, it still has
  2324. * deg=1.
  2325. * @private
  2326. * @return {number}
  2327. */
  2328. getDegree: function () {
  2329. var deg = this.isNode ?
  2330. this.linksFrom.length + this.linksTo.length :
  2331. 0;
  2332. return deg === 0 ? 1 : deg;
  2333. },
  2334. // Links:
  2335. /**
  2336. * Get presentational attributes of link connecting two nodes.
  2337. * @private
  2338. * @return {Highcharts.SVGAttributes}
  2339. */
  2340. getLinkAttributes: function () {
  2341. var linkOptions = this.series.options.link, pointOptions = this.options;
  2342. return {
  2343. 'stroke-width': pick(pointOptions.width, linkOptions.width),
  2344. stroke: (pointOptions.color || linkOptions.color),
  2345. dashstyle: (pointOptions.dashStyle || linkOptions.dashStyle),
  2346. opacity: pick(pointOptions.opacity, linkOptions.opacity, 1)
  2347. };
  2348. },
  2349. /**
  2350. * Render link and add it to the DOM.
  2351. * @private
  2352. */
  2353. renderLink: function () {
  2354. var attribs;
  2355. if (!this.graphic) {
  2356. this.graphic = this.series.chart.renderer
  2357. .path(this.getLinkPath())
  2358. .add(this.series.group);
  2359. if (!this.series.chart.styledMode) {
  2360. attribs = this.series.pointAttribs(this);
  2361. this.graphic.attr(attribs);
  2362. (this.dataLabels || []).forEach(function (label) {
  2363. if (label) {
  2364. label.attr({
  2365. opacity: attribs.opacity
  2366. });
  2367. }
  2368. });
  2369. }
  2370. }
  2371. },
  2372. /**
  2373. * Redraw link's path.
  2374. * @private
  2375. */
  2376. redrawLink: function () {
  2377. var path = this.getLinkPath(), attribs;
  2378. if (this.graphic) {
  2379. this.shapeArgs = {
  2380. d: path
  2381. };
  2382. if (!this.series.chart.styledMode) {
  2383. attribs = this.series.pointAttribs(this);
  2384. this.graphic.attr(attribs);
  2385. (this.dataLabels || []).forEach(function (label) {
  2386. if (label) {
  2387. label.attr({
  2388. opacity: attribs.opacity
  2389. });
  2390. }
  2391. });
  2392. }
  2393. this.graphic.animate(this.shapeArgs);
  2394. // Required for dataLabels
  2395. var start = path[0];
  2396. var end = path[1];
  2397. if (start[0] === 'M' && end[0] === 'L') {
  2398. this.plotX = (start[1] + end[1]) / 2;
  2399. this.plotY = (start[2] + end[2]) / 2;
  2400. }
  2401. }
  2402. },
  2403. /**
  2404. * Get mass fraction applied on two nodes connected to each other. By
  2405. * default, when mass is equal to `1`, mass fraction for both nodes
  2406. * equal to 0.5.
  2407. * @private
  2408. * @return {Highcharts.Dictionary<number>}
  2409. * For example `{ fromNode: 0.5, toNode: 0.5 }`
  2410. */
  2411. getMass: function () {
  2412. var m1 = this.fromNode.mass, m2 = this.toNode.mass, sum = m1 + m2;
  2413. return {
  2414. fromNode: 1 - m1 / sum,
  2415. toNode: 1 - m2 / sum
  2416. };
  2417. },
  2418. /**
  2419. * Get link path connecting two nodes.
  2420. * @private
  2421. * @return {Array<Highcharts.SVGPathArray>}
  2422. * Path: `['M', x, y, 'L', x, y]`
  2423. */
  2424. getLinkPath: function () {
  2425. var left = this.fromNode, right = this.toNode;
  2426. // Start always from left to the right node, to prevent rendering
  2427. // labels upside down
  2428. if (left.plotX > right.plotX) {
  2429. left = this.toNode;
  2430. right = this.fromNode;
  2431. }
  2432. return [
  2433. ['M', left.plotX || 0, left.plotY || 0],
  2434. ['L', right.plotX || 0, right.plotY || 0]
  2435. ];
  2436. /*
  2437. IDEA: different link shapes?
  2438. return [
  2439. 'M',
  2440. from.plotX,
  2441. from.plotY,
  2442. 'Q',
  2443. (to.plotX + from.plotX) / 2,
  2444. (to.plotY + from.plotY) / 2 + 15,
  2445. to.plotX,
  2446. to.plotY
  2447. ];*/
  2448. },
  2449. isValid: function () {
  2450. return !this.isNode || defined(this.id);
  2451. },
  2452. /**
  2453. * Common method for removing points and nodes in networkgraph. To
  2454. * remove `link`, use `series.data[index].remove()`. To remove `node`
  2455. * with all connections, use `series.nodes[index].remove()`.
  2456. * @private
  2457. * @param {boolean} [redraw=true]
  2458. * Whether to redraw the chart or wait for an explicit call. When
  2459. * doing more operations on the chart, for example running
  2460. * `point.remove()` in a loop, it is best practice to set
  2461. * `redraw` to false and call `chart.redraw()` after.
  2462. * @param {boolean|Highcharts.AnimationOptionsObject} [animation=false]
  2463. * Whether to apply animation, and optionally animation
  2464. * configuration.
  2465. * @return {void}
  2466. */
  2467. remove: function (redraw, animation) {
  2468. var point = this, series = point.series, nodesOptions = series.options.nodes || [], index, i = nodesOptions.length;
  2469. // For nodes, remove all connected links:
  2470. if (point.isNode) {
  2471. // Temporary disable series.points array, because
  2472. // Series.removePoint() modifies it
  2473. series.points = [];
  2474. // Remove link from all nodes collections:
  2475. []
  2476. .concat(point.linksFrom)
  2477. .concat(point.linksTo)
  2478. .forEach(function (linkFromTo) {
  2479. // Incoming links
  2480. index = linkFromTo.fromNode.linksFrom.indexOf(linkFromTo);
  2481. if (index > -1) {
  2482. linkFromTo.fromNode.linksFrom.splice(index, 1);
  2483. }
  2484. // Outcoming links
  2485. index = linkFromTo.toNode.linksTo.indexOf(linkFromTo);
  2486. if (index > -1) {
  2487. linkFromTo.toNode.linksTo.splice(index, 1);
  2488. }
  2489. // Remove link from data/points collections
  2490. Series.prototype.removePoint.call(series, series.data.indexOf(linkFromTo), false, false);
  2491. });
  2492. // Restore points array, after links are removed
  2493. series.points = series.data.slice();
  2494. // Proceed with removing node. It's similar to
  2495. // Series.removePoint() method, but doesn't modify other arrays
  2496. series.nodes.splice(series.nodes.indexOf(point), 1);
  2497. // Remove node options from config
  2498. while (i--) {
  2499. if (nodesOptions[i].id === point.options.id) {
  2500. series.options.nodes.splice(i, 1);
  2501. break;
  2502. }
  2503. }
  2504. if (point) {
  2505. point.destroy();
  2506. }
  2507. // Run redraw if requested
  2508. series.isDirty = true;
  2509. series.isDirtyData = true;
  2510. if (redraw) {
  2511. series.chart.redraw(redraw);
  2512. }
  2513. }
  2514. else {
  2515. series.removePoint(series.data.indexOf(point), redraw, animation);
  2516. }
  2517. },
  2518. /**
  2519. * Destroy point. If it's a node, remove all links coming out of this
  2520. * node. Then remove point from the layout.
  2521. * @private
  2522. * @return {void}
  2523. */
  2524. destroy: function () {
  2525. if (this.isNode) {
  2526. this.linksFrom.concat(this.linksTo).forEach(function (link) {
  2527. // Removing multiple nodes at the same time
  2528. // will try to remove link between nodes twice
  2529. if (link.destroyElements) {
  2530. link.destroyElements();
  2531. }
  2532. });
  2533. }
  2534. this.series.layout.removeElementFromCollection(this, this.series.layout[this.isNode ? 'nodes' : 'links']);
  2535. return Point.prototype.destroy.apply(this, arguments);
  2536. }
  2537. });
  2538. /**
  2539. * A `networkgraph` series. If the [type](#series.networkgraph.type) option is
  2540. * not specified, it is inherited from [chart.type](#chart.type).
  2541. *
  2542. * @extends series,plotOptions.networkgraph
  2543. * @excluding boostThreshold, animation, animationLimit, connectEnds,
  2544. * connectNulls, dragDrop, getExtremesFromAll, label, linecap,
  2545. * negativeColor, pointInterval, pointIntervalUnit,
  2546. * pointPlacement, pointStart, softThreshold, stack, stacking,
  2547. * step, threshold, xAxis, yAxis, zoneAxis, dataSorting
  2548. * @product highcharts
  2549. * @requires modules/networkgraph
  2550. * @apioption series.networkgraph
  2551. */
  2552. /**
  2553. * An array of data points for the series. For the `networkgraph` series type,
  2554. * points can be given in the following way:
  2555. *
  2556. * An array of objects with named values. The following snippet shows only a
  2557. * few settings, see the complete options set below. If the total number of
  2558. * data points exceeds the series'
  2559. * [turboThreshold](#series.area.turboThreshold), this option is not available.
  2560. *
  2561. * ```js
  2562. * data: [{
  2563. * from: 'Category1',
  2564. * to: 'Category2'
  2565. * }, {
  2566. * from: 'Category1',
  2567. * to: 'Category3'
  2568. * }]
  2569. * ```
  2570. *
  2571. * @type {Array<Object|Array|Number>}
  2572. * @extends series.line.data
  2573. * @excluding drilldown,marker,x,y,draDrop
  2574. * @sample {highcharts} highcharts/chart/reflow-true/
  2575. * Numerical values
  2576. * @sample {highcharts} highcharts/series/data-array-of-arrays/
  2577. * Arrays of numeric x and y
  2578. * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
  2579. * Arrays of datetime x and y
  2580. * @sample {highcharts} highcharts/series/data-array-of-name-value/
  2581. * Arrays of point.name and y
  2582. * @sample {highcharts} highcharts/series/data-array-of-objects/
  2583. * Config objects
  2584. * @product highcharts
  2585. * @apioption series.networkgraph.data
  2586. */
  2587. /**
  2588. * @type {Highcharts.SeriesNetworkgraphDataLabelsOptionsObject|Array<Highcharts.SeriesNetworkgraphDataLabelsOptionsObject>}
  2589. * @product highcharts
  2590. * @apioption series.networkgraph.data.dataLabels
  2591. */
  2592. /**
  2593. * The node that the link runs from.
  2594. *
  2595. * @type {string}
  2596. * @product highcharts
  2597. * @apioption series.networkgraph.data.from
  2598. */
  2599. /**
  2600. * The node that the link runs to.
  2601. *
  2602. * @type {string}
  2603. * @product highcharts
  2604. * @apioption series.networkgraph.data.to
  2605. */
  2606. /**
  2607. * A collection of options for the individual nodes. The nodes in a
  2608. * networkgraph diagram are auto-generated instances of `Highcharts.Point`,
  2609. * but options can be applied here and linked by the `id`.
  2610. *
  2611. * @sample highcharts/series-networkgraph/data-options/
  2612. * Networkgraph diagram with node options
  2613. *
  2614. * @type {Array<*>}
  2615. * @product highcharts
  2616. * @apioption series.networkgraph.nodes
  2617. */
  2618. /**
  2619. * The id of the auto-generated node, refering to the `from` or `to` setting of
  2620. * the link.
  2621. *
  2622. * @type {string}
  2623. * @product highcharts
  2624. * @apioption series.networkgraph.nodes.id
  2625. */
  2626. /**
  2627. * The color of the auto generated node.
  2628. *
  2629. * @type {Highcharts.ColorString}
  2630. * @product highcharts
  2631. * @apioption series.networkgraph.nodes.color
  2632. */
  2633. /**
  2634. * The color index of the auto generated node, especially for use in styled
  2635. * mode.
  2636. *
  2637. * @type {number}
  2638. * @product highcharts
  2639. * @apioption series.networkgraph.nodes.colorIndex
  2640. */
  2641. /**
  2642. * The name to display for the node in data labels and tooltips. Use this when
  2643. * the name is different from the `id`. Where the id must be unique for each
  2644. * node, this is not necessary for the name.
  2645. *
  2646. * @sample highcharts/series-networkgraph/data-options/
  2647. * Networkgraph diagram with node options
  2648. *
  2649. * @type {string}
  2650. * @product highcharts
  2651. * @apioption series.networkgraph.nodes.name
  2652. */
  2653. /**
  2654. * Mass of the node. By default, each node has mass equal to it's marker radius
  2655. * . Mass is used to determine how two connected nodes should affect
  2656. * each other:
  2657. *
  2658. * Attractive force is multiplied by the ratio of two connected
  2659. * nodes; if a big node has weights twice as the small one, then the small one
  2660. * will move towards the big one twice faster than the big one to the small one
  2661. * .
  2662. *
  2663. * @sample highcharts/series-networkgraph/ragdoll/
  2664. * Mass determined by marker.radius
  2665. *
  2666. * @type {number}
  2667. * @product highcharts
  2668. * @apioption series.networkgraph.nodes.mass
  2669. */
  2670. /**
  2671. * Individual data label for each node. The options are the same as
  2672. * the ones for [series.networkgraph.dataLabels](#series.networkgraph.dataLabels).
  2673. *
  2674. * @type {Highcharts.SeriesNetworkgraphDataLabelsOptionsObject|Array<Highcharts.SeriesNetworkgraphDataLabelsOptionsObject>}
  2675. *
  2676. * @apioption series.networkgraph.nodes.dataLabels
  2677. */
  2678. ''; // adds doclets above to transpiled file
  2679. });
  2680. _registerModule(_modules, 'masters/modules/networkgraph.src.js', [], function () {
  2681. });
  2682. }));