networkgraph.src.js 117 KB

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